From 97bb93c18e37115f3fc9f1ca291f348bfcc40438 Mon Sep 17 00:00:00 2001 From: David Perez Date: Mon, 20 Oct 2025 15:42:47 -0500 Subject: [PATCH] PM-27136: Replace FirstTimeSyncSnackbarHost with BitwardenSnackbarHost (#6058) --- .../itemlisting/FirstTimeSyncSnackbarHost.kt | 69 ------------------- .../feature/itemlisting/ItemListingScreen.kt | 18 ++--- .../itemlisting/ItemListingViewModel.kt | 28 +++++++- .../itemlisting/ItemListingScreenTest.kt | 6 +- .../itemlisting/ItemListingViewModelTest.kt | 10 ++- .../components/snackbar/BitwardenSnackbar.kt | 11 +-- 6 files changed, 49 insertions(+), 93 deletions(-) delete mode 100644 authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt deleted file mode 100644 index 80dd73ffa5..0000000000 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/FirstTimeSyncSnackbarHost.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.bitwarden.authenticator.ui.authenticator.feature.itemlisting - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.SnackbarHost -import androidx.compose.material3.SnackbarHostState -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.shadow -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.unit.dp -import com.bitwarden.ui.platform.resource.BitwardenDrawable -import com.bitwarden.ui.platform.resource.BitwardenString -import com.bitwarden.ui.platform.theme.BitwardenTheme - -/** - * Show a snackbar that says "Account synced from Bitwarden app" with a close action. - * - * @param state Snackbar state used to show/hide. The message and title from this state are unused. - */ -@Composable -fun FirstTimeSyncSnackbarHost( - state: SnackbarHostState, -) { - SnackbarHost( - hostState = state, - snackbar = { - Row( - modifier = Modifier - .padding(16.dp) - .fillMaxWidth() - .shadow(elevation = 6.dp) - .background( - color = BitwardenTheme.colorScheme.background.alert, - shape = BitwardenTheme.shapes.snackbar, - ), - verticalAlignment = Alignment.CenterVertically, - ) { - Text( - modifier = Modifier - .padding(16.dp) - .weight(1f, fill = true), - text = stringResource(BitwardenString.account_synced_from_bitwarden_app), - style = BitwardenTheme.typography.bodyLarge, - color = BitwardenTheme.colorScheme.text.reversed, - ) - IconButton( - onClick = { state.currentSnackbarData?.dismiss() }, - ) { - Icon( - painter = painterResource(id = BitwardenDrawable.ic_close), - contentDescription = stringResource(id = BitwardenString.close), - tint = BitwardenTheme.colorScheme.icon.reversed, - modifier = Modifier - .size(24.dp), - ) - } - } - }, - ) -} diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt index 98be4a7819..305bd7045d 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreen.kt @@ -17,7 +17,6 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon -import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState @@ -25,7 +24,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -70,6 +68,8 @@ import com.bitwarden.ui.platform.components.fab.model.ExpandableFabOption import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold +import com.bitwarden.ui.platform.components.snackbar.BitwardenSnackbarHost +import com.bitwarden.ui.platform.components.snackbar.model.rememberBitwardenSnackbarHostState import com.bitwarden.ui.platform.components.util.rememberVectorPainter import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme @@ -79,7 +79,6 @@ import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.theme.BitwardenTheme import com.bitwarden.ui.util.asText import kotlinx.collections.immutable.persistentListOf -import kotlinx.coroutines.launch /** * Displays the item listing screen. @@ -108,9 +107,7 @@ fun ItemListingScreen( viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) } } - val snackbarHostState = remember { SnackbarHostState() } - val coroutineScope = rememberCoroutineScope() - + val snackbarHostState = rememberBitwardenSnackbarHostState() EventsEffect(viewModel = viewModel) { event -> when (event) { is ItemListingEvent.NavigateBack -> onNavigateBack() @@ -140,11 +137,8 @@ fun ItemListingScreen( intentManager.startBitwardenAccountSettings() } - is ItemListingEvent.ShowFirstTimeSyncSnackbar -> { - // Message property is overridden by FirstTimeSyncSnackbarHost: - coroutineScope.launch { - snackbarHostState.showSnackbar("") - } + is ItemListingEvent.ShowSnackbar -> { + snackbarHostState.showSnackbar(snackbarData = event.data) } } } @@ -214,7 +208,7 @@ fun ItemListingScreen( ), ) }, - snackbarHost = { FirstTimeSyncSnackbarHost(state = snackbarHostState) }, + snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) }, ) { when (val currentState = state.viewState) { is ItemListingState.ViewState.Content -> { diff --git a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt index cae7f4b223..c211887b77 100644 --- a/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt +++ b/authenticator/src/main/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModel.kt @@ -26,6 +26,7 @@ import com.bitwarden.authenticator.ui.platform.components.listitem.model.Verific import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.ui.platform.base.BaseViewModel +import com.bitwarden.ui.platform.components.snackbar.model.BitwardenSnackbarData import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.util.Text @@ -263,7 +264,12 @@ class ItemListingViewModel @Inject constructor( } private fun handleFirstTimeUserSync() { - sendEvent(ItemListingEvent.ShowFirstTimeSyncSnackbar) + sendEvent( + event = ItemListingEvent.ShowSnackbar( + message = BitwardenString.account_synced_from_bitwarden_app.asText(), + withDismissAction = true, + ), + ) } private fun handleAppThemeChangeReceive(appTheme: AppTheme) { @@ -887,9 +893,25 @@ sealed class ItemListingEvent { ) : ItemListingEvent() /** - * Show a Snackbar letting the user know accounts have synced. + * Show a Snackbar with the given [data]. */ - data object ShowFirstTimeSyncSnackbar : ItemListingEvent() + data class ShowSnackbar( + val data: BitwardenSnackbarData, + ) : ItemListingEvent() { + constructor( + message: Text, + messageHeader: Text? = null, + actionLabel: Text? = null, + withDismissAction: Boolean = false, + ) : this( + data = BitwardenSnackbarData( + message = message, + messageHeader = messageHeader, + actionLabel = actionLabel, + withDismissAction = withDismissAction, + ), + ) + } } /** diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt index dc2ef101f4..99dd253f59 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingScreenTest.kt @@ -418,8 +418,10 @@ class ItemListingScreenTest : AuthenticatorComposeTest() { .onNodeWithText("Account synced from Bitwarden app") .assertIsNotDisplayed() - // Send ShowFirstTimeSyncSnackbar event - mutableEventFlow.tryEmit(ItemListingEvent.ShowFirstTimeSyncSnackbar) + // Send ShowSnackbar event + mutableEventFlow.tryEmit( + ItemListingEvent.ShowSnackbar(message = "Account synced from Bitwarden app".asText()), + ) // Make sure the snackbar is showing: composeTestRule diff --git a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt index bc59173993..494ff833c6 100644 --- a/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt +++ b/authenticator/src/test/kotlin/com/bitwarden/authenticator/ui/authenticator/feature/itemlisting/ItemListingViewModelTest.kt @@ -442,11 +442,17 @@ class ItemListingViewModelTest : BaseViewModelTest() { } @Test - fun `on FirstTimeUserSyncReceive should emit ShowFirstTimeSyncSnackbar`() = runTest { + fun `on FirstTimeUserSyncReceive should emit ShowSnackbar`() = runTest { val viewModel = createViewModel() viewModel.eventFlow.test { firstTimeAccountSyncChannel.send(Unit) - assertEquals(ItemListingEvent.ShowFirstTimeSyncSnackbar, awaitItem()) + assertEquals( + ItemListingEvent.ShowSnackbar( + message = BitwardenString.account_synced_from_bitwarden_app.asText(), + withDismissAction = true, + ), + awaitItem(), + ) } } diff --git a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/snackbar/BitwardenSnackbar.kt b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/snackbar/BitwardenSnackbar.kt index d9170301ef..f5cb0f870f 100644 --- a/ui/src/main/kotlin/com/bitwarden/ui/platform/components/snackbar/BitwardenSnackbar.kt +++ b/ui/src/main/kotlin/com/bitwarden/ui/platform/components/snackbar/BitwardenSnackbar.kt @@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.displayCutout import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBars -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.union @@ -73,10 +72,13 @@ fun BitwardenSnackbar( .clickable( enabled = !bitwardenSnackbarData.withDismissAction, onClick = onDismiss, - ) - .padding(16.dp), + ), ) { - Column(modifier = Modifier.weight(weight = 1f)) { + Column( + modifier = Modifier + .padding(top = 16.dp, bottom = 16.dp, start = 16.dp) + .weight(weight = 1f), + ) { bitwardenSnackbarData.messageHeader?.let { Text( text = it(), @@ -111,7 +113,6 @@ fun BitwardenSnackbar( vectorIconRes = BitwardenDrawable.ic_close, contentDescription = stringResource(BitwardenString.close), contentColor = BitwardenTheme.colorScheme.icon.reversed, - modifier = Modifier.offset(x = 12.dp, y = (-12).dp), ) } }