diff --git a/app/src/main/java/org/mozilla/fenix/compose/DropdownMenu.kt b/app/src/main/java/org/mozilla/fenix/compose/DropdownMenu.kt index 171f138925..b32ac7bb99 100644 --- a/app/src/main/java/org/mozilla/fenix/compose/DropdownMenu.kt +++ b/app/src/main/java/org/mozilla/fenix/compose/DropdownMenu.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme @@ -28,6 +29,7 @@ import org.mozilla.fenix.theme.FirefoxTheme * @param showMenu Whether or not the menu is currently displayed to the user. * @param onDismissRequest Invoked when user dismisses the menu or on orientation changes. * @param modifier Modifier to be applied to the menu. + * @param offset Offset to be added to the position of the menu. */ @Composable fun DropdownMenu( @@ -35,6 +37,7 @@ fun DropdownMenu( showMenu: Boolean, onDismissRequest: () -> Unit, modifier: Modifier = Modifier, + offset: DpOffset = DpOffset.Zero, ) { DisposableEffect(LocalConfiguration.current.orientation) { onDispose { onDismissRequest() } @@ -44,6 +47,7 @@ fun DropdownMenu( DropdownMenu( expanded = showMenu && menuItems.isNotEmpty(), onDismissRequest = { onDismissRequest() }, + offset = offset, modifier = Modifier .background(color = FirefoxTheme.colors.layer2) .then(modifier), diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt index 5f0b0453d5..2ac1b90613 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTray.kt @@ -50,6 +50,7 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab * @param browserStore [BrowserStore] used to listen for changes to [BrowserState]. * @param tabsTrayStore [TabsTrayStore] used to listen for changes to [TabsTrayState]. * @param displayTabsInGrid Whether the normal and private tabs should be displayed in a grid. + * @param isInDebugMode True for debug variant or if secret menu is enabled for this session. * @param onTabClose Invoked when the user clicks to close a tab. * @param onTabMediaClick Invoked when the user interacts with a tab's media controls. * @param onTabClick Invoked when the user clicks on a tab. @@ -68,13 +69,14 @@ import mozilla.components.browser.storage.sync.Tab as SyncTab * @param onSyncedTabClick Invoked when the user clicks on a synced tab. */ @OptIn(ExperimentalPagerApi::class, ExperimentalComposeUiApi::class) -@Suppress("LongMethod", "LongParameterList") +@Suppress("LongMethod", "LongParameterList", "ComplexMethod") @Composable fun TabsTray( appStore: AppStore, browserStore: BrowserStore, tabsTrayStore: TabsTrayStore, displayTabsInGrid: Boolean, + isInDebugMode: Boolean, shouldShowInactiveTabsAutoCloseDialog: (Int) -> Boolean, onTabPageClick: (Page) -> Unit, onTabClose: (TabSessionState) -> Unit, @@ -116,6 +118,12 @@ fun TabsTray( } } + val shapeModifier = if (isInMultiSelectMode) { + Modifier + } else { + Modifier.clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) + } + LaunchedEffect(selectedPage) { pagerState.animateScrollToPage(selectedPage.ordinal) } @@ -123,14 +131,15 @@ fun TabsTray( Column( modifier = Modifier .fillMaxSize() - .clip(RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)) + .then(shapeModifier) .background(FirefoxTheme.colors.layer1), ) { Box(modifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())) { TabsTrayBanner( - isInMultiSelectMode = isInMultiSelectMode, + selectMode = multiselectMode, selectedPage = selectedPage, normalTabCount = normalTabs.size + inactiveTabs.size, + isInDebugMode = isInDebugMode, onTabPageIndicatorClicked = onTabPageClick, ) } @@ -341,6 +350,7 @@ private fun TabsTrayPreviewRoot( browserStore = browserStore, tabsTrayStore = tabsTrayStore, displayTabsInGrid = displayTabsInGrid, + isInDebugMode = false, shouldShowInactiveTabsAutoCloseDialog = { true }, onTabPageClick = { page -> selectedPageState = page diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt index aaa543bfaf..339ddb8414 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayBanner.kt @@ -39,29 +39,42 @@ import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import mozilla.components.browser.state.state.ContentState +import mozilla.components.browser.state.state.TabSessionState import mozilla.components.ui.tabcounter.TabCounter import org.mozilla.fenix.R +import org.mozilla.fenix.compose.DropdownMenu +import org.mozilla.fenix.compose.MenuItem import org.mozilla.fenix.compose.annotation.LightDarkPreview import org.mozilla.fenix.theme.FirefoxTheme +private val ICON_SIZE = 24.dp + /** * Top-level UI for displaying the banner in [TabsTray]. * - * @param isInMultiSelectMode Whether the tab list is in multi-select mode. + * @param selectMode Current [TabsTrayState.Mode] used in the tabs tray. * @param selectedPage The active [Page] of the Tabs Tray. * @param normalTabCount The total amount of normal browsing tabs currently open. + * @param isInDebugMode True for debug variant or if secret menu is enabled for this session. * @param onTabPageIndicatorClicked Invoked when the user clicks on a tab page indicator. */ @Composable fun TabsTrayBanner( - isInMultiSelectMode: Boolean, + selectMode: TabsTrayState.Mode, selectedPage: Page, normalTabCount: Int, + isInDebugMode: Boolean, onTabPageIndicatorClicked: (Page) -> Unit, ) { - if (isInMultiSelectMode) { - MultiSelectBanner() + if (selectMode is TabsTrayState.Mode.Select) { + MultiSelectBanner( + selectedTabCount = selectMode.selectedTabs.size, + shouldShowInactiveButton = isInDebugMode, + ) } else { SingleSelectBanner( onTabPageIndicatorClicked = onTabPageIndicatorClicked, @@ -198,20 +211,94 @@ private fun NormalTabsTabIcon(normalTabCount: Int) { } } +/** + * Banner displayed in multi select mode. + * + * @param selectedTabCount Number of selected tabs. + * @param shouldShowInactiveButton Whether or not to show the inactive tabs menu item. + */ +@Suppress("LongMethod") @Composable -private fun MultiSelectBanner() { - Box( +private fun MultiSelectBanner( + selectedTabCount: Int, + shouldShowInactiveButton: Boolean, +) { + var showMenu by remember { mutableStateOf(false) } + val menuItems = mutableListOf( + MenuItem( + title = stringResource(R.string.tab_tray_multiselect_menu_item_bookmark), + ) {}, + MenuItem( + title = stringResource(R.string.tab_tray_multiselect_menu_item_close), + ) {}, + ) + if (shouldShowInactiveButton) { + menuItems.add( + MenuItem( + title = stringResource(R.string.inactive_tabs_menu_item), + ) {}, + ) + } + + Row( modifier = Modifier .fillMaxWidth() - .height(80.dp) + .height(88.dp) .background(color = FirefoxTheme.colors.layerAccent), - contentAlignment = Alignment.Center, + verticalAlignment = Alignment.CenterVertically, ) { + IconButton(onClick = {}) { + Icon( + painter = painterResource(id = R.drawable.ic_close), + contentDescription = stringResource(id = R.string.tab_tray_close_multiselect_content_description), + tint = FirefoxTheme.colors.iconOnColor, + ) + } + Text( - text = "Multi selection mode", - color = FirefoxTheme.colors.textOnColorPrimary, + text = stringResource(R.string.tab_tray_multi_select_title, selectedTabCount), style = FirefoxTheme.typography.body1, + color = FirefoxTheme.colors.textOnColorPrimary, + fontSize = 20.sp, + fontWeight = FontWeight.W500, ) + + Spacer(modifier = Modifier.weight(1.0f)) + + IconButton(onClick = {}) { + Icon( + painter = painterResource(id = R.drawable.ic_tab_collection), + contentDescription = stringResource( + id = R.string.tab_tray_collection_button_multiselect_content_description, + ), + tint = FirefoxTheme.colors.iconOnColor, + ) + } + + IconButton(onClick = {}) { + Icon( + painter = painterResource(id = R.drawable.ic_share), + contentDescription = stringResource( + id = R.string.tab_tray_multiselect_share_content_description, + ), + tint = FirefoxTheme.colors.iconOnColor, + ) + } + + IconButton(onClick = { showMenu = true }) { + Icon( + painter = painterResource(id = R.drawable.ic_menu), + contentDescription = stringResource(id = R.string.tab_tray_multiselect_menu_content_description), + tint = FirefoxTheme.colors.iconOnColor, + ) + + DropdownMenu( + menuItems = menuItems, + showMenu = showMenu, + offset = DpOffset(x = 0.dp, y = -ICON_SIZE), + onDismissRequest = { showMenu = false }, + ) + } } } @@ -236,13 +323,28 @@ private fun TabsTrayBannerInfinityPreview() { @Composable private fun TabsTrayBannerMultiselectPreview() { TabsTrayBannerPreviewRoot( - isInMultiSelectMode = true, + selectMode = TabsTrayState.Mode.Select( + setOf( + TabSessionState( + id = "1", + content = ContentState( + url = "www.mozilla.com", + ), + ), + TabSessionState( + id = "2", + content = ContentState( + url = "www.mozilla.com", + ), + ), + ), + ), ) } @Composable private fun TabsTrayBannerPreviewRoot( - isInMultiSelectMode: Boolean = false, + selectMode: TabsTrayState.Mode = TabsTrayState.Mode.Normal, selectedPage: Page = Page.NormalTabs, normalTabCount: Int = 10, ) { @@ -251,9 +353,10 @@ private fun TabsTrayBannerPreviewRoot( FirefoxTheme { Box(modifier = Modifier.background(color = FirefoxTheme.colors.layer1)) { TabsTrayBanner( - isInMultiSelectMode = isInMultiSelectMode, + selectMode = selectMode, selectedPage = selectedPageState, normalTabCount = normalTabCount, + isInDebugMode = true, onTabPageIndicatorClicked = { page -> selectedPageState = page }, diff --git a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt index e4f8f73bbb..c2c26f02d4 100644 --- a/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/tabstray/TabsTrayFragment.kt @@ -32,6 +32,7 @@ import mozilla.components.feature.downloads.ui.DownloadCancelDialogFragment import mozilla.components.feature.tabs.tabstray.TabsFeature import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.telemetry.glean.private.NoExtras +import org.mozilla.fenix.Config import org.mozilla.fenix.GleanMetrics.TabsTray import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.NavGraphDirections @@ -223,6 +224,8 @@ class TabsTrayFragment : AppCompatDialogFragment() { browserStore = requireComponents.core.store, tabsTrayStore = tabsTrayStore, displayTabsInGrid = requireContext().settings().gridTabView, + isInDebugMode = Config.channel.isDebug || + requireComponents.settings.showSecretDebugMenuThisSession, shouldShowInactiveTabsAutoCloseDialog = requireContext().settings()::shouldShowInactiveTabsAutoCloseDialog, onTabPageClick = { page ->