From d113fc27f66c1c4b3be446c41bf674465d267188 Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:44:21 -0700 Subject: [PATCH] BIT-1897-vault-appearing-when-policy-set (#1064) --- .../ui/vault/feature/vault/VaultScreen.kt | 7 +- .../ui/vault/feature/vault/VaultViewModel.kt | 16 ++-- .../vault/util/VaultStateExtensions.kt | 29 +++++++ .../ui/vault/feature/vault/VaultScreenTest.kt | 15 ++++ .../vault/util/VaultStateExtensionsTest.kt | 87 +++++++++++++++++++ 5 files changed, 141 insertions(+), 13 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt index 0152102144..7e49d5f553 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreen.kt @@ -272,11 +272,10 @@ private fun VaultScreenScaffold( .fillMaxSize() .padding(paddingValues) Column(modifier = outerModifier) { - val vaultFilterData = state.vaultFilterData - if (state.viewState.hasVaultFilter && vaultFilterData != null) { + state.vaultFilterDataWithFilter?.let { VaultFilter( - selectedVaultFilterType = vaultFilterData.selectedVaultFilterType, - vaultFilterTypes = vaultFilterData.vaultFilterTypes.toImmutableList(), + selectedVaultFilterType = it.selectedVaultFilterType, + vaultFilterTypes = it.vaultFilterTypes.toImmutableList(), onVaultFilterTypeSelect = vaultHandlers.vaultFilterTypeSelect, topAppBarScrollBehavior = scrollBehavior, modifier = Modifier 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 d01373ac91..fa9e2112a1 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 @@ -28,6 +28,7 @@ import com.x8bit.bitwarden.ui.platform.components.model.IconRes import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType +import com.x8bit.bitwarden.ui.vault.feature.vault.util.vaultFilterDataIfRequired import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials import com.x8bit.bitwarden.ui.vault.feature.vault.util.toAccountSummaries import com.x8bit.bitwarden.ui.vault.feature.vault.util.toActiveAccountSummary @@ -625,6 +626,12 @@ data class VaultState( val isPullToRefreshEnabled: Boolean get() = isPullToRefreshSettingEnabled && viewState.isPullToRefreshEnabled + /** + * VaultFilterData that the user has access to. + */ + val vaultFilterDataWithFilter: VaultFilterData? + get() = viewState.vaultFilterDataIfRequired(vaultFilterData = vaultFilterData) + /** * Represents the specific view states for the [VaultScreen]. */ @@ -637,11 +644,6 @@ data class VaultState( */ abstract val hasFab: Boolean - /** - * Determines whether or not the the Vault Filter may be shown (when applicable). - */ - abstract val hasVaultFilter: Boolean - /** * Indicates the pull-to-refresh feature should be available during the current state. */ @@ -653,7 +655,6 @@ data class VaultState( @Parcelize data object Loading : ViewState() { override val hasFab: Boolean get() = false - override val hasVaultFilter: Boolean get() = false override val isPullToRefreshEnabled: Boolean get() = false } @@ -663,7 +664,6 @@ data class VaultState( @Parcelize data object NoItems : ViewState() { override val hasFab: Boolean get() = true - override val hasVaultFilter: Boolean get() = true override val isPullToRefreshEnabled: Boolean get() = true } @@ -676,7 +676,6 @@ data class VaultState( val message: Text, ) : ViewState() { override val hasFab: Boolean get() = false - override val hasVaultFilter: Boolean get() = false override val isPullToRefreshEnabled: Boolean get() = true } @@ -708,7 +707,6 @@ data class VaultState( val trashItemsCount: Int, ) : ViewState() { override val hasFab: Boolean get() = true - override val hasVaultFilter: Boolean get() = true override val isPullToRefreshEnabled: Boolean get() = true } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensions.kt new file mode 100644 index 0000000000..bcba7cfebe --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensions.kt @@ -0,0 +1,29 @@ +package com.x8bit.bitwarden.ui.vault.feature.vault.util + +import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState +import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData +import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType + +/** + * Converts the given [VaultState.ViewState] to a [VaultFilterData] (if applicable). + */ +fun VaultState.ViewState.vaultFilterDataIfRequired( + vaultFilterData: VaultFilterData?, +): VaultFilterData? = + when (this) { + is VaultState.ViewState.Content, + is VaultState.ViewState.NoItems, + -> vaultFilterData?.let { + if (it.vaultFilterTypes.contains(VaultFilterType.MyVault) || + it.vaultFilterTypes.size > 2 + ) { + it + } else { + null + } + } + + is VaultState.ViewState.Error, + is VaultState.ViewState.Loading, + -> null + } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt index 02e7d1c398..835b1eb762 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultScreenTest.kt @@ -122,6 +122,21 @@ class VaultScreenTest : BaseComposeTest() { composeTestRule.onNodeWithText("Vault: My vault").assertDoesNotExist() composeTestRule.onNodeWithText("Vault: Test Organization").assertDoesNotExist() + mutableStateFlow.update { + it.copy( + vaultFilterData = VAULT_FILTER_DATA.copy( + vaultFilterTypes = listOf( + VaultFilterType.AllVaults, + ORGANIZATION_VAULT_FILTER, + ), + ), + ) + } + + composeTestRule.onNodeWithText("Vault: All").assertDoesNotExist() + composeTestRule.onNodeWithText("Vault: My vault").assertDoesNotExist() + composeTestRule.onNodeWithText("Vault: Test Organization").assertDoesNotExist() + mutableStateFlow.update { it.copy( vaultFilterData = VAULT_FILTER_DATA.copy( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt new file mode 100644 index 0000000000..6d275b9eb9 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultStateExtensionsTest.kt @@ -0,0 +1,87 @@ +package com.x8bit.bitwarden.ui.vault.feature.vault.util + +import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState +import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData +import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class VaultStateExtensionsTest { + @Test + fun `vaultFilterDataIfRequired with my vault should return correct data`() { + val vaultFilterData = createVaultFilterData(hasMyVault = true) + mapOf( + VaultState.ViewState.NoItems to vaultFilterData, + VaultState.ViewState.Loading to null, + VaultState.ViewState.Error(message = "error".asText()) to null, + createContentState() to vaultFilterData, + ) + .forEach { (viewState, expected) -> + assertEquals( + expected, + viewState.vaultFilterDataIfRequired(vaultFilterData = vaultFilterData), + ) + } + } + + @Test + fun `vaultFilterDataIfRequired without my vault should return correct data`() { + val vaultFilterData = createVaultFilterData(hasMyVault = false) + mapOf( + VaultState.ViewState.NoItems to null, + VaultState.ViewState.Loading to null, + VaultState.ViewState.Error(message = "error".asText()) to null, + createContentState() to null, + ) + .forEach { (viewState, expected) -> + assertEquals( + expected, + viewState.vaultFilterDataIfRequired(vaultFilterData = vaultFilterData), + ) + } + } + + private fun createVaultFilterData(hasMyVault: Boolean = false): VaultFilterData = + VaultFilterData( + selectedVaultFilterType = VaultFilterType.AllVaults, + vaultFilterTypes = listOfNotNull( + VaultFilterType.AllVaults, + if (hasMyVault) { + VaultFilterType.MyVault + } else { + null + }, + VaultFilterType.OrganizationVault( + organizationId = "organizationId-A", + organizationName = "Organization A", + ), + ), + ) + + private fun createContentState(): VaultState.ViewState.Content = + 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, + totpItemsCount = 1, + ) +}