From cee686f92e93e85e24dff0ba6aa388ece2b8a235 Mon Sep 17 00:00:00 2001 From: Matt Gibson Date: Thu, 24 Oct 2024 11:19:43 -0700 Subject: [PATCH] wip --- .../repository/util/DataStateExtensions.kt | 28 ++++++++++++ .../vault/datasource/disk/VaultDiskSource.kt | 7 ++- .../datasource/disk/VaultDiskSourceImpl.kt | 29 +++++++++++- .../data/vault/manager/CipherManager.kt | 5 +++ .../data/vault/manager/CipherManagerImpl.kt | 20 +++++++++ .../data/vault/repository/VaultRepository.kt | 7 +++ .../vault/repository/VaultRepositoryImpl.kt | 45 ++++++++++++++++++- .../data/vault/repository/model/VaultData.kt | 2 + .../util/VaultSdkCipherExtensions.kt | 16 +++++++ .../unsyncedvaultitem/NotificationCenter.kt | 22 --------- .../NotificationCenterViewModel.kt | 8 ---- .../ui/vault/feature/vault/VaultViewModel.kt | 5 ++- 12 files changed, 159 insertions(+), 35 deletions(-) delete mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenterViewModel.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/util/DataStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/util/DataStateExtensions.kt index 998bd1e2b..140b1eb56 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/util/DataStateExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/platform/repository/util/DataStateExtensions.kt @@ -164,6 +164,34 @@ fun combineDataStates( transform(t1t2t3Triple.first, t1t2t3Triple.second, t1t2t3Triple.third, t3) } +/** + * Combines the [dataState1], [dataState2], [dataState3], and [dataState4] [DataState]s together + * using the provided [transform]. + * + * See [combineDataStates] for details. + * + * I'm not proud of this... + */ +@OmitFromCoverage +fun combineDataStates( + dataState1: DataState, + dataState2: DataState, + dataState3: DataState, + dataState4: DataState, + dataState5: DataState, + transform: (t1: T1, t2: T2, t3: T3, t4: T4, t5: T5) -> R, +): DataState = + dataState1 + .combineDataStatesWith(dataState2) { t1, t2 -> t1 to t2 } + .combineDataStatesWith(dataState3) { t1t2Pair, t3 -> + Triple(t1t2Pair.first, t1t2Pair.second, t3) + } + .combineDataStatesWith(dataState4) { t1t2t3Triple, t4 -> t1t2t3Triple to t4 } + .combineDataStatesWith(dataState5) { t1t2t3Triplet4Pair, t5 -> + transform(t1t2t3Triplet4Pair.first.first, t1t2t3Triplet4Pair.first.second, t1t2t3Triplet4Pair.first.third, t1t2t3Triplet4Pair.second, t5) + } + + /** * Combines [dataState2] with the given [DataState] using the provided [transform]. * diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt index 83b7c9d45..956fb7396 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSource.kt @@ -1,7 +1,7 @@ package com.x8bit.bitwarden.data.vault.datasource.disk import com.bitwarden.vault.Cipher -import com.bitwarden.vault.CipherView +import com.x8bit.bitwarden.data.vault.datasource.network.model.OfflineCipherJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import kotlinx.coroutines.flow.Flow @@ -21,6 +21,11 @@ interface VaultDiskSource { */ suspend fun saveCipher(userId: String, cipher: SyncResponseJson.Cipher) + /** + * Retrieves all ciphers from the offline cache for a given [userId] + */ + fun getOfflineCiphers(userId: String): Flow> + /** * Retrieves all ciphers from the data source for a given [userId]. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt index ea1b3e490..abfe008f8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/datasource/disk/VaultDiskSourceImpl.kt @@ -15,6 +15,7 @@ import com.x8bit.bitwarden.data.vault.datasource.disk.entity.DomainsEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.FolderEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.OfflineCipherEntity import com.x8bit.bitwarden.data.vault.datasource.disk.entity.SendEntity +import com.x8bit.bitwarden.data.vault.datasource.network.model.OfflineCipherJson import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.repository.util.toOfflineCipher import com.x8bit.bitwarden.data.vault.repository.util.toOfflineCipherJson @@ -46,6 +47,7 @@ class VaultDiskSourceImpl( private val dispatcherManager: DispatcherManager, ) : VaultDiskSource { + private val forceOfflineCiphersFlow = bufferedMutableSharedFlow>() private val forceCiphersFlow = bufferedMutableSharedFlow>() private val forceCollectionsFlow = bufferedMutableSharedFlow>() @@ -59,7 +61,9 @@ class VaultDiskSourceImpl( id = cipher.id ?: "create_${UUID.randomUUID()}", userId = userId, cipherType = json.encodeToString(cipher.type), - cipherJson = json.encodeToString(cipher.toOfflineCipher().toOfflineCipherJson()), + cipherJson = json.encodeToString( + cipher.toOfflineCipher().toOfflineCipherJson() + ), ), ), ) @@ -78,6 +82,29 @@ class VaultDiskSourceImpl( ) } + + override fun getOfflineCiphers( + userId: String, + ): Flow> = + merge( + forceOfflineCiphersFlow, + offlineCiphersDao + .getAllCiphers(userId = userId) + .map { entities -> + withContext(context = dispatcherManager.default) { + entities + .map { entity -> + async { + json.decodeFromString( + string = entity.cipherJson, + ) + } + } + .awaitAll() + } + }, + ) + override fun getCiphers( userId: String, ): Flow> = diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt index 320038c02..a4429a1fd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManager.kt @@ -100,6 +100,11 @@ interface CipherManager { collectionIds: List, ): ShareCipherResult + suspend fun updateOfflineCipher( + cipherId: String, + cipherView: CipherView, + ): UpdateCipherResult + /** * Attempt to update a cipher. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt index f4f53b9ab..a11dfb35a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/manager/CipherManagerImpl.kt @@ -208,6 +208,26 @@ class CipherManagerImpl( ) } + override suspend fun updateOfflineCipher( + cipherId: String, + cipherView: CipherView, + ): UpdateCipherResult { + val userId = activeUserId ?: return UpdateCipherResult.Error(errorMessage = null) + + return vaultSdkSource.encryptCipher( + userId = userId, + cipherView = cipherView + ) + .map { + vaultDiskSource.saveOfflineCipher(userId = userId, cipher = it) + UpdateCipherResult.Success + } + .fold( + onFailure = { UpdateCipherResult.Error(errorMessage = null) }, + onSuccess = { it }, + ) + } + override suspend fun updateCipher( cipherId: String, cipherView: CipherView, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt index 1e40afe8c..a51f0959f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepository.kt @@ -56,6 +56,13 @@ interface VaultRepository : CipherManager, VaultLockManager { */ val vaultDataStateFlow: StateFlow> + /** + * Flow that represents all ciphers stored in the offline cache for the active user. + * + * Note that the [StateFlow.value] will return the last known value but the [StateFlow] itself + * must be collected in order to trigger state changes. + */ + val offlineCiphersStateFlow: StateFlow>> /** * Flow that represents all ciphers for the active user. * diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt index 00ea536b8..ab417c238 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/VaultRepositoryImpl.kt @@ -72,6 +72,7 @@ import com.x8bit.bitwarden.data.vault.repository.model.UpdateSendResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData import com.x8bit.bitwarden.data.vault.repository.model.VaultUnlockResult import com.x8bit.bitwarden.data.vault.repository.util.sortAlphabetically +import com.x8bit.bitwarden.data.vault.repository.util.toCipherList import com.x8bit.bitwarden.data.vault.repository.util.toDomainsData import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkFolder import com.x8bit.bitwarden.data.vault.repository.util.toEncryptedNetworkSend @@ -156,6 +157,9 @@ class VaultRepositoryImpl( private val mutableSendDataStateFlow = MutableStateFlow>(DataState.Loading) + private val mutableOfflineCiphersStateFlow = + MutableStateFlow>>(DataState.Loading) + private val mutableCiphersStateFlow = MutableStateFlow>>(DataState.Loading) @@ -172,18 +176,21 @@ class VaultRepositoryImpl( override val vaultDataStateFlow: StateFlow> = combine( + offlineCiphersStateFlow, ciphersStateFlow, foldersStateFlow, collectionsStateFlow, sendDataStateFlow, - ) { ciphersDataState, foldersDataState, collectionsDataState, sendsDataState -> + ) { offlineCiphersDataState, ciphersDataState, foldersDataState, collectionsDataState, sendsDataState -> combineDataStates( + offlineCiphersDataState, ciphersDataState, foldersDataState, collectionsDataState, sendsDataState, - ) { ciphersData, foldersData, collectionsData, sendsData -> + ) { offlineCiphersData, ciphersData, foldersData, collectionsData, sendsData -> VaultData( + offlineCipherViewList = offlineCiphersData, cipherViewList = ciphersData, fido2CredentialAutofillViewList = null, folderViewList = foldersData, @@ -201,6 +208,9 @@ class VaultRepositoryImpl( override val totpCodeFlow: Flow get() = mutableTotpCodeResultFlow.asSharedFlow() + override val offlineCiphersStateFlow: StateFlow>> + get() = mutableOfflineCiphersStateFlow.asStateFlow(); + override val ciphersStateFlow: StateFlow>> get() = mutableCiphersStateFlow.asStateFlow() @@ -234,6 +244,11 @@ class VaultRepositoryImpl( } .launchIn(unconfinedScope) + // Setup offline ciphers MutableStateFlow + mutableOfflineCiphersStateFlow + .observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId -> + observeVaultDiskOfflineCiphers(activeUserId) + }.launchIn(unconfinedScope) // Setup ciphers MutableStateFlow mutableCiphersStateFlow .observeWhenSubscribedAndLoggedIn(authDiskSource.userStateFlow) { activeUserId -> @@ -302,6 +317,7 @@ class VaultRepositoryImpl( } private fun clearUnlockedData() { + mutableOfflineCiphersStateFlow.update { DataState.Loading } mutableCiphersStateFlow.update { DataState.Loading } mutableDomainsStateFlow.update { DataState.Loading } mutableFoldersStateFlow.update { DataState.Loading } @@ -318,6 +334,7 @@ class VaultRepositoryImpl( override fun sync() { val userId = activeUserId ?: return if (!syncJob.isCompleted) return + mutableOfflineCiphersStateFlow.updateToPendingOrLoading() mutableCiphersStateFlow.updateToPendingOrLoading() mutableDomainsStateFlow.updateToPendingOrLoading() mutableFoldersStateFlow.updateToPendingOrLoading() @@ -926,6 +943,25 @@ class VaultRepositoryImpl( ) } + private fun observeVaultDiskOfflineCiphers( + userId: String, + ): Flow>> = + vaultDiskSource.getOfflineCiphers(userId = userId) + .onStart { mutableOfflineCiphersStateFlow.updateToPendingOrLoading() } + .map { + waitUntilUnlocked(userId = userId) + vaultSdkSource + .decryptCipherList( + userId = userId, + cipherList = it.toCipherList(), + ) + .fold( + onSuccess = { ciphers -> DataState.Loaded(ciphers.sortAlphabetically()) }, + onFailure = { throwable -> DataState.Error(throwable) }, + ) + } + .onEach { mutableOfflineCiphersStateFlow.value = it } + private fun observeVaultDiskCiphers( userId: String, ): Flow>> = @@ -1029,6 +1065,11 @@ class VaultRepositoryImpl( .onEach { mutableSendDataStateFlow.value = it } private fun updateVaultStateFlowsToError(throwable: Throwable) { + mutableOfflineCiphersStateFlow.update { currentState -> + throwable.toNetworkOrErrorState( + data = currentState.data, + ) + } mutableCiphersStateFlow.update { currentState -> throwable.toNetworkOrErrorState( data = currentState.data, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt index 6cc9e5664..4f736505e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/model/VaultData.kt @@ -9,6 +9,7 @@ import com.bitwarden.vault.FolderView /** * Represents decrypted vault data. * + * @param offlineCipherViewList List of decrypted ciphers from offline cache. * @param cipherViewList List of decrypted ciphers. * @param collectionViewList List of decrypted collections. * @param folderViewList List of decrypted folders. @@ -16,6 +17,7 @@ import com.bitwarden.vault.FolderView * @param fido2CredentialAutofillViewList List of decrypted fido 2 credentials. */ data class VaultData( + val offlineCipherViewList: List, val cipherViewList: List, val collectionViewList: List, val folderViewList: List, diff --git a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt index 1938cb926..cba86c45d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/data/vault/repository/util/VaultSdkCipherExtensions.kt @@ -31,6 +31,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.SecureNoteTypeJso import com.x8bit.bitwarden.data.vault.datasource.network.model.SyncResponseJson import com.x8bit.bitwarden.data.vault.datasource.network.model.UriMatchTypeJson import com.x8bit.bitwarden.data.vault.datasource.sdk.model.OfflineCipher +import com.x8bit.bitwarden.ui.vault.feature.vault.model.NotificationSummary import java.time.ZoneOffset import java.time.ZonedDateTime import java.util.UUID @@ -111,6 +112,12 @@ fun OfflineCipher.toOfflineCipherJson(): OfflineCipherJson = mergeConflict = false, // TODO: Copy from the new OfflineCipher type ) +fun CipherView.toNotificationSummary(): NotificationSummary = + NotificationSummary( + title = name, + subtitle = "edited on ${revisionDate.toString()}", + ) + fun OfflineCipherJson.toOfflineCipher(): OfflineCipher = OfflineCipher( id = if(id.startsWith("create")) null else id, @@ -136,6 +143,8 @@ fun OfflineCipherJson.toOfflineCipher(): OfflineCipher = mergeConflict = mergeConflict ) + + fun OfflineCipher.toCipher(): Cipher = Cipher( id = id, @@ -427,6 +436,13 @@ private fun CipherType.toNetworkCipherType(): CipherTypeJson = fun List.toEncryptedSdkCipherList(): List = map { it.toEncryptedSdkCipher() } +/** + * Converts a list of [OfflineCipherJson] objects to a list of corresponding + * Bitwarden SDK [Cipher] objects. + */ +fun List.toCipherList(): List = + map { it.toOfflineCipher().toCipher() } + /** * Converts a [SyncResponseJson.Cipher] object to a corresponding * Bitwarden SDK [Cipher] object. diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenter.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenter.kt index 6c072d2c4..6f00fa5ec 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenter.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenter.kt @@ -1,6 +1,5 @@ package com.x8bit.bitwarden.ui.vault.feature.unsyncedvaultitem -import android.util.Range import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.ExperimentalAnimationApi import androidx.compose.animation.core.updateTransition @@ -42,14 +41,10 @@ import androidx.compose.ui.semantics.clearAndSetSemantics import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.hilt.navigation.compose.hiltViewModel import com.x8bit.bitwarden.R -import com.x8bit.bitwarden.ui.platform.base.util.lowercaseWithCurrentLocal import com.x8bit.bitwarden.ui.platform.base.util.scrolledContainerBackground import com.x8bit.bitwarden.ui.platform.base.util.toSafeOverlayColor import com.x8bit.bitwarden.ui.platform.base.util.toUnscaledTextUnit -import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLogoutConfirmationDialog -import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenRemovalConfirmationDialog import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenBasicDialogRow import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider @@ -57,12 +52,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary import com.x8bit.bitwarden.ui.platform.components.scrim.BitwardenAnimatedScrim import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme -import com.x8bit.bitwarden.ui.vault.feature.vault.VaultViewModel import com.x8bit.bitwarden.ui.vault.feature.vault.model.NotificationSummary -import com.x8bit.bitwarden.ui.vault.feature.vault.util.iconRes -import com.x8bit.bitwarden.ui.vault.feature.vault.util.iconTestTag -import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials -import com.x8bit.bitwarden.ui.vault.feature.vault.util.supportingTextResOrNull import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -83,19 +73,8 @@ private const val MAXIMUM_ACCOUNT_LIMIT = 5 * * @param isVisible Whether or not this component is visible. Changing this value will animate the * component in or out of view. - * @param accountSummaries The accounts to display in the switcher. - * @param onSwitchAccountClick A callback when an account is clicked indicating that the account - * should be switched to. - * @param onLockAccountClick A callback when an account is clicked indicating that the account - * should be locked. - * @param onLogoutAccountClick A callback when an account is clicked indicating that the account - * should be logged out. - * @param onAddAccountClick A callback when the Add Account row is clicked. * @param onDismissRequest A callback when the component requests to be dismissed. This is triggered * whenever the user clicks on the scrim or any of the switcher items. - * @param isAddAccountAvailable Whether or not the "Add account" button is available. Note that even - * when `true`, this button may be hidden when there are more than [MAXIMUM_ACCOUNT_LIMIT] accounts - * present. * @param modifier A [Modifier] for the composable. * @param topAppBarScrollBehavior Used to derive the background color of the content and keep it in * sync with the associated app bar. @@ -104,7 +83,6 @@ private const val MAXIMUM_ACCOUNT_LIMIT = 5 @Suppress("LongMethod") @Composable fun NotificationCenter( - viewModel: NotificationCenterViewModel = hiltViewModel(), isVisible: Boolean, notificationSummaries: ImmutableList, onNotificationClick: (NotificationSummary) -> Unit, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenterViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenterViewModel.kt deleted file mode 100644 index 3df92e551..000000000 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/unsyncedvaultitem/NotificationCenterViewModel.kt +++ /dev/null @@ -1,8 +0,0 @@ -import androidx.lifecycle.ViewModel -import dagger.hilt.android.lifecycle.HiltViewModel -import javax.inject.Inject - -@HiltViewModel -class NotificationCenterViewModel @Inject constructor( - -): ViewModel() {} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt index 6f0715a3a..2333a6b61 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModel.kt @@ -4,6 +4,7 @@ import android.os.Build import android.os.Parcelable import androidx.compose.ui.graphics.Color import androidx.lifecycle.viewModelScope +import com.bitwarden.vault.CipherView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.auth.repository.AuthRepository import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult @@ -24,6 +25,7 @@ import com.x8bit.bitwarden.data.vault.datasource.network.model.PolicyTypeJson import com.x8bit.bitwarden.data.vault.repository.VaultRepository import com.x8bit.bitwarden.data.vault.repository.model.GenerateTotpResult import com.x8bit.bitwarden.data.vault.repository.model.VaultData +import com.x8bit.bitwarden.data.vault.repository.util.toNotificationSummary import com.x8bit.bitwarden.ui.platform.base.BaseViewModel import com.x8bit.bitwarden.ui.platform.base.util.Text import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -553,8 +555,9 @@ class VaultViewModel @Inject constructor( ), ) } - mutableStateFlow.update { + mutableStateFlow.update { it -> it.copy( + notificationSummaries = vaultData.data.offlineCipherViewList.map { view -> view.toNotificationSummary()}, viewState = vaultData.data.toViewState( baseIconUrl = state.baseIconUrl, isIconLoadingDisabled = state.isIconLoadingDisabled,