From 378ff21c3c4747a60325aec24cd7b7784294610e Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Tue, 9 Jan 2024 14:51:01 -0700 Subject: [PATCH] Store information when switching vault add edit types. (#554) --- .../addedit/VaultAddEditItemContent.kt | 2 +- .../feature/addedit/VaultAddEditViewModel.kt | 77 +++++++++++++---- .../addedit/VaultAddEditViewModelTest.kt | 83 +++++++++++++++++++ 3 files changed, 144 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt index 72ff9008d6..068cffd298 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditItemContent.kt @@ -137,7 +137,7 @@ private fun TypeOptionsItem( BitwardenMultiSelectButton( label = stringResource(id = R.string.type), options = optionsWithStrings.values.toImmutableList(), - selectedOption = stringResource(id = itemType.displayStringResId), + selectedOption = stringResource(id = itemType.itemTypeOption.labelRes), onOptionSelected = { selectedOption -> val selectedOptionId = optionsWithStrings .entries diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index f2c3e8d900..b72158636f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -148,8 +148,11 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddLoginItem() { updateContent { currentContent -> currentContent.copy( - common = currentContent.common.clearNonSharedData(), - type = VaultAddEditState.ViewState.Content.ItemType.Login(), + common = currentContent.clearNonSharedData(), + type = currentContent.previousItemTypeOrDefault( + itemType = VaultAddEditState.ItemTypeOption.LOGIN, + ), + previousItemTypes = currentContent.toUpdatedPreviousItemTypes(), ) } } @@ -157,8 +160,11 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddSecureNotesItem() { updateContent { currentContent -> currentContent.copy( - common = currentContent.common.clearNonSharedData(), - type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + common = currentContent.clearNonSharedData(), + type = currentContent.previousItemTypeOrDefault( + itemType = VaultAddEditState.ItemTypeOption.SECURE_NOTES, + ), + previousItemTypes = currentContent.toUpdatedPreviousItemTypes(), ) } } @@ -166,8 +172,11 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddCardItem() { updateContent { currentContent -> currentContent.copy( - common = currentContent.common.clearNonSharedData(), - type = VaultAddEditState.ViewState.Content.ItemType.Card(), + common = currentContent.clearNonSharedData(), + type = currentContent.previousItemTypeOrDefault( + itemType = VaultAddEditState.ItemTypeOption.CARD, + ), + previousItemTypes = currentContent.toUpdatedPreviousItemTypes(), ) } } @@ -175,8 +184,11 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddIdentityItem() { updateContent { currentContent -> currentContent.copy( - common = currentContent.common.clearNonSharedData(), - type = VaultAddEditState.ViewState.Content.ItemType.Identity(), + common = currentContent.clearNonSharedData(), + type = currentContent.previousItemTypeOrDefault( + itemType = VaultAddEditState.ItemTypeOption.IDENTITY, + ), + previousItemTypes = currentContent.toUpdatedPreviousItemTypes(), ) } } @@ -933,13 +945,43 @@ class VaultAddEditViewModel @Inject constructor( } } - private fun VaultAddEditState.ViewState.Content.Common.clearNonSharedData(): + private fun VaultAddEditState.ViewState.Content.clearNonSharedData(): VaultAddEditState.ViewState.Content.Common = - copy( - customFieldData = customFieldData + common.copy( + customFieldData = common.customFieldData .filterNot { it is VaultAddEditState.Custom.LinkedField }, ) + private fun VaultAddEditState.ViewState.Content.toUpdatedPreviousItemTypes(): + Map = + previousItemTypes + .toMutableMap() + .apply { set(type.itemTypeOption, type) } + + private fun VaultAddEditState.ViewState.Content.previousItemTypeOrDefault( + itemType: VaultAddEditState.ItemTypeOption, + ): VaultAddEditState.ViewState.Content.ItemType = + previousItemTypes.getOrDefault( + key = itemType, + defaultValue = when (itemType) { + VaultAddEditState.ItemTypeOption.LOGIN -> { + VaultAddEditState.ViewState.Content.ItemType.Login() + } + + VaultAddEditState.ItemTypeOption.CARD -> { + VaultAddEditState.ViewState.Content.ItemType.Card() + } + + VaultAddEditState.ItemTypeOption.IDENTITY -> { + VaultAddEditState.ViewState.Content.ItemType.Identity() + } + + VaultAddEditState.ItemTypeOption.SECURE_NOTES -> { + VaultAddEditState.ViewState.Content.ItemType.SecureNotes + } + }, + ) + //endregion Utility Functions } @@ -1009,6 +1051,7 @@ data class VaultAddEditState( data class Content( val common: Common, val type: ItemType, + val previousItemTypes: Map = emptyMap(), ) : ViewState() { /** @@ -1064,7 +1107,7 @@ data class VaultAddEditState( * that must be overridden by each subclass to provide the appropriate string * resource for display purposes. */ - abstract val displayStringResId: Int + abstract val itemTypeOption: ItemTypeOption /** * Represents the login item information. @@ -1084,7 +1127,7 @@ data class VaultAddEditState( val totp: String? = null, val canViewPassword: Boolean = true, ) : ItemType() { - override val displayStringResId: Int get() = ItemTypeOption.LOGIN.labelRes + override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.LOGIN } /** @@ -1106,7 +1149,7 @@ data class VaultAddEditState( val expirationYear: String = "", val securityCode: String = "", ) : ItemType() { - override val displayStringResId: Int get() = ItemTypeOption.CARD.labelRes + override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.CARD } /** @@ -1153,7 +1196,7 @@ data class VaultAddEditState( val country: String = "", ) : ItemType() { - override val displayStringResId: Int get() = ItemTypeOption.IDENTITY.labelRes + override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.IDENTITY } /** @@ -1161,8 +1204,7 @@ data class VaultAddEditState( */ @Parcelize data object SecureNotes : ItemType() { - override val displayStringResId: Int - get() = ItemTypeOption.SECURE_NOTES.labelRes + override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SECURE_NOTES } } } @@ -1223,6 +1265,7 @@ data class VaultAddEditState( /** * Displays a dialog. */ + @Parcelize sealed class DialogState : Parcelable { /** diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt index a29d806506..d5cb4bd193 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModelTest.kt @@ -41,6 +41,7 @@ import org.junit.jupiter.api.Nested import org.junit.jupiter.api.Test import java.util.UUID +@Suppress("LargeClass") class VaultAddEditViewModelTest : BaseViewModelTest() { private val loginInitialState = createVaultAddItemState( @@ -424,6 +425,88 @@ class VaultAddEditViewModelTest : BaseViewModelTest() { viewState = VaultAddEditState.ViewState.Content( common = createCommonContentViewState(), type = createLoginTypeContentViewState(), + previousItemTypes = mapOf( + VaultAddEditState.ItemTypeOption.LOGIN + to VaultAddEditState.ViewState.Content.ItemType.Login(), + ), + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value, + ) + } + + @Test + fun `TypeOptionSelect CARD should switch to CardItem`() = runTest { + val viewModel = createAddVaultItemViewModel() + val action = VaultAddEditAction.Common.TypeOptionSelect( + VaultAddEditState.ItemTypeOption.CARD, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = loginInitialState.copy( + viewState = VaultAddEditState.ViewState.Content( + common = createCommonContentViewState(), + type = VaultAddEditState.ViewState.Content.ItemType.Card(), + previousItemTypes = mapOf( + VaultAddEditState.ItemTypeOption.LOGIN + to VaultAddEditState.ViewState.Content.ItemType.Login(), + ), + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value, + ) + } + + @Test + fun `TypeOptionSelect IDENTITY should switch to IdentityItem`() = runTest { + val viewModel = createAddVaultItemViewModel() + val action = VaultAddEditAction.Common.TypeOptionSelect( + VaultAddEditState.ItemTypeOption.IDENTITY, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = loginInitialState.copy( + viewState = VaultAddEditState.ViewState.Content( + common = createCommonContentViewState(), + type = VaultAddEditState.ViewState.Content.ItemType.Identity(), + previousItemTypes = mapOf( + VaultAddEditState.ItemTypeOption.LOGIN + to VaultAddEditState.ViewState.Content.ItemType.Login(), + ), + ), + ) + + assertEquals( + expectedState, + viewModel.stateFlow.value, + ) + } + + @Test + fun `TypeOptionSelect SECURE_NOTES should switch to SecureNotesItem`() = runTest { + val viewModel = createAddVaultItemViewModel() + val action = VaultAddEditAction.Common.TypeOptionSelect( + VaultAddEditState.ItemTypeOption.SECURE_NOTES, + ) + + viewModel.actionChannel.trySend(action) + + val expectedState = loginInitialState.copy( + viewState = VaultAddEditState.ViewState.Content( + common = createCommonContentViewState(), + type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + previousItemTypes = mapOf( + VaultAddEditState.ItemTypeOption.LOGIN + to VaultAddEditState.ViewState.Content.ItemType.Login(), + ), ), )