diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillSelectionData.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillSelectionData.kt index 0327ebf2413..5254eb93efd 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillSelectionData.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/model/AutofillSelectionData.kt @@ -23,6 +23,7 @@ data class AutofillSelectionData( enum class Type { CARD, LOGIN, + IDENTITY, } /** diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensions.kt index 4af7c3545ae..67baa1be9de 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/data/autofill/util/FilledDataExtensions.kt @@ -37,10 +37,7 @@ fun FilledData.buildVaultItemDataset( type = when (this.originalPartition) { is AutofillPartition.Card -> AutofillSelectionData.Type.CARD is AutofillPartition.Login -> AutofillSelectionData.Type.LOGIN - // AutofillSelectionData.Type has no IDENTITY value yet (deliberately deferred -- it - // cascades into the vault item listing/add-edit UI flows). This is a no-op placeholder - // today since nothing yet produces an Identity-only partition. - is AutofillPartition.Identity -> AutofillSelectionData.Type.LOGIN + is AutofillPartition.Identity -> AutofillSelectionData.Type.IDENTITY }, uri = this.uri, ) diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensions.kt index 89e5f2da7e9..ffad59450da 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensions.kt @@ -10,4 +10,5 @@ fun AutofillSelectionData.Type.toVaultItemListingType(): VaultItemListingType = when (this) { AutofillSelectionData.Type.CARD -> VaultItemListingType.Card AutofillSelectionData.Type.LOGIN -> VaultItemListingType.Login + AutofillSelectionData.Type.IDENTITY -> VaultItemListingType.Identity } diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensions.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensions.kt index d7b0927540d..479aa2980f0 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensions.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensions.kt @@ -32,6 +32,10 @@ fun AutofillSelectionData.toDefaultAddTypeContent( ), ) } + + AutofillSelectionData.Type.IDENTITY -> { + VaultAddEditState.ViewState.Content.ItemType.Identity() + } } return VaultAddEditState.ViewState.Content( common = VaultAddEditState.ViewState.Content.Common( diff --git a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 9107d6871d8..cc747a24ac8 100644 --- a/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -2867,6 +2867,20 @@ class VaultItemListingViewModel @Inject constructor( ) } } + + AutofillSelectionData.Type.IDENTITY -> { + this.map { vaultData -> + vaultData.copy( + decryptCipherListResult = vaultData.decryptCipherListResult.copy( + successes = vaultData + .decryptCipherListResult + .successes + .filter { it.type is CipherListViewType.Identity }, + failures = emptyList(), + ), + ) + } + } } } @@ -3039,19 +3053,23 @@ data class VaultItemListingState( val appBarTitle: Text get() = autofillSelectionData ?.let { data -> - data.uri - ?.toHostOrPathOrNull() - ?.let { - when (data.type) { - AutofillSelectionData.Type.CARD -> { - BitwardenString.select_a_card_for_x.asText(it) - } + when (data.type) { + AutofillSelectionData.Type.IDENTITY -> { + BitwardenString.choose_an_identity.asText() + } - AutofillSelectionData.Type.LOGIN -> { - BitwardenString.items_for_uri.asText(it) - } - } + AutofillSelectionData.Type.CARD -> { + data.uri + ?.toHostOrPathOrNull() + ?.let { BitwardenString.select_a_card_for_x.asText(it) } + } + + AutofillSelectionData.Type.LOGIN -> { + data.uri + ?.toHostOrPathOrNull() + ?.let { BitwardenString.items_for_uri.asText(it) } } + } } ?: createCredentialRequest ?.relyingPartyIdOrNull diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensionsTest.kt index 30881e342ad..81c4a4017af 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/platform/feature/rootnav/util/AutofillSelectionDataExtensionsTest.kt @@ -11,6 +11,7 @@ class AutofillSelectionDataExtensionsTest { mapOf( AutofillSelectionData.Type.CARD to VaultItemListingType.Card, AutofillSelectionData.Type.LOGIN to VaultItemListingType.Login, + AutofillSelectionData.Type.IDENTITY to VaultItemListingType.Identity, ) .forEach { (type, expected) -> assertEquals( diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensionsTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensionsTest.kt index 4b0f6d9c1a5..5308b5da306 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensionsTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/addedit/util/AutofillSelectionDataExtensionsTest.kt @@ -39,6 +39,23 @@ class AutofillSelectionDataExtensionsTest { ) } + @Test + fun `toDefaultAddTypeContent for an Identity type should return the correct Content`() { + assertEquals( + VaultAddEditState.ViewState.Content( + common = VaultAddEditState.ViewState.Content.Common(), + isIndividualVaultDisabled = false, + type = VaultAddEditState.ViewState.Content.ItemType.Identity(), + ), + AutofillSelectionData( + type = AutofillSelectionData.Type.IDENTITY, + framework = AutofillSelectionData.Framework.AUTOFILL, + uri = null, + ) + .toDefaultAddTypeContent(isIndividualVaultDisabled = false), + ) + } + @Test fun `toDefaultAddTypeContent for a Login type should return the correct Content`() { every { UUID.randomUUID().toString() } returns "uuid" diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index d25192a97e5..faf87247bb0 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -1371,6 +1371,33 @@ class VaultItemListingScreenTest : BitwardenComposeTest() { .assertIsDisplayed() } + @Test + fun `topBar title should be displayed for identity autofill selection regardless of the uri`() { + mutableStateFlow.update { + it.copy( + itemListingType = VaultItemListingState.ItemListingType.Vault.Identity, + autofillSelectionData = AUTOFILL_SELECTION_DATA.copy( + type = AutofillSelectionData.Type.IDENTITY, + uri = null, + ), + ) + } + composeTestRule + .onNodeWithText(text = "Choose an identity") + .assertIsDisplayed() + + mutableStateFlow.update { + it.copy( + autofillSelectionData = AUTOFILL_SELECTION_DATA.copy( + type = AutofillSelectionData.Type.IDENTITY, + ), + ) + } + composeTestRule + .onNodeWithText(text = "Choose an identity") + .assertIsDisplayed() + } + @Test fun `on overflow item click should display menu`() { composeTestRule diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index 9aee5faef81..c03b62138d7 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -2906,6 +2906,76 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ) } + @Suppress("MaxLineLength") + @Test + fun `vaultDataStateFlow Loaded with items and autofill filtering for Identity should update ViewState to Content with filtered data`() = + runTest { + setupMockUri() + + val cipherView1 = createMockCipherListView( + number = 1, + type = CipherListViewType.Identity, + ) + val cipherView2 = createMockCipherListView( + number = 2, + type = CipherListViewType.Login( + createMockLoginListView(number = 2), + ), + ) + + val autofillSelectionData = AutofillSelectionData( + type = AutofillSelectionData.Type.IDENTITY, + framework = AutofillSelectionData.Framework.AUTOFILL, + uri = "https://www.test.com", + ) + specialCircumstanceManager.specialCircumstance = + SpecialCircumstance.AutofillSelection( + autofillSelectionData = autofillSelectionData, + shouldFinishWhenComplete = true, + ) + val dataState = DataState.Loaded( + data = VaultData( + decryptCipherListResult = createMockDecryptCipherListResult( + number = 1, + successes = listOf(cipherView1, cipherView2), + ), + folderViewList = listOf(createMockFolderView(number = 1)), + collectionViewList = listOf(createMockCollectionView(number = 1)), + sendViewList = listOf(createMockSendView(number = 1)), + ), + ) + + val viewModel = createVaultItemListingViewModel( + savedStateHandle = createSavedStateHandleWithVaultItemListingType( + vaultItemListingType = VaultItemListingType.Identity, + ), + ) + + mutableVaultDataStateFlow.value = dataState + + assertEquals( + createVaultItemListingState( + itemListingType = VaultItemListingState.ItemListingType.Vault.Identity, + viewState = VaultItemListingState.ViewState.Content( + displayCollectionList = emptyList(), + displayItemList = listOf( + createMockDisplayItemForCipher( + number = 1, + cipherType = CipherType.IDENTITY, + subtitle = "mockSubtitle-1", + secondSubtitleTestTag = "PasskeySite", + subtitleTestTag = "PasswordName", + isAutofill = true, + ), + ), + displayFolderList = emptyList(), + ), + ) + .copy(autofillSelectionData = autofillSelectionData), + viewModel.stateFlow.value, + ) + } + @Suppress("MaxLineLength") @Test fun `vaultDataStateFlow Loaded with items and totp data filtering should update ViewState to Content with filtered data`() = diff --git a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt index f08bead83a5..a2d55a0bbc9 100644 --- a/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt +++ b/app/src/test/kotlin/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt @@ -97,7 +97,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_note), extraIconList = persistentListOf( IconData.Local( @@ -145,7 +145,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_payment_card), extraIconList = persistentListOf( IconData.Local( @@ -197,7 +197,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_id_card), extraIconList = persistentListOf( IconData.Local( @@ -225,9 +225,9 @@ fun createMockDisplayItemForCipher( ListingItemOverflowAction.VaultAction.ArchiveClick(cipherId = "mockId-$number"), ), optionsTestTag = "CipherOptionsButton", - isAutofill = false, - isCredentialCreation = false, - shouldShowMasterPasswordReprompt = false, + isAutofill = isAutofill, + isCredentialCreation = isCredentialCreation, + shouldShowMasterPasswordReprompt = shouldShowMasterPasswordReprompt, iconTestTag = "IdentityCipherIcon", itemType = VaultItemListingState.DisplayItem.ItemType.Vault(type = cipherType), ) @@ -241,7 +241,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_ssh_key), extraIconList = persistentListOf( IconData.Local( @@ -285,7 +285,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_payment_card), extraIconList = persistentListOf( IconData.Local( @@ -329,7 +329,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_note), extraIconList = persistentListOf( IconData.Local( @@ -373,7 +373,7 @@ fun createMockDisplayItemForCipher( secondSubtitle = secondSubtitle, secondSubtitleTestTag = secondSubtitleTestTag, subtitle = subtitle, - subtitleTestTag = "CipherSubTitleLabel", + subtitleTestTag = subtitleTestTag, iconData = IconData.Local(BitwardenDrawable.ic_note), extraIconList = persistentListOf( IconData.Local( diff --git a/ui/src/main/res/values/strings.xml b/ui/src/main/res/values/strings.xml index f38700c934f..94d7682b026 100644 --- a/ui/src/main/res/values/strings.xml +++ b/ui/src/main/res/values/strings.xml @@ -1161,6 +1161,7 @@ Do you want to switch to this account? Bitwarden could not decrypt %1$d vault items. Copy and share this error report with customer success to avoid additional data loss. Select a card for %s + Choose an identity Advanced options More about match detection Keep your credentials secure