Add UI for saving a send (#526)

This commit is contained in:
David Perez
2024-01-07 20:30:22 -06:00
committed by Álison Fernandes
parent 978e72899b
commit 1cfd85d9f8
6 changed files with 457 additions and 4 deletions

View File

@@ -19,11 +19,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.tools.feature.send.addsend.handlers.AddSendHandlers
/**
@@ -49,6 +53,13 @@ fun AddSendScreen(
}
}
AddSendDialogs(
dialogState = state.dialogState,
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(AddSendAction.DismissDialogClick) }
},
)
BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
@@ -96,3 +107,25 @@ fun AddSendScreen(
}
}
}
@Composable
private fun AddSendDialogs(
dialogState: AddSendState.DialogState?,
onDismissRequest: () -> Unit,
) {
when (dialogState) {
is AddSendState.DialogState.Error -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = dialogState.title,
message = dialogState.message,
),
onDismissRequest = onDismissRequest,
)
is AddSendState.DialogState.Loading -> BitwardenLoadingDialog(
visibilityState = LoadingDialogState.Shown(dialogState.message),
)
null -> Unit
}
}

View File

@@ -3,12 +3,18 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import android.os.Parcelable
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.data.vault.repository.VaultRepository
import com.x8bit.bitwarden.data.vault.repository.model.CreateSendResult
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
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.util.toSendView
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import kotlinx.parcelize.Parcelize
import javax.inject.Inject
@@ -21,6 +27,7 @@ private const val KEY_STATE = "state"
@HiltViewModel
class AddSendViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val vaultRepo: VaultRepository,
) : BaseViewModel<AddSendState, AddSendEvent, AddSendAction>(
initialState = savedStateHandle[KEY_STATE] ?: AddSendState(
viewState = AddSendState.ViewState.Content(
@@ -37,6 +44,7 @@ class AddSendViewModel @Inject constructor(
isHideByDefaultChecked = false,
),
),
dialogState = null,
),
) {
@@ -48,6 +56,7 @@ class AddSendViewModel @Inject constructor(
override fun handleAction(action: AddSendAction): Unit = when (action) {
is AddSendAction.CloseClick -> handleCloseClick()
AddSendAction.DismissDialogClick -> handleDismissDialogClick()
is AddSendAction.SaveClick -> handleSaveClick()
is AddSendAction.FileTypeClick -> handleFileTypeClick()
is AddSendAction.TextTypeClick -> handleTextTypeClick()
@@ -60,6 +69,33 @@ class AddSendViewModel @Inject constructor(
is AddSendAction.HideByDefaultToggle -> handleHideByDefaultToggle(action)
is AddSendAction.DeactivateThisSendToggle -> handleDeactivateThisSendToggle(action)
is AddSendAction.HideMyEmailToggle -> handleHideMyEmailToggle(action)
is AddSendAction.Internal -> handleInternalAction(action)
}
private fun handleInternalAction(action: AddSendAction.Internal): Unit = when (action) {
is AddSendAction.Internal.CreateSendResultReceive -> handleCreateSendResultReceive(action)
}
private fun handleCreateSendResultReceive(
action: AddSendAction.Internal.CreateSendResultReceive,
) {
when (action.result) {
CreateSendResult.Error -> {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.generic_error_message.asText(),
),
)
}
}
CreateSendResult.Success -> {
mutableStateFlow.update { it.copy(dialogState = null) }
sendEvent(AddSendEvent.NavigateBack)
}
}
}
private fun handlePasswordChange(action: AddSendAction.PasswordChange) {
@@ -88,7 +124,38 @@ class AddSendViewModel @Inject constructor(
private fun handleCloseClick() = sendEvent(AddSendEvent.NavigateBack)
private fun handleSaveClick() = sendEvent(AddSendEvent.ShowToast("Save Not Implemented"))
private fun handleSaveClick() {
onContent { content ->
if (content.common.name.isBlank()) {
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Error(
title = R.string.an_error_has_occurred.asText(),
message = R.string.validation_field_required.asText(
R.string.name.asText(),
),
),
)
}
return@onContent
}
mutableStateFlow.update {
it.copy(
dialogState = AddSendState.DialogState.Loading(
message = R.string.saving.asText(),
),
)
}
viewModelScope.launch {
val result = vaultRepo.createSend(content.toSendView())
sendAction(AddSendAction.Internal.CreateSendResultReceive(result))
}
}
}
private fun handleDismissDialogClick() {
mutableStateFlow.update { it.copy(dialogState = null) }
}
private fun handleNameChange(action: AddSendAction.NameChange) {
updateCommonContent {
@@ -188,6 +255,7 @@ class AddSendViewModel @Inject constructor(
*/
@Parcelize
data class AddSendState(
val dialogState: DialogState?,
val viewState: ViewState,
) : Parcelable {
@@ -251,6 +319,29 @@ data class AddSendState(
}
}
}
/**
* 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()
}
}
/**
@@ -278,6 +369,11 @@ sealed class AddSendAction {
*/
data object CloseClick : AddSendAction()
/**
* User clicked to dismiss the current dialog.
*/
data object DismissDialogClick : AddSendAction()
/**
* User clicked the save button.
*/
@@ -337,4 +433,14 @@ sealed class AddSendAction {
* User toggled the "deactivate this send" toggle.
*/
data class DeactivateThisSendToggle(val isChecked: Boolean) : AddSendAction()
/**
* Models actions that the [AddSendViewModel] itself might send.
*/
sealed class Internal : AddSendAction() {
/**
* Indicates a result for creating a send has been received.
*/
data class CreateSendResultReceive(val result: CreateSendResult) : Internal()
}
}

View File

@@ -0,0 +1,57 @@
package com.x8bit.bitwarden.ui.tools.feature.send.addsend.util
import com.bitwarden.core.SendFileView
import com.bitwarden.core.SendTextView
import com.bitwarden.core.SendType
import com.bitwarden.core.SendView
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendState
import java.time.Instant
/**
* Transforms [AddSendState] into [SendView].
*/
// TODO: The 'key' needs to be updated in order to get the save operation to work (BIT-480)
fun AddSendState.ViewState.Content.toSendView(): SendView =
SendView(
id = null,
accessId = null,
name = common.name,
notes = common.noteInput,
key = "",
password = common.passwordInput.takeUnless { it.isBlank() },
type = selectedType.toSendType(),
file = toSendFileView(),
text = toSendTextView(),
maxAccessCount = common.maxAccessCount?.toUInt(),
accessCount = 0U,
disabled = common.isDeactivateChecked,
hideEmail = common.isHideEmailChecked,
revisionDate = Instant.now(),
deletionDate = Instant.now(),
expirationDate = null,
)
private fun AddSendState.ViewState.Content.SendType.toSendType(): SendType =
when (this) {
AddSendState.ViewState.Content.SendType.File -> SendType.FILE
is AddSendState.ViewState.Content.SendType.Text -> SendType.TEXT
}
private fun AddSendState.ViewState.Content.toSendFileView(): SendFileView? =
(this.selectedType as? AddSendState.ViewState.Content.SendType.File)?.let {
// TODO: Add support for these properties in order to save a file (BIT-480)
SendFileView(
id = "",
fileName = "",
size = "",
sizeName = "",
)
}
private fun AddSendState.ViewState.Content.toSendTextView(): SendTextView? =
(this.selectedType as? AddSendState.ViewState.Content.SendType.Text)?.let {
SendTextView(
text = it.input,
hidden = it.isHideByDefaultChecked,
)
}