Vault screen overflow option actions (#739)

This commit is contained in:
David Perez
2024-01-23 18:32:57 -06:00
committed by Álison Fernandes
parent 9a371843ee
commit 376278e97a
8 changed files with 285 additions and 14 deletions

View File

@@ -79,6 +79,8 @@ fun VaultContent(
label = favoriteItem.name(),
supportingLabel = favoriteItem.supportingLabel?.invoke(),
onClick = { vaultHandlers.vaultItemClick(favoriteItem) },
overflowOptions = favoriteItem.overflowOptions,
onOverflowOptionClick = vaultHandlers.overflowOptionClick,
modifier = Modifier
.fillMaxWidth()
.padding(
@@ -227,12 +229,13 @@ fun VaultContent(
)
}
items(state.noFolderItems) { noFolderItem ->
VaultEntryListItem(
startIcon = noFolderItem.startIcon,
label = noFolderItem.name(),
supportingLabel = noFolderItem.supportingLabel?.invoke(),
onClick = { vaultHandlers.vaultItemClick(noFolderItem) },
overflowOptions = noFolderItem.overflowOptions,
onOverflowOptionClick = vaultHandlers.overflowOptionClick,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),

View File

@@ -1,16 +1,15 @@
package com.x8bit.bitwarden.ui.vault.feature.vault
import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import kotlinx.collections.immutable.persistentListOf
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import kotlinx.collections.immutable.toImmutableList
/**
* A Composable function that displays a row item for different types of vault entries.
@@ -18,6 +17,8 @@ import kotlinx.collections.immutable.persistentListOf
* @param label The primary text label to display for the item.
* @param supportingLabel An optional secondary text label to display beneath the primary label.
* @param onClick The lambda to be invoked when the item is clicked.
* @param overflowOptions List of options to display for the item.
* @param onOverflowOptionClick The lambda to be invoked when an overflow option is clicked.
* @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier.
* This allows the caller to specify things like padding, size, etc.
*/
@@ -26,25 +27,25 @@ fun VaultEntryListItem(
startIcon: IconData,
label: String,
onClick: () -> Unit,
overflowOptions: List<ListingItemOverflowAction.VaultAction>,
onOverflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit,
modifier: Modifier = Modifier,
supportingLabel: String? = null,
) {
val context = LocalContext.current
BitwardenListItem(
modifier = modifier,
label = label,
supportingLabel = supportingLabel,
startIcon = startIcon,
onClick = onClick,
selectionDataList = persistentListOf(
SelectionItemData(
text = "Not yet implemented",
onClick = {
// TODO: Provide dialog-based implementation (BIT-1353 - BIT-1356)
Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show()
},
),
),
selectionDataList = overflowOptions
.map { option ->
SelectionItemData(
text = option.title(),
onClick = { onOverflowOptionClick(option) },
)
}
.toImmutableList(),
)
}
@@ -57,6 +58,8 @@ private fun VaultEntryListItem_preview() {
label = "Example Login",
supportingLabel = "Username",
onClick = {},
overflowOptions = emptyList(),
onOverflowOptionClick = {},
modifier = Modifier,
)
}

View File

@@ -31,6 +31,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 com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
@@ -51,7 +52,9 @@ import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.platform.feature.search.model.SearchType
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
import com.x8bit.bitwarden.ui.platform.theme.LocalExitManager
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
import com.x8bit.bitwarden.ui.vault.feature.vault.handlers.VaultHandlers
import com.x8bit.bitwarden.ui.vault.model.VaultItemListingType
import kotlinx.collections.immutable.persistentListOf
@@ -73,6 +76,7 @@ fun VaultScreen(
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
onDimBottomNavBarRequest: (shouldDim: Boolean) -> Unit,
exitManager: ExitManager = LocalExitManager.current,
intentManager: IntentManager = LocalIntentManager.current,
) {
val state by viewModel.stateFlow.collectAsState()
val context = LocalContext.current
@@ -102,6 +106,8 @@ fun VaultScreen(
onNavigateToVaultItemListingScreen(event.itemListingType)
}
is VaultEvent.NavigateToUrl -> intentManager.launchUri(event.url.toUri())
VaultEvent.NavigateOutOfApp -> exitManager.exitApplication()
is VaultEvent.ShowToast -> {
Toast

View File

@@ -7,6 +7,7 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
import com.x8bit.bitwarden.data.auth.repository.model.SwitchAccountResult
import com.x8bit.bitwarden.data.auth.repository.model.UserState
import com.x8bit.bitwarden.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.data.platform.repository.model.DataState
import com.x8bit.bitwarden.data.platform.repository.util.baseIconUrl
@@ -19,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.platform.base.util.hexToColor
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterData
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
import com.x8bit.bitwarden.ui.vault.feature.vault.util.initials
@@ -43,6 +45,7 @@ import javax.inject.Inject
@Suppress("TooManyFunctions")
@HiltViewModel
class VaultViewModel @Inject constructor(
private val clipboardManager: BitwardenClipboardManager,
private val authRepository: AuthRepository,
private val vaultRepository: VaultRepository,
private val settingsRepository: SettingsRepository,
@@ -128,6 +131,7 @@ class VaultViewModel @Inject constructor(
is VaultAction.TryAgainClick -> handleTryAgainClick()
is VaultAction.DialogDismiss -> handleDialogDismiss()
is VaultAction.RefreshPull -> handleRefreshPull()
is VaultAction.OverflowOptionClick -> handleOverflowOptionClick(action)
is VaultAction.Internal -> handleInternalAction(action)
}
}
@@ -270,6 +274,82 @@ class VaultViewModel @Inject constructor(
vaultRepository.sync()
}
private fun handleOverflowOptionClick(action: VaultAction.OverflowOptionClick) {
when (val overflowAction = action.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 -> {
handleEditClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.LaunchClick -> {
handleLaunchClick(overflowAction)
}
is ListingItemOverflowAction.VaultAction.ViewClick -> {
handleViewClick(overflowAction)
}
}
}
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 handleEditClick(action: ListingItemOverflowAction.VaultAction.EditClick) {
sendEvent(VaultEvent.NavigateToEditVaultItem(action.cipherId))
}
private fun handleLaunchClick(action: ListingItemOverflowAction.VaultAction.LaunchClick) {
sendEvent(VaultEvent.NavigateToUrl(action.url))
}
private fun handleViewClick(action: ListingItemOverflowAction.VaultAction.ViewClick) {
sendEvent(VaultEvent.NavigateToVaultItem(action.cipherId))
}
private fun handleInternalAction(action: VaultAction.Internal) {
when (action) {
is VaultAction.Internal.PullToRefreshEnableReceive -> {
@@ -584,6 +664,11 @@ data class VaultState(
*/
abstract val supportingLabel: Text?
/**
* The overflow options to be displayed for the vault item.
*/
abstract val overflowOptions: List<ListingItemOverflowAction.VaultAction>
/**
* Represents a login item within the vault.
*
@@ -594,6 +679,7 @@ data class VaultState(
override val id: String,
override val name: Text,
override val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
val username: Text?,
) : VaultItem() {
override val supportingLabel: Text? get() = username
@@ -610,6 +696,7 @@ data class VaultState(
override val id: String,
override val name: Text,
override val startIcon: IconData = IconData.Local(R.drawable.ic_card_item),
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
val brand: Text? = null,
val lastFourDigits: Text? = null,
) : VaultItem() {
@@ -636,6 +723,7 @@ data class VaultState(
override val id: String,
override val name: Text,
override val startIcon: IconData = IconData.Local(R.drawable.ic_identity_item),
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
val firstName: Text?,
) : VaultItem() {
override val supportingLabel: Text? get() = firstName
@@ -650,6 +738,7 @@ data class VaultState(
override val id: String,
override val name: Text,
override val startIcon: IconData = IconData.Local(R.drawable.ic_secure_note_item),
override val overflowOptions: List<ListingItemOverflowAction.VaultAction>,
) : VaultItem() {
override val supportingLabel: Text? get() = null
}
@@ -726,6 +815,13 @@ sealed class VaultEvent {
val itemListingType: VaultItemListingType,
) : VaultEvent()
/**
* Navigates to the given [url].
*/
data class NavigateToUrl(
val url: String,
) : VaultEvent()
/**
* Navigate to the verification code screen.
*/
@@ -874,6 +970,13 @@ sealed class VaultAction {
*/
data object TryAgainClick : VaultAction()
/**
* User clicked an overflow action.
*/
data class OverflowOptionClick(
val overflowAction: ListingItemOverflowAction.VaultAction,
) : VaultAction()
/**
* Models actions that the [VaultViewModel] itself might send.
*/

View File

@@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.vault.feature.vault.handlers
import com.x8bit.bitwarden.ui.platform.components.model.AccountSummary
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultAction
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultViewModel
@@ -31,6 +32,7 @@ data class VaultHandlers(
val trashClick: () -> Unit,
val tryAgainClick: () -> Unit,
val dialogDismiss: () -> Unit,
val overflowOptionClick: (ListingItemOverflowAction.VaultAction) -> Unit,
) {
companion object {
/**
@@ -74,6 +76,9 @@ data class VaultHandlers(
trashClick = { viewModel.trySendAction(VaultAction.TrashClick) },
tryAgainClick = { viewModel.trySendAction(VaultAction.TryAgainClick) },
dialogDismiss = { viewModel.trySendAction(VaultAction.DialogDismiss) },
overflowOptionClick = {
viewModel.trySendAction(VaultAction.OverflowOptionClick(it))
},
)
}
}

View File

@@ -10,6 +10,7 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.vault.feature.util.toOverflowActions
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
import com.x8bit.bitwarden.ui.vault.feature.vault.model.VaultFilterType
@@ -156,11 +157,13 @@ private fun CipherView.toVaultItemOrNull(
isIconLoadingDisabled = isIconLoadingDisabled,
baseIconUrl = baseIconUrl,
),
overflowOptions = toOverflowActions(),
)
CipherType.SECURE_NOTE -> VaultState.ViewState.VaultItem.SecureNote(
id = id,
name = name.asText(),
overflowOptions = toOverflowActions(),
)
CipherType.CARD -> VaultState.ViewState.VaultItem.Card(
@@ -170,12 +173,14 @@ private fun CipherView.toVaultItemOrNull(
lastFourDigits = card?.number
?.takeLast(4)
?.asText(),
overflowOptions = toOverflowActions(),
)
CipherType.IDENTITY -> VaultState.ViewState.VaultItem.Identity(
id = id,
name = name.asText(),
firstName = identity?.firstName?.asText(),
overflowOptions = toOverflowActions(),
)
}
}