mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-406: Allow item listing screen to display Collections data (#394)
This commit is contained in:
parent
41a0817c5a
commit
9a05b7168e
10 changed files with 129 additions and 18 deletions
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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<CipherView>.toViewState(): VaultItemListingState.ViewState =
|
|||
/** * Updates a [VaultItemListingState.ItemListingType] with the given data if necessary. */
|
||||
fun VaultItemListingState.ItemListingType.updateWithAdditionalDataIfNecessary(
|
||||
folderList: List<FolderView>,
|
||||
collectionList: List<CollectionView>,
|
||||
): 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 }
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
|
|
@ -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<VaultState.ViewState.CollectionItem> {
|
||||
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<VaultState.ViewState.CollectionItem> {
|
||||
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`() =
|
||||
|
|
Loading…
Add table
Reference in a new issue