mirror of
https://github.com/bitwarden/android.git
synced 2026-06-09 08:09:16 -05:00
BIT-484: Add deletion date and time pickers (#548)
This commit is contained in:
committed by
Álison Fernandes
parent
b8d397f71f
commit
8c2e2f8af6
@@ -27,6 +27,8 @@ import androidx.compose.ui.semantics.contentDescription
|
||||
import androidx.compose.ui.semantics.role
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
@@ -46,7 +48,7 @@ import java.time.ZonedDateTime
|
||||
fun BitwardenDateSelectButton(
|
||||
currentZonedDateTime: ZonedDateTime,
|
||||
formatPattern: String,
|
||||
onDateSelect: (millis: Long) -> Unit,
|
||||
onDateSelect: (ZonedDateTime) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowDialog: Boolean by rememberSaveable { mutableStateOf(false) }
|
||||
@@ -100,7 +102,16 @@ fun BitwardenDateSelectButton(
|
||||
confirmButton = {
|
||||
TextButton(
|
||||
onClick = {
|
||||
onDateSelect(requireNotNull(datePickerState.selectedDateMillis))
|
||||
onDateSelect(
|
||||
ZonedDateTime
|
||||
.ofInstant(
|
||||
Instant.ofEpochMilli(
|
||||
requireNotNull(datePickerState.selectedDateMillis),
|
||||
),
|
||||
ZoneOffset.UTC,
|
||||
)
|
||||
.withZoneSameLocal(currentZonedDateTime.zone),
|
||||
)
|
||||
shouldShowDialog = false
|
||||
},
|
||||
modifier = modifier,
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
package com.x8bit.bitwarden.ui.tools.feature.send
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
@@ -17,17 +22,27 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenDateSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTimeSelectButton
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import kotlin.time.Duration.Companion.hours
|
||||
import kotlin.time.Duration.Companion.minutes
|
||||
|
||||
/**
|
||||
* Displays UX for choosing deletion date of a send.
|
||||
*
|
||||
* TODO: Implement custom date choosing and send choices to the VM: BIT-1090.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun SendDeletionDateChooser(
|
||||
currentZonedDateTime: ZonedDateTime,
|
||||
dateFormatPattern: String,
|
||||
timeFormatPattern: String,
|
||||
onDateSelect: (ZonedDateTime) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val customOption = stringResource(id = R.string.custom)
|
||||
val options = listOf(
|
||||
stringResource(id = R.string.one_hour),
|
||||
stringResource(id = R.string.one_day),
|
||||
@@ -35,7 +50,7 @@ fun SendDeletionDateChooser(
|
||||
stringResource(id = R.string.three_days),
|
||||
stringResource(id = R.string.seven_days),
|
||||
stringResource(id = R.string.thirty_days),
|
||||
stringResource(id = R.string.custom),
|
||||
customOption,
|
||||
)
|
||||
val defaultOption = stringResource(id = R.string.seven_days)
|
||||
var selectedOption: String by rememberSaveable { mutableStateOf(defaultOption) }
|
||||
@@ -48,6 +63,52 @@ fun SendDeletionDateChooser(
|
||||
selectedOption = selectedOption,
|
||||
onOptionSelected = { selectedOption = it },
|
||||
)
|
||||
|
||||
AnimatedVisibility(visible = selectedOption == customOption) {
|
||||
// This tracks the date component (year, month, and day) and ignores lower level
|
||||
// components.
|
||||
var date: ZonedDateTime by remember {
|
||||
mutableStateOf(currentZonedDateTime)
|
||||
}
|
||||
// This tracks just the time component (hours and minutes) and ignores the higher level
|
||||
// components. 0 representing midnight and counting up from there.
|
||||
var timeMillis: Long by remember {
|
||||
mutableStateOf(
|
||||
currentZonedDateTime.hour.hours.inWholeMilliseconds +
|
||||
currentZonedDateTime.minute.minutes.inWholeMilliseconds,
|
||||
)
|
||||
}
|
||||
val derivedDateTimeMillis: ZonedDateTime by remember {
|
||||
derivedStateOf { date.plus(timeMillis, ChronoUnit.MILLIS) }
|
||||
}
|
||||
|
||||
Column {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Row {
|
||||
BitwardenDateSelectButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
formatPattern = dateFormatPattern,
|
||||
currentZonedDateTime = currentZonedDateTime,
|
||||
onDateSelect = {
|
||||
date = it
|
||||
onDateSelect(derivedDateTimeMillis)
|
||||
},
|
||||
)
|
||||
Spacer(modifier = Modifier.width(16.dp))
|
||||
BitwardenTimeSelectButton(
|
||||
modifier = Modifier.weight(1f),
|
||||
formatPattern = timeFormatPattern,
|
||||
currentZonedDateTime = currentZonedDateTime,
|
||||
onTimeSelect = { hour, minute ->
|
||||
timeMillis = hour.hours.inWholeMilliseconds +
|
||||
minute.minutes.inWholeMilliseconds
|
||||
onDateSelect(derivedDateTimeMillis)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(4.dp))
|
||||
Text(
|
||||
text = stringResource(id = R.string.deletion_date_info),
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.x8bit.bitwarden.ui.platform.components.SegmentedButtonState
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SendDeletionDateChooser
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.SendExpirationDateChooser
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* Content view for the [AddSendScreen].
|
||||
@@ -154,6 +155,7 @@ fun AddSendContent(
|
||||
onNoteChange = addSendHandlers.onNoteChange,
|
||||
onHideEmailChecked = addSendHandlers.onHideEmailToggle,
|
||||
onDeactivateSendChecked = addSendHandlers.onDeactivateSendToggle,
|
||||
onDeletionDateChange = addSendHandlers.onDeletionDateChange,
|
||||
)
|
||||
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
@@ -180,6 +182,7 @@ private fun AddSendOptions(
|
||||
onNoteChange: (String) -> Unit,
|
||||
onHideEmailChecked: (Boolean) -> Unit,
|
||||
onDeactivateSendChecked: (Boolean) -> Unit,
|
||||
onDeletionDateChange: (ZonedDateTime) -> Unit,
|
||||
) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
Row(
|
||||
@@ -223,6 +226,10 @@ private fun AddSendOptions(
|
||||
Column {
|
||||
SendDeletionDateChooser(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
dateFormatPattern = state.common.dateFormatPattern,
|
||||
timeFormatPattern = state.common.timeFormatPattern,
|
||||
currentZonedDateTime = state.common.deletionDate,
|
||||
onDateSelect = onDeletionDateChange,
|
||||
)
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
SendExpirationDateChooser(
|
||||
|
||||
@@ -23,16 +23,12 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.time.Clock
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.temporal.ChronoUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
||||
/**
|
||||
* The default amount of time in the future the deletion date should be set to.
|
||||
*/
|
||||
private const val DELETION_DATE_OFFSET_SECONDS = 604_800L
|
||||
|
||||
/**
|
||||
* View model for the new send screen.
|
||||
*/
|
||||
@@ -54,7 +50,11 @@ class AddSendViewModel @Inject constructor(
|
||||
noteInput = "",
|
||||
isHideEmailChecked = false,
|
||||
isDeactivateChecked = false,
|
||||
deletionDate = clock.instant().plusSeconds(DELETION_DATE_OFFSET_SECONDS),
|
||||
deletionDate = ZonedDateTime
|
||||
.now(clock)
|
||||
// We want the default time to be midnight, so we remove all values beyond days
|
||||
.truncatedTo(ChronoUnit.DAYS)
|
||||
.plusWeeks(1),
|
||||
expirationDate = null,
|
||||
),
|
||||
selectedType = AddSendState.ViewState.Content.SendType.Text(
|
||||
@@ -82,6 +82,7 @@ class AddSendViewModel @Inject constructor(
|
||||
|
||||
override fun handleAction(action: AddSendAction): Unit = when (action) {
|
||||
is AddSendAction.CloseClick -> handleCloseClick()
|
||||
is AddSendAction.DeletionDateChange -> handleDeletionDateChange(action)
|
||||
AddSendAction.DismissDialogClick -> handleDismissDialogClick()
|
||||
is AddSendAction.SaveClick -> handleSaveClick()
|
||||
is AddSendAction.FileTypeClick -> handleFileTypeClick()
|
||||
@@ -162,6 +163,12 @@ class AddSendViewModel @Inject constructor(
|
||||
|
||||
private fun handleCloseClick() = sendEvent(AddSendEvent.NavigateBack)
|
||||
|
||||
private fun handleDeletionDateChange(action: AddSendAction.DeletionDateChange) {
|
||||
updateCommonContent {
|
||||
it.copy(deletionDate = action.deletionDate)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSaveClick() {
|
||||
onContent { content ->
|
||||
if (content.common.name.isBlank()) {
|
||||
@@ -185,7 +192,7 @@ class AddSendViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
viewModelScope.launch {
|
||||
val result = vaultRepo.createSend(content.toSendView())
|
||||
val result = vaultRepo.createSend(content.toSendView(clock))
|
||||
sendAction(AddSendAction.Internal.CreateSendResultReceive(result))
|
||||
}
|
||||
}
|
||||
@@ -347,9 +354,13 @@ data class AddSendState(
|
||||
val noteInput: String,
|
||||
val isHideEmailChecked: Boolean,
|
||||
val isDeactivateChecked: Boolean,
|
||||
val deletionDate: Instant,
|
||||
val expirationDate: Instant?,
|
||||
) : Parcelable
|
||||
val deletionDate: ZonedDateTime,
|
||||
val expirationDate: ZonedDateTime?,
|
||||
) : Parcelable {
|
||||
val dateFormatPattern: String get() = "M/d/yyyy"
|
||||
|
||||
val timeFormatPattern: String get() = "hh:mm a"
|
||||
}
|
||||
|
||||
/**
|
||||
* Models what type the user is trying to send.
|
||||
@@ -492,6 +503,11 @@ sealed class AddSendAction {
|
||||
*/
|
||||
data class DeactivateThisSendToggle(val isChecked: Boolean) : AddSendAction()
|
||||
|
||||
/**
|
||||
* User toggled the "deactivate this send" toggle.
|
||||
*/
|
||||
data class DeletionDateChange(val deletionDate: ZonedDateTime) : AddSendAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [AddSendViewModel] itself might send.
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendAction
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.AddSendViewModel
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
/**
|
||||
* A collection of handler functions for managing actions within the context of adding
|
||||
@@ -19,6 +20,7 @@ data class AddSendHandlers(
|
||||
val onNoteChange: (String) -> Unit,
|
||||
val onHideEmailToggle: (Boolean) -> Unit,
|
||||
val onDeactivateSendToggle: (Boolean) -> Unit,
|
||||
val onDeletionDateChange: (ZonedDateTime) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
@@ -48,6 +50,9 @@ data class AddSendHandlers(
|
||||
onDeactivateSendToggle = {
|
||||
viewModel.trySendAction(AddSendAction.DeactivateThisSendToggle(it))
|
||||
},
|
||||
onDeletionDateChange = {
|
||||
viewModel.trySendAction(AddSendAction.DeletionDateChange(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,12 +5,14 @@ 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
|
||||
import java.time.Clock
|
||||
|
||||
/**
|
||||
* Transforms [AddSendState] into [SendView].
|
||||
*/
|
||||
fun AddSendState.ViewState.Content.toSendView(): SendView =
|
||||
fun AddSendState.ViewState.Content.toSendView(
|
||||
clock: Clock,
|
||||
): SendView =
|
||||
SendView(
|
||||
id = null,
|
||||
accessId = null,
|
||||
@@ -26,9 +28,9 @@ fun AddSendState.ViewState.Content.toSendView(): SendView =
|
||||
accessCount = 0U,
|
||||
disabled = common.isDeactivateChecked,
|
||||
hideEmail = common.isHideEmailChecked,
|
||||
revisionDate = Instant.now(),
|
||||
deletionDate = common.deletionDate,
|
||||
expirationDate = common.expirationDate,
|
||||
revisionDate = clock.instant(),
|
||||
deletionDate = common.deletionDate.toInstant(),
|
||||
expirationDate = common.expirationDate?.toInstant(),
|
||||
)
|
||||
|
||||
private fun AddSendState.ViewState.Content.SendType.toSendType(): SendType =
|
||||
|
||||
Reference in New Issue
Block a user