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 8ac5638f7..59999afaa 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/BaseBrowserFragment.kt @@ -21,6 +21,8 @@ import androidx.activity.result.ActivityResultLauncher import androidx.annotation.CallSuper import androidx.annotation.VisibleForTesting import androidx.appcompat.app.AlertDialog +import androidx.compose.foundation.layout.Column +import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.getSystemService @@ -137,11 +139,14 @@ import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarController import org.mozilla.fenix.components.toolbar.DefaultBrowserToolbarMenuController import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.ToolbarIntegration +import org.mozilla.fenix.components.toolbar.ToolbarMenu 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.BrowserNavBar import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration +import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.crashes.CrashContentIntegration import org.mozilla.fenix.customtabs.ExternalAppBrowserActivity import org.mozilla.fenix.databinding.FragmentBrowserBinding @@ -170,6 +175,7 @@ import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.biometric.BiometricPromptFeature import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.ext.toDisplayTitle +import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration @@ -461,6 +467,7 @@ abstract class BaseBrowserFragment : // We need a second menu button, but we could reuse the existing builder. val menuButton = MenuButton(requireContext()).apply { menuBuilder = browserToolbarView.menuToolbar.menuBuilder + // We have to set colorFilter manually as the button isn't being managed by a [BrowserToolbarView]. setColorFilter( ContextCompat.getColor( context, @@ -472,9 +479,63 @@ abstract class BaseBrowserFragment : _bottomToolbarContainerView = BottomToolbarContainerView( context = context, parent = binding.browserLayout, - androidToolbarView = if (isToolbarAtBottom) browserToolbar else null, - menuButton = menuButton, - isPrivateMode = activity.browsingModeManager.mode.isPrivate, + composableContent = { + FirefoxTheme { + Column { + if (isToolbarAtBottom) { + AndroidView(factory = { _ -> browserToolbar }) + } else { + Divider() + } + + BrowserNavBar( + isPrivateMode = activity.browsingModeManager.mode.isPrivate, + browserStore = context.components.core.store, + onBackButtonClick = { + browserToolbarInteractor.onBrowserToolbarMenuItemTapped( + ToolbarMenu.Item.Back(viewHistory = false), + ) + }, + onBackButtonLongPress = { + browserToolbarInteractor.onBrowserToolbarMenuItemTapped( + ToolbarMenu.Item.Back(viewHistory = true), + ) + }, + onForwardButtonClick = { + browserToolbarInteractor.onBrowserToolbarMenuItemTapped( + ToolbarMenu.Item.Forward(viewHistory = false), + ) + }, + onForwardButtonLongPress = { + browserToolbarInteractor.onBrowserToolbarMenuItemTapped( + ToolbarMenu.Item.Forward(viewHistory = true), + ) + }, + onHomeButtonClick = { + Events.browserToolbarHomeTapped.record(NoExtras()) + browserAnimator.captureEngineViewAndDrawStatically { + findNavController().navigate( + BrowserFragmentDirections.actionGlobalHome(), + ) + } + }, + menuButton = menuButton, + onTabsButtonClick = { + thumbnailsFeature.get()?.requestScreenshot() + findNavController().nav( + R.id.browserFragment, + BrowserFragmentDirections.actionGlobalTabsTrayFragment( + page = when (activity.browsingModeManager.mode) { + BrowsingMode.Normal -> Page.NormalTabs + BrowsingMode.Private -> Page.PrivateTabs + }, + ), + ) + }, + ) + } + } + }, ) navbarIntegration.set( diff --git a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt index e16afc387..a80d0a472 100644 --- a/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt +++ b/app/src/main/java/org/mozilla/fenix/browser/TabPreview.kt @@ -10,6 +10,8 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.foundation.layout.Column +import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.doOnNextLayout import androidx.core.view.isVisible @@ -23,9 +25,12 @@ import org.mozilla.fenix.R 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.components.toolbar.navbar.BrowserNavBar +import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.databinding.TabPreviewBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.settings +import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.ThemeManager import kotlin.math.min @@ -61,12 +66,47 @@ class TabPreview @JvmOverloads constructor( binding.menuButton.isVisible = !isNavBarEnabled if (isNavBarEnabled) { + val browserStore = context.components.core.store BottomToolbarContainerView( context = context, parent = this, - androidToolbarView = if (!isToolbarAtTop) binding.fakeToolbar else null, - menuButton = MenuButton(context), + composableContent = { + FirefoxTheme { + Column { + if (!isToolbarAtTop) { + AndroidView(factory = { _ -> binding.fakeToolbar }) + } else { + Divider() + } + + BrowserNavBar( + isPrivateMode = browserStore.state.selectedTab?.content?.private ?: false, + browserStore = browserStore, + onBackButtonClick = { + // no-op + }, + onBackButtonLongPress = { + // no-op + }, + onForwardButtonClick = { + // no-op + }, + onForwardButtonLongPress = { + // no-op + }, + onHomeButtonClick = { + // no-op + }, + menuButton = MenuButton(context), + onTabsButtonClick = { + // no-op + }, + ) + } + } + }, ) + removeView(binding.fakeToolbar) } diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt index 5f37d54ce..12462c770 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/BottomToolbarContainerView.kt @@ -7,44 +7,26 @@ 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.runtime.Composable import androidx.compose.ui.platform.ComposeView -import androidx.compose.ui.viewinterop.AndroidView 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.compose.Divider -import org.mozilla.fenix.ext.components -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 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 isPrivateMode If browsing in [BrowsingMode.Private]. - * - * Defaults to [NavigationItems.defaultItems] which provides a standard set of navigation items. + * @param composableContent */ class BottomToolbarContainerView( context: Context, parent: ViewGroup, - navigationItems: List = NavigationItems.defaultItems, - androidToolbarView: View? = null, - menuButton: MenuButton, - isPrivateMode: Boolean = false, + composableContent: @Composable () -> Unit, ) { val toolbarContainerView = ToolbarContainerView(context) @@ -53,29 +35,7 @@ class BottomToolbarContainerView( init { composeView = ComposeView(context).apply { setContent { - val tabCount = context.components.core.store.observeAsState(initialValue = 0) { browserState -> - if (isPrivateMode) { - browserState.privateTabs.size - } else { - browserState.normalTabs.size - } - }.value - - FirefoxTheme { - Column { - if (androidToolbarView != null) { - AndroidView(factory = { _ -> androidToolbarView }) - } else { - Divider() - } - - NavigationBar( - actionItems = navigationItems, - tabCount = tabCount, - menuButton = menuButton, - ) - } - } + composableContent() } toolbarContainerView.addView(this) diff --git a/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt index e6bc1194a..3c2137379 100644 --- a/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt +++ b/app/src/main/java/org/mozilla/fenix/components/toolbar/navbar/NavigationBar.kt @@ -6,8 +6,8 @@ package org.mozilla.fenix.components.toolbar.navbar import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -17,166 +17,337 @@ import androidx.compose.material.IconButton import androidx.compose.material.LocalContentColor import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider -import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat import mozilla.components.browser.menu.view.MenuButton +import mozilla.components.browser.state.selector.normalTabs +import mozilla.components.browser.state.selector.privateTabs +import mozilla.components.browser.state.store.BrowserStore +import mozilla.components.lib.state.ext.observeAsState import org.mozilla.fenix.R -import org.mozilla.fenix.components.toolbar.navbar.ItemType.STANDARD -import org.mozilla.fenix.components.toolbar.navbar.ItemType.TAB_COUNTER +import org.mozilla.fenix.browser.browsingmode.BrowsingMode +import org.mozilla.fenix.compose.LongPressIconButton import org.mozilla.fenix.compose.TabCounter import org.mozilla.fenix.compose.annotation.LightDarkPreview +import org.mozilla.fenix.search.SearchDialogFragment import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.Theme +import org.mozilla.fenix.theme.ThemeManager /** * Top-level UI for displaying the navigation bar. * - * @param actionItems A list of [ActionItem] used to populate the bar. - * @param tabCount The number of opened tabs. - * @param menuButton A [MenuButton] to be used for [ItemType.MENU]. + * @param isPrivateMode If browsing in [BrowsingMode.Private]. + * @param browserStore The [BrowserStore] instance used to observe tabs state. + * @param menuButton A [MenuButton] to be used as an [AndroidView]. The view implementation + * contains the builder for the menu, so for the time being we are not implementing it as a composable. + * @param onBackButtonClick Invoked when the user clicks the back button in the nav bar. + * @param onBackButtonLongPress Invoked when the user long-presses the back button in the nav bar. + * @param onForwardButtonClick Invoked when the user clicks the forward button in the nav bar. + * @param onForwardButtonLongPress Invoked when the user long-presses the forward button in the nav bar. + * @param onHomeButtonClick Invoked when the user clicks the home button in the nav bar. + * @param onTabsButtonClick Invoked when the user clicks the tabs button in the nav bar. */ +@Suppress("LongParameterList") @Composable -fun NavigationBar( - actionItems: List, - tabCount: Int, - menuButton: MenuButton? = null, +fun BrowserNavBar( + isPrivateMode: Boolean, + browserStore: BrowserStore, + menuButton: MenuButton, + onBackButtonClick: () -> Unit, + onBackButtonLongPress: () -> Unit, + onForwardButtonClick: () -> Unit, + onForwardButtonLongPress: () -> Unit, + onHomeButtonClick: () -> Unit, + onTabsButtonClick: () -> Unit, ) { - Box( - modifier = Modifier - .background(FirefoxTheme.colors.layer1) - .height(48.dp) - .fillMaxWidth(), - ) { - Row( - modifier = Modifier - .align(Alignment.Center) - .fillMaxWidth() - .padding(horizontal = 16.dp), - horizontalArrangement = Arrangement.SpaceBetween, - ) { - actionItems.forEach { - when (it.type) { - ItemType.STANDARD -> { - IconButton(onClick = {}) { - Icon( - painter = painterResource(it.iconId), - stringResource(id = it.descriptionResourceId), - tint = FirefoxTheme.colors.iconPrimary, - ) - } - } - ItemType.TAB_COUNTER -> { - CompositionLocalProvider(LocalContentColor provides FirefoxTheme.colors.iconPrimary) { - IconButton(onClick = {}) { - TabCounter(tabCount = tabCount) - } - } - } - - ItemType.MENU -> { - // [ActionItem] will be refactored here: - // https://bugzilla.mozilla.org/show_bug.cgi?id=1878827 - if (menuButton != null) { - AndroidView( - modifier = Modifier.size(48.dp), - factory = { _ -> menuButton }, - ) - } else { - IconButton(onClick = {}) { - Icon( - painter = painterResource(it.iconId), - stringResource(id = it.descriptionResourceId), - tint = FirefoxTheme.colors.iconPrimary, - ) - } - } - } - } - } + val tabCount = browserStore.observeAsState(initialValue = 0) { browserState -> + if (isPrivateMode) { + browserState.privateTabs.size + } else { + browserState.normalTabs.size } + }.value + + NavBar { + BackButton( + onBackButtonClick = onBackButtonClick, + onBackButtonLongPress = onBackButtonLongPress, + ) + + ForwardButton( + onForwardButtonClick = onForwardButtonClick, + onForwardButtonLongPress = onForwardButtonLongPress, + ) + + HomeButton( + onHomeButtonClick = onHomeButtonClick, + ) + + TabsButton( + onTabsButtonClick = onTabsButtonClick, + tabCount = tabCount, + ) + + MenuButton(menuButton = menuButton) } } /** - * Represents a navigation bar element. + * Top-level UI for displaying the navigation bar. * - * @property iconId Resource ID of the icon that item should display. - * @property descriptionResourceId Text used as a content description by accessibility services. - * @property type Type of the item, defaults to [ItemType.STANDARD]. + * @param isPrivateMode If browsing in [BrowsingMode.Private]. + * @param browserStore The [BrowserStore] instance used to observe tabs state. + * @param menuButton A [MenuButton] to be used as an [AndroidView]. The view implementation + * contains the builder for the menu, so for the time being we are not implementing it as a composable. + * @param onSearchButtonClick Invoked when the user clicks the search button in the nav bar. The button + * is visible only on home screen and activates [SearchDialogFragment]. + * @param onTabsButtonClick Invoked when the user clicks the tabs button in the nav bar. */ -data class ActionItem( - val iconId: Int, - val descriptionResourceId: Int, - val type: ItemType = ItemType.STANDARD, -) +@Composable +fun HomeNavBar( + isPrivateMode: Boolean, + browserStore: BrowserStore, + menuButton: MenuButton, + onSearchButtonClick: () -> Unit, + onTabsButtonClick: () -> Unit, +) { + val tabCount = browserStore.observeAsState(initialValue = 0) { browserState -> + if (isPrivateMode) { + browserState.privateTabs.size + } else { + browserState.normalTabs.size + } + }.value -/** - * Enumerates the types of items that can be used in a navigation bar. - * - * [STANDARD] - Represents a regular navigation item. Used for most navigation actions. - * [TAB_COUNTER] - Represents a specialized item used to display a count, such as the number of open tabs in a browser. - */ -enum class ItemType { - STANDARD, TAB_COUNTER, MENU + NavBar { + BackButton( + onBackButtonClick = { + // no-op + }, + onBackButtonLongPress = { + // no-op + }, + ) + + ForwardButton( + onForwardButtonClick = { + // no-op + }, + onForwardButtonLongPress = { + // no-op + }, + ) + + SearchWebButton( + onSearchButtonClick = onSearchButtonClick, + ) + + TabsButton( + onTabsButtonClick = onTabsButtonClick, + tabCount = tabCount, + ) + + MenuButton(menuButton = menuButton) + } } -/** - * Provides a collection of navigation items used in the application's navigation bar. - */ -object NavigationItems { - val home = ActionItem( - iconId = R.drawable.mozac_ic_home_24, - descriptionResourceId = R.string.browser_toolbar_home, +@Composable +private fun NavBar( + content: @Composable RowScope.() -> Unit, +) { + Row( + modifier = Modifier + .background(FirefoxTheme.colors.layer1) + .height(48.dp) + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalArrangement = Arrangement.SpaceBetween, + content = content, ) +} - val menu = ActionItem( - iconId = R.drawable.mozac_ic_ellipsis_vertical_24, - descriptionResourceId = R.string.mozac_browser_menu_button, - type = ItemType.MENU, - ) +@Composable +private fun BackButton( + onBackButtonClick: () -> Unit, + onBackButtonLongPress: () -> Unit, +) { + LongPressIconButton( + onClick = onBackButtonClick, + onLongClick = onBackButtonLongPress, + ) { + Icon( + painter = painterResource(R.drawable.mozac_ic_back_24), + stringResource(id = R.string.browser_menu_back), + tint = FirefoxTheme.colors.iconPrimary, + ) + } +} + +@Composable +private fun ForwardButton( + onForwardButtonClick: () -> Unit, + onForwardButtonLongPress: () -> Unit, +) { + LongPressIconButton( + onClick = onForwardButtonClick, + onLongClick = onForwardButtonLongPress, + ) { + Icon( + painter = painterResource(R.drawable.mozac_ic_forward_24), + stringResource(id = R.string.browser_menu_forward), + tint = FirefoxTheme.colors.iconPrimary, + ) + } +} - val back = ActionItem( - iconId = R.drawable.mozac_ic_back_24, - descriptionResourceId = R.string.browser_menu_back, +@Composable +private fun HomeButton( + onHomeButtonClick: () -> Unit, +) { + IconButton( + onClick = onHomeButtonClick, + ) { + Icon( + painter = painterResource(R.drawable.mozac_ic_home_24), + stringResource(id = R.string.browser_toolbar_home), + tint = FirefoxTheme.colors.iconPrimary, + ) + } +} + +@Composable +private fun SearchWebButton( + onSearchButtonClick: () -> Unit, +) { + IconButton( + onClick = onSearchButtonClick, + ) { + Icon( + painter = painterResource(R.drawable.mozac_ic_search_24), + stringResource(id = R.string.search_hint), + tint = FirefoxTheme.colors.iconPrimary, + ) + } +} + +@Composable +private fun MenuButton( + menuButton: MenuButton, +) { + // Should refactor it to be a simple IconButton with a click listener + // once the redesigned menu is implemented. + // https://bugzilla.mozilla.org/show_bug.cgi?id=1884049 + AndroidView( + modifier = Modifier.size(48.dp), + factory = { _ -> menuButton }, ) +} + +@Composable +private fun TabsButton( + onTabsButtonClick: () -> Unit, + tabCount: Int, +) { + CompositionLocalProvider(LocalContentColor provides FirefoxTheme.colors.iconPrimary) { + IconButton(onClick = { onTabsButtonClick() }) { + TabCounter(tabCount = tabCount) + } + } +} - val forward = ActionItem( - iconId = R.drawable.mozac_ic_forward_24, - descriptionResourceId = R.string.browser_menu_forward, +@Composable +private fun HomeNavBarPreviewRoot(isPrivateMode: Boolean) { + val context = LocalContext.current + val colorId = if (isPrivateMode) { + // private mode preview keeps using black colour as textPrimary + ThemeManager.resolveAttribute(R.attr.textOnColorPrimary, context) + } else { + ThemeManager.resolveAttribute(R.attr.textPrimary, context) + } + val menuButton = MenuButton(context).apply { + setColorFilter( + ContextCompat.getColor( + context, + colorId, + ), + ) + } + + HomeNavBar( + onSearchButtonClick = {}, + menuButton = menuButton, + onTabsButtonClick = {}, + isPrivateMode = false, + browserStore = BrowserStore(), ) +} - val tabs = ActionItem( - iconId = R.drawable.mozac_ui_tabcounter_box, - descriptionResourceId = R.string.mozac_tab_counter_content_description, - type = TAB_COUNTER, +@Composable +private fun OpenTabNavBarNavBarPreviewRoot(isPrivateMode: Boolean) { + val context = LocalContext.current + val colorId = if (isPrivateMode) { + // private mode preview keeps using black colour as textPrimary + ThemeManager.resolveAttribute(R.attr.textOnColorPrimary, context) + } else { + ThemeManager.resolveAttribute(R.attr.textPrimary, context) + } + val menuButton = MenuButton(context).apply { + setColorFilter( + ContextCompat.getColor( + context, + colorId, + ), + ) + } + + BrowserNavBar( + onBackButtonClick = {}, + onBackButtonLongPress = {}, + onForwardButtonClick = {}, + onForwardButtonLongPress = {}, + onHomeButtonClick = {}, + menuButton = menuButton, + onTabsButtonClick = {}, + isPrivateMode = false, + browserStore = BrowserStore(), ) +} - val defaultItems = listOf(back, forward, home, tabs, menu) +@LightDarkPreview +@Composable +private fun HomeNavBarPreview() { + FirefoxTheme { + HomeNavBarPreviewRoot(isPrivateMode = false) + } +} + +@Preview +@Composable +private fun HomeNavBarPrivatePreview() { + FirefoxTheme(theme = Theme.Private) { + HomeNavBarPreviewRoot(isPrivateMode = true) + } } @LightDarkPreview @Composable -private fun NavigationBarPreview() { +private fun OpenTabNavBarPreview() { FirefoxTheme { - NavigationBar( - actionItems = NavigationItems.defaultItems, - tabCount = 0, - ) + OpenTabNavBarNavBarPreviewRoot(isPrivateMode = false) } } @Preview @Composable -private fun NavigationBarPrivatePreview() { +private fun OpenTabNavBarPrivatePreview() { FirefoxTheme(theme = Theme.Private) { - NavigationBar( - actionItems = NavigationItems.defaultItems, - tabCount = 0, - ) + OpenTabNavBarNavBarPreviewRoot(isPrivateMode = true) } } diff --git a/app/src/main/java/org/mozilla/fenix/compose/LongPressIconButton.kt b/app/src/main/java/org/mozilla/fenix/compose/LongPressIconButton.kt new file mode 100644 index 000000000..cfebeaf8c --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/compose/LongPressIconButton.kt @@ -0,0 +1,56 @@ +/* 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.compose + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.combinedClickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.Box +import androidx.compose.material.ContentAlpha +import androidx.compose.material.IconButton +import androidx.compose.material.LocalContentAlpha +import androidx.compose.material.minimumInteractiveComponentSize +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.semantics.Role +import androidx.compose.ui.unit.dp + +/** + * An [IconButton] that supports a long press gesture. + */ + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun LongPressIconButton( + onClick: () -> Unit, + onLongClick: () -> Unit, + modifier: Modifier = Modifier, + enabled: Boolean = true, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, + content: @Composable () -> Unit, +) { + Box( + modifier = modifier + .minimumInteractiveComponentSize() + .combinedClickable( + onClick = onClick, + onLongClick = onLongClick, + enabled = enabled, + role = Role.Button, + interactionSource = interactionSource, + indication = rememberRipple(bounded = false, radius = RippleRadius), + ), + contentAlignment = Alignment.Center, + ) { + val contentAlpha = if (enabled) LocalContentAlpha.current else ContentAlpha.disabled + CompositionLocalProvider(LocalContentAlpha provides contentAlpha, content = content) + } +} + +private val RippleRadius = 24.dp 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 a847f7508..983e57943 100644 --- a/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/home/HomeFragment.kt @@ -14,6 +14,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.annotation.VisibleForTesting +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding @@ -29,6 +30,7 @@ import androidx.compose.ui.semantics.testTag import androidx.compose.ui.semantics.testTagsAsResourceId import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat.getColor @@ -81,12 +83,16 @@ import mozilla.components.service.glean.private.NoExtras import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.ui.colors.PhotonColors import org.mozilla.fenix.BrowserDirection +import org.mozilla.fenix.GleanMetrics.Events import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.Homepage import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr +import org.mozilla.fenix.GleanMetrics.StartOnHome import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.NavGraphDirections import org.mozilla.fenix.R import org.mozilla.fenix.addons.showSnackBar +import org.mozilla.fenix.browser.BrowserAnimator import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.tabstrip.TabStrip import org.mozilla.fenix.components.FenixSnackbar @@ -96,7 +102,9 @@ import org.mozilla.fenix.components.appstate.AppAction 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.components.toolbar.navbar.HomeNavBar import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration +import org.mozilla.fenix.compose.Divider import org.mozilla.fenix.databinding.FragmentHomeBinding import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.containsQueryParameters @@ -131,6 +139,7 @@ import org.mozilla.fenix.nimbus.FxNimbus import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.SearchSelectorMenu +import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD @@ -466,9 +475,49 @@ class HomeFragment : Fragment() { _bottomToolbarContainerView = BottomToolbarContainerView( context = requireContext(), parent = binding.homeLayout, - androidToolbarView = if (isToolbarAtBottom) binding.toolbarLayout else null, - menuButton = menuButton, - isPrivateMode = activity.browsingModeManager.mode.isPrivate, + composableContent = { + FirefoxTheme { + Column { + if (isToolbarAtBottom) { + AndroidView(factory = { _ -> binding.toolbarLayout }) + } else { + Divider() + } + + HomeNavBar( + isPrivateMode = activity.browsingModeManager.mode.isPrivate, + browserStore = requireContext().components.core.store, + onSearchButtonClick = { + val directions = + NavGraphDirections.actionGlobalSearchDialog( + sessionId = null, + ) + + findNavController().nav( + findNavController().currentDestination?.id, + directions, + BrowserAnimator.getToolbarNavOptions(activity), + ) + + Events.searchBarTapped.record(Events.SearchBarTappedExtra("HOME")) + }, + menuButton = menuButton, + onTabsButtonClick = { + StartOnHome.openTabsTray.record(NoExtras()) + findNavController().nav( + findNavController().currentDestination?.id, + NavGraphDirections.actionGlobalTabsTrayFragment( + page = when (browsingModeManager.mode) { + BrowsingMode.Normal -> Page.NormalTabs + BrowsingMode.Private -> Page.PrivateTabs + }, + ), + ) + }, + ) + } + } + }, ) navbarIntegration.set(