Add sealed class to model reusable overflow actions (#682)

This commit is contained in:
David Perez
2024-01-19 13:03:38 -06:00
committed by Álison Fernandes
parent 6cbfff254c
commit cf930438c2
11 changed files with 400 additions and 288 deletions

View File

@@ -3,6 +3,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send.util
import com.bitwarden.core.SendView
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import java.time.Clock
/**
@@ -20,6 +21,31 @@ fun SendView.toLabelIcons(clock: Clock = Clock.systemDefaultZone()): List<IconRe
)
.map { IconRes(iconRes = it.iconRes, contentDescription = it.contentDescription) }
/**
* Creates the list of overflow actions to be displayed for a [SendView].
*/
fun SendView.toOverflowActions(
baseWebSendUrl: String,
): List<ListingItemOverflowAction.SendAction> =
this
.id
?.let { sendId ->
listOfNotNull(
ListingItemOverflowAction.SendAction.EditClick(sendId = sendId),
ListingItemOverflowAction.SendAction.CopyUrlClick(
sendUrl = toSendUrl(baseWebSendUrl = baseWebSendUrl),
),
ListingItemOverflowAction.SendAction.ShareUrlClick(
sendUrl = toSendUrl(baseWebSendUrl = baseWebSendUrl),
),
ListingItemOverflowAction.SendAction.RemovePasswordClick(sendId = sendId).takeIf {
hasPassword
},
ListingItemOverflowAction.SendAction.DeleteClick(sendId = sendId),
)
}
.orEmpty()
/**
* Creates a sharable url from a [SendView].
*/

View File

@@ -5,24 +5,31 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderTextWithSupportLabel
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
import com.x8bit.bitwarden.ui.platform.components.model.toIconResources
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import kotlinx.collections.immutable.toPersistentList
/**
* Content view for the [VaultItemListingScreen].
*/
@Suppress("LongMethod")
@Composable
fun VaultItemListingContent(
state: VaultItemListingState.ViewState.Content,
vaultItemClick: (id: String) -> Unit,
onOverflowItemClick: (action: VaultItemListingsAction) -> Unit,
onOverflowItemClick: (action: ListingItemOverflowAction) -> Unit,
modifier: Modifier = Modifier,
) {
LazyColumn(
@@ -38,6 +45,9 @@ fun VaultItemListingContent(
)
}
items(state.displayItemList) {
var showConfirmationDialog: ListingItemOverflowAction? by rememberSaveable {
mutableStateOf(null)
}
BitwardenListItem(
startIcon = it.iconData,
label = it.title,
@@ -52,7 +62,15 @@ fun VaultItemListingContent(
.map { option ->
SelectionItemData(
text = option.title(),
onClick = { onOverflowItemClick(option.action) },
onClick = {
when (option) {
is ListingItemOverflowAction.SendAction.DeleteClick -> {
showConfirmationDialog = option
}
else -> onOverflowItemClick(option)
}
},
)
}
.toPersistentList(),
@@ -65,6 +83,29 @@ 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
}
}
}
}

View File

@@ -30,7 +30,6 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
@@ -93,9 +92,6 @@ fun VaultItemListingScreen(
VaultItemListingDialogs(
dialogState = state.dialogState,
onDeleteSendConfirm = remember(viewModel) {
{ viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(it)) }
},
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(VaultItemListingsAction.DismissDialogClick) }
},
@@ -112,20 +108,9 @@ fun VaultItemListingScreen(
@Composable
private fun VaultItemListingDialogs(
dialogState: VaultItemListingState.DialogState?,
onDeleteSendConfirm: (sendId: String) -> Unit,
onDismissRequest: () -> Unit,
) {
when (dialogState) {
is VaultItemListingState.DialogState.DeleteSendConfirmation -> 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 = { onDeleteSendConfirm(dialogState.sendId) },
onDismissClick = onDismissRequest,
onDismissRequest = onDismissRequest,
)
is VaultItemListingState.DialogState.Error -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = dialogState.title,

View File

@@ -20,6 +20,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.base.util.concat
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.platform.components.model.IconRes
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.determineListingPredicate
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toItemListingType
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.util.toViewState
@@ -81,20 +82,10 @@ class VaultItemListingViewModel @Inject constructor(
is VaultItemListingsAction.LockClick -> handleLockClick()
is VaultItemListingsAction.SyncClick -> handleSyncClick()
is VaultItemListingsAction.SearchIconClick -> handleSearchIconClick()
is VaultItemListingsAction.OverflowOptionClick -> handleOverflowOptionClick(action)
is VaultItemListingsAction.ItemClick -> handleItemClick(action)
is VaultItemListingsAction.AddVaultItemClick -> handleAddVaultItemClick()
is VaultItemListingsAction.RefreshClick -> handleRefreshClick()
is VaultItemListingsAction.CopySendUrlClick -> handleCopySendUrlClick(action)
is VaultItemListingsAction.DeleteSendClick -> handleDeleteSendClick(action)
is VaultItemListingsAction.DeleteSendConfirmClick -> {
handleDeleteSendConfirmClick(action)
}
is VaultItemListingsAction.ShareSendUrlClick -> handleShareSendUrlClick(action)
is VaultItemListingsAction.RemoveSendPasswordClick -> {
handleRemoveSendPasswordClick(action)
}
is VaultItemListingsAction.Internal -> handleInternalAction(action)
}
}
@@ -104,23 +95,11 @@ class VaultItemListingViewModel @Inject constructor(
vaultRepository.sync()
}
private fun handleCopySendUrlClick(action: VaultItemListingsAction.CopySendUrlClick) {
private fun handleCopySendUrlClick(action: ListingItemOverflowAction.SendAction.CopyUrlClick) {
clipboardManager.setText(text = action.sendUrl)
}
private fun handleDeleteSendClick(action: VaultItemListingsAction.DeleteSendClick) {
mutableStateFlow.update {
it.copy(
dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation(
sendId = action.sendId,
),
)
}
}
private fun handleDeleteSendConfirmClick(
action: VaultItemListingsAction.DeleteSendConfirmClick,
) {
private fun handleDeleteSendClick(action: ListingItemOverflowAction.SendAction.DeleteClick) {
mutableStateFlow.update {
it.copy(
dialogState = VaultItemListingState.DialogState.Loading(
@@ -134,12 +113,14 @@ class VaultItemListingViewModel @Inject constructor(
}
}
private fun handleShareSendUrlClick(action: VaultItemListingsAction.ShareSendUrlClick) {
private fun handleShareSendUrlClick(
action: ListingItemOverflowAction.SendAction.ShareUrlClick,
) {
sendEvent(VaultItemListingEvent.ShowShareSheet(action.sendUrl))
}
private fun handleRemoveSendPasswordClick(
action: VaultItemListingsAction.RemoveSendPasswordClick,
action: ListingItemOverflowAction.SendAction.RemovePasswordClick,
) {
mutableStateFlow.update {
it.copy(
@@ -167,6 +148,10 @@ class VaultItemListingViewModel @Inject constructor(
sendEvent(event)
}
private fun handleEditSendClick(action: ListingItemOverflowAction.SendAction.EditClick) {
sendEvent(VaultItemListingEvent.NavigateToSendItem(id = action.sendId))
}
private fun handleItemClick(action: VaultItemListingsAction.ItemClick) {
val event = when (state.itemListingType) {
is VaultItemListingState.ItemListingType.Vault -> {
@@ -211,6 +196,30 @@ class VaultItemListingViewModel @Inject constructor(
)
}
private fun handleOverflowOptionClick(action: VaultItemListingsAction.OverflowOptionClick) {
when (val overflowAction = action.action) {
is ListingItemOverflowAction.SendAction.CopyUrlClick -> {
handleCopySendUrlClick(overflowAction)
}
is ListingItemOverflowAction.SendAction.DeleteClick -> {
handleDeleteSendClick(overflowAction)
}
is ListingItemOverflowAction.SendAction.EditClick -> {
handleEditSendClick(overflowAction)
}
is ListingItemOverflowAction.SendAction.RemovePasswordClick -> {
handleRemoveSendPasswordClick(overflowAction)
}
is ListingItemOverflowAction.SendAction.ShareUrlClick -> {
handleShareSendUrlClick(overflowAction)
}
}
}
private fun handleInternalAction(action: VaultItemListingsAction.Internal) {
when (action) {
is VaultItemListingsAction.Internal.DeleteSendResultReceive -> {
@@ -411,14 +420,6 @@ data class VaultItemListingState(
*/
sealed class DialogState : Parcelable {
/**
* Represents a dismissible dialog with the given error [message].
*/
@Parcelize
data class DeleteSendConfirmation(
val sendId: String,
) : DialogState()
/**
* Represents a dismissible dialog with the given error [message].
*/
@@ -487,19 +488,8 @@ data class VaultItemListingState(
val subtitle: String?,
val iconData: IconData,
val extraIconList: List<IconRes>,
val overflowOptions: List<OverflowItem>,
) {
/**
* Represents a single option to be displayed in an [DisplayItem]s overflow menu.
*
* @property title the display title of the option.
* @property action the action to be sent back to the view model when the option is clicks.
*/
data class OverflowItem(
val title: Text,
val action: VaultItemListingsAction,
)
}
val overflowOptions: List<ListingItemOverflowAction>,
)
/**
* Represents different types of item listing.
@@ -707,6 +697,13 @@ sealed class VaultItemListingsAction {
*/
data object AddVaultItemClick : VaultItemListingsAction()
/**
* Click on overflow option.
*/
data class OverflowOptionClick(
val action: ListingItemOverflowAction,
) : VaultItemListingsAction()
/**
* Click on an item.
*
@@ -714,31 +711,6 @@ sealed class VaultItemListingsAction {
*/
data class ItemClick(val id: String) : VaultItemListingsAction()
/**
* Click on the copy send URL overflow option.
*/
data class CopySendUrlClick(val sendUrl: String) : VaultItemListingsAction()
/**
* Click on the share send URL overflow option.
*/
data class ShareSendUrlClick(val sendUrl: String) : VaultItemListingsAction()
/**
* Click on the remove password send overflow option.
*/
data class RemoveSendPasswordClick(val sendId: String) : VaultItemListingsAction()
/**
* Click on the delete send overflow option.
*/
data class DeleteSendClick(val sendId: String) : VaultItemListingsAction()
/**
* Click on the delete send confirmation button.
*/
data class DeleteSendConfirmClick(val sendId: String) : VaultItemListingsAction()
/**
* Models actions that the [VaultItemListingViewModel] itself might send.
*/

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.vault.feature.itemlisting.handlers
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingViewModel
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.VaultItemListingsAction
import com.x8bit.bitwarden.ui.vault.feature.itemlisting.model.ListingItemOverflowAction
/**
* A collection of handler functions for managing actions within the context of viewing a list of
@@ -15,7 +16,7 @@ data class VaultItemListingHandlers(
val refreshClick: () -> Unit,
val syncClick: () -> Unit,
val lockClick: () -> Unit,
val overflowItemClick: (action: VaultItemListingsAction) -> Unit,
val overflowItemClick: (action: ListingItemOverflowAction) -> Unit,
) {
companion object {
/**
@@ -37,7 +38,9 @@ data class VaultItemListingHandlers(
refreshClick = { viewModel.trySendAction(VaultItemListingsAction.RefreshClick) },
syncClick = { viewModel.trySendAction(VaultItemListingsAction.SyncClick) },
lockClick = { viewModel.trySendAction(VaultItemListingsAction.LockClick) },
overflowItemClick = { viewModel.trySendAction(it) },
overflowItemClick = {
viewModel.trySendAction(VaultItemListingsAction.OverflowOptionClick(it))
},
)
}
}

View File

@@ -0,0 +1,63 @@
package com.x8bit.bitwarden.ui.vault.feature.itemlisting.model
import android.os.Parcelable
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.Text
import com.x8bit.bitwarden.ui.platform.base.util.asText
import kotlinx.parcelize.Parcelize
/**
* Represents the actions for an individual item's overflow menu.
*/
sealed class ListingItemOverflowAction : Parcelable {
/**
* The display title of the option.
*/
abstract val title: Text
/**
* Represents the send actions.
*/
sealed class SendAction : ListingItemOverflowAction() {
/**
* Click on the edit send overflow option.
*/
@Parcelize
data class EditClick(val sendId: String) : SendAction() {
override val title: Text get() = R.string.edit.asText()
}
/**
* Click on the copy send URL overflow option.
*/
@Parcelize
data class CopyUrlClick(val sendUrl: String) : SendAction() {
override val title: Text get() = R.string.copy_link.asText()
}
/**
* Click on the share send URL overflow option.
*/
@Parcelize
data class ShareUrlClick(val sendUrl: String) : SendAction() {
override val title: Text get() = R.string.share_link.asText()
}
/**
* Click on the remove password send overflow option.
*/
@Parcelize
data class RemovePasswordClick(val sendId: String) : SendAction() {
override val title: Text get() = R.string.remove_password.asText()
}
/**
* Click on the delete send overflow option.
*/
@Parcelize
data class DeleteClick(val sendId: String) : SendAction() {
override val title: Text get() = R.string.delete.asText()
}
}
}

View File

@@ -8,12 +8,10 @@ import com.bitwarden.core.FolderView
import com.bitwarden.core.SendType
import com.bitwarden.core.SendView
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.model.IconData
import com.x8bit.bitwarden.ui.tools.feature.send.util.toLabelIcons
import com.x8bit.bitwarden.ui.tools.feature.send.util.toSendUrl
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.itemlisting.VaultItemListingsAction
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toLoginIconData
import java.time.Clock
@@ -207,33 +205,7 @@ private fun SendView.toDisplayItem(
},
),
extraIconList = toLabelIcons(clock = clock),
overflowOptions = listOfNotNull(
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.edit.asText(),
action = VaultItemListingsAction.ItemClick(id = id.orEmpty()),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.copy_link.asText(),
action = VaultItemListingsAction.CopySendUrlClick(
sendUrl = toSendUrl(baseWebSendUrl),
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.share_link.asText(),
action = VaultItemListingsAction.ShareSendUrlClick(
sendUrl = toSendUrl(baseWebSendUrl),
),
),
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.remove_password.asText(),
action = VaultItemListingsAction.RemoveSendPasswordClick(sendId = id.orEmpty()),
)
.takeIf { hasPassword },
VaultItemListingState.DisplayItem.OverflowItem(
title = R.string.delete.asText(),
action = VaultItemListingsAction.DeleteSendClick(sendId = id.orEmpty()),
),
),
overflowOptions = toOverflowActions(baseWebSendUrl = baseWebSendUrl),
)
@Suppress("MagicNumber")