mirror of
https://github.com/bitwarden/android.git
synced 2026-04-30 12:59:02 -05:00
PM-27494: Update custom vault timeout UI (#6085)
This commit is contained in:
@@ -32,7 +32,6 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.net.toUri
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.bitwarden.core.data.util.toFormattedPattern
|
||||
import com.bitwarden.ui.platform.base.util.EventsEffect
|
||||
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.bitwarden.ui.platform.components.account.dialog.BitwardenLogoutConfirmationDialog
|
||||
@@ -43,9 +42,9 @@ import com.bitwarden.ui.platform.components.card.BitwardenActionCard
|
||||
import com.bitwarden.ui.platform.components.card.actionCardExitAnimation
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenTimePickerDialog
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
|
||||
import com.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
||||
import com.bitwarden.ui.platform.components.dropdown.BitwardenTimePickerButton
|
||||
import com.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
|
||||
@@ -73,11 +72,8 @@ import com.x8bit.bitwarden.ui.platform.util.displayLabel
|
||||
import com.x8bit.bitwarden.ui.platform.util.minutes
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import java.time.LocalTime
|
||||
import javax.crypto.Cipher
|
||||
|
||||
private const val MINUTES_PER_HOUR = 60
|
||||
|
||||
/**
|
||||
* Displays the account security screen.
|
||||
*/
|
||||
@@ -532,48 +528,22 @@ private fun SessionCustomTimeoutRow(
|
||||
onCustomVaultTimeoutSelect: (VaultTimeout.Custom) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowTimePickerDialog by rememberSaveable { mutableStateOf(false) }
|
||||
var shouldShowViolatesPoliciesDialog by remember { mutableStateOf(false) }
|
||||
val vaultTimeoutInMinutes = customVaultTimeout.vaultTimeoutInMinutes
|
||||
BitwardenTextRow(
|
||||
text = stringResource(id = BitwardenString.custom),
|
||||
onClick = { shouldShowTimePickerDialog = true },
|
||||
cardStyle = CardStyle.Middle(),
|
||||
modifier = modifier,
|
||||
) {
|
||||
Text(
|
||||
text = LocalTime
|
||||
.ofSecondOfDay(vaultTimeoutInMinutes * MINUTES_PER_HOUR.toLong())
|
||||
.toFormattedPattern(pattern = "HH:mm"),
|
||||
style = BitwardenTheme.typography.labelSmall,
|
||||
color = BitwardenTheme.colorScheme.text.primary,
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldShowTimePickerDialog) {
|
||||
BitwardenTimePickerDialog(
|
||||
initialHour = vaultTimeoutInMinutes / MINUTES_PER_HOUR,
|
||||
initialMinute = vaultTimeoutInMinutes.mod(MINUTES_PER_HOUR),
|
||||
onTimeSelect = { hour, minute ->
|
||||
shouldShowTimePickerDialog = false
|
||||
|
||||
val totalMinutes = (hour * MINUTES_PER_HOUR) + minute
|
||||
if (vaultTimeoutPolicy?.minutes != null &&
|
||||
totalMinutes > vaultTimeoutPolicy.minutes
|
||||
) {
|
||||
BitwardenTimePickerButton(
|
||||
label = stringResource(id = BitwardenString.custom_timeout),
|
||||
totalMinutes = customVaultTimeout.vaultTimeoutInMinutes,
|
||||
onTimeSelect = { minutes ->
|
||||
if (vaultTimeoutPolicy?.minutes != null && minutes > vaultTimeoutPolicy.minutes) {
|
||||
shouldShowViolatesPoliciesDialog = true
|
||||
} else {
|
||||
onCustomVaultTimeoutSelect(
|
||||
VaultTimeout.Custom(
|
||||
vaultTimeoutInMinutes = totalMinutes,
|
||||
),
|
||||
)
|
||||
onCustomVaultTimeoutSelect(VaultTimeout.Custom(minutes))
|
||||
}
|
||||
},
|
||||
onDismissRequest = { shouldShowTimePickerDialog = false },
|
||||
is24Hour = true,
|
||||
supportingContent = null,
|
||||
cardStyle = CardStyle.Middle(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
if (shouldShowViolatesPoliciesDialog) {
|
||||
BitwardenBasicDialog(
|
||||
@@ -582,11 +552,7 @@ private fun SessionCustomTimeoutRow(
|
||||
onDismissRequest = {
|
||||
shouldShowViolatesPoliciesDialog = false
|
||||
vaultTimeoutPolicy?.minutes?.let {
|
||||
onCustomVaultTimeoutSelect(
|
||||
VaultTimeout.Custom(
|
||||
vaultTimeoutInMinutes = it,
|
||||
),
|
||||
)
|
||||
onCustomVaultTimeoutSelect(VaultTimeout.Custom(it))
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1047,7 +1047,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
composeTestRule
|
||||
// Check for exact text to differentiate from the Custom label on the Vault Timeout
|
||||
// item above.
|
||||
.onNode(hasTextExactly("Custom", "00:00"))
|
||||
.onNode(hasTextExactly("Custom timeout", "0 minutes"))
|
||||
.performScrollTo()
|
||||
.assertIsDisplayed()
|
||||
|
||||
@@ -1056,7 +1056,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "02:03"))
|
||||
.onNode(hasTextExactly("Custom timeout", "2 hours, 3 minutes"))
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update {
|
||||
@@ -1064,7 +1064,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "20:34"))
|
||||
.onNode(hasTextExactly("Custom timeout", "20 hours, 34 minutes"))
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@@ -1076,7 +1076,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
it.copy(vaultTimeout = VaultTimeout.Custom(vaultTimeoutInMinutes = 123))
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "02:03"))
|
||||
.onNode(hasTextExactly("Custom timeout", "2 hours, 3 minutes"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
@@ -1102,7 +1102,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
it.copy(vaultTimeout = VaultTimeout.Custom(vaultTimeoutInMinutes = 123))
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "02:03"))
|
||||
.onNode(hasTextExactly("Custom timeout", "2 hours, 3 minutes"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
@@ -1123,7 +1123,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
it.copy(vaultTimeout = VaultTimeout.Custom(vaultTimeoutInMinutes = 123))
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "02:03"))
|
||||
.onNode(hasTextExactly("Custom timeout", "2 hours, 3 minutes"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
@@ -1158,7 +1158,7 @@ class AccountSecurityScreenTest : BitwardenComposeTest() {
|
||||
)
|
||||
}
|
||||
composeTestRule
|
||||
.onNode(hasTextExactly("Custom", "02:03"))
|
||||
.onNode(hasTextExactly("Custom timeout", "2 hours, 3 minutes"))
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
|
||||
@@ -99,6 +99,7 @@ fun BitwardenTextSelectionButton(
|
||||
cardStyle: CardStyle?,
|
||||
modifier: Modifier = Modifier,
|
||||
enabled: Boolean = true,
|
||||
showChevron: Boolean = true,
|
||||
tooltip: TooltipData? = null,
|
||||
insets: PaddingValues = PaddingValues(),
|
||||
textFieldTestTag: String? = null,
|
||||
@@ -161,11 +162,15 @@ fun BitwardenTextSelectionButton(
|
||||
BitwardenRowOfActions(
|
||||
modifier = Modifier.padding(paddingValues = actionsPadding),
|
||||
actions = {
|
||||
if (showChevron) {
|
||||
Icon(
|
||||
painter = rememberVectorPainter(id = BitwardenDrawable.ic_chevron_down),
|
||||
painter = rememberVectorPainter(
|
||||
id = BitwardenDrawable.ic_chevron_down,
|
||||
),
|
||||
contentDescription = null,
|
||||
modifier = Modifier.minimumInteractiveComponentSize(),
|
||||
)
|
||||
}
|
||||
actions()
|
||||
},
|
||||
)
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
package com.bitwarden.ui.platform.components.dropdown
|
||||
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.material3.OutlinedTextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.pluralStringResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.bitwarden.ui.platform.components.button.BitwardenTextSelectionButton
|
||||
import com.bitwarden.ui.platform.components.dialog.BitwardenTimePickerDialog
|
||||
import com.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.bitwarden.ui.platform.components.model.TooltipData
|
||||
import com.bitwarden.ui.platform.resource.BitwardenPlurals
|
||||
import com.bitwarden.ui.platform.resource.BitwardenString
|
||||
|
||||
private const val MINUTES_PER_HOUR: Int = 60
|
||||
|
||||
/**
|
||||
* A button that displays a selected time duration and opens a time picker dialog when clicked.
|
||||
*
|
||||
* @param label The descriptive text label for the [OutlinedTextField].
|
||||
* @param totalMinutes The currently selected time value in minutes.
|
||||
* @param onTimeSelect A lambda that is invoked when a time is selected from the menu.
|
||||
* @param is24Hour Whether or not the time should be displayed in 24-hour format.
|
||||
* @param cardStyle Indicates the type of card style to be applied.
|
||||
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
|
||||
* @param isEnabled Whether or not the button is enabled.
|
||||
* @param supportingContent An optional supporting content that will appear below the button.
|
||||
* @param tooltip A nullable [TooltipData], representing the tooltip icon.
|
||||
* @param insets Inner padding to be applied within the card.
|
||||
* @param textFieldTestTag The optional test tag associated with the inner text field.
|
||||
* @param actionsPadding Padding to be applied to the [actions] block.
|
||||
* @param actions A lambda containing the set of actions (usually icons or similar) to display
|
||||
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
|
||||
* defining the layout of the actions.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenTimePickerButton(
|
||||
label: String,
|
||||
totalMinutes: Int,
|
||||
onTimeSelect: (minutes: Int) -> Unit,
|
||||
is24Hour: Boolean,
|
||||
cardStyle: CardStyle?,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
supportingContent: @Composable (ColumnScope.() -> Unit)?,
|
||||
tooltip: TooltipData? = null,
|
||||
insets: PaddingValues = PaddingValues(),
|
||||
textFieldTestTag: String? = null,
|
||||
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
BitwardenTimePickerButton(
|
||||
label = label,
|
||||
hours = totalMinutes / MINUTES_PER_HOUR,
|
||||
minutes = totalMinutes.mod(MINUTES_PER_HOUR),
|
||||
onTimeSelect = { hour, minute -> onTimeSelect((hour * MINUTES_PER_HOUR) + minute) },
|
||||
cardStyle = cardStyle,
|
||||
is24Hour = is24Hour,
|
||||
modifier = modifier,
|
||||
isEnabled = isEnabled,
|
||||
supportingContent = supportingContent,
|
||||
tooltip = tooltip,
|
||||
insets = insets,
|
||||
textFieldTestTag = textFieldTestTag,
|
||||
actionsPadding = actionsPadding,
|
||||
actions = actions,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A button that displays a selected time duration and opens a time picker dialog when clicked.
|
||||
*
|
||||
* @param label The descriptive text label for the [OutlinedTextField].
|
||||
* @param hours The currently selected time value in hours.
|
||||
* @param minutes The currently selected time value in minutes.
|
||||
* @param onTimeSelect A lambda that is invoked when a time is selected from the menu.
|
||||
* @param is24Hour Whether or not the time should be displayed in 24-hour format.
|
||||
* @param cardStyle Indicates the type of card style to be applied.
|
||||
* @param modifier A [Modifier] that you can use to apply custom modifications to the composable.
|
||||
* @param isEnabled Whether or not the button is enabled.
|
||||
* @param supportingContent An optional supporting content that will appear below the button.
|
||||
* @param tooltip A nullable [TooltipData], representing the tooltip icon.
|
||||
* @param insets Inner padding to be applied within the card.
|
||||
* @param textFieldTestTag The optional test tag associated with the inner text field.
|
||||
* @param actionsPadding Padding to be applied to the [actions] block.
|
||||
* @param actions A lambda containing the set of actions (usually icons or similar) to display
|
||||
* in the app bar's trailing side. This lambda extends [RowScope], allowing flexibility in
|
||||
* defining the layout of the actions.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenTimePickerButton(
|
||||
label: String,
|
||||
hours: Int,
|
||||
minutes: Int,
|
||||
onTimeSelect: (hour: Int, minute: Int) -> Unit,
|
||||
is24Hour: Boolean,
|
||||
cardStyle: CardStyle?,
|
||||
modifier: Modifier = Modifier,
|
||||
isEnabled: Boolean = true,
|
||||
supportingContent: @Composable (ColumnScope.() -> Unit)?,
|
||||
tooltip: TooltipData? = null,
|
||||
insets: PaddingValues = PaddingValues(),
|
||||
textFieldTestTag: String? = null,
|
||||
actionsPadding: PaddingValues = PaddingValues(end = 4.dp),
|
||||
actions: @Composable RowScope.() -> Unit = {},
|
||||
) {
|
||||
var shouldShowDialog by rememberSaveable { mutableStateOf(value = false) }
|
||||
BitwardenTextSelectionButton(
|
||||
label = label,
|
||||
selectedOption = if (hours != 0 && minutes != 0) {
|
||||
// Since both hours and minutes are non-zero, we display both of them.
|
||||
stringResource(
|
||||
id = BitwardenString.hours_minutes_format,
|
||||
formatArgs = arrayOf(
|
||||
pluralStringResource(
|
||||
id = BitwardenPlurals.hours_format,
|
||||
count = hours,
|
||||
formatArgs = arrayOf(hours),
|
||||
),
|
||||
pluralStringResource(
|
||||
id = BitwardenPlurals.minutes_format,
|
||||
count = minutes,
|
||||
formatArgs = arrayOf(minutes),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else if (hours != 0) {
|
||||
// Since only hours are non-zero, we only display hours.
|
||||
pluralStringResource(
|
||||
id = BitwardenPlurals.hours_format,
|
||||
count = hours,
|
||||
formatArgs = arrayOf(hours),
|
||||
)
|
||||
} else {
|
||||
// We display this if there are only minutes or if both hours and minutes are 0.
|
||||
pluralStringResource(
|
||||
id = BitwardenPlurals.minutes_format,
|
||||
count = minutes,
|
||||
formatArgs = arrayOf(minutes),
|
||||
)
|
||||
},
|
||||
onClick = { shouldShowDialog = true },
|
||||
cardStyle = cardStyle,
|
||||
enabled = isEnabled,
|
||||
showChevron = false,
|
||||
supportingContent = supportingContent,
|
||||
tooltip = tooltip,
|
||||
insets = insets,
|
||||
textFieldTestTag = textFieldTestTag,
|
||||
actionsPadding = actionsPadding,
|
||||
actions = actions,
|
||||
modifier = modifier,
|
||||
)
|
||||
if (shouldShowDialog) {
|
||||
BitwardenTimePickerDialog(
|
||||
initialHour = hours,
|
||||
initialMinute = minutes,
|
||||
onTimeSelect = { hour, minute ->
|
||||
onTimeSelect(hour, minute)
|
||||
shouldShowDialog = false
|
||||
},
|
||||
onDismissRequest = { shouldShowDialog = false },
|
||||
is24Hour = is24Hour,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -479,6 +479,7 @@ Scanning will happen automatically.</string>
|
||||
<string name="seven_days">7 days</string>
|
||||
<string name="thirty_days">30 days</string>
|
||||
<string name="custom">Custom</string>
|
||||
<string name="custom_timeout">Custom timeout</string>
|
||||
<string name="add_this_authenticator_key_to_a_login">Add this authenticator key to an existing login, or create a new login.</string>
|
||||
<string name="send_disabled_warning">Due to an enterprise policy, you are only able to delete an existing Send.</string>
|
||||
<string name="about_send">About Send</string>
|
||||
@@ -497,6 +498,7 @@ Scanning will happen automatically.</string>
|
||||
<string name="fido2_authenticate_web_authn">Authenticate WebAuthn</string>
|
||||
<string name="fido2_return_to_app">Return to app</string>
|
||||
<string name="reset_password_auto_enroll_invite_warning">This organization has an enterprise policy that will automatically enroll you in password reset. Enrollment will allow organization administrators to change your master password.</string>
|
||||
<string name="hours_minutes_format" comment="Used to display a number of hours and minutes">%1$s, %2$s</string>
|
||||
<plurals name="hours_format" comment="Can be injected into a sentence with %1$s and %2$s">
|
||||
<item quantity="one">%1$d hour</item>
|
||||
<item quantity="other">%1$d hours</item>
|
||||
|
||||
Reference in New Issue
Block a user