PM-27136: Replace FirstTimeSyncSnackbarHost with BitwardenSnackbarHost (#6058)

This commit is contained in:
David Perez 2025-10-20 15:42:47 -05:00 committed by GitHub
parent 31e7e05eda
commit 97bb93c18e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 49 additions and 93 deletions

View File

@ -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),
)
}
}
},
)
}

View File

@ -17,7 +17,6 @@ import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
@ -25,7 +24,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment 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.header.BitwardenListHeaderText
import com.bitwarden.ui.platform.components.icon.model.IconData import com.bitwarden.ui.platform.components.icon.model.IconData
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold 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.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.composition.LocalIntentManager import com.bitwarden.ui.platform.composition.LocalIntentManager
import com.bitwarden.ui.platform.feature.settings.appearance.model.AppTheme 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.platform.theme.BitwardenTheme
import com.bitwarden.ui.util.asText import com.bitwarden.ui.util.asText
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.launch
/** /**
* Displays the item listing screen. * Displays the item listing screen.
@ -108,9 +107,7 @@ fun ItemListingScreen(
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick)
} }
} }
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = rememberBitwardenSnackbarHostState()
val coroutineScope = rememberCoroutineScope()
EventsEffect(viewModel = viewModel) { event -> EventsEffect(viewModel = viewModel) { event ->
when (event) { when (event) {
is ItemListingEvent.NavigateBack -> onNavigateBack() is ItemListingEvent.NavigateBack -> onNavigateBack()
@ -140,11 +137,8 @@ fun ItemListingScreen(
intentManager.startBitwardenAccountSettings() intentManager.startBitwardenAccountSettings()
} }
is ItemListingEvent.ShowFirstTimeSyncSnackbar -> { is ItemListingEvent.ShowSnackbar -> {
// Message property is overridden by FirstTimeSyncSnackbarHost: snackbarHostState.showSnackbar(snackbarData = event.data)
coroutineScope.launch {
snackbarHostState.showSnackbar("")
}
} }
} }
} }
@ -214,7 +208,7 @@ fun ItemListingScreen(
), ),
) )
}, },
snackbarHost = { FirstTimeSyncSnackbarHost(state = snackbarHostState) }, snackbarHost = { BitwardenSnackbarHost(bitwardenHostState = snackbarHostState) },
) { ) {
when (val currentState = state.viewState) { when (val currentState = state.viewState) {
is ItemListingState.ViewState.Content -> { is ItemListingState.ViewState.Content -> {

View File

@ -26,6 +26,7 @@ import com.bitwarden.authenticator.ui.platform.components.listitem.model.Verific
import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager import com.bitwarden.authenticatorbridge.manager.AuthenticatorBridgeManager
import com.bitwarden.core.data.repository.model.DataState import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.ui.platform.base.BaseViewModel 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.feature.settings.appearance.model.AppTheme
import com.bitwarden.ui.platform.resource.BitwardenString import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.Text import com.bitwarden.ui.util.Text
@ -263,7 +264,12 @@ class ItemListingViewModel @Inject constructor(
} }
private fun handleFirstTimeUserSync() { 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) { private fun handleAppThemeChangeReceive(appTheme: AppTheme) {
@ -887,9 +893,25 @@ sealed class ItemListingEvent {
) : 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,
),
)
}
} }
/** /**

View File

@ -418,8 +418,10 @@ class ItemListingScreenTest : AuthenticatorComposeTest() {
.onNodeWithText("Account synced from Bitwarden app") .onNodeWithText("Account synced from Bitwarden app")
.assertIsNotDisplayed() .assertIsNotDisplayed()
// Send ShowFirstTimeSyncSnackbar event // Send ShowSnackbar event
mutableEventFlow.tryEmit(ItemListingEvent.ShowFirstTimeSyncSnackbar) mutableEventFlow.tryEmit(
ItemListingEvent.ShowSnackbar(message = "Account synced from Bitwarden app".asText()),
)
// Make sure the snackbar is showing: // Make sure the snackbar is showing:
composeTestRule composeTestRule

View File

@ -442,11 +442,17 @@ class ItemListingViewModelTest : BaseViewModelTest() {
} }
@Test @Test
fun `on FirstTimeUserSyncReceive should emit ShowFirstTimeSyncSnackbar`() = runTest { fun `on FirstTimeUserSyncReceive should emit ShowSnackbar`() = runTest {
val viewModel = createViewModel() val viewModel = createViewModel()
viewModel.eventFlow.test { viewModel.eventFlow.test {
firstTimeAccountSyncChannel.send(Unit) firstTimeAccountSyncChannel.send(Unit)
assertEquals(ItemListingEvent.ShowFirstTimeSyncSnackbar, awaitItem()) assertEquals(
ItemListingEvent.ShowSnackbar(
message = BitwardenString.account_synced_from_bitwarden_app.asText(),
withDismissAction = true,
),
awaitItem(),
)
} }
} }

View File

@ -13,7 +13,6 @@ import androidx.compose.foundation.layout.displayCutout
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.navigationBars import androidx.compose.foundation.layout.navigationBars
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.only import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.union import androidx.compose.foundation.layout.union
@ -73,10 +72,13 @@ fun BitwardenSnackbar(
.clickable( .clickable(
enabled = !bitwardenSnackbarData.withDismissAction, enabled = !bitwardenSnackbarData.withDismissAction,
onClick = onDismiss, onClick = onDismiss,
) ),
.padding(16.dp), ) {
Column(
modifier = Modifier
.padding(top = 16.dp, bottom = 16.dp, start = 16.dp)
.weight(weight = 1f),
) { ) {
Column(modifier = Modifier.weight(weight = 1f)) {
bitwardenSnackbarData.messageHeader?.let { bitwardenSnackbarData.messageHeader?.let {
Text( Text(
text = it(), text = it(),
@ -111,7 +113,6 @@ fun BitwardenSnackbar(
vectorIconRes = BitwardenDrawable.ic_close, vectorIconRes = BitwardenDrawable.ic_close,
contentDescription = stringResource(BitwardenString.close), contentDescription = stringResource(BitwardenString.close),
contentColor = BitwardenTheme.colorScheme.icon.reversed, contentColor = BitwardenTheme.colorScheme.icon.reversed,
modifier = Modifier.offset(x = 12.dp, y = (-12).dp),
) )
} }
} }