tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit bb59f1cb896835c631c5b9b0e8b7674c504a1a0d
parent cf6d075e3308f0fb83b37501831a3244a48a6aff
Author: iorgamgabriel <iorgamgabriel@yahoo.com>
Date:   Tue, 11 Nov 2025 07:20:18 +0000

Bug 1983111 - [Tab Management Phase 1] Make the status bar transparent when Tab Manager is scrolled. r=android-reviewers,calu

Differential Revision: https://phabricator.services.mozilla.com/D271071

Diffstat:
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt | 190++++++++++++++++++++++++++++++++++++++++++++++---------------------------------
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt | 1-
Mmobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt | 158+++++++++++++++++++++++++++++++++++++++++++++++--------------------------------
3 files changed, 206 insertions(+), 143 deletions(-)

diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/banner/TabsTrayBanner.kt @@ -8,8 +8,11 @@ package org.mozilla.fenix.tabstray.ui.banner import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsetsSides import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.CenterAlignedTopAppBar @@ -75,6 +78,7 @@ private val TabIndicatorRoundedCornerDp = 100.dp * @param syncedTabCount The total number of open synced tabs. * @param selectionMode [TabsTrayState.Mode] indicating the current selection mode (e.g., normal, multi-select). * @param isInDebugMode True for debug variant or if secret menu is enabled for this session. + * @param statusBarHeight The height of the system status bar. * @param shouldShowTabAutoCloseBanner Whether the tab auto-close banner should be displayed. * @param shouldShowLockPbmBanner Whether the lock private browsing mode banner should be displayed. * @param scrollBehavior Defines how the [TabPageBanner] should behave when the content under it is scrolled. @@ -101,6 +105,7 @@ fun TabsTrayBanner( syncedTabCount: Int, selectionMode: Mode, isInDebugMode: Boolean, + statusBarHeight: Dp, shouldShowTabAutoCloseBanner: Boolean, shouldShowLockPbmBanner: Boolean, scrollBehavior: TopAppBarScrollBehavior, @@ -158,6 +163,7 @@ fun TabsTrayBanner( normalTabCount = normalTabCount, privateTabCount = privateTabCount, syncedTabCount = syncedTabCount, + statusBarHeight = statusBarHeight, scrollBehavior = scrollBehavior, onTabPageIndicatorClicked = onTabPageIndicatorClicked, ) @@ -167,6 +173,8 @@ fun TabsTrayBanner( !hasAcknowledgedAutoCloseBanner && showTabAutoCloseBanner -> { onTabAutoCloseBannerShown() + BannerPadding(scrollBehavior = scrollBehavior, statusBarHeight = statusBarHeight) + HorizontalDivider() Banner( @@ -185,6 +193,8 @@ fun TabsTrayBanner( } !hasAcknowledgedPbmLockBanner && shouldShowLockPbmBanner -> { + BannerPadding(scrollBehavior = scrollBehavior, statusBarHeight = statusBarHeight) + // After this bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1965545 // is resolved, we should swap the button 1 and button 2 click actions. Banner( @@ -206,6 +216,18 @@ fun TabsTrayBanner( } } +@Composable +private fun BannerPadding( + scrollBehavior: TopAppBarScrollBehavior, + statusBarHeight: Dp, +) { + val padding by remember(statusBarHeight, scrollBehavior.state.collapsedFraction) { + derivedStateOf { statusBarHeight * scrollBehavior.state.collapsedFraction } + } + + Spacer(modifier = Modifier.height(padding)) +} + /** * Banner displayed when in [Mode.Normal]. * @@ -213,6 +235,7 @@ fun TabsTrayBanner( * @param normalTabCount The amount of open Normal tabs. * @param privateTabCount The amount of open Private tabs. * @param syncedTabCount The amount of synced tabs. + * @param statusBarHeight The height of the system status bar. * @param scrollBehavior Defines how the [TabPageBanner] should behave when the content under it is scrolled. * @param onTabPageIndicatorClicked Invoked when the user clicks on a tab page button. Passes along the * [Page] that was clicked. @@ -224,6 +247,7 @@ private fun TabPageBanner( normalTabCount: Int, privateTabCount: Int, syncedTabCount: Int, + statusBarHeight: Dp, scrollBehavior: TopAppBarScrollBehavior, onTabPageIndicatorClicked: (Page) -> Unit, ) { @@ -232,95 +256,104 @@ private fun TabPageBanner( CenterAlignedTopAppBar( title = { - PrimaryTabRow( - selectedTabIndex = selectedTabIndex, - modifier = Modifier.fillMaxWidth(), - contentColor = MaterialTheme.colorScheme.primary, - containerColor = Color.Transparent, - indicator = { - TabRowDefaults.PrimaryIndicator( - modifier = Modifier.tabIndicatorOffset( - selectedTabIndex = selectedTabIndex, - matchContentSize = true, - ), - width = Dp.Unspecified, - shape = RoundedCornerShape( - topStart = TabIndicatorRoundedCornerDp, - topEnd = TabIndicatorRoundedCornerDp, - ), - ) - }, - divider = {}, - ) { - val privateTabDescription = stringResource( - id = R.string.tabs_header_private_tabs_counter_title, - privateTabCount.toString(), - ) - val normalTabDescription = stringResource( - id = R.string.tabs_header_normal_tabs_counter_title, - normalTabCount.toString(), - ) - val syncedTabDescription = stringResource( - id = R.string.tabs_header_synced_tabs_counter_title, - syncedTabCount.toString(), - ) - - Tab( - selected = selectedPage == Page.PrivateTabs, - onClick = { onTabPageIndicatorClicked(Page.PrivateTabs) }, + Column { + Spacer( modifier = Modifier - .testTag(TabsTrayTestTag.PRIVATE_TABS_PAGE_BUTTON) - .semantics { - contentDescription = privateTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, + .height(statusBarHeight) + .fillMaxWidth(), + ) + PrimaryTabRow( + selectedTabIndex = selectedTabIndex, + modifier = Modifier.fillMaxWidth(), + contentColor = MaterialTheme.colorScheme.primary, + containerColor = Color.Transparent, + indicator = { + TabRowDefaults.PrimaryIndicator( + modifier = Modifier.tabIndicatorOffset( + selectedTabIndex = selectedTabIndex, + matchContentSize = true, + ), + width = Dp.Unspecified, + shape = RoundedCornerShape( + topStart = TabIndicatorRoundedCornerDp, + topEnd = TabIndicatorRoundedCornerDp, + ), + ) + }, + divider = {}, ) { - Text( - text = stringResource(id = R.string.tabs_header_private_tabs_title), - style = FirefoxTheme.typography.button, + val privateTabDescription = stringResource( + id = R.string.tabs_header_private_tabs_counter_title, + privateTabCount.toString(), ) - } - - Tab( - selected = selectedPage == Page.NormalTabs, - onClick = { onTabPageIndicatorClicked(Page.NormalTabs) }, - modifier = Modifier - .testTag(TabsTrayTestTag.NORMAL_TABS_PAGE_BUTTON) - .semantics { - contentDescription = normalTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, - ) { - Text( - text = stringResource(R.string.tabs_header_normal_tabs_title), - style = FirefoxTheme.typography.button, + val normalTabDescription = stringResource( + id = R.string.tabs_header_normal_tabs_counter_title, + normalTabCount.toString(), ) - } - - Tab( - selected = selectedPage == Page.SyncedTabs, - onClick = { onTabPageIndicatorClicked(Page.SyncedTabs) }, - modifier = Modifier - .testTag(TabsTrayTestTag.SYNCED_TABS_PAGE_BUTTON) - .semantics { - contentDescription = syncedTabDescription - } - .height(RowHeight), - unselectedContentColor = inactiveColor, - ) { - Text( - text = stringResource(id = R.string.tabs_header_synced_tabs_title), - style = FirefoxTheme.typography.button, + val syncedTabDescription = stringResource( + id = R.string.tabs_header_synced_tabs_counter_title, + syncedTabCount.toString(), ) + + Tab( + selected = selectedPage == Page.PrivateTabs, + onClick = { onTabPageIndicatorClicked(Page.PrivateTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.PRIVATE_TABS_PAGE_BUTTON) + .semantics { + contentDescription = privateTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, + ) { + Text( + text = stringResource(id = R.string.tabs_header_private_tabs_title), + style = FirefoxTheme.typography.button, + ) + } + + Tab( + selected = selectedPage == Page.NormalTabs, + onClick = { onTabPageIndicatorClicked(Page.NormalTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.NORMAL_TABS_PAGE_BUTTON) + .semantics { + contentDescription = normalTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, + ) { + Text( + text = stringResource(R.string.tabs_header_normal_tabs_title), + style = FirefoxTheme.typography.button, + ) + } + + Tab( + selected = selectedPage == Page.SyncedTabs, + onClick = { onTabPageIndicatorClicked(Page.SyncedTabs) }, + modifier = Modifier + .testTag(TabsTrayTestTag.SYNCED_TABS_PAGE_BUTTON) + .semantics { + contentDescription = syncedTabDescription + } + .height(RowHeight), + unselectedContentColor = inactiveColor, + ) { + Text( + text = stringResource(id = R.string.tabs_header_synced_tabs_title), + style = FirefoxTheme.typography.button, + ) + } } } }, - expandedHeight = RowHeight, + expandedHeight = RowHeight + statusBarHeight, colors = TopAppBarDefaults.centerAlignedTopAppBarColors( scrolledContainerColor = MaterialTheme.colorScheme.surfaceContainerHigh, ), + // Allow this TopAppBar to be drawn behind the status bar instead of stopping at it. + windowInsets = TopAppBarDefaults.windowInsets.only(WindowInsetsSides.Horizontal), scrollBehavior = scrollBehavior, ) } @@ -540,6 +573,7 @@ private fun TabsTrayBannerPreviewRoot( syncedTabCount = 0, selectionMode = state.mode, isInDebugMode = false, + statusBarHeight = 50.dp, shouldShowTabAutoCloseBanner = shouldShowTabAutoCloseBanner, shouldShowLockPbmBanner = shouldShowLockPbmBanner, scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(), diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabpage/TabLayout.kt @@ -335,7 +335,6 @@ private fun TabList( .padding( start = TabListPadding, end = TabListPadding, - top = TabListPadding, ) .clip(TabListCornerShape) .background(MaterialTheme.colorScheme.surface) diff --git a/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt b/mobile/android/fenix/app/src/main/java/org/mozilla/fenix/tabstray/ui/tabstray/TabsTray.kt @@ -6,9 +6,17 @@ package org.mozilla.fenix.tabstray.ui.tabstray +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBars import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.BottomAppBarDefaults @@ -68,6 +76,7 @@ import org.mozilla.fenix.tabstray.ui.syncedtabs.OnTabCloseClick as OnSyncedTabCl * BottomAppBar. */ private val ScaffoldFabOffsetCorrection = 4.dp +private const val SPACER_BACKGROUND_ALPHA = 0.75f /** * Top-level UI for displaying the Tabs Tray feature. @@ -188,6 +197,11 @@ fun TabsTray( .sumOf { deviceSection: SyncedTabsListItem.DeviceSection -> deviceSection.tabs.size } } + val systemBarsInsets = WindowInsets.systemBars.asPaddingValues() + val statusBarHeight = remember(systemBarsInsets) { + systemBarsInsets.calculateTopPadding() + } + val topAppBarScrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior() val bottomAppBarScrollBehavior = BottomAppBarDefaults.exitAlwaysScrollBehavior() @@ -216,6 +230,7 @@ fun TabsTray( syncedTabCount = syncedTabCount, selectionMode = tabsTrayState.mode, isInDebugMode = isInDebugMode, + statusBarHeight = statusBarHeight, shouldShowTabAutoCloseBanner = shouldShowTabAutoCloseBanner, shouldShowLockPbmBanner = shouldShowLockPbmBanner, scrollBehavior = topAppBarScrollBehavior, @@ -261,71 +276,83 @@ fun TabsTray( floatingActionButtonPosition = FabPosition.EndOverlay, containerColor = MaterialTheme.colorScheme.surface, ) { paddingValues -> - HorizontalPager( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize(), - state = pagerState, - beyondViewportPageCount = 2, - userScrollEnabled = false, - ) { position -> - when (Page.positionToPage(position)) { - Page.NormalTabs -> { - NormalTabsPage( - normalTabs = tabsTrayState.normalTabs, - inactiveTabs = tabsTrayState.inactiveTabs, - selectedTabId = tabsTrayState.selectedTabId, - selectionMode = tabsTrayState.mode, - inactiveTabsExpanded = tabsTrayState.inactiveTabsExpanded, - displayTabsInGrid = displayTabsInGrid, - onTabClose = onTabClose, - onTabClick = onTabClick, - onTabLongClick = onTabLongClick, - shouldShowInactiveTabsAutoCloseDialog = shouldShowInactiveTabsAutoCloseDialog, - onInactiveTabsHeaderClick = onInactiveTabsHeaderClick, - onDeleteAllInactiveTabsClick = onDeleteAllInactiveTabsClick, - onInactiveTabsAutoCloseDialogShown = onInactiveTabsAutoCloseDialogShown, - onInactiveTabAutoCloseDialogCloseButtonClick = onInactiveTabAutoCloseDialogCloseButtonClick, - onEnableInactiveTabAutoCloseClick = onEnableInactiveTabAutoCloseClick, - onInactiveTabClick = onInactiveTabClick, - onInactiveTabClose = onInactiveTabClose, - onMove = onMove, - shouldShowInactiveTabsCFR = shouldShowInactiveTabsCFR, - onInactiveTabsCFRShown = onInactiveTabsCFRShown, - onInactiveTabsCFRClick = onInactiveTabsCFRClick, - onInactiveTabsCFRDismiss = onInactiveTabsCFRDismiss, - onTabDragStart = { - tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode) - }, - ) - } + Box { + HorizontalPager( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize(), + state = pagerState, + beyondViewportPageCount = 2, + userScrollEnabled = false, + ) { position -> + when (Page.positionToPage(position)) { + Page.NormalTabs -> { + NormalTabsPage( + normalTabs = tabsTrayState.normalTabs, + inactiveTabs = tabsTrayState.inactiveTabs, + selectedTabId = tabsTrayState.selectedTabId, + selectionMode = tabsTrayState.mode, + inactiveTabsExpanded = tabsTrayState.inactiveTabsExpanded, + displayTabsInGrid = displayTabsInGrid, + onTabClose = onTabClose, + onTabClick = onTabClick, + onTabLongClick = onTabLongClick, + shouldShowInactiveTabsAutoCloseDialog = shouldShowInactiveTabsAutoCloseDialog, + onInactiveTabsHeaderClick = onInactiveTabsHeaderClick, + onDeleteAllInactiveTabsClick = onDeleteAllInactiveTabsClick, + onInactiveTabsAutoCloseDialogShown = onInactiveTabsAutoCloseDialogShown, + onInactiveTabAutoCloseDialogCloseButtonClick = onInactiveTabAutoCloseDialogCloseButtonClick, + onEnableInactiveTabAutoCloseClick = onEnableInactiveTabAutoCloseClick, + onInactiveTabClick = onInactiveTabClick, + onInactiveTabClose = onInactiveTabClose, + onMove = onMove, + shouldShowInactiveTabsCFR = shouldShowInactiveTabsCFR, + onInactiveTabsCFRShown = onInactiveTabsCFRShown, + onInactiveTabsCFRClick = onInactiveTabsCFRClick, + onInactiveTabsCFRDismiss = onInactiveTabsCFRDismiss, + onTabDragStart = { + tabsTrayStore.dispatch(TabsTrayAction.ExitSelectMode) + }, + ) + } - Page.PrivateTabs -> { - PrivateTabsPage( - privateTabs = tabsTrayState.privateTabs, - selectedTabId = tabsTrayState.selectedTabId, - selectionMode = tabsTrayState.mode, - displayTabsInGrid = displayTabsInGrid, - privateTabsLocked = isPbmLocked, - onTabClose = onTabClose, - onTabClick = onTabClick, - onTabLongClick = onTabLongClick, - onMove = onMove, - onUnlockPbmClick = onUnlockPbmClick, - ) - } + Page.PrivateTabs -> { + PrivateTabsPage( + privateTabs = tabsTrayState.privateTabs, + selectedTabId = tabsTrayState.selectedTabId, + selectionMode = tabsTrayState.mode, + displayTabsInGrid = displayTabsInGrid, + privateTabsLocked = isPbmLocked, + onTabClose = onTabClose, + onTabClick = onTabClick, + onTabLongClick = onTabLongClick, + onMove = onMove, + onUnlockPbmClick = onUnlockPbmClick, + ) + } - Page.SyncedTabs -> { - SyncedTabsPage( - isSignedIn = isSignedIn, - syncedTabs = tabsTrayState.syncedTabs, - onTabClick = onSyncedTabClick, - onTabClose = onSyncedTabClose, - onSignInClick = onSignInClick, - ) + Page.SyncedTabs -> { + SyncedTabsPage( + isSignedIn = isSignedIn, + syncedTabs = tabsTrayState.syncedTabs, + onTabClick = onSyncedTabClick, + onTabClose = onSyncedTabClose, + onSignInClick = onSignInClick, + ) + } } } } + Spacer( + Modifier + .height(statusBarHeight) + .fillMaxWidth() + .background( + MaterialTheme.colorScheme.surface.copy( + SPACER_BACKGROUND_ALPHA, + ), + ), + ) } } @@ -526,9 +553,9 @@ private class TabsTrayStateParameterProvider : PreviewParameterProvider<TabsTray showInactiveTabsAutoCloseDialog = true, ), // TabsTray Private Tabs Preview - TabsTrayPreviewModel( - selectedPage = Page.PrivateTabs, - privateTabs = generateFakeTabsList(isPrivate = true), + TabsTrayPreviewModel( + selectedPage = Page.PrivateTabs, + privateTabs = generateFakeTabsList(isPrivate = true), ), // TabsTray Synced Tab Preview TabsTrayPreviewModel( @@ -569,7 +596,10 @@ private data class TabsTrayPreviewModel( val isSignedIn: Boolean = true, ) -private fun generateFakeTabsList(tabCount: Int = 10, isPrivate: Boolean = false): List<TabSessionState> = +private fun generateFakeTabsList( + tabCount: Int = 10, + isPrivate: Boolean = false, +): List<TabSessionState> = List(tabCount) { index -> TabSessionState( id = "tabId$index-$isPrivate",