From e19ba9df51b2b315a7af6e1cf80310932467cd47 Mon Sep 17 00:00:00 2001 From: Oleg Semenenko <146032743+oleg-livefront@users.noreply.github.com> Date: Sun, 28 Jan 2024 18:52:49 -0600 Subject: [PATCH] BIT-1545 Allowing the user to search for verification code items (#836) --- .../ui/platform/feature/search/SearchNavigation.kt | 5 +++++ .../ui/platform/feature/search/SearchViewModel.kt | 11 +++++++++++ .../ui/platform/feature/search/model/SearchType.kt | 5 +++++ .../feature/search/util/SearchTypeDataExtensions.kt | 4 ++++ .../feature/search/util/SearchTypeExtensions.kt | 1 + .../ui/vault/feature/vault/VaultGraphNavigation.kt | 3 +++ .../verificationcode/VerificationCodeNavigation.kt | 2 ++ .../verificationcode/VerificationCodeScreen.kt | 6 ++---- .../ui/platform/feature/search/SearchScreenTest.kt | 5 +++++ .../ui/platform/feature/search/SearchViewModelTest.kt | 2 ++ .../ui/platform/feature/search/util/SearchUtil.kt | 6 ++++++ .../verificationcode/VerificationCodeScreenTest.kt | 8 ++++++++ 12 files changed, 54 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt index 83f53809d9..40f59b5176 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchNavigation.kt @@ -23,6 +23,8 @@ private const val SEARCH_TYPE_VAULT_COLLECTION: String = "search_type_vault_coll private const val SEARCH_TYPE_VAULT_NO_FOLDER: String = "search_type_vault_no_folder" private const val SEARCH_TYPE_VAULT_FOLDER: String = "search_type_vault_folder" private const val SEARCH_TYPE_VAULT_TRASH: String = "search_type_vault_trash" +private const val SEARCH_TYPE_VAULT_VERIFICATION_CODES: String = + "search_type_vault_verification_codes" private const val SEARCH_TYPE_ID: String = "search_type_id" private const val SEARCH_ROUTE_PREFIX: String = "search" @@ -101,6 +103,7 @@ private fun determineSearchType( SEARCH_TYPE_VAULT_NO_FOLDER -> SearchType.Vault.NoFolder SEARCH_TYPE_VAULT_FOLDER -> SearchType.Vault.Folder(requireNotNull(id)) SEARCH_TYPE_VAULT_TRASH -> SearchType.Vault.Trash + SEARCH_TYPE_VAULT_VERIFICATION_CODES -> SearchType.Vault.VerificationCodes else -> throw IllegalArgumentException("Invalid Search Type") } @@ -118,6 +121,7 @@ private fun SearchType.toTypeString(): String = SearchType.Vault.NoFolder -> SEARCH_TYPE_VAULT_NO_FOLDER SearchType.Vault.SecureNotes -> SEARCH_TYPE_VAULT_SECURE_NOTES SearchType.Vault.Trash -> SEARCH_TYPE_VAULT_TRASH + SearchType.Vault.VerificationCodes -> SEARCH_TYPE_VAULT_VERIFICATION_CODES } private fun SearchType.toIdOrNull(): String? = @@ -134,4 +138,5 @@ private fun SearchType.toIdOrNull(): String? = SearchType.Vault.NoFolder -> null SearchType.Vault.SecureNotes -> null SearchType.Vault.Trash -> null + SearchType.Vault.VerificationCodes -> null } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt index b1362cb2a3..6f47c460bd 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModel.kt @@ -545,6 +545,7 @@ data class SearchState( val id: String, val title: String, val subtitle: String?, + val totpCode: String?, val iconData: IconData, val extraIconList: List, val overflowOptions: List, @@ -684,6 +685,16 @@ sealed class SearchTypeData : Parcelable { .concat(" ".asText()) .concat(R.string.trash.asText()) } + + /** + * Indicates that we should be searching only for verification code items. + */ + data object VerificationCodes : Vault() { + override val title: Text + get() = R.string.search.asText() + .concat(" ".asText()) + .concat(R.string.verification_codes.asText()) + } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt index 7e296a9814..3eb74d8878 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/model/SearchType.kt @@ -81,5 +81,10 @@ sealed class SearchType : Parcelable { * Indicates that we should be searching only ciphers in the trash. */ data object Trash : Vault() + + /** + * Indicates that we should be searching only for verification code items. + */ + data object VerificationCodes : Vault() } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt index ddfa7ccac4..51cac60bc6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeDataExtensions.kt @@ -57,6 +57,7 @@ fun SearchTypeData.updateWithAdditionalDataIfNecessary( SearchTypeData.Vault.NoFolder -> this SearchTypeData.Vault.SecureNotes -> this SearchTypeData.Vault.Trash -> this + SearchTypeData.Vault.VerificationCodes -> this } /** @@ -97,6 +98,7 @@ private fun CipherView.filterBySearchType( is SearchTypeData.Vault.Identities -> type == CipherType.IDENTITY is SearchTypeData.Vault.Logins -> type == CipherType.LOGIN is SearchTypeData.Vault.SecureNotes -> type == CipherType.SECURE_NOTE + is SearchTypeData.Vault.VerificationCodes -> login?.totp != null is SearchTypeData.Vault.Trash -> deletedDate != null } @@ -171,6 +173,7 @@ private fun CipherView.toDisplayItem( ), extraIconList = emptyList(), overflowOptions = toOverflowActions(), + totpCode = login?.totp, ) private fun CipherView.toIconData( @@ -302,6 +305,7 @@ private fun SendView.toDisplayItem( ), extraIconList = toLabelIcons(clock = clock), overflowOptions = toOverflowActions(baseWebSendUrl = baseWebSendUrl), + totpCode = null, ) private enum class SortPriority { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt index d6889a2448..29b865ec90 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchTypeExtensions.kt @@ -20,4 +20,5 @@ fun SearchType.toSearchTypeData(): SearchTypeData = SearchType.Vault.NoFolder -> SearchTypeData.Vault.NoFolder SearchType.Vault.SecureNotes -> SearchTypeData.Vault.SecureNotes SearchType.Vault.Trash -> SearchTypeData.Vault.Trash + SearchType.Vault.VerificationCodes -> SearchTypeData.Vault.VerificationCodes } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultGraphNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultGraphNavigation.kt index 6004928d66..ca5610227c 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultGraphNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/VaultGraphNavigation.kt @@ -49,6 +49,9 @@ fun NavGraphBuilder.vaultGraph( vaultVerificationCodeDestination( onNavigateBack = { navController.popBackStack() }, + onNavigateToSearchVault = { + onNavigateToSearchVault(SearchType.Vault.VerificationCodes) + }, onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeNavigation.kt index 2e4750bb9a..4415feb58d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeNavigation.kt @@ -12,6 +12,7 @@ private const val VERIFICATION_CODE_ROUTE: String = "verification_code" */ fun NavGraphBuilder.vaultVerificationCodeDestination( onNavigateBack: () -> Unit, + onNavigateToSearchVault: () -> Unit, onNavigateToVaultItemScreen: (String) -> Unit, ) { composableWithPushTransitions( @@ -19,6 +20,7 @@ fun NavGraphBuilder.vaultVerificationCodeDestination( ) { VerificationCodeScreen( onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, + onNavigateToSearch = onNavigateToSearchVault, onNavigateBack = onNavigateBack, ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt index 5035024938..373870d284 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreen.kt @@ -16,14 +16,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll -import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect -import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent @@ -46,10 +44,10 @@ import kotlinx.collections.immutable.persistentListOf fun VerificationCodeScreen( viewModel: VerificationCodeViewModel = hiltViewModel(), onNavigateBack: () -> Unit, + onNavigateToSearch: () -> Unit, onNavigateToVaultItemScreen: (String) -> Unit, ) { val state by viewModel.stateFlow.collectAsState() - val context = LocalContext.current val verificationCodeHandler = remember(viewModel) { VerificationCodeHandlers.create(viewModel) } @@ -67,7 +65,7 @@ fun VerificationCodeScreen( is VerificationCodeEvent.NavigateBack -> onNavigateBack() is VerificationCodeEvent.NavigateToVaultItem -> onNavigateToVaultItemScreen(event.id) is VerificationCodeEvent.NavigateToVaultSearchScreen -> { - showNotYetImplementedToast(context = context) + onNavigateToSearch() } } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt index 25d8796849..b862df8a7c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreenTest.kt @@ -222,6 +222,11 @@ class SearchScreenTest : BaseComposeTest() { } composeTestRule.onNodeWithText(text = "Search Trash").assertIsDisplayed() + mutableStateFlow.update { + it.copy(searchType = SearchTypeData.Vault.VerificationCodes) + } + composeTestRule.onNodeWithText(text = "Search Verification codes").assertIsDisplayed() + mutableStateFlow.update { it.copy( searchType = SearchTypeData.Vault.Folder( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt index 326c8b454b..579522de36 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchViewModelTest.kt @@ -864,6 +864,7 @@ class SearchViewModelTest : BaseViewModelTest() { SearchTypeData.Vault.Logins -> "search_type_vault_logins" SearchTypeData.Vault.NoFolder -> "search_type_vault_no_folder" SearchTypeData.Vault.SecureNotes -> "search_type_vault_secure_notes" + SearchTypeData.Vault.VerificationCodes -> "search_type_vault_verification_codes" SearchTypeData.Vault.Trash -> "search_type_vault_trash" null -> "search_type_vault_all" }, @@ -882,6 +883,7 @@ class SearchViewModelTest : BaseViewModelTest() { SearchTypeData.Vault.Logins -> null SearchTypeData.Vault.NoFolder -> null SearchTypeData.Vault.SecureNotes -> null + SearchTypeData.Vault.VerificationCodes -> null SearchTypeData.Vault.Trash -> null null -> null }, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt index 293e10d008..8c2b2ab664 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/search/util/SearchUtil.kt @@ -40,6 +40,7 @@ fun createMockDisplayItemForCipher( url = "www.mockuri$number.com", ), ), + totpCode = "mockTotp-$number", ) } @@ -57,6 +58,7 @@ fun createMockDisplayItemForCipher( notes = "mockNotes-$number", ), ), + totpCode = null, ) } @@ -77,6 +79,7 @@ fun createMockDisplayItemForCipher( securityCode = "mockCode-$number", ), ), + totpCode = null, ) } @@ -91,6 +94,7 @@ fun createMockDisplayItemForCipher( ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), ), + totpCode = null, ) } } @@ -131,6 +135,7 @@ fun createMockDisplayItemForSend( ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"), ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"), ), + totpCode = null, ) } @@ -161,6 +166,7 @@ fun createMockDisplayItemForSend( ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = "mockId-$number"), ListingItemOverflowAction.SendAction.DeleteClick(sendId = "mockId-$number"), ), + totpCode = null, ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt index 72a3901774..2ccec2e8c9 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/verificationcode/VerificationCodeScreenTest.kt @@ -35,6 +35,7 @@ import org.junit.jupiter.api.Assertions.assertTrue class VerificationCodeScreenTest : BaseComposeTest() { private var onNavigateBackCalled = false + private var onNavigateToSearchCalled = false private var onNavigateToVaultItemId: String? = null private val mutableEventFlow = bufferedMutableSharedFlow() @@ -51,6 +52,7 @@ class VerificationCodeScreenTest : BaseComposeTest() { viewModel = viewModel, onNavigateBack = { onNavigateBackCalled = true }, onNavigateToVaultItemScreen = { onNavigateToVaultItemId = it }, + onNavigateToSearch = { onNavigateToSearchCalled = true }, ) } } @@ -61,6 +63,12 @@ class VerificationCodeScreenTest : BaseComposeTest() { assertTrue(onNavigateBackCalled) } + @Test + fun `NavigateToVaultSearchScreen event should invoke onNavigateToSearch`() { + mutableEventFlow.tryEmit(VerificationCodeEvent.NavigateToVaultSearchScreen) + assertTrue(onNavigateToSearchCalled) + } + @Test fun `NavigateToVaultItem event should call onNavigateToVaultItemScreen`() { val id = "id4321"