From 336e8a96cd9385dcd8117697fe6b730681d24f30 Mon Sep 17 00:00:00 2001 From: David Perez Date: Tue, 23 Jan 2024 16:28:01 -0600 Subject: [PATCH] Add overflow options to the listing screen and the search screen (#734) --- .../platform/feature/search/SearchContent.kt | 8 + .../platform/feature/search/SearchScreen.kt | 2 + .../feature/search/SearchViewModel.kt | 81 +++++++ .../search/util/SearchTypeDataExtensions.kt | 3 +- .../itemlisting/VaultItemListingContent.kt | 61 +++--- .../itemlisting/VaultItemListingNavigation.kt | 5 + .../itemlisting/VaultItemListingScreen.kt | 10 + .../itemlisting/VaultItemListingViewModel.kt | 88 ++++++++ .../model/ListingItemOverflowAction.kt | 69 ++++++ .../util/VaultItemListingDataExtensions.kt | 3 +- .../feature/util/CipherViewExtensions.kt | 38 ++++ .../feature/vault/VaultGraphNavigation.kt | 1 + .../datasource/sdk/model/CipherViewUtil.kt | 10 +- .../feature/search/SearchScreenTest.kt | 11 + .../feature/search/SearchViewModelTest.kt | 124 +++++++++++ .../feature/search/util/SearchUtil.kt | 38 +++- .../itemlisting/VaultItemListingScreenTest.kt | 20 ++ .../VaultItemListingViewModelTest.kt | 136 +++++++++++- .../util/VaultItemListingDataUtil.kt | 38 +++- .../feature/util/CipherViewExtensionsTest.kt | 205 ++++++++++++++++++ 20 files changed, 904 insertions(+), 47 deletions(-) create mode 100644 app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt create mode 100644 app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchContent.kt index 408d410dab..c41e9393c2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchContent.kt @@ -56,6 +56,14 @@ fun SearchContent( is ListingItemOverflowAction.SendAction.EditClick, is ListingItemOverflowAction.SendAction.RemovePasswordClick, is ListingItemOverflowAction.SendAction.ShareUrlClick, + is ListingItemOverflowAction.VaultAction.CopyNoteClick, + is ListingItemOverflowAction.VaultAction.CopyNumberClick, + is ListingItemOverflowAction.VaultAction.CopyPasswordClick, + is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, + is ListingItemOverflowAction.VaultAction.CopyUsernameClick, + is ListingItemOverflowAction.VaultAction.EditClick, + is ListingItemOverflowAction.VaultAction.LaunchClick, + is ListingItemOverflowAction.VaultAction.ViewClick, null, -> Unit } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreen.kt index cf146cb1ba..5f99692df4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/search/SearchScreen.kt @@ -18,6 +18,7 @@ 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.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.x8bit.bitwarden.R @@ -61,6 +62,7 @@ fun SearchScreen( is SearchEvent.NavigateToEditSend -> onNavigateToEditSend(event.sendId) is SearchEvent.NavigateToEditCipher -> onNavigateToEditCipher(event.cipherId) is SearchEvent.NavigateToViewCipher -> onNavigateToViewCipher(event.cipherId) + is SearchEvent.NavigateToUrl -> intentManager.launchUri(event.url.toUri()) is SearchEvent.ShowShareSheet -> intentManager.shareText(event.content) is SearchEvent.ShowToast -> { Toast 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 ae1c2bf83e..b1362cb2a3 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 @@ -158,6 +158,38 @@ class SearchViewModel @Inject constructor( is ListingItemOverflowAction.SendAction.ShareUrlClick -> { handleShareUrlClick(overflowAction) } + + is ListingItemOverflowAction.VaultAction.CopyNoteClick -> { + handleCopyNoteClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyNumberClick -> { + handleCopyNumberClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyPasswordClick -> { + handleCopyPasswordClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick -> { + handleCopySecurityCodeClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyUsernameClick -> { + handleCopyUsernameClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.EditClick -> { + handleEditCipherClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.LaunchClick -> { + handleLaunchCipherUrlClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.ViewClick -> { + handleViewCipherClick(overflowAction) + } } } @@ -199,6 +231,48 @@ class SearchViewModel @Inject constructor( sendEvent(SearchEvent.ShowShareSheet(action.sendUrl)) } + private fun handleCopyNoteClick(action: ListingItemOverflowAction.VaultAction.CopyNoteClick) { + clipboardManager.setText(action.notes) + } + + private fun handleCopyNumberClick( + action: ListingItemOverflowAction.VaultAction.CopyNumberClick, + ) { + clipboardManager.setText(action.number) + } + + private fun handleCopyPasswordClick( + action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, + ) { + clipboardManager.setText(action.password) + } + + private fun handleCopySecurityCodeClick( + action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, + ) { + clipboardManager.setText(action.securityCode) + } + + private fun handleCopyUsernameClick( + action: ListingItemOverflowAction.VaultAction.CopyUsernameClick, + ) { + clipboardManager.setText(action.username) + } + + private fun handleEditCipherClick(action: ListingItemOverflowAction.VaultAction.EditClick) { + sendEvent(SearchEvent.NavigateToEditCipher(action.cipherId)) + } + + private fun handleLaunchCipherUrlClick( + action: ListingItemOverflowAction.VaultAction.LaunchClick, + ) { + sendEvent(SearchEvent.NavigateToUrl(action.url)) + } + + private fun handleViewCipherClick(action: ListingItemOverflowAction.VaultAction.ViewClick) { + sendEvent(SearchEvent.NavigateToViewCipher(action.cipherId)) + } + private fun handleInternalAction(action: SearchAction.Internal) { when (action) { is SearchAction.Internal.IconLoadingSettingReceive -> { @@ -719,6 +793,13 @@ sealed class SearchEvent { val cipherId: String, ) : SearchEvent() + /** + * Navigates to the given [url]. + */ + data class NavigateToUrl( + val url: String, + ) : SearchEvent() + /** * Shares the [content] with share sheet. */ 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 b20cd9abf8..ddfa7ccac4 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 @@ -19,6 +19,7 @@ import com.x8bit.bitwarden.ui.platform.feature.search.SearchTypeData import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons import com.x8bit.bitwarden.ui.tools.feature.send.util.toOverflowActions +import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData import java.time.Clock @@ -169,7 +170,7 @@ private fun CipherView.toDisplayItem( isIconLoadingDisabled = isIconLoadingDisabled, ), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = toOverflowActions(), ) private fun CipherView.toIconData( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt index 0690002417..539d46e477 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingContent.kt @@ -35,6 +35,41 @@ fun VaultItemListingContent( onOverflowItemClick: (action: ListingItemOverflowAction) -> Unit, modifier: Modifier = Modifier, ) { + var showConfirmationDialog: ListingItemOverflowAction? by rememberSaveable { + mutableStateOf(null) + } + when (val option = showConfirmationDialog) { + is ListingItemOverflowAction.SendAction.DeleteClick -> { + BitwardenTwoButtonDialog( + title = stringResource(id = R.string.delete), + message = stringResource(id = R.string.are_you_sure_delete_send), + confirmButtonText = stringResource(id = R.string.yes), + dismissButtonText = stringResource(id = R.string.cancel), + onConfirmClick = { + showConfirmationDialog = null + onOverflowItemClick(option) + }, + onDismissClick = { showConfirmationDialog = null }, + onDismissRequest = { showConfirmationDialog = null }, + ) + } + + is ListingItemOverflowAction.SendAction.CopyUrlClick, + is ListingItemOverflowAction.SendAction.EditClick, + is ListingItemOverflowAction.SendAction.RemovePasswordClick, + is ListingItemOverflowAction.SendAction.ShareUrlClick, + is ListingItemOverflowAction.VaultAction.CopyNoteClick, + is ListingItemOverflowAction.VaultAction.CopyNumberClick, + is ListingItemOverflowAction.VaultAction.CopyPasswordClick, + is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, + is ListingItemOverflowAction.VaultAction.CopyUsernameClick, + is ListingItemOverflowAction.VaultAction.EditClick, + is ListingItemOverflowAction.VaultAction.LaunchClick, + is ListingItemOverflowAction.VaultAction.ViewClick, + null, + -> Unit + } + LazyColumn( modifier = modifier, ) { @@ -48,9 +83,6 @@ fun VaultItemListingContent( ) } items(state.displayItemList) { - var showConfirmationDialog: ListingItemOverflowAction? by rememberSaveable { - mutableStateOf(null) - } BitwardenListItem( startIcon = it.iconData, label = it.title, @@ -86,29 +118,6 @@ fun VaultItemListingContent( end = 12.dp, ), ) - when (val option = showConfirmationDialog) { - is ListingItemOverflowAction.SendAction.DeleteClick -> { - BitwardenTwoButtonDialog( - title = stringResource(id = R.string.delete), - message = stringResource(id = R.string.are_you_sure_delete_send), - confirmButtonText = stringResource(id = R.string.yes), - dismissButtonText = stringResource(id = R.string.cancel), - onConfirmClick = { - showConfirmationDialog = null - onOverflowItemClick(option) - }, - onDismissClick = { showConfirmationDialog = null }, - onDismissRequest = { showConfirmationDialog = null }, - ) - } - - is ListingItemOverflowAction.SendAction.CopyUrlClick, - is ListingItemOverflowAction.SendAction.EditClick, - is ListingItemOverflowAction.SendAction.RemovePasswordClick, - is ListingItemOverflowAction.SendAction.ShareUrlClick, - null, - -> Unit - } } item { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt index 6f18d750b4..4c370d6c6d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt @@ -54,6 +54,7 @@ data class VaultItemListingArgs( fun NavGraphBuilder.vaultItemListingDestination( onNavigateBack: () -> Unit, onNavigateToVaultItemScreen: (id: String) -> Unit, + onNavigateToVaultEditItemScreen: (cipherId: String) -> Unit, onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit, ) { @@ -64,6 +65,7 @@ fun NavGraphBuilder.vaultItemListingDestination( onNavigateToEditSendItem = { }, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, + onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen, onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) }, ) } @@ -84,6 +86,7 @@ fun NavGraphBuilder.sendItemListingDestination( onNavigateToEditSendItem = onNavigateToEditSendItem, onNavigateToVaultAddItemScreen = { }, onNavigateToVaultItemScreen = { }, + onNavigateToVaultEditItemScreen = { }, onNavigateToSearch = { onNavigateToSearchSend(it as SearchType.Sends) }, ) } @@ -96,6 +99,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination( route: String, onNavigateBack: () -> Unit, onNavigateToVaultItemScreen: (id: String) -> Unit, + onNavigateToVaultEditItemScreen: (cipherId: String) -> Unit, onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToAddSendItem: () -> Unit, onNavigateToEditSendItem: (sendId: String) -> Unit, @@ -120,6 +124,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination( VaultItemListingScreen( onNavigateBack = onNavigateBack, onNavigateToVaultItem = onNavigateToVaultItemScreen, + onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToAddSendItem = onNavigateToAddSendItem, onNavigateToEditSendItem = onNavigateToEditSendItem, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index 0bf2e809fb..f7c7e2329e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -21,6 +21,7 @@ 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.core.net.toUri import androidx.hilt.navigation.compose.hiltViewModel import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect @@ -49,6 +50,7 @@ import kotlinx.collections.immutable.persistentListOf fun VaultItemListingScreen( onNavigateBack: () -> Unit, onNavigateToVaultItem: (id: String) -> Unit, + onNavigateToVaultEditItemScreen: (cipherId: String) -> Unit, onNavigateToVaultAddItemScreen: () -> Unit, onNavigateToAddSendItem: () -> Unit, onNavigateToEditSendItem: (sendId: String) -> Unit, @@ -88,6 +90,14 @@ fun VaultItemListingScreen( onNavigateToVaultAddItemScreen() } + is VaultItemListingEvent.NavigateToEditCipher -> { + onNavigateToVaultEditItemScreen(event.cipherId) + } + + is VaultItemListingEvent.NavigateToUrl -> { + intentManager.launchUri(event.url.toUri()) + } + is VaultItemListingEvent.NavigateToAddSendItem -> { onNavigateToAddSendItem() } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index ba8b5d90bd..fa00b1ae40 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -182,6 +182,48 @@ class VaultItemListingViewModel @Inject constructor( sendEvent(event) } + private fun handleCopyNoteClick(action: ListingItemOverflowAction.VaultAction.CopyNoteClick) { + clipboardManager.setText(action.notes) + } + + private fun handleCopyNumberClick( + action: ListingItemOverflowAction.VaultAction.CopyNumberClick, + ) { + clipboardManager.setText(action.number) + } + + private fun handleCopyPasswordClick( + action: ListingItemOverflowAction.VaultAction.CopyPasswordClick, + ) { + clipboardManager.setText(action.password) + } + + private fun handleCopySecurityCodeClick( + action: ListingItemOverflowAction.VaultAction.CopySecurityCodeClick, + ) { + clipboardManager.setText(action.securityCode) + } + + private fun handleCopyUsernameClick( + action: ListingItemOverflowAction.VaultAction.CopyUsernameClick, + ) { + clipboardManager.setText(action.username) + } + + private fun handleEditCipherClick(action: ListingItemOverflowAction.VaultAction.EditClick) { + sendEvent(VaultItemListingEvent.NavigateToEditCipher(action.cipherId)) + } + + private fun handleLaunchCipherUrlClick( + action: ListingItemOverflowAction.VaultAction.LaunchClick, + ) { + sendEvent(VaultItemListingEvent.NavigateToUrl(action.url)) + } + + private fun handleViewCipherClick(action: ListingItemOverflowAction.VaultAction.ViewClick) { + sendEvent(VaultItemListingEvent.NavigateToVaultItem(action.cipherId)) + } + private fun handleDismissDialogClick() { mutableStateFlow.update { it.copy(dialogState = null) } } @@ -236,6 +278,38 @@ class VaultItemListingViewModel @Inject constructor( is ListingItemOverflowAction.SendAction.ShareUrlClick -> { handleShareSendUrlClick(overflowAction) } + + is ListingItemOverflowAction.VaultAction.CopyNoteClick -> { + handleCopyNoteClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyNumberClick -> { + handleCopyNumberClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyPasswordClick -> { + handleCopyPasswordClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopySecurityCodeClick -> { + handleCopySecurityCodeClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.CopyUsernameClick -> { + handleCopyUsernameClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.EditClick -> { + handleEditCipherClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.LaunchClick -> { + handleLaunchCipherUrlClick(overflowAction) + } + + is ListingItemOverflowAction.VaultAction.ViewClick -> { + handleViewCipherClick(overflowAction) + } } } @@ -696,6 +770,20 @@ sealed class VaultItemListingEvent { */ data class NavigateToVaultItem(val id: String) : VaultItemListingEvent() + /** + * Navigates to view a cipher. + */ + data class NavigateToEditCipher( + val cipherId: String, + ) : VaultItemListingEvent() + + /** + * Navigates to the given [url]. + */ + data class NavigateToUrl( + val url: String, + ) : VaultItemListingEvent() + /** * Navigates to the SearchScreen with the given type filter. */ diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt index 5036cc867d..385ae05370 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/model/ListingItemOverflowAction.kt @@ -60,4 +60,73 @@ sealed class ListingItemOverflowAction : Parcelable { override val title: Text get() = R.string.delete.asText() } } + + /** + * Represents the vault actions. + */ + sealed class VaultAction : ListingItemOverflowAction() { + /** + * Click on the view cipher overflow option. + */ + @Parcelize + data class ViewClick(val cipherId: String) : VaultAction() { + override val title: Text get() = R.string.view.asText() + } + + /** + * Click on the edit cipher overflow option. + */ + @Parcelize + data class EditClick(val cipherId: String) : VaultAction() { + override val title: Text get() = R.string.edit.asText() + } + + /** + * Click on the copy username overflow option. + */ + @Parcelize + data class CopyUsernameClick(val username: String) : VaultAction() { + override val title: Text get() = R.string.copy_username.asText() + } + + /** + * Click on the copy password overflow option. + */ + @Parcelize + data class CopyPasswordClick(val password: String) : VaultAction() { + override val title: Text get() = R.string.copy_password.asText() + } + + /** + * Click on the copy number overflow option. + */ + @Parcelize + data class CopyNumberClick(val number: String) : VaultAction() { + override val title: Text get() = R.string.copy_number.asText() + } + + /** + * Click on the copy security code overflow option. + */ + @Parcelize + data class CopySecurityCodeClick(val securityCode: String) : VaultAction() { + override val title: Text get() = R.string.copy_security_code.asText() + } + + /** + * Click on the copy secure note overflow option. + */ + @Parcelize + data class CopyNoteClick(val notes: String) : VaultAction() { + override val title: Text get() = R.string.copy_notes.asText() + } + + /** + * Click on the launch overflow option. + */ + @Parcelize + data class LaunchClick(val url: String) : VaultAction() { + override val title: Text get() = R.string.launch.asText() + } + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt index 6d23d59579..479ae32805 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataExtensions.kt @@ -14,6 +14,7 @@ import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons import com.x8bit.bitwarden.ui.tools.feature.send.util.toOverflowActions import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingState +import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData import java.time.Clock @@ -173,7 +174,7 @@ private fun CipherView.toDisplayItem( isIconLoadingDisabled = isIconLoadingDisabled, ), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = toOverflowActions(), ) private fun CipherView.toIconData( diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt new file mode 100644 index 0000000000..0b1da31701 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensions.kt @@ -0,0 +1,38 @@ +package com.x8bit.bitwarden.ui.vault.feature.util + +import com.bitwarden.core.CipherType +import com.bitwarden.core.CipherView +import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction + +/** + * Creates the list of overflow actions to be displayed for a [CipherView]. + */ +fun CipherView.toOverflowActions(): List = + this + .id + ?.let { cipherId -> + listOfNotNull( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = cipherId), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = cipherId) + .takeUnless { this.deletedDate != null }, + this.login?.username?.let { + ListingItemOverflowAction.VaultAction.CopyUsernameClick(username = it) + }, + this.login?.password?.let { + ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = it) + }, + this.card?.number?.let { + ListingItemOverflowAction.VaultAction.CopyNumberClick(number = it) + }, + this.card?.code?.let { + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick(securityCode = it) + }, + this.notes + ?.let { ListingItemOverflowAction.VaultAction.CopyNoteClick(notes = it) } + .takeIf { this.type == CipherType.SECURE_NOTE }, + this.login?.uris?.firstOrNull { it.uri != null }?.uri?.let { + ListingItemOverflowAction.VaultAction.LaunchClick(url = it) + }, + ) + } + .orEmpty() 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 ce5d05d3ef..6004928d66 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 @@ -44,6 +44,7 @@ fun NavGraphBuilder.vaultGraph( onNavigateToVaultItemScreen = onNavigateToVaultItemScreen, onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen, onNavigateToSearchVault = onNavigateToSearchVault, + onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen, ) vaultVerificationCodeDestination( diff --git a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt index 49635aeb54..0f1bbd16a6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/data/vault/datasource/sdk/model/CipherViewUtil.kt @@ -37,7 +37,7 @@ fun createMockCipherView( name = "mockName-$number", notes = "mockNotes-$number", type = cipherType, - login = createMockLoginView(number = number), + login = createMockLoginView(number = number).takeIf { cipherType == CipherType.LOGIN }, creationDate = ZonedDateTime .parse("2023-10-27T12:00:00Z") .toInstant(), @@ -52,13 +52,15 @@ fun createMockCipherView( .parse("2023-10-27T12:00:00Z") .toInstant(), attachments = listOf(createMockAttachmentView(number = number)), - card = createMockCardView(number = number), + card = createMockCardView(number = number).takeIf { cipherType == CipherType.CARD }, fields = listOf(createMockFieldView(number = number)), - identity = createMockIdentityView(number = number), + identity = createMockIdentityView(number = number).takeIf { + cipherType == CipherType.IDENTITY + }, favorite = false, passwordHistory = listOf(createMockPasswordHistoryView(number = number)), reprompt = CipherRepromptType.NONE, - secureNote = createMockSecureNoteView(), + secureNote = createMockSecureNoteView().takeIf { cipherType == CipherType.SECURE_NOTE }, edit = false, organizationUseTotp = false, viewPassword = false, 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 9e91eca29e..25d8796849 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 @@ -13,6 +13,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode +import androidx.core.net.toUri import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFlow import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText @@ -43,6 +44,7 @@ class SearchScreenTest : BaseComposeTest() { } private val intentManager: IntentManager = mockk { every { shareText(any()) } just runs + every { launchUri(any()) } just runs } private var onNavigateBackCalled = false @@ -91,6 +93,15 @@ class SearchScreenTest : BaseComposeTest() { assertEquals(cipherId, onNavigateToViewCipherId) } + @Test + fun `NavigateToUrl should call launchUri on the IntentManager`() { + val url = "www.test.com" + mutableEventFlow.tryEmit(SearchEvent.NavigateToUrl(url)) + verify(exactly = 1) { + intentManager.launchUri(url.toUri()) + } + } + @Test fun `ShowShareSheet should call onNavigateBack`() { val sendUrl = "www.test.com" 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 ad89ca2bb7..a57ff9c5be 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 @@ -301,6 +301,130 @@ class SearchViewModelTest : BaseViewModelTest() { } } + @Test + fun `OverflowOptionClick Vault CopyNoteClick should call setText on the ClipboardManager`() = + runTest { + val notes = "notes" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNoteClick(notes = notes), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(notes) + } + } + + @Test + fun `OverflowOptionClick Vault CopyNumberClick should call setText on the ClipboardManager`() = + runTest { + val number = "12345-4321-9876-6789" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNumberClick(number = number), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(number) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = + runTest { + val password = "passTheWord" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = password), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(password) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = + runTest { + val securityCode = "234" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = securityCode, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(securityCode) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyUsernameClick should call setText on the ClipboardManager`() = + runTest { + val username = "bitwarden" + val viewModel = createViewModel() + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyUsernameClick( + username = username, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(username) + } + } + + @Test + fun `OverflowOptionClick Vault EditClick should emit NavigateToEditCipher`() = runTest { + val cipherId = "cipherId-1234" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.EditClick(cipherId = cipherId), + ), + ) + assertEquals(SearchEvent.NavigateToEditCipher(cipherId), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault LaunchClick should emit NavigateToUrl`() = runTest { + val url = "www.test.com" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.LaunchClick(url = url), + ), + ) + assertEquals(SearchEvent.NavigateToUrl(url), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault ViewClick should emit NavigateToUrl`() = runTest { + val cipherId = "cipherId-9876" + val viewModel = createViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + SearchAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = cipherId), + ), + ) + assertEquals(SearchEvent.NavigateToViewCipher(cipherId), awaitItem()) + } + } + @Test fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() = runTest { setupMockUri() 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 c255258e23..293e10d008 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 @@ -27,7 +27,19 @@ fun createMockDisplayItemForCipher( fallbackIconRes = R.drawable.ic_login_item, ), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyUsernameClick( + username = "mockUsername-$number", + ), + ListingItemOverflowAction.VaultAction.CopyPasswordClick( + password = "mockPassword-$number", + ), + ListingItemOverflowAction.VaultAction.LaunchClick( + url = "www.mockuri$number.com", + ), + ), ) } @@ -38,7 +50,13 @@ fun createMockDisplayItemForCipher( subtitle = null, iconData = IconData.Local(R.drawable.ic_secure_note_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyNoteClick( + notes = "mockNotes-$number", + ), + ), ) } @@ -49,7 +67,16 @@ fun createMockDisplayItemForCipher( subtitle = "er-$number", iconData = IconData.Local(R.drawable.ic_card_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyNumberClick( + number = "mockNumber-$number", + ), + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = "mockCode-$number", + ), + ), ) } @@ -60,7 +87,10 @@ fun createMockDisplayItemForCipher( subtitle = "mockFirstName-${number}mockLastName-$number", iconData = IconData.Local(R.drawable.ic_identity_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ), ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index ecfb7dad99..c9993ddfc0 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText import androidx.compose.ui.test.performClick import androidx.compose.ui.test.performScrollToNode +import androidx.core.net.toUri import com.x8bit.bitwarden.R import com.x8bit.bitwarden.data.platform.repository.model.Environment import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl @@ -51,10 +52,12 @@ class VaultItemListingScreenTest : BaseComposeTest() { private var onNavigateToAddSendScreenCalled = false private var onNavigateToEditSendItemId: String? = null private var onNavigateToVaultItemId: String? = null + private var onNavigateToVaultEditItemScreenId: String? = null private var onNavigateToSearchType: SearchType? = null private val intentManager: IntentManager = mockk { every { shareText(any()) } just runs + every { launchUri(any()) } just runs } private val mutableEventFlow = bufferedMutableSharedFlow() private val mutableStateFlow = MutableStateFlow(DEFAULT_STATE) @@ -75,6 +78,7 @@ class VaultItemListingScreenTest : BaseComposeTest() { onNavigateToAddSendItem = { onNavigateToAddSendScreenCalled = true }, onNavigateToEditSendItem = { onNavigateToEditSendItemId = it }, onNavigateToSearch = { onNavigateToSearchType = it }, + onNavigateToVaultEditItemScreen = { onNavigateToVaultEditItemScreenId = it }, ) } } @@ -157,6 +161,13 @@ class VaultItemListingScreenTest : BaseComposeTest() { assertEquals(searchType, onNavigateToSearchType) } + @Test + fun `NavigateToEditCipher should call onNavigateToVaultEditItemScreen`() { + val cipherId = "cipherId" + mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToEditCipher(cipherId)) + assertEquals(cipherId, onNavigateToVaultEditItemScreenId) + } + @Test fun `NavigateToSendItem event should call onNavigateToEditSendItemId`() { val sendId = "sendId" @@ -171,6 +182,15 @@ class VaultItemListingScreenTest : BaseComposeTest() { assertEquals(id, onNavigateToVaultItemId) } + @Test + fun `NavigateToUrl should call launchUri on the IntentManager`() { + val url = "www.test.com" + mutableEventFlow.tryEmit(VaultItemListingEvent.NavigateToUrl(url)) + verify(exactly = 1) { + intentManager.launchUri(url.toUri()) + } + } + @Test fun `progressbar should be displayed according to state`() { mutableStateFlow.update { DEFAULT_STATE } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index cfec78426e..3c403302e1 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -53,7 +53,9 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { ZoneOffset.UTC, ) - private val clipboardManager: BitwardenClipboardManager = mockk() + private val clipboardManager: BitwardenClipboardManager = mockk { + every { setText(any()) } just runs + } private val mutableVaultDataStateFlow = MutableStateFlow>(DataState.Loading) @@ -350,18 +352,138 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { } } + @Test + fun `OverflowOptionClick Vault CopyNoteClick should call setText on the ClipboardManager`() = + runTest { + val notes = "notes" + val viewModel = createVaultItemListingViewModel() + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNoteClick(notes = notes), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(notes) + } + } + + @Test + fun `OverflowOptionClick Vault CopyNumberClick should call setText on the ClipboardManager`() = + runTest { + val number = "12345-4321-9876-6789" + val viewModel = createVaultItemListingViewModel() + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyNumberClick(number = number), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(number) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyPasswordClick should call setText on the ClipboardManager`() = + runTest { + val password = "passTheWord" + val viewModel = createVaultItemListingViewModel() + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = password), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(password) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopySecurityCodeClick should call setText on the ClipboardManager`() = + runTest { + val securityCode = "234" + val viewModel = createVaultItemListingViewModel() + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = securityCode, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(securityCode) + } + } + + @Suppress("MaxLineLength") + @Test + fun `OverflowOptionClick Vault CopyUsernameClick should call setText on the ClipboardManager`() = + runTest { + val username = "bitwarden" + val viewModel = createVaultItemListingViewModel() + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.CopyUsernameClick( + username = username, + ), + ), + ) + verify(exactly = 1) { + clipboardManager.setText(username) + } + } + + @Test + fun `OverflowOptionClick Vault EditClick should emit NavigateToEditCipher`() = runTest { + val cipherId = "cipherId-1234" + val viewModel = createVaultItemListingViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.EditClick(cipherId = cipherId), + ), + ) + assertEquals(VaultItemListingEvent.NavigateToEditCipher(cipherId), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault LaunchClick should emit NavigateToUrl`() = runTest { + val url = "www.test.com" + val viewModel = createVaultItemListingViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.LaunchClick(url = url), + ), + ) + assertEquals(VaultItemListingEvent.NavigateToUrl(url), awaitItem()) + } + } + + @Test + fun `OverflowOptionClick Vault ViewClick should emit NavigateToUrl`() = runTest { + val cipherId = "cipherId-9876" + val viewModel = createVaultItemListingViewModel() + viewModel.eventFlow.test { + viewModel.actionChannel.trySend( + VaultItemListingsAction.OverflowOptionClick( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = cipherId), + ), + ) + assertEquals(VaultItemListingEvent.NavigateToVaultItem(cipherId), awaitItem()) + } + } + @Test fun `vaultDataStateFlow Loaded with items should update ViewState to Content`() = runTest { setupMockUri() + val dataState = DataState.Loaded( data = VaultData( - cipherViewList = listOf( - createMockCipherView( - number = 1, - isDeleted = false, - ), - ), + cipherViewList = listOf(createMockCipherView(number = 1, isDeleted = false)), folderViewList = listOf(createMockFolderView(number = 1)), collectionViewList = listOf(createMockCollectionView(number = 1)), sendViewList = listOf(createMockSendView(number = 1)), diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt index a16d70c3ea..2eb650f96e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/util/VaultItemListingDataUtil.kt @@ -28,7 +28,19 @@ fun createMockDisplayItemForCipher( fallbackIconRes = R.drawable.ic_login_item, ), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyUsernameClick( + username = "mockUsername-$number", + ), + ListingItemOverflowAction.VaultAction.CopyPasswordClick( + password = "mockPassword-$number", + ), + ListingItemOverflowAction.VaultAction.LaunchClick( + url = "www.mockuri$number.com", + ), + ), ) } @@ -39,7 +51,13 @@ fun createMockDisplayItemForCipher( subtitle = subtitle, iconData = IconData.Local(R.drawable.ic_secure_note_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyNoteClick( + notes = "mockNotes-$number", + ), + ), ) } @@ -50,7 +68,16 @@ fun createMockDisplayItemForCipher( subtitle = subtitle, iconData = IconData.Local(R.drawable.ic_card_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.CopyNumberClick( + number = "mockNumber-$number", + ), + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = "mockCode-$number", + ), + ), ) } @@ -61,7 +88,10 @@ fun createMockDisplayItemForCipher( subtitle = subtitle, iconData = IconData.Local(R.drawable.ic_identity_item), extraIconList = emptyList(), - overflowOptions = emptyList(), + overflowOptions = listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = "mockId-$number"), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = "mockId-$number"), + ), ) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt new file mode 100644 index 0000000000..beebd0afb3 --- /dev/null +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/util/CipherViewExtensionsTest.kt @@ -0,0 +1,205 @@ +package com.x8bit.bitwarden.ui.vault.feature.util + +import com.bitwarden.core.CipherType +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCardView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockCipherView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockIdentityView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockLoginView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockSecureNoteView +import com.x8bit.bitwarden.data.vault.datasource.sdk.model.createMockUriView +import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test + +class CipherViewExtensionsTest { + + @Test + fun `toOverflowActions should return all actions for a login cipher`() { + val id = "mockId-1" + val username = "Bitwarden" + val password = "password" + val uri = "www.test.com" + val cipher = createMockCipherView(number = 1, cipherType = CipherType.LOGIN).copy( + id = id, + login = createMockLoginView(number = 1).copy( + username = username, + password = password, + uris = listOf(createMockUriView(number = 1).copy(uri = uri)), + ), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = id), + ListingItemOverflowAction.VaultAction.CopyUsernameClick(username = username), + ListingItemOverflowAction.VaultAction.CopyPasswordClick(password = password), + ListingItemOverflowAction.VaultAction.LaunchClick(url = uri), + ), + result, + ) + } + + @Test + fun `toOverflowActions should return minimum actions for a login cipher`() { + val id = "mockId-1" + val cipher = createMockCipherView( + number = 1, + isDeleted = true, + cipherType = CipherType.LOGIN, + ) + .copy( + id = id, + login = createMockLoginView(number = 1).copy( + username = null, + password = null, + uris = null, + ), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), + result, + ) + } + + @Test + fun `toOverflowActions should return all actions for a card cipher`() { + val id = "mockId-1" + val number = "1322-2414-7634-2354" + val securityCode = "123" + val cipher = createMockCipherView(number = 1, cipherType = CipherType.CARD).copy( + id = id, + card = createMockCardView(number = 1).copy( + number = number, + code = securityCode, + ), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = id), + ListingItemOverflowAction.VaultAction.CopyNumberClick(number = number), + ListingItemOverflowAction.VaultAction.CopySecurityCodeClick( + securityCode = securityCode, + ), + ), + result, + ) + } + + @Test + fun `toOverflowActions should return minimum actions for a card cipher`() { + val id = "mockId-1" + val cipher = createMockCipherView( + number = 1, + isDeleted = true, + cipherType = CipherType.CARD, + ) + .copy( + id = id, + card = createMockCardView(number = 1).copy( + number = null, + code = null, + ), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), + result, + ) + } + + @Test + fun `toOverflowActions should return all actions for a identity cipher`() { + val id = "mockId-1" + val cipher = createMockCipherView(number = 1, cipherType = CipherType.IDENTITY).copy( + id = id, + identity = createMockIdentityView(number = 1), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = id), + ), + result, + ) + } + + @Test + fun `toOverflowActions should return minimum actions for a identity cipher`() { + val id = "mockId-1" + val cipher = createMockCipherView( + number = 1, + isDeleted = true, + cipherType = CipherType.IDENTITY, + ) + .copy( + id = id, + identity = createMockIdentityView(number = 1), + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), + result, + ) + } + + @Test + fun `toOverflowActions should return all actions for a secure note cipher`() { + val id = "mockId-1" + val notes = "so secure" + val cipher = createMockCipherView(number = 1, cipherType = CipherType.SECURE_NOTE).copy( + id = id, + secureNote = createMockSecureNoteView(), + notes = notes, + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf( + ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id), + ListingItemOverflowAction.VaultAction.EditClick(cipherId = id), + ListingItemOverflowAction.VaultAction.CopyNoteClick(notes = notes), + ), + result, + ) + } + + @Test + fun `toOverflowActions should return minimum actions for a secure note cipher`() { + val id = "mockId-1" + val cipher = createMockCipherView( + number = 1, + isDeleted = true, + cipherType = CipherType.SECURE_NOTE, + ) + .copy( + id = id, + secureNote = createMockSecureNoteView(), + notes = null, + ) + + val result = cipher.toOverflowActions() + + assertEquals( + listOf(ListingItemOverflowAction.VaultAction.ViewClick(cipherId = id)), + result, + ) + } +}