Skip to content

Commit

Permalink
Implement context menu on item long press (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
SaintPatrck authored Apr 16, 2024
1 parent 8da98f9 commit 612c8e8
Show file tree
Hide file tree
Showing 8 changed files with 277 additions and 49 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
Expand All @@ -45,6 +46,7 @@ import com.x8bit.bitwarden.authenticator.ui.platform.components.button.Bitwarden
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BasicDialogState
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.authenticator.ui.platform.components.dialog.LoadingDialogState
import com.x8bit.bitwarden.authenticator.ui.platform.components.fab.ExpandableFabIcon
import com.x8bit.bitwarden.authenticator.ui.platform.components.fab.ExpandableFloatingActionButton
Expand Down Expand Up @@ -95,6 +97,24 @@ fun ItemListingScreen(
}
}

ItemListingDialogs(
dialog = state.dialog,
onDismissRequest = remember(viewModel) {
{
viewModel.trySendAction(
ItemListingAction.DialogDismiss,
)
}
},
onConfirmDeleteClick = remember(viewModel) {
{ itemId ->
viewModel.trySendAction(
ItemListingAction.ConfirmDeleteClick(itemId = itemId),
)
}
}
)

BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
Expand Down Expand Up @@ -175,10 +195,26 @@ fun ItemListingScreen(
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = {
viewModel.trySendAction(
ItemListingAction.ItemClick(it.authCode)
)
onItemClick = remember(viewModel) {
{
viewModel.trySendAction(
ItemListingAction.ItemClick(it.id)
)
}
},
onEditItemClick = remember(viewModel) {
{
viewModel.trySendAction(
ItemListingAction.ItemClick(it.id)
)
}
},
onDeleteItemClick = remember(viewModel) {
{
viewModel.trySendAction(
ItemListingAction.DeleteItemClick(it.id)
)
}
},
modifier = Modifier
.fillMaxWidth()
Expand Down Expand Up @@ -206,31 +242,50 @@ fun ItemListingScreen(
)
}
}
}
}
}

when (val dialog = state.dialog) {
ItemListingState.DialogState.Syncing -> {
BitwardenLoadingDialog(
visibilityState = LoadingDialogState.Shown(
text = R.string.syncing.asText(),
),
)
}
@Composable
private fun ItemListingDialogs(
dialog: ItemListingState.DialogState?,
onDismissRequest: () -> Unit,
onConfirmDeleteClick: (itemId: String) -> Unit,
) {
when (dialog) {
ItemListingState.DialogState.Loading -> {
BitwardenLoadingDialog(
visibilityState = LoadingDialogState.Shown(
text = R.string.syncing.asText(),
),
)
}

is ItemListingState.DialogState.Error -> {
BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = dialog.title,
message = dialog.message,
),
onDismissRequest = {
viewModel.trySendAction(ItemListingAction.DialogDismiss)
},
)
}
is ItemListingState.DialogState.Error -> {
BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = dialog.title,
message = dialog.message,
),
onDismissRequest = onDismissRequest,
)
}

null -> Unit
}
is ItemListingState.DialogState.DeleteConfirmationPrompt -> {
BitwardenTwoButtonDialog(
title = stringResource(id = R.string.delete),
message = dialog.message(),
confirmButtonText = stringResource(id = R.string.ok),
dismissButtonText = stringResource(id = R.string.cancel),
onConfirmClick = {
onConfirmDeleteClick(dialog.itemId)
},
onDismissClick = onDismissRequest,
onDismissRequest = onDismissRequest
)
}

null -> Unit
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,17 @@ import com.x8bit.bitwarden.authenticator.data.authenticator.datasource.disk.enti
import com.x8bit.bitwarden.authenticator.data.authenticator.manager.model.VerificationCodeItem
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.AuthenticatorRepository
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.CreateItemResult
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.DeleteItemResult
import com.x8bit.bitwarden.authenticator.data.authenticator.repository.model.TotpCodeResult
import com.x8bit.bitwarden.authenticator.data.platform.manager.clipboard.BitwardenClipboardManager
import com.x8bit.bitwarden.authenticator.data.platform.repository.SettingsRepository
import com.x8bit.bitwarden.authenticator.data.platform.repository.model.DataState
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.model.VerificationCodeDisplayItem
import com.x8bit.bitwarden.authenticator.ui.authenticator.feature.itemlisting.util.toViewState
import com.x8bit.bitwarden.authenticator.ui.platform.base.BaseViewModel
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.Text
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.asText
import com.x8bit.bitwarden.authenticator.ui.platform.base.util.concat
import com.x8bit.bitwarden.authenticator.ui.platform.components.model.IconData
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
Expand Down Expand Up @@ -81,6 +82,14 @@ class ItemListingViewModel @Inject constructor(
sendEvent(ItemListingEvent.NavigateBack)
}

is ItemListingAction.DeleteItemClick -> {
handleDeleteItemClick(action)
}

is ItemListingAction.ConfirmDeleteClick -> {
handleConfirmDeleteClick(action)
}

is ItemListingAction.SearchClick -> {
sendEvent(ItemListingEvent.NavigateToSearch)
}
Expand Down Expand Up @@ -108,6 +117,33 @@ class ItemListingViewModel @Inject constructor(
)
}

private fun handleDeleteItemClick(action: ItemListingAction.DeleteItemClick) {
mutableStateFlow.update {
it.copy(
dialog = ItemListingState.DialogState.DeleteConfirmationPrompt(
message = R.string.do_you_really_want_to_permanently_delete_cipher.asText(),
itemId = action.itemId,
)
)
}
}

private fun handleConfirmDeleteClick(action: ItemListingAction.ConfirmDeleteClick) {
mutableStateFlow.update {
it.copy(
dialog = ItemListingState.DialogState.Loading,
)
}

viewModelScope.launch {
trySendAction(
ItemListingAction.Internal.DeleteItemReceive(
authenticatorRepository.hardDeleteItem(action.itemId)
)
)
}
}

private fun handleInternalAction(internalAction: ItemListingAction.Internal) {
when (internalAction) {
is ItemListingAction.Internal.AuthCodesUpdated -> {
Expand All @@ -125,6 +161,36 @@ class ItemListingViewModel @Inject constructor(
is ItemListingAction.Internal.CreateItemResultReceive -> {
handleCreateItemResultReceive(internalAction)
}

is ItemListingAction.Internal.DeleteItemReceive -> {
handleDeleteItemReceive(internalAction.result)
}
}
}

private fun handleDeleteItemReceive(result: DeleteItemResult) {
when (result) {
DeleteItemResult.Error -> {
mutableStateFlow.update {
it.copy(
dialog = ItemListingState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
)
)
}
}

DeleteItemResult.Success -> {
mutableStateFlow.update {
it.copy(dialog = null)
}
sendEvent(
ItemListingEvent.ShowToast(
message = R.string.item_deleted.asText(),
),
)
}
}
}

Expand Down Expand Up @@ -242,7 +308,7 @@ class ItemListingViewModel @Inject constructor(
) {
updateStateWithVerificationCodeItems(
authenticatorData = authenticatorData.data,
clearDialogState = true
clearDialogState = false
)
}

Expand Down Expand Up @@ -401,10 +467,10 @@ data class ItemListingState(
sealed class DialogState : Parcelable {

/**
* Displays the syncing dialog to the user.
* Displays the loading dialog to the user.
*/
@Parcelize
data object Syncing : DialogState()
data object Loading : DialogState()

/**
* Displays a generic error dialog to the user.
Expand All @@ -414,6 +480,12 @@ data class ItemListingState(
val title: Text,
val message: Text,
) : DialogState()

@Parcelize
data class DeleteConfirmationPrompt(
val message: Text,
val itemId: String,
) : DialogState()
}
}

Expand Down Expand Up @@ -525,21 +597,20 @@ sealed class ItemListingAction {
* Indicates a result for creating and item has been received.
*/
data class CreateItemResultReceive(val result: CreateItemResult) : Internal()

/**
* Indicates a result for deleting an item has been received.
*/
data class DeleteItemReceive(val result: DeleteItemResult) : Internal()
}
}

/**
* The data for the verification code item to display.
*/
@Parcelize
data class VerificationCodeDisplayItem(
val id: String,
val label: String,
val issuer: String?,
val supportingLabel: String?,
val timeLeftSeconds: Int,
val periodSeconds: Int,
val alertThresholdSeconds: Int,
val authCode: String,
val startIcon: IconData = IconData.Local(R.drawable.ic_login_item),
) : Parcelable
/**
* The user clicked Delete.
*/
data class DeleteItemClick(val itemId: String) : ItemListingAction()

/**
* The user clicked confirm when prompted to delete an item.
*/
data class ConfirmDeleteClick(val itemId: String) : ItemListingAction()
}
Loading

0 comments on commit 612c8e8

Please sign in to comment.