Skip to content

Commit 51c23ec

Browse files
Minor clean up for the Account Security Screen (#6076)
1 parent 7d7951d commit 51c23ec

File tree

5 files changed

+162
-97
lines changed

5 files changed

+162
-97
lines changed

app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt

Lines changed: 46 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
4040
import com.bitwarden.ui.platform.components.badge.NotificationBadge
4141
import com.bitwarden.ui.platform.components.button.BitwardenTextButton
4242
import com.bitwarden.ui.platform.components.card.BitwardenActionCard
43-
import com.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
4443
import com.bitwarden.ui.platform.components.card.actionCardExitAnimation
4544
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
4645
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
@@ -52,6 +51,7 @@ import com.bitwarden.ui.platform.components.model.CardStyle
5251
import com.bitwarden.ui.platform.components.row.BitwardenExternalLinkRow
5352
import com.bitwarden.ui.platform.components.row.BitwardenTextRow
5453
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
54+
import com.bitwarden.ui.platform.components.support.BitwardenSupportingText
5555
import com.bitwarden.ui.platform.components.toggle.BitwardenSwitch
5656
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
5757
import com.bitwarden.ui.platform.composition.LocalIntentManager
@@ -60,8 +60,6 @@ import com.bitwarden.ui.platform.resource.BitwardenDrawable
6060
import com.bitwarden.ui.platform.resource.BitwardenString
6161
import com.bitwarden.ui.platform.theme.BitwardenTheme
6262
import com.bitwarden.ui.util.Text
63-
import com.bitwarden.ui.util.asText
64-
import com.x8bit.bitwarden.data.auth.repository.model.PolicyInformation
6563
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeout
6664
import com.x8bit.bitwarden.data.platform.repository.model.VaultTimeoutAction
6765
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenUnlockWithBiometricsSwitch
@@ -72,6 +70,7 @@ import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
7270
import com.x8bit.bitwarden.ui.platform.manager.utils.startApplicationDetailsSettingsActivity
7371
import com.x8bit.bitwarden.ui.platform.util.displayLabel
7472
import com.x8bit.bitwarden.ui.platform.util.minutes
73+
import kotlinx.collections.immutable.ImmutableList
7574
import kotlinx.collections.immutable.toImmutableList
7675
import java.time.LocalTime
7776
import javax.crypto.Cipher
@@ -297,15 +296,8 @@ fun AccountSecurityScreen(
297296
.padding(horizontal = 16.dp),
298297
)
299298
Spacer(modifier = Modifier.height(height = 8.dp))
300-
SessionTimeoutPolicyRow(
301-
vaultTimeoutPolicyMinutes = state.vaultTimeoutPolicyMinutes,
302-
vaultTimeoutPolicyAction = state.vaultTimeoutPolicyAction,
303-
modifier = Modifier
304-
.fillMaxWidth()
305-
.standardHorizontalMargin(),
306-
)
307299
SessionTimeoutRow(
308-
vaultTimeoutPolicyMinutes = state.vaultTimeoutPolicyMinutes,
300+
vaultTimeoutPolicy = state.vaultTimeoutPolicy,
309301
selectedVaultTimeoutType = state.vaultTimeout.type,
310302
onVaultTimeoutTypeSelect = remember(viewModel) {
311303
{ viewModel.trySendAction(AccountSecurityAction.VaultTimeoutTypeSelect(it)) }
@@ -317,7 +309,7 @@ fun AccountSecurityScreen(
317309
)
318310
(state.vaultTimeout as? VaultTimeout.Custom)?.let { customTimeout ->
319311
SessionCustomTimeoutRow(
320-
vaultTimeoutPolicyMinutes = state.vaultTimeoutPolicyMinutes,
312+
vaultTimeoutPolicy = state.vaultTimeoutPolicy,
321313
customVaultTimeout = customTimeout,
322314
onCustomVaultTimeoutSelect = remember(viewModel) {
323315
{
@@ -331,13 +323,28 @@ fun AccountSecurityScreen(
331323
.standardHorizontalMargin(),
332324
)
333325
}
326+
state.sessionTimeoutSupportText?.let { text ->
327+
BitwardenSupportingText(
328+
text = text(),
329+
cardStyle = CardStyle.Bottom,
330+
modifier = Modifier
331+
.fillMaxWidth()
332+
.standardHorizontalMargin(),
333+
)
334+
Spacer(modifier = Modifier.height(height = 8.dp))
335+
}
334336
SessionTimeoutActionRow(
335-
isEnabled = state.hasUnlockMechanism,
336-
vaultTimeoutPolicyAction = state.vaultTimeoutPolicyAction,
337+
isEnabled = state.isSessionTimeoutActionEnabled,
337338
selectedVaultTimeoutAction = state.vaultTimeoutAction,
338339
onVaultTimeoutActionSelect = remember(viewModel) {
339340
{ viewModel.trySendAction(AccountSecurityAction.VaultTimeoutActionSelect(it)) }
340341
},
342+
supportingText = state.sessionTimeoutActionSupportingText?.invoke(),
343+
cardStyle = if (state.sessionTimeoutSupportText == null) {
344+
CardStyle.Bottom
345+
} else {
346+
CardStyle.Full
347+
},
341348
modifier = Modifier
342349
.testTag("VaultTimeoutActionChooser")
343350
.fillMaxWidth()
@@ -471,57 +478,16 @@ private fun AccountSecurityDialogs(
471478
}
472479
}
473480

474-
@Composable
475-
private fun SessionTimeoutPolicyRow(
476-
vaultTimeoutPolicyMinutes: Int?,
477-
vaultTimeoutPolicyAction: PolicyInformation.VaultTimeout.Action?,
478-
modifier: Modifier = Modifier,
479-
) {
480-
// Show the policy warning if applicable.
481-
if (vaultTimeoutPolicyMinutes != null || vaultTimeoutPolicyAction != null) {
482-
// Calculate the hours and minutes to show in the policy label.
483-
val hours = vaultTimeoutPolicyMinutes?.floorDiv(MINUTES_PER_HOUR)
484-
val minutes = vaultTimeoutPolicyMinutes?.mod(MINUTES_PER_HOUR)
485-
486-
// Get the localized version of the action.
487-
val action = when (vaultTimeoutPolicyAction) {
488-
PolicyInformation.VaultTimeout.Action.LOCK -> BitwardenString.lock.asText()
489-
PolicyInformation.VaultTimeout.Action.LOGOUT -> BitwardenString.log_out.asText()
490-
null -> BitwardenString.log_out.asText()
491-
}
492-
493-
val policyText = if (hours == null || minutes == null) {
494-
BitwardenString.vault_timeout_action_policy_in_effect.asText(action)
495-
} else if (vaultTimeoutPolicyAction == null) {
496-
BitwardenString.vault_timeout_policy_in_effect.asText(hours, minutes)
497-
} else {
498-
BitwardenString.vault_timeout_policy_with_action_in_effect.asText(
499-
hours,
500-
minutes,
501-
action,
502-
)
503-
}
504-
505-
BitwardenInfoCalloutCard(
506-
text = policyText(),
507-
modifier = modifier,
508-
)
509-
Spacer(modifier = Modifier.height(height = 8.dp))
510-
}
511-
}
512-
513481
@Composable
514482
private fun SessionTimeoutRow(
515-
vaultTimeoutPolicyMinutes: Int?,
483+
vaultTimeoutPolicy: VaultTimeoutPolicy?,
516484
selectedVaultTimeoutType: VaultTimeout.Type,
517485
onVaultTimeoutTypeSelect: (VaultTimeout.Type) -> Unit,
518486
modifier: Modifier = Modifier,
519487
resources: Resources = LocalResources.current,
520488
) {
521489
var shouldShowNeverTimeoutConfirmationDialog by remember { mutableStateOf(false) }
522-
val vaultTimeoutOptions = VaultTimeout.Type
523-
.entries
524-
.filter { it.minutes <= (vaultTimeoutPolicyMinutes ?: Int.MAX_VALUE) }
490+
val vaultTimeoutOptions = rememberSessionTimeoutOptions(vaultTimeoutPolicy)
525491
BitwardenMultiSelectButton(
526492
label = stringResource(id = BitwardenString.session_timeout),
527493
options = vaultTimeoutOptions.map { it.displayLabel() }.toImmutableList(),
@@ -557,10 +523,9 @@ private fun SessionTimeoutRow(
557523
}
558524
}
559525

560-
@Suppress("LongMethod")
561526
@Composable
562527
private fun SessionCustomTimeoutRow(
563-
vaultTimeoutPolicyMinutes: Int?,
528+
vaultTimeoutPolicy: VaultTimeoutPolicy?,
564529
customVaultTimeout: VaultTimeout.Custom,
565530
onCustomVaultTimeoutSelect: (VaultTimeout.Custom) -> Unit,
566531
modifier: Modifier = Modifier,
@@ -574,7 +539,6 @@ private fun SessionCustomTimeoutRow(
574539
cardStyle = CardStyle.Middle(),
575540
modifier = modifier,
576541
) {
577-
578542
Text(
579543
text = LocalTime
580544
.ofSecondOfDay(vaultTimeoutInMinutes * MINUTES_PER_HOUR.toLong())
@@ -592,8 +556,8 @@ private fun SessionCustomTimeoutRow(
592556
shouldShowTimePickerDialog = false
593557

594558
val totalMinutes = (hour * MINUTES_PER_HOUR) + minute
595-
if (vaultTimeoutPolicyMinutes != null &&
596-
totalMinutes > vaultTimeoutPolicyMinutes
559+
if (vaultTimeoutPolicy?.minutes != null &&
560+
totalMinutes > vaultTimeoutPolicy.minutes
597561
) {
598562
shouldShowViolatesPoliciesDialog = true
599563
} else {
@@ -615,7 +579,7 @@ private fun SessionCustomTimeoutRow(
615579
message = stringResource(id = BitwardenString.vault_timeout_to_large),
616580
onDismissRequest = {
617581
shouldShowViolatesPoliciesDialog = false
618-
vaultTimeoutPolicyMinutes?.let {
582+
vaultTimeoutPolicy?.minutes?.let {
619583
onCustomVaultTimeoutSelect(
620584
VaultTimeout.Custom(
621585
vaultTimeoutInMinutes = it,
@@ -630,9 +594,10 @@ private fun SessionCustomTimeoutRow(
630594
@Composable
631595
private fun SessionTimeoutActionRow(
632596
isEnabled: Boolean,
633-
vaultTimeoutPolicyAction: PolicyInformation.VaultTimeout.Action?,
634597
selectedVaultTimeoutAction: VaultTimeoutAction,
635598
onVaultTimeoutActionSelect: (VaultTimeoutAction) -> Unit,
599+
supportingText: String?,
600+
cardStyle: CardStyle,
636601
modifier: Modifier = Modifier,
637602
resources: Resources = LocalResources.current,
638603
) {
@@ -643,8 +608,6 @@ private fun SessionTimeoutActionRow(
643608
options = VaultTimeoutAction.entries.map { it.displayLabel() }.toImmutableList(),
644609
selectedOption = selectedVaultTimeoutAction.displayLabel(),
645610
onOptionSelected = { action ->
646-
// The option is not selectable if there's a policy in place.
647-
if (vaultTimeoutPolicyAction != null) return@BitwardenMultiSelectButton
648611
val selectedAction = VaultTimeoutAction.entries.first {
649612
it.displayLabel.toString(resources) == action
650613
}
@@ -654,12 +617,9 @@ private fun SessionTimeoutActionRow(
654617
onVaultTimeoutActionSelect(selectedAction)
655618
}
656619
},
657-
supportingText = stringResource(
658-
id = BitwardenString.set_up_an_unlock_option_to_change_your_vault_timeout_action,
659-
)
660-
.takeUnless { isEnabled },
620+
supportingText = supportingText,
661621
textFieldTestTag = "SessionTimeoutActionStatusLabel",
662-
cardStyle = CardStyle.Bottom,
622+
cardStyle = cardStyle,
663623
modifier = modifier,
664624
)
665625

@@ -760,3 +720,18 @@ private fun FingerPrintPhraseDialog(
760720
textContentColor = BitwardenTheme.colorScheme.text.primary,
761721
)
762722
}
723+
724+
@Composable
725+
private fun rememberSessionTimeoutOptions(
726+
vaultTimeoutPolicy: VaultTimeoutPolicy?,
727+
): ImmutableList<VaultTimeout.Type> = remember(vaultTimeoutPolicy) {
728+
VaultTimeout.Type
729+
.entries
730+
.filter { timeoutType ->
731+
vaultTimeoutPolicy
732+
?.minutes
733+
?.let { minutes -> timeoutType.minutes <= minutes }
734+
?: true
735+
}
736+
.toImmutableList()
737+
}

app/src/main/kotlin/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityViewModel.kt

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,10 @@ import com.bitwarden.core.util.isBuildVersionAtLeast
88
import com.bitwarden.data.repository.util.baseWebVaultUrlOrDefault
99
import com.bitwarden.network.model.PolicyTypeJson
1010
import com.bitwarden.ui.platform.base.BaseViewModel
11+
import com.bitwarden.ui.platform.resource.BitwardenPlurals
1112
import com.bitwarden.ui.platform.resource.BitwardenString
1213
import com.bitwarden.ui.util.Text
14+
import com.bitwarden.ui.util.asPluralsText
1315
import com.bitwarden.ui.util.asText
1416
import com.x8bit.bitwarden.data.auth.repository.AuthRepository
1517
import com.x8bit.bitwarden.data.auth.repository.model.LogoutReason
@@ -37,6 +39,7 @@ import javax.crypto.Cipher
3739
import javax.inject.Inject
3840

3941
private const val KEY_STATE = "state"
42+
private const val MINUTES_PER_HOUR = 60
4043

4144
/**
4245
* View model for the account security screen.
@@ -75,8 +78,7 @@ class AccountSecurityViewModel @Inject constructor(
7578
userId = userId,
7679
vaultTimeout = settingsRepository.vaultTimeout,
7780
vaultTimeoutAction = settingsRepository.vaultTimeoutAction,
78-
vaultTimeoutPolicyMinutes = null,
79-
vaultTimeoutPolicyAction = null,
81+
vaultTimeoutPolicy = null,
8082
shouldShowUnlockActionCard = false,
8183
removeUnlockWithPinPolicyEnabled = false,
8284
)
@@ -476,10 +478,15 @@ class AccountSecurityViewModel @Inject constructor(
476478
// The vault timeout policy can only be implemented in organizations that have
477479
// the single organization policy, meaning that if this is enabled, the user is
478480
// only in one organization and hence there is only one result in the list.
481+
val vaultTimeoutPolicy = action.vaultTimeoutPolicies?.firstOrNull()
479482
mutableStateFlow.update {
480483
it.copy(
481-
vaultTimeoutPolicyMinutes = action.vaultTimeoutPolicies?.firstOrNull()?.minutes,
482-
vaultTimeoutPolicyAction = action.vaultTimeoutPolicies?.firstOrNull()?.action,
484+
vaultTimeoutPolicy = vaultTimeoutPolicy?.let { policy ->
485+
VaultTimeoutPolicy(
486+
minutes = policy.minutes,
487+
action = policy.action,
488+
)
489+
},
483490
)
484491
}
485492
}
@@ -518,8 +525,7 @@ data class AccountSecurityState(
518525
val userId: String,
519526
val vaultTimeout: VaultTimeout,
520527
val vaultTimeoutAction: VaultTimeoutAction,
521-
val vaultTimeoutPolicyMinutes: Int?,
522-
val vaultTimeoutPolicyAction: PolicyInformation.VaultTimeout.Action?,
528+
val vaultTimeoutPolicy: VaultTimeoutPolicy?,
523529
val shouldShowUnlockActionCard: Boolean,
524530
val removeUnlockWithPinPolicyEnabled: Boolean,
525531
) : Parcelable {
@@ -530,8 +536,74 @@ data class AccountSecurityState(
530536
get() = isUnlockWithPasswordEnabled ||
531537
isUnlockWithPinEnabled ||
532538
isUnlockWithBiometricsEnabled
539+
540+
/**
541+
* Indicates that the vault timeout action is enabled.
542+
*/
543+
val isSessionTimeoutActionEnabled: Boolean
544+
get() = hasUnlockMechanism && vaultTimeoutPolicy?.action == null
545+
546+
/**
547+
* The text to display for the session timeout.
548+
*/
549+
val sessionTimeoutSupportText: Text?
550+
get() = vaultTimeoutPolicy?.let { policy ->
551+
// Calculate the hours and minutes to show in the policy label.
552+
val hours = policy.minutes?.floorDiv(MINUTES_PER_HOUR).takeUnless { it == 0 }
553+
val minutes = policy.minutes?.mod(MINUTES_PER_HOUR).takeUnless { it == 0 }
554+
if (hours != null && minutes != null) {
555+
if (hours == 1 && minutes == 1) {
556+
BitwardenString
557+
.vault_timeout_policy_in_effect_no_plural
558+
.asText(hours, minutes)
559+
} else if (hours == 1) {
560+
BitwardenString
561+
.vault_timeout_policy_in_effect_minutes_plural
562+
.asText(hours, minutes)
563+
} else if (minutes == 1) {
564+
BitwardenString
565+
.vault_timeout_policy_in_effect_hours_plural
566+
.asText(hours, minutes)
567+
} else {
568+
BitwardenString
569+
.vault_timeout_policy_in_effect_both_plural
570+
.asText(hours, minutes)
571+
}
572+
} else if (hours != null) {
573+
BitwardenPlurals
574+
.vault_timeout_policy_in_effect_hours
575+
.asPluralsText(hours, hours)
576+
} else if (minutes != null) {
577+
BitwardenPlurals
578+
.vault_timeout_policy_in_effect_minutes
579+
.asPluralsText(minutes, minutes)
580+
} else {
581+
null
582+
}
583+
}
584+
585+
/**
586+
* The text to display for the session timeout action.
587+
*/
588+
val sessionTimeoutActionSupportingText: Text?
589+
get() = if (vaultTimeoutPolicy?.action != null) {
590+
BitwardenString.this_setting_is_managed_by_your_organization.asText()
591+
} else if (!hasUnlockMechanism) {
592+
BitwardenString.set_up_an_unlock_option_to_change_your_vault_timeout_action.asText()
593+
} else {
594+
null
595+
}
533596
}
534597

598+
/**
599+
* Models the vault timeout policy.
600+
*/
601+
@Parcelize
602+
data class VaultTimeoutPolicy(
603+
val minutes: Int?,
604+
val action: PolicyInformation.VaultTimeout.Action?,
605+
) : Parcelable
606+
535607
/**
536608
* Representation of the dialogs that can be displayed on account security screen.
537609
*/

0 commit comments

Comments
 (0)