Bug 1878827 - Add nav buttons click handling

fenix/125.0
mike a 3 months ago committed by mergify[bot]
parent f56de3bb73
commit f03a6a7215

@ -21,6 +21,8 @@ import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.CallSuper import androidx.annotation.CallSuper
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.compose.foundation.layout.Column
import androidx.compose.ui.viewinterop.AndroidView
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService 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.DefaultBrowserToolbarMenuController
import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature import org.mozilla.fenix.components.toolbar.IncompleteRedesignToolbarFeature
import org.mozilla.fenix.components.toolbar.ToolbarIntegration 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.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.BottomToolbarContainerView
import org.mozilla.fenix.components.toolbar.navbar.BrowserNavBar
import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration
import org.mozilla.fenix.compose.Divider
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
@ -170,6 +175,7 @@ import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.settings.biometric.BiometricPromptFeature import org.mozilla.fenix.settings.biometric.BiometricPromptFeature
import org.mozilla.fenix.tabstray.Page import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.ext.toDisplayTitle import org.mozilla.fenix.tabstray.ext.toDisplayTitle
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import org.mozilla.fenix.utils.allowUndo import org.mozilla.fenix.utils.allowUndo
import org.mozilla.fenix.wifi.SitePermissionsWifiIntegration 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. // We need a second menu button, but we could reuse the existing builder.
val menuButton = MenuButton(requireContext()).apply { val menuButton = MenuButton(requireContext()).apply {
menuBuilder = browserToolbarView.menuToolbar.menuBuilder menuBuilder = browserToolbarView.menuToolbar.menuBuilder
// We have to set colorFilter manually as the button isn't being managed by a [BrowserToolbarView].
setColorFilter( setColorFilter(
ContextCompat.getColor( ContextCompat.getColor(
context, context,
@ -472,9 +479,63 @@ abstract class BaseBrowserFragment :
_bottomToolbarContainerView = BottomToolbarContainerView( _bottomToolbarContainerView = BottomToolbarContainerView(
context = context, context = context,
parent = binding.browserLayout, parent = binding.browserLayout,
androidToolbarView = if (isToolbarAtBottom) browserToolbar else null, composableContent = {
menuButton = menuButton, FirefoxTheme {
isPrivateMode = activity.browsingModeManager.mode.isPrivate, 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( navbarIntegration.set(

@ -10,6 +10,8 @@ import android.view.Gravity
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.appcompat.content.res.AppCompatResources 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.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.view.doOnNextLayout import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible 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.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.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.databinding.TabPreviewBinding
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
import kotlin.math.min import kotlin.math.min
@ -61,12 +66,47 @@ class TabPreview @JvmOverloads constructor(
binding.menuButton.isVisible = !isNavBarEnabled binding.menuButton.isVisible = !isNavBarEnabled
if (isNavBarEnabled) { if (isNavBarEnabled) {
val browserStore = context.components.core.store
BottomToolbarContainerView( BottomToolbarContainerView(
context = context, context = context,
parent = this, parent = this,
androidToolbarView = if (!isToolbarAtTop) binding.fakeToolbar else null, composableContent = {
menuButton = MenuButton(context), 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) removeView(binding.fakeToolbar)
} }

@ -7,44 +7,26 @@ package org.mozilla.fenix.components.toolbar.navbar
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.Gravity import android.view.Gravity
import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout 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.platform.ComposeView
import androidx.compose.ui.viewinterop.AndroidView
import androidx.coordinatorlayout.widget.CoordinatorLayout 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.concept.toolbar.ScrollableToolbar
import mozilla.components.lib.state.ext.observeAsState
import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior import mozilla.components.ui.widgets.behavior.EngineViewScrollingBehavior
import mozilla.components.ui.widgets.behavior.ViewPosition 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]. * 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 parent 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 composableContent
* @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.
*/ */
class BottomToolbarContainerView( class BottomToolbarContainerView(
context: Context, context: Context,
parent: ViewGroup, parent: ViewGroup,
navigationItems: List<ActionItem> = NavigationItems.defaultItems, composableContent: @Composable () -> Unit,
androidToolbarView: View? = null,
menuButton: MenuButton,
isPrivateMode: Boolean = false,
) { ) {
val toolbarContainerView = ToolbarContainerView(context) val toolbarContainerView = ToolbarContainerView(context)
@ -53,29 +35,7 @@ class BottomToolbarContainerView(
init { init {
composeView = ComposeView(context).apply { composeView = ComposeView(context).apply {
setContent { setContent {
val tabCount = context.components.core.store.observeAsState(initialValue = 0) { browserState -> composableContent()
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,
)
}
}
} }
toolbarContainerView.addView(this) toolbarContainerView.addView(this)

@ -6,8 +6,8 @@ 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
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -17,166 +17,337 @@ import androidx.compose.material.IconButton
import androidx.compose.material.LocalContentColor import androidx.compose.material.LocalContentColor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
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.privateTabs
import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.lib.state.ext.observeAsState
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.components.toolbar.navbar.ItemType.STANDARD import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.components.toolbar.navbar.ItemType.TAB_COUNTER import org.mozilla.fenix.compose.LongPressIconButton
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.search.SearchDialogFragment
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.theme.Theme import org.mozilla.fenix.theme.Theme
import org.mozilla.fenix.theme.ThemeManager
/** /**
* Top-level UI for displaying the navigation bar. * Top-level UI for displaying the navigation bar.
* *
* @param actionItems A list of [ActionItem] used to populate the bar. * @param isPrivateMode If browsing in [BrowsingMode.Private].
* @param tabCount The number of opened tabs. * @param browserStore The [BrowserStore] instance used to observe tabs state.
* @param menuButton A [MenuButton] to be used for [ItemType.MENU]. * @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 @Composable
fun NavigationBar( fun BrowserNavBar(
actionItems: List<ActionItem>, isPrivateMode: Boolean,
tabCount: Int, browserStore: BrowserStore,
menuButton: MenuButton? = null, menuButton: MenuButton,
onBackButtonClick: () -> Unit,
onBackButtonLongPress: () -> Unit,
onForwardButtonClick: () -> Unit,
onForwardButtonLongPress: () -> Unit,
onHomeButtonClick: () -> Unit,
onTabsButtonClick: () -> Unit,
) { ) {
Box( val tabCount = browserStore.observeAsState(initialValue = 0) { browserState ->
modifier = Modifier if (isPrivateMode) {
.background(FirefoxTheme.colors.layer1) browserState.privateTabs.size
.height(48.dp) } else {
.fillMaxWidth(), browserState.normalTabs.size
) {
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,
)
}
}
}
}
}
} }
}.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. * @param isPrivateMode If browsing in [BrowsingMode.Private].
* @property descriptionResourceId Text used as a content description by accessibility services. * @param browserStore The [BrowserStore] instance used to observe tabs state.
* @property type Type of the item, defaults to [ItemType.STANDARD]. * @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( @Composable
val iconId: Int, fun HomeNavBar(
val descriptionResourceId: Int, isPrivateMode: Boolean,
val type: ItemType = ItemType.STANDARD, 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
/** NavBar {
* Enumerates the types of items that can be used in a navigation bar. BackButton(
* onBackButtonClick = {
* [STANDARD] - Represents a regular navigation item. Used for most navigation actions. // no-op
* [TAB_COUNTER] - Represents a specialized item used to display a count, such as the number of open tabs in a browser. },
*/ onBackButtonLongPress = {
enum class ItemType { // no-op
STANDARD, TAB_COUNTER, MENU },
)
ForwardButton(
onForwardButtonClick = {
// no-op
},
onForwardButtonLongPress = {
// no-op
},
)
SearchWebButton(
onSearchButtonClick = onSearchButtonClick,
)
TabsButton(
onTabsButtonClick = onTabsButtonClick,
tabCount = tabCount,
)
MenuButton(menuButton = menuButton)
}
} }
/** @Composable
* Provides a collection of navigation items used in the application's navigation bar. private fun NavBar(
*/ content: @Composable RowScope.() -> Unit,
object NavigationItems { ) {
val home = ActionItem( Row(
iconId = R.drawable.mozac_ic_home_24, modifier = Modifier
descriptionResourceId = R.string.browser_toolbar_home, .background(FirefoxTheme.colors.layer1)
.height(48.dp)
.fillMaxWidth()
.padding(horizontal = 16.dp),
horizontalArrangement = Arrangement.SpaceBetween,
content = content,
) )
}
val menu = ActionItem( @Composable
iconId = R.drawable.mozac_ic_ellipsis_vertical_24, private fun BackButton(
descriptionResourceId = R.string.mozac_browser_menu_button, onBackButtonClick: () -> Unit,
type = ItemType.MENU, 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( @Composable
iconId = R.drawable.mozac_ic_back_24, private fun HomeButton(
descriptionResourceId = R.string.browser_menu_back, 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( @Composable
iconId = R.drawable.mozac_ic_forward_24, private fun HomeNavBarPreviewRoot(isPrivateMode: Boolean) {
descriptionResourceId = R.string.browser_menu_forward, 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( @Composable
iconId = R.drawable.mozac_ui_tabcounter_box, private fun OpenTabNavBarNavBarPreviewRoot(isPrivateMode: Boolean) {
descriptionResourceId = R.string.mozac_tab_counter_content_description, val context = LocalContext.current
type = TAB_COUNTER, 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 @LightDarkPreview
@Composable @Composable
private fun NavigationBarPreview() { private fun OpenTabNavBarPreview() {
FirefoxTheme { FirefoxTheme {
NavigationBar( OpenTabNavBarNavBarPreviewRoot(isPrivateMode = false)
actionItems = NavigationItems.defaultItems,
tabCount = 0,
)
} }
} }
@Preview @Preview
@Composable @Composable
private fun NavigationBarPrivatePreview() { private fun OpenTabNavBarPrivatePreview() {
FirefoxTheme(theme = Theme.Private) { FirefoxTheme(theme = Theme.Private) {
NavigationBar( OpenTabNavBarNavBarPreviewRoot(isPrivateMode = true)
actionItems = NavigationItems.defaultItems,
tabCount = 0,
)
} }
} }

@ -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

@ -14,6 +14,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.VisibleForTesting import androidx.annotation.VisibleForTesting
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding 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.semantics.testTagsAsResourceId
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.viewinterop.AndroidView
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getColor 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.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.ui.colors.PhotonColors import mozilla.components.ui.colors.PhotonColors
import org.mozilla.fenix.BrowserDirection import org.mozilla.fenix.BrowserDirection
import org.mozilla.fenix.GleanMetrics.Events
import org.mozilla.fenix.GleanMetrics.HomeScreen import org.mozilla.fenix.GleanMetrics.HomeScreen
import org.mozilla.fenix.GleanMetrics.Homepage import org.mozilla.fenix.GleanMetrics.Homepage
import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr import org.mozilla.fenix.GleanMetrics.PrivateBrowsingShortcutCfr
import org.mozilla.fenix.GleanMetrics.StartOnHome
import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.HomeActivity
import org.mozilla.fenix.NavGraphDirections
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.addons.showSnackBar import org.mozilla.fenix.addons.showSnackBar
import org.mozilla.fenix.browser.BrowserAnimator
import org.mozilla.fenix.browser.browsingmode.BrowsingMode import org.mozilla.fenix.browser.browsingmode.BrowsingMode
import org.mozilla.fenix.browser.tabstrip.TabStrip import org.mozilla.fenix.browser.tabstrip.TabStrip
import org.mozilla.fenix.components.FenixSnackbar 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.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.components.toolbar.navbar.BottomToolbarContainerView
import org.mozilla.fenix.components.toolbar.navbar.HomeNavBar
import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration import org.mozilla.fenix.components.toolbar.navbar.NavbarIntegration
import org.mozilla.fenix.compose.Divider
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
@ -131,6 +139,7 @@ import org.mozilla.fenix.nimbus.FxNimbus
import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks import org.mozilla.fenix.perf.MarkersFragmentLifecycleCallbacks
import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController import org.mozilla.fenix.search.toolbar.DefaultSearchSelectorController
import org.mozilla.fenix.search.toolbar.SearchSelectorMenu import org.mozilla.fenix.search.toolbar.SearchSelectorMenu
import org.mozilla.fenix.tabstray.Page
import org.mozilla.fenix.tabstray.TabsTrayAccessPoint import org.mozilla.fenix.tabstray.TabsTrayAccessPoint
import org.mozilla.fenix.theme.FirefoxTheme import org.mozilla.fenix.theme.FirefoxTheme
import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD import org.mozilla.fenix.utils.Settings.Companion.TOP_SITES_PROVIDER_MAX_THRESHOLD
@ -466,9 +475,49 @@ class HomeFragment : Fragment() {
_bottomToolbarContainerView = BottomToolbarContainerView( _bottomToolbarContainerView = BottomToolbarContainerView(
context = requireContext(), context = requireContext(),
parent = binding.homeLayout, parent = binding.homeLayout,
androidToolbarView = if (isToolbarAtBottom) binding.toolbarLayout else null, composableContent = {
menuButton = menuButton, FirefoxTheme {
isPrivateMode = activity.browsingModeManager.mode.isPrivate, 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( navbarIntegration.set(

Loading…
Cancel
Save