mirror of
https://github.com/bitwarden/android.git
synced 2024-11-23 01:46:00 +03:00
BIT-383: Finish UI for syncing the Vault with the Sync button (#463)
This commit is contained in:
parent
17b50d96f1
commit
23479d6750
3 changed files with 169 additions and 5 deletions
|
@ -33,16 +33,19 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.IntentHandler
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenAccountSwitcher
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMediumTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
|
||||
|
@ -87,7 +90,7 @@ fun VaultScreen(
|
|||
VaultEvent.NavigateOutOfApp -> intentHandler.exitApplication()
|
||||
is VaultEvent.ShowToast -> {
|
||||
Toast
|
||||
.makeText(context, event.message, Toast.LENGTH_SHORT)
|
||||
.makeText(context, event.message(context.resources), Toast.LENGTH_SHORT)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
@ -206,6 +209,14 @@ private fun VaultScreenScaffold(
|
|||
|
||||
// Dynamic dialogs
|
||||
when (val dialog = state.dialog) {
|
||||
is VaultState.DialogState.Syncing -> {
|
||||
BitwardenLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown(
|
||||
text = R.string.syncing.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
is VaultState.DialogState.Error -> {
|
||||
BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
|
|
|
@ -186,6 +186,9 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleSyncClick() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(dialog = VaultState.DialogState.Syncing)
|
||||
}
|
||||
vaultRepository.sync()
|
||||
}
|
||||
|
||||
|
@ -285,8 +288,18 @@ class VaultViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun vaultLoadedReceive(vaultData: DataState.Loaded<VaultData>) {
|
||||
if (state.dialog == VaultState.DialogState.Syncing) {
|
||||
sendEvent(
|
||||
VaultEvent.ShowToast(
|
||||
message = R.string.syncing_complete.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
mutableStateFlow.update {
|
||||
it.copy(viewState = vaultData.data.toViewState(vaultFilterTypeOrDefault))
|
||||
it.copy(
|
||||
viewState = vaultData.data.toViewState(vaultFilterTypeOrDefault),
|
||||
dialog = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -308,7 +321,6 @@ class VaultViewModel @Inject constructor(
|
|||
mutableStateFlow.update {
|
||||
it.copy(viewState = vaultData.data.toViewState(vaultFilterTypeOrDefault))
|
||||
}
|
||||
sendEvent(VaultEvent.ShowToast(message = "Refreshing"))
|
||||
}
|
||||
|
||||
//endregion VaultAction Handlers
|
||||
|
@ -550,6 +562,12 @@ data class VaultState(
|
|||
*/
|
||||
sealed class DialogState : Parcelable {
|
||||
|
||||
/**
|
||||
* Represents a dialog indication and ongoing manual sync.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Syncing : DialogState()
|
||||
|
||||
/**
|
||||
* Represents an error dialog with the given [title] and [message].
|
||||
*/
|
||||
|
@ -612,7 +630,7 @@ sealed class VaultEvent {
|
|||
/**
|
||||
* Show a toast with the given [message].
|
||||
*/
|
||||
data class ShowToast(val message: String) : VaultEvent()
|
||||
data class ShowToast(val message: Text) : VaultEvent()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -778,6 +796,7 @@ private fun MutableStateFlow<VaultState>.updateToErrorStateOrDialog(
|
|||
viewState = VaultState.ViewState.Error(
|
||||
message = errorMessage,
|
||||
),
|
||||
dialog = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -281,9 +281,15 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `on SyncClick should call sync on the VaultRepository`() {
|
||||
fun `on SyncClick should call sync on the VaultRepository and show the syncing dialog`() {
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.SyncClick)
|
||||
assertEquals(
|
||||
DEFAULT_STATE.copy(
|
||||
dialog = VaultState.DialogState.Syncing,
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
verify {
|
||||
vaultRepository.sync()
|
||||
}
|
||||
|
@ -405,6 +411,45 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loaded with items when manually syncing with the sync button should update state to Content and show a success Toast`() =
|
||||
runTest {
|
||||
val expectedState = createMockVaultState(
|
||||
viewState = VaultState.ViewState.Content(
|
||||
loginItemsCount = 1,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
folderItems = listOf(),
|
||||
collectionItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.SyncClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||
collectionViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
VaultEvent.ShowToast(R.string.syncing_complete.asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loaded with empty items should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
|
@ -423,6 +468,95 @@ class VaultViewModelTest : BaseViewModelTest() {
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loaded with empty items when manually syncing with the sync button should update state to NoItems and show a success Toast`() =
|
||||
runTest {
|
||||
val expectedState = createMockVaultState(
|
||||
viewState = VaultState.ViewState.NoItems,
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
viewModel.trySendAction(VaultAction.SyncClick)
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
mutableVaultDataStateFlow.value = DataState.Loaded(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
),
|
||||
)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
assertEquals(
|
||||
VaultEvent.ShowToast(R.string.syncing_complete.asText()),
|
||||
awaitItem(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Pending with items should update state to Content`() {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Pending(
|
||||
data = VaultData(
|
||||
cipherViewList = listOf(createMockCipherView(number = 1)),
|
||||
collectionViewList = listOf(createMockCollectionView(number = 1)),
|
||||
folderViewList = listOf(createMockFolderView(number = 1)),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val viewModel = createViewModel()
|
||||
|
||||
assertEquals(
|
||||
createMockVaultState(
|
||||
viewState = VaultState.ViewState.Content(
|
||||
loginItemsCount = 1,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
folderItems = listOf(
|
||||
VaultState.ViewState.FolderItem(
|
||||
id = "mockId-1",
|
||||
name = "mockName-1".asText(),
|
||||
itemCount = 1,
|
||||
),
|
||||
),
|
||||
collectionItems = listOf(
|
||||
VaultState.ViewState.CollectionItem(
|
||||
id = "mockId-1",
|
||||
name = "mockName-1",
|
||||
itemCount = 1,
|
||||
),
|
||||
),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
),
|
||||
),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Pending with empty items should update state to NoItems`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(
|
||||
value = DataState.Pending(
|
||||
data = VaultData(
|
||||
cipherViewList = emptyList(),
|
||||
collectionViewList = emptyList(),
|
||||
folderViewList = emptyList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
val viewModel = createViewModel()
|
||||
assertEquals(
|
||||
createMockVaultState(viewState = VaultState.ViewState.NoItems),
|
||||
viewModel.stateFlow.value,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `vaultDataStateFlow Loading should update state to Loading`() = runTest {
|
||||
mutableVaultDataStateFlow.tryEmit(value = DataState.Loading)
|
||||
|
|
Loading…
Reference in a new issue