mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
BIT-1623: Loading State Not Shown on Initial Vault Access (#1045)
This commit is contained in:
parent
7b7a1d15f5
commit
e6883d9599
2 changed files with 146 additions and 2 deletions
|
@ -367,8 +367,8 @@ class VaultRepositoryImpl(
|
|||
userId = userId,
|
||||
policies = syncResponse.policies,
|
||||
)
|
||||
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
|
||||
settingsDiskSource.storeLastSyncTime(userId = userId, clock.instant())
|
||||
vaultDiskSource.replaceVaultData(userId = userId, vault = syncResponse)
|
||||
},
|
||||
onFailure = { throwable ->
|
||||
updateVaultStateFlowsToError(throwable)
|
||||
|
@ -1335,6 +1335,7 @@ class VaultRepositoryImpl(
|
|||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.map { it.orLoadingIfNotSynced(userId = userId) }
|
||||
.onEach { mutableCiphersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskDomains(
|
||||
|
@ -1368,6 +1369,7 @@ class VaultRepositoryImpl(
|
|||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.map { it.orLoadingIfNotSynced(userId = userId) }
|
||||
.onEach { mutableFoldersStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskCollections(
|
||||
|
@ -1392,6 +1394,7 @@ class VaultRepositoryImpl(
|
|||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.map { it.orLoadingIfNotSynced(userId = userId) }
|
||||
.onEach { mutableCollectionsStateFlow.value = it }
|
||||
|
||||
private fun observeVaultDiskSends(
|
||||
|
@ -1408,10 +1411,12 @@ class VaultRepositoryImpl(
|
|||
sendList = it.toEncryptedSdkSendList(),
|
||||
)
|
||||
.fold(
|
||||
onSuccess = { sends -> DataState.Loaded(SendData(sends)) },
|
||||
onSuccess = { sends -> DataState.Loaded(sends) },
|
||||
onFailure = { throwable -> DataState.Error(throwable) },
|
||||
)
|
||||
}
|
||||
.map { it.orLoadingIfNotSynced(userId = userId) }
|
||||
.map { dataState -> dataState.map { SendData(it) } }
|
||||
.onEach { mutableSendDataStateFlow.value = it }
|
||||
|
||||
private fun updateVaultStateFlowsToError(throwable: Throwable) {
|
||||
|
@ -1442,6 +1447,20 @@ class VaultRepositoryImpl(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the given [DataState] as-is, or [DataState.Loading] if vault data for the given
|
||||
* [userId] has not synced. This can be used to distinguish between empty data in the database
|
||||
* because we are in the process of syncing from legitimately having no vault data.
|
||||
*/
|
||||
private fun <T> DataState<List<T>>.orLoadingIfNotSynced(
|
||||
userId: String,
|
||||
): DataState<List<T>> =
|
||||
this
|
||||
.takeUnless {
|
||||
settingsDiskSource.getLastSyncTime(userId = userId) == null
|
||||
}
|
||||
?: DataState.Loading
|
||||
|
||||
//region Push notification helpers
|
||||
/**
|
||||
* Deletes the cipher specified by [syncCipherDeleteData] from disk.
|
||||
|
|
|
@ -1573,6 +1573,103 @@ class VaultRepositoryTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow should return empty when last sync time is populated`() =
|
||||
runTest {
|
||||
val userId = "mockId-1"
|
||||
coEvery {
|
||||
vaultLockManager.waitUntilUnlocked(userId = userId)
|
||||
} just runs
|
||||
every {
|
||||
settingsDiskSource.getLastSyncTime(userId = userId)
|
||||
} returns clock.instant()
|
||||
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
setupEmptyDecryptionResults()
|
||||
setupVaultDiskSourceFlows(
|
||||
ciphersFlow = flowOf(emptyList()),
|
||||
collectionsFlow = flowOf(emptyList()),
|
||||
domainsFlow = flowOf(
|
||||
SyncResponseJson.Domains(
|
||||
globalEquivalentDomains = emptyList(),
|
||||
equivalentDomains = emptyList(),
|
||||
),
|
||||
),
|
||||
foldersFlow = flowOf(emptyList()),
|
||||
sendsFlow = flowOf(emptyList()),
|
||||
)
|
||||
|
||||
turbineScope {
|
||||
val ciphersStateFlow = vaultRepository.ciphersStateFlow.testIn(backgroundScope)
|
||||
val collectionsStateFlow =
|
||||
vaultRepository.collectionsStateFlow.testIn(backgroundScope)
|
||||
val foldersStateFlow = vaultRepository.foldersStateFlow.testIn(backgroundScope)
|
||||
val sendsStateFlow = vaultRepository.sendDataStateFlow.testIn(backgroundScope)
|
||||
val domainsStateFlow = vaultRepository.domainsStateFlow.testIn(backgroundScope)
|
||||
|
||||
assertEquals(
|
||||
DataState.Loaded(emptyList<CipherView>()),
|
||||
ciphersStateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Loaded(emptyList<CollectionView>()),
|
||||
collectionsStateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Loaded(emptyList<FolderView>()),
|
||||
foldersStateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Loaded(SendData(sendViewList = emptyList())),
|
||||
sendsStateFlow.awaitItem(),
|
||||
)
|
||||
assertEquals(
|
||||
DataState.Loaded(
|
||||
DomainsData(
|
||||
equivalentDomains = emptyList(),
|
||||
globalEquivalentDomains = emptyList(),
|
||||
),
|
||||
),
|
||||
domainsStateFlow.awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow should return loading when last sync time is null`() =
|
||||
runTest {
|
||||
val userId = "mockId-1"
|
||||
coEvery {
|
||||
vaultLockManager.waitUntilUnlocked(userId = userId)
|
||||
} just runs
|
||||
every {
|
||||
settingsDiskSource.getLastSyncTime(userId = userId)
|
||||
} returns null
|
||||
fakeAuthDiskSource.userState = MOCK_USER_STATE
|
||||
setupEmptyDecryptionResults()
|
||||
setupVaultDiskSourceFlows(
|
||||
ciphersFlow = flowOf(emptyList()),
|
||||
collectionsFlow = flowOf(emptyList()),
|
||||
domainsFlow = flowOf(),
|
||||
foldersFlow = flowOf(emptyList()),
|
||||
sendsFlow = flowOf(emptyList()),
|
||||
)
|
||||
turbineScope {
|
||||
val ciphersStateFlow = vaultRepository.ciphersStateFlow.testIn(backgroundScope)
|
||||
val collectionsStateFlow =
|
||||
vaultRepository.collectionsStateFlow.testIn(backgroundScope)
|
||||
val foldersStateFlow = vaultRepository.foldersStateFlow.testIn(backgroundScope)
|
||||
val sendsStateFlow = vaultRepository.sendDataStateFlow.testIn(backgroundScope)
|
||||
val domainsStateFlow = vaultRepository.domainsStateFlow.testIn(backgroundScope)
|
||||
|
||||
assertEquals(DataState.Loading, ciphersStateFlow.awaitItem())
|
||||
assertEquals(DataState.Loading, collectionsStateFlow.awaitItem())
|
||||
assertEquals(DataState.Loading, foldersStateFlow.awaitItem())
|
||||
assertEquals(DataState.Loading, sendsStateFlow.awaitItem())
|
||||
assertEquals(DataState.Loading, domainsStateFlow.awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `getVaultFolderStateFlow should update to NoNetwork when a sync fails from no network`() =
|
||||
runTest {
|
||||
|
@ -5463,6 +5560,34 @@ class VaultRepositoryTest {
|
|||
coEvery { vaultDiskSource.getSends(MOCK_USER_STATE.activeUserId) } returns sendsFlow
|
||||
}
|
||||
|
||||
private fun setupEmptyDecryptionResults() {
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
cipherList = emptyList(),
|
||||
)
|
||||
} returns emptyList<CipherView>().asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptFolderList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
folderList = emptyList(),
|
||||
)
|
||||
} returns emptyList<FolderView>().asSuccess()
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCollectionList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
collectionList = emptyList(),
|
||||
)
|
||||
} returns emptyList<CollectionView>().asSuccess()
|
||||
|
||||
coEvery {
|
||||
vaultSdkSource.decryptSendList(
|
||||
userId = MOCK_USER_STATE.activeUserId,
|
||||
sendList = emptyList(),
|
||||
)
|
||||
} returns emptyList<SendView>().asSuccess()
|
||||
}
|
||||
|
||||
private suspend fun setupDataStateFlow(userId: String) {
|
||||
coEvery {
|
||||
vaultSdkSource.decryptCipherList(
|
||||
|
|
Loading…
Reference in a new issue