mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-1724: No folder vault section (#1021)
This commit is contained in:
parent
e9fba5b99c
commit
6b4e367c66
6 changed files with 120 additions and 24 deletions
|
@ -952,7 +952,10 @@ data class VaultItemListingState(
|
|||
// The folderName will always initially be an empty string
|
||||
val folderName: String = "",
|
||||
) : Vault() {
|
||||
override val titleText: Text get() = folderName.asText()
|
||||
override val titleText: Text
|
||||
get() = folderId
|
||||
?.let { folderName.asText() }
|
||||
?: R.string.folder_none.asText()
|
||||
override val hasFab: Boolean get() = false
|
||||
}
|
||||
|
||||
|
|
|
@ -888,14 +888,6 @@ data class VaultState(
|
|||
val message: Text,
|
||||
) : DialogState()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* The maximum number of no folder items that can be displayed before the UI creates a
|
||||
* no folder "folder".
|
||||
*/
|
||||
private const val NO_FOLDER_ITEM_THRESHOLD: Int = 100
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -20,6 +20,12 @@ import com.x8bit.bitwarden.ui.vault.model.findVaultCardBrandWithNameOrNull
|
|||
private const val ANDROID_URI = "androidapp://"
|
||||
private const val IOS_URI = "iosapp://"
|
||||
|
||||
/**
|
||||
* The maximum number of no folder items that can be displayed before the UI creates a
|
||||
* no folder "folder".
|
||||
*/
|
||||
private const val NO_FOLDER_ITEM_THRESHOLD: Int = 100
|
||||
|
||||
/**
|
||||
* Transforms [VaultData] into [VaultState.ViewState] using the given [vaultFilterType].
|
||||
*/
|
||||
|
@ -38,6 +44,8 @@ fun VaultData.toViewState(
|
|||
.filter { it.deletedDate == null }
|
||||
val filteredFolderViewList = folderViewList.toFilteredList(vaultFilterType)
|
||||
val filteredCollectionViewList = collectionViewList.toFilteredList(vaultFilterType)
|
||||
val noFolderItems = filteredCipherViewList
|
||||
.filter { it.folderId.isNullOrBlank() }
|
||||
|
||||
return if (filteredCipherViewList.isEmpty()) {
|
||||
VaultState.ViewState.NoItems
|
||||
|
@ -61,25 +69,40 @@ fun VaultData.toViewState(
|
|||
baseIconUrl = baseIconUrl,
|
||||
)
|
||||
},
|
||||
folderItems = filteredFolderViewList.map { folderView ->
|
||||
VaultState.ViewState.FolderItem(
|
||||
id = folderView.id,
|
||||
name = folderView.name.asText(),
|
||||
itemCount = filteredCipherViewList
|
||||
.count {
|
||||
!it.id.isNullOrBlank() &&
|
||||
folderView.id == it.folderId
|
||||
},
|
||||
)
|
||||
},
|
||||
noFolderItems = filteredCipherViewList
|
||||
.filter { it.folderId.isNullOrBlank() }
|
||||
folderItems = filteredFolderViewList
|
||||
.map { folderView ->
|
||||
VaultState.ViewState.FolderItem(
|
||||
id = folderView.id,
|
||||
name = folderView.name.asText(),
|
||||
itemCount = filteredCipherViewList
|
||||
.count {
|
||||
!it.id.isNullOrBlank() &&
|
||||
folderView.id == it.folderId
|
||||
},
|
||||
)
|
||||
}
|
||||
.let { folderItems ->
|
||||
if (noFolderItems.size < NO_FOLDER_ITEM_THRESHOLD) {
|
||||
folderItems
|
||||
} else {
|
||||
folderItems.plus(
|
||||
VaultState.ViewState.FolderItem(
|
||||
id = null,
|
||||
name = R.string.folder_none.asText(),
|
||||
itemCount = noFolderItems.size,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
noFolderItems = noFolderItems
|
||||
.mapNotNull {
|
||||
it.toVaultItemOrNull(
|
||||
isIconLoadingDisabled = isIconLoadingDisabled,
|
||||
baseIconUrl = baseIconUrl,
|
||||
)
|
||||
},
|
||||
}
|
||||
.takeIf { it.size < NO_FOLDER_ITEM_THRESHOLD }
|
||||
.orEmpty(),
|
||||
collectionItems = filteredCollectionViewList
|
||||
.filter { it.id != null }
|
||||
.map { collectionView ->
|
||||
|
|
|
@ -28,11 +28,12 @@ fun createMockCipherView(
|
|||
isDeleted: Boolean = false,
|
||||
cipherType: CipherType = CipherType.LOGIN,
|
||||
totp: String? = "mockTotp-$number",
|
||||
folderId: String? = "mockId-$number",
|
||||
): CipherView =
|
||||
CipherView(
|
||||
id = "mockId-$number",
|
||||
organizationId = "mockOrganizationId-$number",
|
||||
folderId = "mockId-$number",
|
||||
folderId = folderId,
|
||||
collectionIds = listOf("mockId-$number"),
|
||||
key = "mockKey-$number",
|
||||
name = "mockName-$number",
|
||||
|
|
|
@ -244,6 +244,36 @@ class VaultItemListingDataExtensionsTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `determineListingPredicate should return the correct predicate for item not in a folder`() {
|
||||
val cipherView = createMockCipherView(
|
||||
number = 1,
|
||||
isDeleted = false,
|
||||
cipherType = CipherType.SECURE_NOTE,
|
||||
folderId = null,
|
||||
)
|
||||
|
||||
mapOf(
|
||||
VaultItemListingState.ItemListingType.Vault.Login to false,
|
||||
VaultItemListingState.ItemListingType.Vault.Card to false,
|
||||
VaultItemListingState.ItemListingType.Vault.SecureNote to true,
|
||||
VaultItemListingState.ItemListingType.Vault.Identity to false,
|
||||
VaultItemListingState.ItemListingType.Vault.Trash to false,
|
||||
VaultItemListingState.ItemListingType.Vault.Folder(folderId = null) to true,
|
||||
VaultItemListingState.ItemListingType.Vault.Collection(collectionId = "mockId-1") to true,
|
||||
)
|
||||
.forEach { (type, expected) ->
|
||||
val result = cipherView.determineListingPredicate(
|
||||
itemListingType = type,
|
||||
)
|
||||
assertEquals(
|
||||
expected,
|
||||
result,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Suppress("MaxLineLength")
|
||||
fun `determineListingPredicate should return the correct predicate for trash SecureNote cipherView`() {
|
||||
|
|
|
@ -419,4 +419,51 @@ class VaultDataExtensionsTest {
|
|||
actual,
|
||||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `toViewState with over 100 no folder items should show no folder option`() {
|
||||
mockkStatic(Uri::class)
|
||||
val uriMock = mockk<Uri>()
|
||||
every { Uri.parse(any()) } returns uriMock
|
||||
every { uriMock.host } returns "www.mockuri1.com"
|
||||
val vaultData = VaultData(
|
||||
cipherViewList = List(100) {
|
||||
createMockCipherView(number = it, folderId = null)
|
||||
},
|
||||
collectionViewList = listOf(),
|
||||
folderViewList = listOf(),
|
||||
sendViewList = listOf(),
|
||||
)
|
||||
|
||||
val actual = vaultData.toViewState(
|
||||
isPremium = true,
|
||||
isIconLoadingDisabled = false,
|
||||
baseIconUrl = Environment.Us.environmentUrlData.baseIconUrl,
|
||||
vaultFilterType = VaultFilterType.AllVaults,
|
||||
)
|
||||
|
||||
assertEquals(
|
||||
VaultState.ViewState.Content(
|
||||
loginItemsCount = 100,
|
||||
cardItemsCount = 0,
|
||||
identityItemsCount = 0,
|
||||
secureNoteItemsCount = 0,
|
||||
favoriteItems = listOf(),
|
||||
folderItems = listOf(
|
||||
VaultState.ViewState.FolderItem(
|
||||
id = null,
|
||||
name = R.string.folder_none.asText(),
|
||||
itemCount = 100,
|
||||
),
|
||||
),
|
||||
collectionItems = listOf(),
|
||||
noFolderItems = listOf(),
|
||||
trashItemsCount = 0,
|
||||
totpItemsCount = 100,
|
||||
),
|
||||
actual,
|
||||
)
|
||||
unmockkStatic(Uri::class)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue