diff --git a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt index 07e36dea2d..0f237666ef 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -131,7 +131,6 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.FindInPageIntegration import org.mozilla.fenix.components.StoreProvider 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.BrowserFragmentStore 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.interactor.BrowserToolbarInteractor 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.customtabs.ExternalAppBrowserActivity import org.mozilla.fenix.databinding.FragmentBrowserBinding @@ -223,6 +224,7 @@ abstract class BaseBrowserFragment : private val promptsFeature = ViewBoundFeatureWrapper() private val findInPageIntegration = ViewBoundFeatureWrapper() private val toolbarIntegration = ViewBoundFeatureWrapper() + private val navbarIntegration = ViewBoundFeatureWrapper() private val sitePermissionsFeature = ViewBoundFeatureWrapper() private val fullScreenFeature = ViewBoundFeatureWrapper() private val swipeRefreshFeature = ViewBoundFeatureWrapper() @@ -467,11 +469,17 @@ abstract class BaseBrowserFragment : BottomToolbarContainerView( context = context, - container = binding.browserLayout, + parent = binding.browserLayout, androidToolbarView = if (isToolbarAtBottom) browserToolbar else null, menuButton = menuButton, browsingModeManager = activity.browsingModeManager, - ) + ).also { + navbarIntegration.set( + feature = it.navbarIntegration, + owner = this, + view = view, + ) + } } toolbarIntegration.set( diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/BottomToolbarContainerView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt similarity index 58% rename from app/src/main/java/org/mozilla/fenix/components/toolbar/BottomToolbarContainerView.kt rename to app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt index 1422febdd3..f65d9c7106 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/BottomToolbarContainerView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt @@ -2,12 +2,14 @@ * 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 +package org.mozilla.fenix.components.toolbar.navbar import android.content.Context +import android.util.AttributeSet import android.view.Gravity import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import androidx.compose.foundation.layout.Column import androidx.compose.ui.platform.ComposeView 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.state.selector.normalTabs import mozilla.components.browser.state.selector.privateTabs +import mozilla.components.concept.toolbar.ScrollableToolbar 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.BrowsingModeManager 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]. * * @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 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 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. */ class BottomToolbarContainerView( context: Context, - container: ViewGroup, + parent: ViewGroup, navigationItems: List = NavigationItems.defaultItems, androidToolbarView: View? = null, menuButton: MenuButton, browsingModeManager: BrowsingModeManager, + customTabSessionId: String? = null, ) { + private val toolbarContainerView = ToolbarContainerView(context) + val navbarIntegration = + NavbarIntegration(toolbarContainerView, parent.context.components.core.store, customTabSessionId) + init { - val composeView = ComposeView(context).apply { + ComposeView(parent.context).apply { setContent { val isPrivate = browsingModeManager.mode.isPrivate 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.WRAP_CONTENT, ).apply { 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 - container.addView(composeView) + override fun expand() { + (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) + } } } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavbarIntegration.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavbarIntegration.kt new file mode 100644 index 0000000000..c07d1dc3a1 --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavbarIntegration.kt @@ -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() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/NavigationBar.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt similarity index 96% rename from app/src/main/java/org/mozilla/fenix/components/toolbar/NavigationBar.kt rename to app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt index 9660cd8293..e6bc1194a2 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/NavigationBar.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt @@ -2,7 +2,7 @@ * 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 +package org.mozilla.fenix.components.toolbar.navbar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement @@ -26,8 +26,8 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import mozilla.components.browser.menu.view.MenuButton import org.mozilla.fenix.R -import org.mozilla.fenix.components.toolbar.ItemType.STANDARD -import org.mozilla.fenix.components.toolbar.ItemType.TAB_COUNTER +import org.mozilla.fenix.components.toolbar.navbar.ItemType.STANDARD +import org.mozilla.fenix.components.toolbar.navbar.ItemType.TAB_COUNTER import org.mozilla.fenix.compose.TabCounter import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme @@ -153,7 +153,7 @@ object NavigationItems { val tabs = ActionItem( iconId = R.drawable.mozac_ui_tabcounter_box, descriptionResourceId = R.string.mozac_tab_counter_content_description, - type = ItemType.TAB_COUNTER, + type = TAB_COUNTER, ) val defaultItems = listOf(back, forward, home, tabs, menu) diff --git a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt index e64adaebe5..cb31a951ed 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -92,9 +92,9 @@ import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.PrivateShortcutCreateManager import org.mozilla.fenix.components.TabCollectionStorage 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.ToolbarPosition +import org.mozilla.fenix.components.toolbar.navbar.BottomToolbarContainerView import org.mozilla.fenix.databinding.FragmentHomeBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.containsQueryParameters @@ -458,7 +458,7 @@ class HomeFragment : Fragment() { BottomToolbarContainerView( context = requireContext(), - container = binding.homeLayout, + parent = binding.homeLayout, androidToolbarView = if (isToolbarAtBottom) binding.toolbarLayout else null, menuButton = menuButton, browsingModeManager = browsingModeManager, diff --git a/app/src/test/java/org/mozilla/fenix/components/toolbar/NavbarIntegrationTest.kt b/app/src/test/java/org/mozilla/fenix/components/toolbar/NavbarIntegrationTest.kt new file mode 100644 index 0000000000..ba308bcea2 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/components/toolbar/NavbarIntegrationTest.kt @@ -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() } + } +}