PM-25069: Update VaultAddEditViewModel toasts to snackbars (#5769)

This commit is contained in:
David Perez
2025-08-25 13:45:12 -05:00
committed by GitHub
parent 191ff4c652
commit ff23dc3ab2
8 changed files with 103 additions and 80 deletions

View File

@@ -160,7 +160,9 @@ class SearchViewModel @Inject constructor(
snackbarRelayManager
.getSnackbarDataFlow(
SnackbarRelay.CIPHER_DELETED,
SnackbarRelay.CIPHER_DELETED_SOFT,
SnackbarRelay.CIPHER_RESTORED,
SnackbarRelay.CIPHER_UPDATED,
SnackbarRelay.SEND_DELETED,
SnackbarRelay.SEND_UPDATED,
)
@@ -1329,7 +1331,7 @@ sealed class SearchAction {
*/
data class SnackbarDataReceived(
val data: BitwardenSnackbarData,
) : Internal()
) : Internal(), BackgroundEvent
/**
* Indicates a result for updating a cipher during the autofill-and-save process.

View File

@@ -9,9 +9,12 @@ import kotlinx.serialization.Serializable
*/
@Serializable
enum class SnackbarRelay {
CIPHER_CREATED,
CIPHER_DELETED,
CIPHER_DELETED_SOFT,
CIPHER_MOVED_TO_ORGANIZATION,
CIPHER_RESTORED,
CIPHER_UPDATED,
ENVIRONMENT_SAVED,
LOGIN_APPROVAL,
LOGIN_SUCCESS,

View File

@@ -1,6 +1,5 @@
package com.x8bit.bitwarden.ui.vault.feature.addedit
import android.widget.Toast
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
@@ -29,7 +28,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@@ -114,8 +112,6 @@ fun VaultAddEditScreen(
onNavigateToMoveToOrganization: (cipherId: String, showOnlyCollections: Boolean) -> Unit,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val context = LocalContext.current
val resources = context.resources
val userVerificationHandlers = remember(viewModel) {
VaultAddEditUserVerificationHandlers.create(viewModel = viewModel)
}
@@ -141,10 +137,6 @@ fun VaultAddEditScreen(
onNavigateToGeneratorModal(event.generatorMode)
}
is VaultAddEditEvent.ShowToast -> {
Toast.makeText(context, event.message(resources), Toast.LENGTH_SHORT).show()
}
is VaultAddEditEvent.NavigateToAttachments -> onNavigateToAttachments(event.cipherId)
is VaultAddEditEvent.NavigateToMoveToOrganization -> {
onNavigateToMoveToOrganization(event.cipherId, false)

View File

@@ -7,6 +7,7 @@ import androidx.credentials.provider.ProviderCreateCredentialRequest
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.bitwarden.core.DateTime
import com.bitwarden.core.data.manager.toast.ToastManager
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.takeUntilLoaded
import com.bitwarden.network.model.PolicyTypeJson
@@ -114,7 +115,8 @@ private const val KEY_STATE = "state"
class VaultAddEditViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
generatorRepository: GeneratorRepository,
snackbarRelayManager: SnackbarRelayManager,
private val snackbarRelayManager: SnackbarRelayManager,
private val toastManager: ToastManager,
private val authRepository: AuthRepository,
private val clipboardManager: BitwardenClipboardManager,
private val policyManager: PolicyManager,
@@ -1192,7 +1194,7 @@ class VaultAddEditViewModel @Inject constructor(
updateLoginContent { loginType ->
loginType.copy(fido2CredentialCreationDateTime = null)
}
sendEvent(event = VaultAddEditEvent.ShowToast(BitwardenString.passkey_removed.asText()))
sendEvent(event = VaultAddEditEvent.ShowSnackbar(BitwardenString.passkey_removed.asText()))
}
private fun handlePasswordVisibilityChange(
@@ -1656,10 +1658,9 @@ class VaultAddEditViewModel @Inject constructor(
if (state.shouldExitOnSave) {
sendEvent(event = VaultAddEditEvent.ExitApp)
} else {
sendEvent(
event = VaultAddEditEvent.ShowToast(
BitwardenString.new_item_created.asText(),
),
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.new_item_created.asText()),
relay = SnackbarRelay.CIPHER_CREATED,
)
sendEvent(event = VaultAddEditEvent.NavigateBack)
}
@@ -1690,10 +1691,9 @@ class VaultAddEditViewModel @Inject constructor(
if (state.shouldExitOnSave) {
sendEvent(event = VaultAddEditEvent.ExitApp)
} else {
sendEvent(
event = VaultAddEditEvent.ShowToast(
BitwardenString.item_updated.asText(),
),
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.item_updated.asText()),
relay = SnackbarRelay.CIPHER_UPDATED,
)
sendEvent(event = VaultAddEditEvent.NavigateBack)
}
@@ -1714,10 +1714,9 @@ class VaultAddEditViewModel @Inject constructor(
DeleteCipherResult.Success -> {
clearDialogState()
sendEvent(
VaultAddEditEvent.ShowToast(
message = BitwardenString.item_soft_deleted.asText(),
),
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.item_soft_deleted.asText()),
relay = SnackbarRelay.CIPHER_DELETED_SOFT,
)
sendEvent(VaultAddEditEvent.NavigateBack)
}
@@ -1887,7 +1886,7 @@ class VaultAddEditViewModel @Inject constructor(
when (val result = action.totpResult) {
is TotpCodeResult.Success -> {
sendEvent(
event = VaultAddEditEvent.ShowToast(
event = VaultAddEditEvent.ShowSnackbar(
message = BitwardenString.authenticator_key_added.asText(),
),
)
@@ -1962,11 +1961,8 @@ class VaultAddEditViewModel @Inject constructor(
clearDialogState()
when (action.result) {
is Fido2RegisterCredentialResult.Error -> {
sendEvent(
VaultAddEditEvent.ShowToast(
BitwardenString.an_error_has_occurred.asText(),
),
)
// Use toast here because we are closing the activity.
toastManager.show(BitwardenString.an_error_has_occurred)
sendEvent(
VaultAddEditEvent.CompleteFido2Registration(
RegisterFido2CredentialResult.Error(
@@ -1977,7 +1973,8 @@ class VaultAddEditViewModel @Inject constructor(
}
is Fido2RegisterCredentialResult.Success -> {
sendEvent(VaultAddEditEvent.ShowToast(BitwardenString.item_updated.asText()))
// Use toast here because we are closing the activity.
toastManager.show(BitwardenString.item_updated)
sendEvent(
VaultAddEditEvent.CompleteFido2Registration(
RegisterFido2CredentialResult.Success(action.result.responseJson),
@@ -2804,12 +2801,21 @@ sealed class VaultAddEditEvent {
*/
data class ShowSnackbar(
val data: BitwardenSnackbarData,
) : VaultAddEditEvent(), BackgroundEvent
/**
* Shows a toast with the given [message].
*/
data class ShowToast(val message: Text) : VaultAddEditEvent()
) : VaultAddEditEvent(), BackgroundEvent {
constructor(
message: Text,
messageHeader: Text? = null,
actionLabel: Text? = null,
withDismissAction: Boolean = false,
) : this(
data = BitwardenSnackbarData(
message = message,
messageHeader = messageHeader,
actionLabel = actionLabel,
withDismissAction = withDismissAction,
),
)
}
/**
* Leave the application.

View File

@@ -207,7 +207,11 @@ class VaultItemViewModel @Inject constructor(
.launchIn(viewModelScope)
snackbarRelayManager
.getSnackbarDataFlow(SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION)
.getSnackbarDataFlow(
SnackbarRelay.CIPHER_DELETED_SOFT,
SnackbarRelay.CIPHER_MOVED_TO_ORGANIZATION,
SnackbarRelay.CIPHER_UPDATED,
)
.map { VaultItemAction.Internal.SnackbarDataReceived(it) }
.onEach(::sendAction)
.launchIn(viewModelScope)

View File

@@ -218,8 +218,11 @@ class VaultItemListingViewModel @Inject constructor(
snackbarRelayManager
.getSnackbarDataFlow(
SnackbarRelay.CIPHER_CREATED,
SnackbarRelay.CIPHER_DELETED,
SnackbarRelay.CIPHER_DELETED_SOFT,
SnackbarRelay.CIPHER_RESTORED,
SnackbarRelay.CIPHER_UPDATED,
SnackbarRelay.SEND_DELETED,
SnackbarRelay.SEND_UPDATED,
)
@@ -3586,7 +3589,7 @@ sealed class VaultItemListingsAction {
*/
data class SnackbarDataReceived(
val data: BitwardenSnackbarData,
) : Internal()
) : Internal(), BackgroundEvent
/**
* Indicates that an error occurred while decrypting a cipher.

View File

@@ -173,8 +173,11 @@ class VaultViewModel @Inject constructor(
delay(timeMillis = LOGIN_SUCCESS_SNACKBAR_DELAY)
},
snackbarRelayManager.getSnackbarDataFlow(
SnackbarRelay.CIPHER_CREATED,
SnackbarRelay.CIPHER_DELETED,
SnackbarRelay.CIPHER_DELETED_SOFT,
SnackbarRelay.CIPHER_RESTORED,
SnackbarRelay.CIPHER_UPDATED,
SnackbarRelay.LOGINS_IMPORTED,
),
)
@@ -1713,7 +1716,7 @@ sealed class VaultAction {
*/
data class SnackbarDataReceive(
val data: BitwardenSnackbarData,
) : Internal()
) : Internal(), BackgroundEvent
/**
* Indicates that the flight recorder data was received.

View File

@@ -9,6 +9,7 @@ import androidx.lifecycle.SavedStateHandle
import app.cash.turbine.test
import com.bitwarden.collections.CollectionView
import com.bitwarden.core.DateTime
import com.bitwarden.core.data.manager.toast.ToastManager
import com.bitwarden.core.data.repository.model.DataState
import com.bitwarden.core.data.repository.util.bufferedMutableSharedFlow
import com.bitwarden.data.datasource.disk.base.FakeDispatcherManager
@@ -75,6 +76,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateCipherResult
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.credentials.manager.model.RegisterFido2CredentialResult
import com.x8bit.bitwarden.ui.platform.manager.resource.ResourceManager
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelay
import com.x8bit.bitwarden.ui.platform.manager.snackbar.SnackbarRelayManager
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
@@ -210,6 +212,11 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
every {
getSnackbarDataFlow(relay = any(), relays = anyVararg())
} returns mutableSnackbarDataFlow
every { sendSnackbarData(data = any(), relay = any()) } just runs
}
private val toastManager: ToastManager = mockk {
every { show(messageId = any(), duration = any()) } just runs
every { show(message = any(), duration = any()) } just runs
}
@BeforeEach
@@ -616,15 +623,17 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
viewModel.trySendAction(VaultAddEditAction.Common.ConfirmDeleteClick)
viewModel.eventFlow.test {
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.item_soft_deleted.asText()),
awaitItem(),
)
assertEquals(
VaultAddEditEvent.NavigateBack,
awaitItem(),
)
}
verify(exactly = 1) {
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.item_soft_deleted.asText()),
relay = SnackbarRelay.CIPHER_DELETED_SOFT,
)
}
}
@Test
@@ -734,17 +743,17 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(stateWithDialog, stateFlow.awaitItem())
assertEquals(stateWithName, stateFlow.awaitItem())
assertEquals(
VaultAddEditEvent.ShowToast(
BitwardenString.new_item_created.asText(),
),
eventFlow.awaitItem(),
)
assertEquals(
VaultAddEditEvent.NavigateBack,
eventFlow.awaitItem(),
)
}
verify(exactly = 1) {
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.new_item_created.asText()),
relay = SnackbarRelay.CIPHER_CREATED,
)
}
coVerify(exactly = 1) {
vaultRepository.createCipherInOrganization(any(), any())
}
@@ -906,13 +915,15 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(stateWithName, stateTurbine.awaitItem())
assertEquals(stateWithDialog, stateTurbine.awaitItem())
assertEquals(stateWithName, stateTurbine.awaitItem())
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.new_item_created.asText()),
eventTurbine.awaitItem(),
)
assertEquals(VaultAddEditEvent.NavigateBack, eventTurbine.awaitItem())
}
assertNotNull(specialCircumstanceManager.specialCircumstance)
verify(exactly = 1) {
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.new_item_created.asText()),
relay = SnackbarRelay.CIPHER_CREATED,
)
}
coVerify(exactly = 1) {
vaultRepository.createCipherInOrganization(any(), any())
}
@@ -1070,10 +1081,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
assertEquals(stateWithName, stateFlow.awaitItem())
assertEquals(stateWithSavingDialog, stateFlow.awaitItem())
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.item_updated.asText()),
eventFlow.awaitItem(),
)
assertEquals(stateWithName, stateFlow.awaitItem())
assertEquals(
VaultAddEditEvent.CompleteFido2Registration(
@@ -1083,6 +1090,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
),
eventFlow.awaitItem(),
)
verify(exactly = 1) {
toastManager.show(messageId = BitwardenString.item_updated)
}
coVerify(exactly = 1) {
bitwardenCredentialManager.registerFido2Credential(
userId = mockUserId,
@@ -1337,14 +1347,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
} returns CreateCipherResult.Success
viewModel.eventFlow.test {
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
assertEquals(
VaultAddEditEvent.ShowToast(
BitwardenString.new_item_created.asText(),
),
awaitItem(),
)
assertEquals(VaultAddEditEvent.NavigateBack, awaitItem())
}
verify(exactly = 1) {
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.new_item_created.asText()),
relay = SnackbarRelay.CIPHER_CREATED,
)
}
}
@Test
@@ -1668,14 +1678,14 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
} returns UpdateCipherResult.Success
viewModel.eventFlow.test {
viewModel.trySendAction(VaultAddEditAction.Common.SaveClick)
assertEquals(
VaultAddEditEvent.ShowToast(
BitwardenString.item_updated.asText(),
),
awaitItem(),
)
assertEquals(VaultAddEditEvent.NavigateBack, awaitItem())
}
verify(exactly = 1) {
snackbarRelayManager.sendSnackbarData(
data = BitwardenSnackbarData(BitwardenString.item_updated.asText()),
relay = SnackbarRelay.CIPHER_UPDATED,
)
}
}
@Test
@@ -2579,7 +2589,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
)
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.authenticator_key_added.asText()),
VaultAddEditEvent.ShowSnackbar(
message = BitwardenString.authenticator_key_added.asText(),
),
awaitItem(),
)
@@ -3306,6 +3318,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
generatorRepository = generatorRepository,
settingsRepository = settingsRepository,
snackbarRelayManager = snackbarRelayManager,
toastManager = toastManager,
specialCircumstanceManager = specialCircumstanceManager,
resourceManager = resourceManager,
clock = fixedClock,
@@ -4508,11 +4521,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
)
viewModel.eventFlow.test {
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.an_error_has_occurred.asText()),
awaitItem(),
)
assertEquals(
VaultAddEditEvent.CompleteFido2Registration(
RegisterFido2CredentialResult.Error(
@@ -4523,6 +4531,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
awaitItem(),
)
}
verify(exactly = 1) {
toastManager.show(messageId = BitwardenString.an_error_has_occurred)
}
}
@Suppress("MaxLineLength")
@@ -4555,11 +4566,6 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
)
viewModel.eventFlow.test {
assertEquals(
VaultAddEditEvent.ShowToast(BitwardenString.item_updated.asText()),
awaitItem(),
)
assertEquals(
VaultAddEditEvent.CompleteFido2Registration(
RegisterFido2CredentialResult.Success(
@@ -4569,6 +4575,9 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
awaitItem(),
)
}
verify(exactly = 1) {
toastManager.show(messageId = BitwardenString.item_updated)
}
}
}
@@ -4709,6 +4718,7 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
generatorRepository = generatorRepo,
settingsRepository = settingsRepository,
snackbarRelayManager = snackbarRelayManager,
toastManager = toastManager,
specialCircumstanceManager = specialCircumstanceManager,
resourceManager = bitwardenResourceManager,
clock = clock,