BIT-462: Add UI for custom vault timeout (#576)

This commit is contained in:
Brian Yencho
2024-01-11 14:42:12 -06:00
committed by Álison Fernandes
parent ff9dd81c55
commit 7e0a14d3a0
5 changed files with 252 additions and 18 deletions

View File

@@ -2,6 +2,7 @@ package com.x8bit.bitwarden.ui.platform.components.dialog
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -45,6 +46,7 @@ import com.x8bit.bitwarden.R
* with AM/PM.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Suppress("LongMethod")
@Composable
fun BitwardenTimePickerDialog(
initialHour: Int,
@@ -99,10 +101,17 @@ fun BitwardenTimePickerDialog(
}
},
) {
val modifier = Modifier.weight(1f)
if (showTimeInput) {
TimeInput(state = timePickerState)
TimeInput(
state = timePickerState,
modifier = modifier,
)
} else {
TimePicker(state = timePickerState)
TimePicker(
state = timePickerState,
modifier = modifier,
)
}
}
}
@@ -113,7 +122,7 @@ private fun TimePickerDialog(
inputToggleButton: @Composable () -> Unit,
dismissButton: @Composable () -> Unit,
confirmButton: @Composable () -> Unit,
content: @Composable () -> Unit,
content: @Composable ColumnScope.() -> Unit,
) {
Dialog(
onDismissRequest = onDismissRequest,

View File

@@ -20,6 +20,7 @@ import androidx.compose.runtime.collectAsState
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
import androidx.compose.ui.input.nestedscroll.nestedScroll
@@ -46,9 +47,14 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenTextRow
import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTimePickerDialog
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialColors
import com.x8bit.bitwarden.ui.platform.theme.LocalNonMaterialTypography
import com.x8bit.bitwarden.ui.platform.util.displayLabel
import com.x8bit.bitwarden.ui.platform.util.toFormattedPattern
import java.time.LocalTime
private const val MINUTES_PER_HOUR = 60
/**
* Displays the account security screen.
@@ -195,12 +201,25 @@ fun AccountSecurityScreen(
.padding(horizontal = 16.dp),
)
SessionTimeoutRow(
selectedVaultTimeoutType = state.vaultTimeoutType,
selectedVaultTimeoutType = state.vaultTimeout.type,
onVaultTimeoutTypeSelect = remember(viewModel) {
{ viewModel.trySendAction(AccountSecurityAction.VaultTimeoutTypeSelect(it)) }
},
modifier = Modifier.fillMaxWidth(),
)
(state.vaultTimeout as? VaultTimeout.Custom)?.let { customTimeout ->
SessionCustomTimeoutRow(
customVaultTimeout = customTimeout,
onCustomVaultTimeoutSelect = remember(viewModel) {
{
viewModel.trySendAction(
AccountSecurityAction.CustomVaultTimeoutSelect(it),
)
}
},
modifier = Modifier.fillMaxWidth(),
)
}
SessionTimeoutActionRow(
selectedVaultTimeoutAction = state.vaultTimeoutAction,
onVaultTimeoutActionSelect = remember(viewModel) {
@@ -334,6 +353,50 @@ private fun SessionTimeoutRow(
}
}
@Suppress("LongMethod")
@Composable
private fun SessionCustomTimeoutRow(
customVaultTimeout: VaultTimeout.Custom,
onCustomVaultTimeoutSelect: (VaultTimeout.Custom) -> Unit,
modifier: Modifier = Modifier,
) {
var shouldShowTimePickerDialog by rememberSaveable { mutableStateOf(false) }
val vaultTimeoutInMinutes = customVaultTimeout.vaultTimeoutInMinutes
BitwardenTextRow(
text = stringResource(id = R.string.custom),
onClick = { shouldShowTimePickerDialog = true },
modifier = modifier,
) {
val formattedTime = LocalTime
.ofSecondOfDay(
vaultTimeoutInMinutes * MINUTES_PER_HOUR.toLong(),
)
.toFormattedPattern("HH:mm")
Text(
text = formattedTime,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
if (shouldShowTimePickerDialog) {
BitwardenTimePickerDialog(
initialHour = vaultTimeoutInMinutes / MINUTES_PER_HOUR,
initialMinute = vaultTimeoutInMinutes.mod(MINUTES_PER_HOUR),
onTimeSelect = { hour, minute ->
shouldShowTimePickerDialog = false
onCustomVaultTimeoutSelect(
VaultTimeout.Custom(
vaultTimeoutInMinutes = hour * MINUTES_PER_HOUR + minute,
),
)
},
onDismissRequest = { shouldShowTimePickerDialog = false },
is24Hour = true,
)
}
}
@Suppress("LongMethod")
@Composable
private fun SessionTimeoutActionRow(

View File

@@ -39,7 +39,7 @@ class AccountSecurityViewModel @Inject constructor(
isApproveLoginRequestsEnabled = false,
isUnlockWithBiometricsEnabled = false,
isUnlockWithPinEnabled = false,
vaultTimeoutType = settingsRepository.vaultTimeout.type,
vaultTimeout = settingsRepository.vaultTimeout,
vaultTimeoutAction = settingsRepository.vaultTimeoutAction,
),
) {
@@ -63,6 +63,7 @@ class AccountSecurityViewModel @Inject constructor(
AccountSecurityAction.LogoutClick -> handleLogoutClick()
AccountSecurityAction.PendingLoginRequestsClick -> handlePendingLoginRequestsClick()
is AccountSecurityAction.VaultTimeoutTypeSelect -> handleVaultTimeoutTypeSelect(action)
is AccountSecurityAction.CustomVaultTimeoutSelect -> handleCustomVaultTimeoutSelect(action)
is AccountSecurityAction.VaultTimeoutActionSelect -> {
handleVaultTimeoutActionSelect(action)
}
@@ -123,13 +124,8 @@ class AccountSecurityViewModel @Inject constructor(
}
private fun handleVaultTimeoutTypeSelect(action: AccountSecurityAction.VaultTimeoutTypeSelect) {
val vaultTimeoutType = action.vaultTimeoutType
mutableStateFlow.update {
it.copy(
vaultTimeoutType = action.vaultTimeoutType,
)
}
val vaultTimeout = when (vaultTimeoutType) {
val previousTimeout = state.vaultTimeout
val vaultTimeout = when (action.vaultTimeoutType) {
VaultTimeout.Type.IMMEDIATELY -> VaultTimeout.Immediately
VaultTimeout.Type.ONE_MINUTE -> VaultTimeout.OneMinute
VaultTimeout.Type.FIVE_MINUTES -> VaultTimeout.FiveMinutes
@@ -139,7 +135,28 @@ class AccountSecurityViewModel @Inject constructor(
VaultTimeout.Type.FOUR_HOURS -> VaultTimeout.FourHours
VaultTimeout.Type.ON_APP_RESTART -> VaultTimeout.OnAppRestart
VaultTimeout.Type.NEVER -> VaultTimeout.Never
VaultTimeout.Type.CUSTOM -> VaultTimeout.Custom(vaultTimeoutInMinutes = 0)
VaultTimeout.Type.CUSTOM -> {
if (previousTimeout is VaultTimeout.Custom) {
previousTimeout
} else {
VaultTimeout.Custom(vaultTimeoutInMinutes = 0)
}
}
}
handleVaultTimeoutSelect(vaultTimeout = vaultTimeout)
}
private fun handleCustomVaultTimeoutSelect(
action: AccountSecurityAction.CustomVaultTimeoutSelect,
) {
handleVaultTimeoutSelect(vaultTimeout = action.customVaultTimeout)
}
private fun handleVaultTimeoutSelect(vaultTimeout: VaultTimeout) {
mutableStateFlow.update {
it.copy(
vaultTimeout = vaultTimeout,
)
}
settingsRepository.vaultTimeout = vaultTimeout
@@ -192,7 +209,7 @@ data class AccountSecurityState(
val isApproveLoginRequestsEnabled: Boolean,
val isUnlockWithBiometricsEnabled: Boolean,
val isUnlockWithPinEnabled: Boolean,
val vaultTimeoutType: VaultTimeout.Type,
val vaultTimeout: VaultTimeout,
val vaultTimeoutAction: VaultTimeoutAction,
) : Parcelable
@@ -317,6 +334,13 @@ sealed class AccountSecurityAction {
val vaultTimeoutType: VaultTimeout.Type,
) : AccountSecurityAction()
/**
* User selected an updated [VaultTimeout.Custom].
*/
data class CustomVaultTimeoutSelect(
val customVaultTimeout: VaultTimeout.Custom,
) : AccountSecurityAction()
/**
* User selected a [VaultTimeoutAction].
*/