Bug 1868261 - Add hiding on scroll behaviour to navbar

fenix/125.0
AndiAJ 4 months ago committed by mergify[bot]
parent 552a96e5ae
commit d42aca60dc

@ -131,7 +131,6 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.FindInPageIntegration
import org.mozilla.fenix.components.StoreProvider import org.mozilla.fenix.components.StoreProvider
import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.components.toolbar.BottomToolbarContainerView
import org.mozilla.fenix.components.toolbar.BrowserFragmentState import org.mozilla.fenix.components.toolbar.BrowserFragmentState
import org.mozilla.fenix.components.toolbar.BrowserFragmentStore import org.mozilla.fenix.components.toolbar.BrowserFragmentStore
import org.mozilla.fenix.components.toolbar.BrowserToolbarView import org.mozilla.fenix.components.toolbar.BrowserToolbarView
@ -142,6 +141,8 @@ import org.mozilla.fenix.components.toolbar.ToolbarIntegration
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor import org.mozilla.fenix.components.toolbar.interactor.BrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor import org.mozilla.fenix.components.toolbar.interactor.DefaultBrowserToolbarInteractor
import org.mozilla.fenix.components.toolbar.navbar.BottomToolbarContainerView
import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration
import org.mozilla.fenix.crashes.CrashContentIntegration import org.mozilla.fenix.crashes.CrashContentIntegration
import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity
import org.mozilla.fenix.databinding.FragmentBrowserBinding import org.mozilla.fenix.databinding.FragmentBrowserBinding
@ -223,6 +224,7 @@ abstract class BaseBrowserFragment :
private val promptsFeature = ViewBoundFeatureWrapper<PromptFeature>() private val promptsFeature = ViewBoundFeatureWrapper<PromptFeature>()
private val findInPageIntegration = ViewBoundFeatureWrapper<FindInPageIntegration>() private val findInPageIntegration = ViewBoundFeatureWrapper<FindInPageIntegration>()
private val toolbarIntegration = ViewBoundFeatureWrapper<ToolbarIntegration>() private val toolbarIntegration = ViewBoundFeatureWrapper<ToolbarIntegration>()
private val navbarIntegration = ViewBoundFeatureWrapper<NavbarIntegration>()
private val sitePermissionsFeature = ViewBoundFeatureWrapper<SitePermissionsFeature>() private val sitePermissionsFeature = ViewBoundFeatureWrapper<SitePermissionsFeature>()
private val fullScreenFeature = ViewBoundFeatureWrapper<FullScreenFeature>() private val fullScreenFeature = ViewBoundFeatureWrapper<FullScreenFeature>()
private val swipeRefreshFeature = ViewBoundFeatureWrapper<SwipeRefreshFeature>() private val swipeRefreshFeature = ViewBoundFeatureWrapper<SwipeRefreshFeature>()
@ -467,11 +469,17 @@ abstract class BaseBrowserFragment :
BottomToolbarContainerView( BottomToolbarContainerView(
context = context, context = context,
container = binding.browserLayout, parent = binding.browserLayout,
androidToolbarView = if (isToolbarAtBottom) browserToolbar else null, androidToolbarView = if (isToolbarAtBottom) browserToolbar else null,
menuButton = menuButton, menuButton = menuButton,
browsingModeManager = activity.browsingModeManager, browsingModeManager = activity.browsingModeManager,
) ).also {
navbarIntegration.set(
feature = it.navbarIntegration,
owner = this,
view = view,
)
}
} }
toolbarIntegration.set( toolbarIntegration.set(

@ -2,12 +2,14 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.toolbar package org.mozilla.fenix.components.toolbar.navbar
import android.content.Context import android.content.Context
import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
@ -15,7 +17,10 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import mozilla.components.browser.menu.view.MenuButton import mozilla.components.browser.menu.view.MenuButton
import mozilla.components.browser.state.selector.normalTabs import mozilla.components.browser.state.selector.normalTabs
import mozilla.components.browser.state.selector.privateTabs import mozilla.components.browser.state.selector.privateTabs
import mozilla.components.concept.toolbar.ScrollableToolbar
import mozilla.components.lib.state.ext.observeAsState import mozilla.components.lib.state.ext.observeAsState
import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior
import mozilla.components.ui.widgets.behavior.ViewPosition
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager import org.mozilla.fenix.browser.browsingmode.BrowsingModeManager
import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.compose.Divider
@ -26,25 +31,31 @@ import org.mozilla.fenix.theme.FirefoxTheme
* A helper class to add NavigationBar composable to a [ViewGroup]. * A helper class to add NavigationBar composable to a [ViewGroup].
* *
* @param context The Context the view is running in. * @param context The Context the view is running in.
* @param container The ViewGroup into which the NavigationBar composable will be added. * @param parent The ViewGroup into which the NavigationBar composable will be added.
* @param navigationItems A list of [ActionItem] objects representing the items to be displayed in the navigation bar. * @param navigationItems A list of [ActionItem] objects representing the items to be displayed in the navigation bar.
* @param androidToolbarView An option toolbar view that will be added atop of the navigation bar. * @param androidToolbarView An option toolbar view that will be added atop of the navigation bar.
* @param menuButton A [MenuButton] to be used for [ItemType.MENU]. * @param menuButton A [MenuButton] to be used for [ItemType.MENU].
* @param browsingModeManager A helper class that provides access to the current [BrowsingMode]. * @param browsingModeManager A helper class that provides access to the current [BrowsingMode].
* @param customTabSessionId Custom tab session ID.
* *
* Defaults to [NavigationItems.defaultItems] which provides a standard set of navigation items. * Defaults to [NavigationItems.defaultItems] which provides a standard set of navigation items.
*/ */
class BottomToolbarContainerView( class BottomToolbarContainerView(
context: Context, context: Context,
container: ViewGroup, parent: ViewGroup,
navigationItems: List<ActionItem> = NavigationItems.defaultItems, navigationItems: List<ActionItem> = NavigationItems.defaultItems,
androidToolbarView: View? = null, androidToolbarView: View? = null,
menuButton: MenuButton, menuButton: MenuButton,
browsingModeManager: BrowsingModeManager, browsingModeManager: BrowsingModeManager,
customTabSessionId: String? = null,
) { ) {
private val toolbarContainerView = ToolbarContainerView(context)
val navbarIntegration =
NavbarIntegration(toolbarContainerView, parent.context.components.core.store, customTabSessionId)
init { init {
val composeView = ComposeView(context).apply { ComposeView(parent.context).apply {
setContent { setContent {
val isPrivate = browsingModeManager.mode.isPrivate val isPrivate = browsingModeManager.mode.isPrivate
val tabCount = context.components.core.store.observeAsState(initialValue = 0) { browserState -> val tabCount = context.components.core.store.observeAsState(initialValue = 0) { browserState ->
@ -71,16 +82,52 @@ class BottomToolbarContainerView(
} }
} }
} }
toolbarContainerView.addView(this)
} }
val layoutParams = CoordinatorLayout.LayoutParams( toolbarContainerView.layoutParams = CoordinatorLayout.LayoutParams(
CoordinatorLayout.LayoutParams.MATCH_PARENT, CoordinatorLayout.LayoutParams.MATCH_PARENT,
CoordinatorLayout.LayoutParams.WRAP_CONTENT, CoordinatorLayout.LayoutParams.WRAP_CONTENT,
).apply { ).apply {
gravity = Gravity.BOTTOM gravity = Gravity.BOTTOM
behavior = EngineViewScrollingBehavior(parent.context, null, ViewPosition.BOTTOM)
}
parent.addView(toolbarContainerView)
}
}
/**
* A container view that hosts a navigation bar and, possibly, a toolbar.
* Facilitates hide-on-scroll behavior.
*/
class ToolbarContainerView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr), ScrollableToolbar {
override fun enableScrolling() {
(layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? EngineViewScrollingBehavior)?.enableScrolling()
} }
}
override fun disableScrolling() {
(layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? EngineViewScrollingBehavior)?.disableScrolling()
}
}
composeView.layoutParams = layoutParams override fun expand() {
container.addView(composeView) (layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? EngineViewScrollingBehavior)?.forceExpand(this@ToolbarContainerView)
}
}
override fun collapse() {
(layoutParams as? CoordinatorLayout.LayoutParams)?.apply {
(behavior as? EngineViewScrollingBehavior)?.forceCollapse(this@ToolbarContainerView)
}
} }
} }

@ -0,0 +1,34 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.toolbar.navbar
import androidx.annotation.VisibleForTesting
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.toolbar.ScrollableToolbar
import mozilla.components.feature.toolbar.ToolbarBehaviorController
import mozilla.components.support.base.feature.LifecycleAwareFeature
/**
* The feature responsible for scrolling behaviour of the navigation bar.
* When the content of a tab is being scrolled, the nav bar will react
* to the user interactions.
*/
class NavbarIntegration(
val toolbar: ScrollableToolbar,
val store: BrowserStore,
sessionId: String?,
) : LifecycleAwareFeature {
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
var toolbarController = ToolbarBehaviorController(toolbar, store, sessionId)
override fun start() {
toolbarController.start()
}
override fun stop() {
toolbarController.stop()
}
}

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.toolbar package org.mozilla.fenix.components.toolbar.navbar
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
@ -26,8 +26,8 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import mozilla.components.browser.menu.view.MenuButton import mozilla.components.browser.menu.view.MenuButton
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.ItemType.STANDARD import org.mozilla.fenix.components.toolbar.navbar.ItemType.STANDARD
import org.mozilla.fenix.components.toolbar.ItemType.TAB_COUNTER import org.mozilla.fenix.components.toolbar.navbar.ItemType.TAB_COUNTER
import org.mozilla.fenix.compose.TabCounter import org.mozilla.fenix.compose.TabCounter
import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.compose.annotation.LightDarkPreview
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
@ -153,7 +153,7 @@ object NavigationItems {
val tabs = ActionItem( val tabs = ActionItem(
iconId = R.drawable.mozac_ui_tabcounter_box, iconId = R.drawable.mozac_ui_tabcounter_box,
descriptionResourceId = R.string.mozac_tab_counter_content_description, descriptionResourceId = R.string.mozac_tab_counter_content_description,
type = ItemType.TAB_COUNTER, type = TAB_COUNTER,
) )
val defaultItems = listOf(back, forward, home, tabs, menu) val defaultItems = listOf(back, forward, home, tabs, menu)

@ -92,9 +92,9 @@ import org.mozilla.fenix.components.FenixSnackbar
import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.PrivateShortcutCreateManager
import org.mozilla.fenix.components.TabCollectionStorage import org.mozilla.fenix.components.TabCollectionStorage
import org.mozilla.fenix.components.appstate.AppAction import org.mozilla.fenix.components.appstate.AppAction
import org.mozilla.fenix.components.toolbar.BottomToolbarContainerView
import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.components.toolbar.navbar.BottomToolbarContainerView
import org.mozilla.fenix.databinding.FragmentHomeBinding import org.mozilla.fenix.databinding.FragmentHomeBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.containsQueryParameters import org.mozilla.fenix.ext.containsQueryParameters
@ -458,7 +458,7 @@ class HomeFragment : Fragment() {
BottomToolbarContainerView( BottomToolbarContainerView(
context = requireContext(), context = requireContext(),
container = binding.homeLayout, parent = binding.homeLayout,
androidToolbarView = if (isToolbarAtBottom) binding.toolbarLayout else null, androidToolbarView = if (isToolbarAtBottom) binding.toolbarLayout else null,
menuButton = menuButton, menuButton = menuButton,
browsingModeManager = browsingModeManager, browsingModeManager = browsingModeManager,

@ -0,0 +1,43 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.components.toolbar
import io.mockk.mockk
import io.mockk.verify
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
@RunWith(FenixRobolectricTestRunner::class)
class NavbarIntegrationTest {
private lateinit var feature: NavbarIntegration
@Before
fun setup() {
feature = NavbarIntegration(
toolbar = mockk(),
store = mockk(),
sessionId = null,
).apply {
toolbarController = mockk(relaxed = true)
}
}
@Test
fun `WHEN the feature starts THEN toolbar controllers starts as well`() {
feature.start()
verify { feature.toolbarController.start() }
}
@Test
fun `WHEN the feature stops THEN toolbar controllers stops as well`() {
feature.stop()
verify { feature.toolbarController.stop() }
}
}
Loading…
Cancel
Save