BIT-1545 Allowing the user to search for verification code items (#836)

This commit is contained in:
Oleg Semenenko 2024-01-28 18:52:49 -06:00 committed by Álison Fernandes
parent 8d81b160f9
commit e19ba9df51
12 changed files with 54 additions and 4 deletions

View file

@ -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
}

View file

@ -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<IconRes>,
val overflowOptions: List<ListingItemOverflowAction>,
@ -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())
}
}
}

View file

@ -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()
}
}

View file

@ -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 {

View file

@ -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
}

View file

@ -49,6 +49,9 @@ fun NavGraphBuilder.vaultGraph(
vaultVerificationCodeDestination(
onNavigateBack = { navController.popBackStack() },
onNavigateToSearchVault = {
onNavigateToSearchVault(SearchType.Vault.VerificationCodes)
},
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
)
}

View file

@ -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,
)
}

View file

@ -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()
}
}
}

View file

@ -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(

View file

@ -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
},

View file

@ -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,
)
}
}

View file

@ -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<VerificationCodeEvent>()
@ -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"