diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt index 575b9ebf2b..51fa7e2cea 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt @@ -11,6 +11,7 @@ import com.x8bit.bitwarden.ui.platform.theme.TransitionProviders import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType private const val CARD: String = "card" +private const val COLLECTION: String = "collection" private const val FOLDER: String = "folder" private const val IDENTITY: String = "identity" private const val LOGIN: String = "login" @@ -92,6 +93,7 @@ fun NavController.navigateToVaultItemListing( private fun VaultItemListingType.toTypeString(): String { return when (this) { is VaultItemListingType.Card -> CARD + is VaultItemListingType.Collection -> COLLECTION is VaultItemListingType.Folder -> FOLDER is VaultItemListingType.Identity -> IDENTITY is VaultItemListingType.Login -> LOGIN @@ -102,6 +104,7 @@ private fun VaultItemListingType.toTypeString(): String { private fun VaultItemListingType.toIdOrNull(): String? = when (this) { + is VaultItemListingType.Collection -> collectionId is VaultItemListingType.Folder -> folderId is VaultItemListingType.Card -> null is VaultItemListingType.Identity -> null @@ -121,6 +124,7 @@ private fun determineVaultItemListingType( SECURE_NOTE -> VaultItemListingType.SecureNote TRASH -> VaultItemListingType.Trash FOLDER -> VaultItemListingType.Folder(folderId = id) + COLLECTION -> VaultItemListingType.Collection(collectionId = requireNotNull(id)) // This should never occur, vaultItemListingTypeString must match else -> throw IllegalStateException() } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index cea511bab1..869abf80ae 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -151,6 +151,8 @@ class VaultItemListingViewModel @Inject constructor( .updateWithAdditionalDataIfNecessary( folderList = vaultData .folderViewList, + collectionList = vaultData + .collectionViewList, ), viewState = vaultData .cipherViewList @@ -305,6 +307,23 @@ data class VaultItemListingState( override val hasFab: Boolean get() = false } + + /** + * A Collection item listing. + * + * @property collectionId the ID of the collection. + * @property collectionName the name of the collection. + */ + data class Collection( + val collectionId: String, + // The collectionName will always initially be an empty string + val collectionName: String = "", + ) : ItemListingType() { + override val titleText: Text + get() = collectionName.asText() + override val hasFab: Boolean + get() = false + } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt index b5331806af..6a9073ff02 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt @@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util import androidx.annotation.DrawableRes import com.bitwarden.core.CipherType import com.bitwarden.core.CipherView +import com.bitwarden.core.CollectionView import com.bitwarden.core.FolderView import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState @@ -19,6 +20,10 @@ fun CipherView.determineListingPredicate( type == CipherType.CARD && deletedDate == null } + is VaultItemListingState.ItemListingType.Collection -> { + itemListingType.collectionId in this.collectionIds && deletedDate == null + } + is VaultItemListingState.ItemListingType.Folder -> { folderId == itemListingType.folderId && deletedDate == null } @@ -53,9 +58,17 @@ fun List.toViewState(): VaultItemListingState.ViewState = /** * Updates a [VaultItemListingState.ItemListingType] with the given data if necessary. */ fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary( folderList: List, + collectionList: List, ): VaultItemListingState.ItemListingType = when (this) { is VaultItemListingState.ItemListingType.Card -> this + is VaultItemListingState.ItemListingType.Collection -> copy( + collectionName = collectionList + .find { it.id == collectionId } + ?.name + .orEmpty(), + ) + is VaultItemListingState.ItemListingType.Folder -> copy( folderName = folderList .find { it.id == folderId } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt index 4fae573f07..22743ebcad 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensions.kt @@ -17,4 +17,7 @@ fun VaultItemListingType.toItemListingType(): VaultItemListingState.ItemListingT is VaultItemListingType.Login -> VaultItemListingState.ItemListingType.Login is VaultItemListingType.SecureNote -> VaultItemListingState.ItemListingType.SecureNote is VaultItemListingType.Trash -> VaultItemListingState.ItemListingType.Trash + is VaultItemListingType.Collection -> { + VaultItemListingState.ItemListingType.Collection(collectionId = collectionId) + } } 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 ecc63d57e0..808e363cd3 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 @@ -123,9 +123,10 @@ class VaultViewModel @Inject constructor( } private fun handleCollectionItemClick(action: VaultAction.CollectionClick) { - // TODO: Navigate to the listing screen for collections (BIT-406). sendEvent( - VaultEvent.ShowToast(message = "Not yet implemented."), + VaultEvent.NavigateToItemListing( + VaultItemListingType.Collection(action.collectionItem.id), + ), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt index 70f3d7ea79..8aca8cab72 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultItemListingType.kt @@ -36,4 +36,11 @@ sealed class VaultItemListingType { * @param folderId the id of the folder, a null value indicates a, "no folder" grouping. */ data class Folder(val folderId: String?) : VaultItemListingType() + + /** + * A Collection listing. + * + * @param collectionId the ID of the collection. + */ + data class Collection(val collectionId: String) : VaultItemListingType() } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 84713ba6a0..514916d567 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -421,6 +421,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) } + @Suppress("CyclomaticComplexMethod") private fun createSavedStateHandleWithVaultItemListingType( vaultItemListingType: VaultItemListingType, ) = SavedStateHandle().apply { @@ -428,6 +429,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { "vault_item_listing_type", when (vaultItemListingType) { is VaultItemListingType.Card -> "card" + is VaultItemListingType.Collection -> "collection" is VaultItemListingType.Folder -> "folder" is VaultItemListingType.Identity -> "identity" is VaultItemListingType.Login -> "login" @@ -439,6 +441,7 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { "id", when (vaultItemListingType) { is VaultItemListingType.Card -> null + is VaultItemListingType.Collection -> vaultItemListingType.collectionId is VaultItemListingType.Folder -> vaultItemListingType.folderId is VaultItemListingType.Identity -> null is VaultItemListingType.Login -> null diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt index 9e8d4c2ddb..990dcd3dcd 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensionsTest.kt @@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting.util import com.bitwarden.core.CipherType import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCollectionView import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockFolderView import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState import org.junit.Assert.assertEquals @@ -25,6 +26,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to false, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to true, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to true, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -53,6 +55,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to true, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to false, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to false, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -81,6 +84,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to false, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to true, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to true, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -109,6 +113,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to true, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to false, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to false, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -137,6 +142,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to true, VaultItemListingState.ItemListingType.Trash to false, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to true, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to true, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -165,6 +171,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to true, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to false, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to false, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -193,6 +200,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to false, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to true, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to true, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -221,6 +229,7 @@ class VaultItemListingDataExtensionsTest { VaultItemListingState.ItemListingType.Identity to false, VaultItemListingState.ItemListingType.Trash to true, VaultItemListingState.ItemListingType.Folder(folderId = "mockId-1") to false, + VaultItemListingState.ItemListingType.Collection(collectionId = "mockId-1") to false, ) .forEach { (type, expected) -> val result = cipherView.determineListingPredicate( @@ -292,12 +301,20 @@ class VaultItemListingDataExtensionsTest { createMockFolderView(number = 2), createMockFolderView(number = 3), ) + val collectionViewList = listOf( + createMockCollectionView(number = 1), + createMockCollectionView(number = 2), + createMockCollectionView(number = 3), + ) val result = VaultItemListingState.ItemListingType.Folder( folderId = "mockId-1", folderName = "wrong name", ) - .updateWithAdditionalDataIfNecessary(folderList = folderViewList) + .updateWithAdditionalDataIfNecessary( + folderList = folderViewList, + collectionList = collectionViewList, + ) assertEquals( VaultItemListingState.ItemListingType.Folder( @@ -309,15 +326,55 @@ class VaultItemListingDataExtensionsTest { } @Test - fun `updateWithAdditionalDataIfNecessary should not change a non folder itemListingType`() { + fun `updateWithAdditionalDataIfNecessary should update a collection itemListingType`() { val folderViewList = listOf( createMockFolderView(number = 1), createMockFolderView(number = 2), createMockFolderView(number = 3), ) + val collectionViewList = listOf( + createMockCollectionView(number = 1), + createMockCollectionView(number = 2), + createMockCollectionView(number = 3), + ) + + val result = VaultItemListingState.ItemListingType.Collection( + collectionId = "mockId-1", + collectionName = "wrong name", + ) + .updateWithAdditionalDataIfNecessary( + folderList = folderViewList, + collectionList = collectionViewList, + ) + + assertEquals( + VaultItemListingState.ItemListingType.Collection( + collectionId = "mockId-1", + collectionName = "mockName-1", + ), + result, + ) + } + + @Suppress("MaxLineLength") + @Test + fun `updateWithAdditionalDataIfNecessary should not change a non-folder or non-collection itemListingType`() { + val folderViewList = listOf( + createMockFolderView(number = 1), + createMockFolderView(number = 2), + createMockFolderView(number = 3), + ) + val collectionViewList = listOf( + createMockCollectionView(number = 1), + createMockCollectionView(number = 2), + createMockCollectionView(number = 3), + ) val result = VaultItemListingState.ItemListingType.Login - .updateWithAdditionalDataIfNecessary(folderList = folderViewList) + .updateWithAdditionalDataIfNecessary( + folderList = folderViewList, + collectionList = collectionViewList, + ) assertEquals( VaultItemListingState.ItemListingType.Login, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt index 8da4362ca9..a995279f32 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingTypeExtensionsTest.kt @@ -13,6 +13,7 @@ class VaultItemListingTypeExtensionsTest { val itemListingTypeList = listOf( VaultItemListingType.Folder(folderId = "mock"), VaultItemListingType.Trash, + VaultItemListingType.Collection(collectionId = "collectionId"), ) val result = itemListingTypeList.map { it.toItemListingType() } @@ -21,6 +22,7 @@ class VaultItemListingTypeExtensionsTest { listOf( VaultItemListingState.ItemListingType.Folder(folderId = "mock"), VaultItemListingState.ItemListingType.Trash, + VaultItemListingState.ItemListingType.Collection(collectionId = "collectionId"), ), result, ) diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt index a2f7508afb..87b0f2545f 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultViewModelTest.kt @@ -594,21 +594,23 @@ class VaultViewModelTest : BaseViewModelTest() { } } + @Suppress("MaxLineLength") @Test - fun `CollectionClick should emit ShowToast`() = runTest { - val viewModel = createViewModel() - val collectionId = "12345" - val collection = mockk { - every { id } returns collectionId + fun `CollectionClick should emit NavigateToItemListing event with Collection type with the correct collection ID`() = + runTest { + val viewModel = createViewModel() + val collectionId = "12345" + val collection = mockk { + every { id } returns collectionId + } + viewModel.eventFlow.test { + viewModel.trySendAction(VaultAction.CollectionClick(collection)) + assertEquals( + VaultEvent.NavigateToItemListing(VaultItemListingType.Collection(collectionId)), + awaitItem(), + ) + } } - viewModel.eventFlow.test { - viewModel.trySendAction(VaultAction.CollectionClick(collection)) - assertEquals( - VaultEvent.ShowToast(message = "Not yet implemented."), - awaitItem(), - ) - } - } @Test fun `IdentityGroupClick should emit NavigateToItemListing event with Identity type`() =