mirror of
https://github.com/bitwarden/android.git
synced 2026-06-06 06:17:21 -05:00
Add support for deleting an attachment (#768)
This commit is contained in:
committed by
Álison Fernandes
parent
dc3081c5d6
commit
04d60a50ff
@@ -18,11 +18,15 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
|
||||
import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState
|
||||
import com.x8bit.bitwarden.ui.platform.components.NavigationIcon
|
||||
import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.LocalIntentManager
|
||||
@@ -65,6 +69,11 @@ fun AttachmentsScreen(
|
||||
}
|
||||
}
|
||||
|
||||
AttachmentsDialogs(
|
||||
dialogState = state.dialogState,
|
||||
onDismissRequest = attachmentsHandlers.onDismissRequest,
|
||||
)
|
||||
|
||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||
BitwardenScaffold(
|
||||
modifier = Modifier
|
||||
@@ -109,3 +118,25 @@ fun AttachmentsScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AttachmentsDialogs(
|
||||
dialogState: AttachmentsState.DialogState?,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
when (dialogState) {
|
||||
is AttachmentsState.DialogState.Error -> BitwardenBasicDialog(
|
||||
visibilityState = BasicDialogState.Shown(
|
||||
title = dialogState.title,
|
||||
message = dialogState.message,
|
||||
),
|
||||
onDismissRequest = onDismissRequest,
|
||||
)
|
||||
|
||||
is AttachmentsState.DialogState.Loading -> BitwardenLoadingDialog(
|
||||
visibilityState = LoadingDialogState.Shown(dialogState.message),
|
||||
)
|
||||
|
||||
null -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import com.bitwarden.core.CipherView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.data.platform.repository.model.DataState
|
||||
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
|
||||
import com.x8bit.bitwarden.data.vault.repository.model.DeleteAttachmentResult
|
||||
import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
@@ -18,6 +19,8 @@ import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import javax.inject.Inject
|
||||
|
||||
@@ -26,6 +29,7 @@ private const val KEY_STATE = "state"
|
||||
/**
|
||||
* ViewModel responsible for handling user interactions in the attachments screen.
|
||||
*/
|
||||
@Suppress("TooManyFunctions")
|
||||
@HiltViewModel
|
||||
class AttachmentsViewModel @Inject constructor(
|
||||
private val vaultRepo: VaultRepository,
|
||||
@@ -36,6 +40,7 @@ class AttachmentsViewModel @Inject constructor(
|
||||
?: AttachmentsState(
|
||||
cipherId = AttachmentsArgs(savedStateHandle).cipherId,
|
||||
viewState = AttachmentsState.ViewState.Loading,
|
||||
dialogState = null,
|
||||
),
|
||||
) {
|
||||
init {
|
||||
@@ -50,6 +55,7 @@ class AttachmentsViewModel @Inject constructor(
|
||||
when (action) {
|
||||
AttachmentsAction.BackClick -> handleBackClick()
|
||||
AttachmentsAction.SaveClick -> handleSaveClick()
|
||||
AttachmentsAction.DismissDialogClick -> handleDismissDialogClick()
|
||||
AttachmentsAction.ChooseFileClick -> handleChooseFileClick()
|
||||
is AttachmentsAction.FileChoose -> handleFileChoose(action)
|
||||
is AttachmentsAction.DeleteClick -> handleDeleteClick(action)
|
||||
@@ -66,6 +72,10 @@ class AttachmentsViewModel @Inject constructor(
|
||||
// TODO: Handle saving the attachments (BIT-522)
|
||||
}
|
||||
|
||||
private fun handleDismissDialogClick() {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
}
|
||||
|
||||
private fun handleChooseFileClick() {
|
||||
sendEvent(AttachmentsEvent.ShowChooserSheet)
|
||||
}
|
||||
@@ -76,13 +86,30 @@ class AttachmentsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun handleDeleteClick(action: AttachmentsAction.DeleteClick) {
|
||||
sendEvent(AttachmentsEvent.ShowToast("Not Yet Implemented".asText()))
|
||||
// TODO: Handle choosing a file the attachments (BIT-522)
|
||||
onContent { content ->
|
||||
val cipherView = content.originalCipher ?: return@onContent
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = AttachmentsState.DialogState.Loading(
|
||||
message = R.string.deleting.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = vaultRepo.deleteCipherAttachment(
|
||||
cipherId = state.cipherId,
|
||||
attachmentId = action.attachmentId,
|
||||
cipherView = cipherView,
|
||||
)
|
||||
sendAction(AttachmentsAction.Internal.DeleteResultReceive(result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleInternalAction(action: AttachmentsAction.Internal) {
|
||||
when (action) {
|
||||
is AttachmentsAction.Internal.CipherReceive -> handleCipherReceive(action)
|
||||
is AttachmentsAction.Internal.DeleteResultReceive -> handleDeleteResultReceive(action)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,6 +169,32 @@ class AttachmentsViewModel @Inject constructor(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleDeleteResultReceive(action: AttachmentsAction.Internal.DeleteResultReceive) {
|
||||
when (action.result) {
|
||||
DeleteAttachmentResult.Error -> {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
dialogState = AttachmentsState.DialogState.Error(
|
||||
title = R.string.an_error_has_occurred.asText(),
|
||||
message = R.string.generic_error_message.asText(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
DeleteAttachmentResult.Success -> {
|
||||
mutableStateFlow.update { it.copy(dialogState = null) }
|
||||
sendEvent(AttachmentsEvent.ShowToast(R.string.attachment_deleted.asText()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun onContent(
|
||||
crossinline block: (AttachmentsState.ViewState.Content) -> Unit,
|
||||
) {
|
||||
(state.viewState as? AttachmentsState.ViewState.Content)?.let(block)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -151,6 +204,7 @@ class AttachmentsViewModel @Inject constructor(
|
||||
data class AttachmentsState(
|
||||
val cipherId: String,
|
||||
val viewState: ViewState,
|
||||
val dialogState: DialogState?,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Represents the specific view states for the [AttachmentsScreen].
|
||||
@@ -174,6 +228,8 @@ data class AttachmentsState(
|
||||
*/
|
||||
@Parcelize
|
||||
data class Content(
|
||||
@IgnoredOnParcel
|
||||
val originalCipher: CipherView? = null,
|
||||
val attachments: List<AttachmentItem>,
|
||||
) : ViewState()
|
||||
}
|
||||
@@ -187,6 +243,28 @@ data class AttachmentsState(
|
||||
val title: String,
|
||||
val displaySize: String,
|
||||
) : Parcelable
|
||||
|
||||
/**
|
||||
* Represents the current state of any dialogs on the screen.
|
||||
*/
|
||||
sealed class DialogState : Parcelable {
|
||||
/**
|
||||
* Represents a dismissible dialog with the given error [message].
|
||||
*/
|
||||
@Parcelize
|
||||
data class Error(
|
||||
val title: Text?,
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
|
||||
/**
|
||||
* Represents a loading dialog with the given [message].
|
||||
*/
|
||||
@Parcelize
|
||||
data class Loading(
|
||||
val message: Text,
|
||||
) : DialogState()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -225,6 +303,11 @@ sealed class AttachmentsAction {
|
||||
*/
|
||||
data object SaveClick : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* User clicked to dismiss a dialog.
|
||||
*/
|
||||
data object DismissDialogClick : AttachmentsAction()
|
||||
|
||||
/**
|
||||
* User clicked to select a new attachment file.
|
||||
*/
|
||||
@@ -254,5 +337,12 @@ sealed class AttachmentsAction {
|
||||
data class CipherReceive(
|
||||
val cipherDataState: DataState<CipherView?>,
|
||||
) : Internal()
|
||||
|
||||
/**
|
||||
* The result of deleting the attachment.
|
||||
*/
|
||||
data class DeleteResultReceive(
|
||||
val result: DeleteAttachmentResult,
|
||||
) : Internal()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ data class AttachmentsHandlers(
|
||||
val onChooseFileClick: () -> Unit,
|
||||
val onFileChoose: (IntentManager.FileData) -> Unit,
|
||||
val onDeleteClick: (attachmentId: String) -> Unit,
|
||||
val onDismissRequest: () -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
@@ -30,6 +31,9 @@ data class AttachmentsHandlers(
|
||||
onDeleteClick = {
|
||||
viewModel.trySendAction(AttachmentsAction.DeleteClick(it))
|
||||
},
|
||||
onDismissRequest = {
|
||||
viewModel.trySendAction(AttachmentsAction.DismissDialogClick)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import com.x8bit.bitwarden.ui.vault.feature.attachments.AttachmentsState
|
||||
*/
|
||||
fun CipherView.toViewState(): AttachmentsState.ViewState.Content =
|
||||
AttachmentsState.ViewState.Content(
|
||||
originalCipher = this,
|
||||
attachments = this
|
||||
.attachments
|
||||
.orEmpty()
|
||||
|
||||
Reference in New Issue
Block a user