diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt index 4a88e364b..8845f81aa 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreen.kt @@ -404,7 +404,8 @@ private fun SessionTimeoutActionRow( onVaultTimeoutActionSelect: (VaultTimeoutAction) -> Unit, modifier: Modifier = Modifier, ) { - var shouldShowSelectionDialog by remember { mutableStateOf(false) } + var shouldShowSelectionDialog by rememberSaveable { mutableStateOf(false) } + var shouldShowLogoutActionConfirmationDialog by rememberSaveable { mutableStateOf(false) } BitwardenTextRow( text = stringResource(id = R.string.session_timeout_action), onClick = { shouldShowSelectionDialog = true }, @@ -416,25 +417,49 @@ private fun SessionTimeoutActionRow( color = MaterialTheme.colorScheme.onSurfaceVariant, ) } - if (shouldShowSelectionDialog) { - BitwardenSelectionDialog( - title = stringResource(id = R.string.vault_timeout_action), - onDismissRequest = { shouldShowSelectionDialog = false }, - ) { - val vaultTimeoutActionOptions = VaultTimeoutAction.entries - vaultTimeoutActionOptions.forEach { option -> - BitwardenSelectionRow( - text = option.displayLabel, - isSelected = option == selectedVaultTimeoutAction, - onClick = { - shouldShowSelectionDialog = false - onVaultTimeoutActionSelect( - vaultTimeoutActionOptions.first { it == option }, - ) - }, - ) + when { + shouldShowSelectionDialog -> { + BitwardenSelectionDialog( + title = stringResource(id = R.string.vault_timeout_action), + onDismissRequest = { shouldShowSelectionDialog = false }, + ) { + val vaultTimeoutActionOptions = VaultTimeoutAction.entries + vaultTimeoutActionOptions.forEach { option -> + BitwardenSelectionRow( + text = option.displayLabel, + isSelected = option == selectedVaultTimeoutAction, + onClick = { + shouldShowSelectionDialog = false + val seletedAction = vaultTimeoutActionOptions.first { it == option } + if (seletedAction == VaultTimeoutAction.LOGOUT) { + shouldShowLogoutActionConfirmationDialog = true + } else { + onVaultTimeoutActionSelect(seletedAction) + } + }, + ) + } } } + + shouldShowLogoutActionConfirmationDialog -> { + BitwardenTwoButtonDialog( + title = stringResource(id = R.string.warning), + message = stringResource(id = R.string.vault_timeout_log_out_confirmation), + confirmButtonText = stringResource(id = R.string.yes), + dismissButtonText = stringResource(id = R.string.cancel), + onConfirmClick = { + shouldShowLogoutActionConfirmationDialog = false + onVaultTimeoutActionSelect(VaultTimeoutAction.LOGOUT) + }, + onDismissClick = { + shouldShowLogoutActionConfirmationDialog = false + }, + onDismissRequest = { + shouldShowLogoutActionConfirmationDialog = false + }, + ) + } } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt index 34769c811..71116a14c 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/accountsecurity/AccountSecurityScreenTest.kt @@ -484,7 +484,7 @@ class AccountSecurityScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `on session timeout action dialog option click should close the dialog and send VaultTimeoutActionSelect`() { + fun `on session timeout action dialog Lock click should close the dialog and send VaultTimeoutActionSelect`() { composeTestRule.assertNoDialogExists() composeTestRule @@ -497,7 +497,7 @@ class AccountSecurityScreenTest : BaseComposeTest() { .filterToOne(hasAnyAncestor(isDialog())) .assertIsDisplayed() composeTestRule - .onAllNodesWithText("Log out") + .onAllNodesWithText("Lock") .filterToOne(hasAnyAncestor(isDialog())) .assertIsDisplayed() .performClick() @@ -505,13 +505,116 @@ class AccountSecurityScreenTest : BaseComposeTest() { verify { viewModel.trySendAction( AccountSecurityAction.VaultTimeoutActionSelect( - VaultTimeoutAction.LOGOUT, + VaultTimeoutAction.LOCK, ), ) } composeTestRule.assertNoDialogExists() } + @Suppress("MaxLineLength") + @Test + fun `on session timeout action dialog Logout click should open a confirmation dialog`() { + composeTestRule.assertNoDialogExists() + composeTestRule + .onNodeWithText("Session timeout action") + .performScrollTo() + .performClick() + + composeTestRule + .onAllNodesWithText("Log out") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + .performClick() + + composeTestRule + .onAllNodesWithText("Warning") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + composeTestRule + .onAllNodesWithText( + "Logging out will remove all access to your vault and requires online " + + "authentication after the timeout period. Are you sure you want to use this " + + "setting?", + ) + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + composeTestRule + .onAllNodesWithText("Cancel") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + composeTestRule + .onAllNodesWithText("Yes") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + + verify(exactly = 0) { viewModel.trySendAction(any()) } + } + + @Suppress("MaxLineLength") + @Test + fun `on session timeout action Logout confirmation dialog cancel click should dismiss the dialog`() { + composeTestRule.assertNoDialogExists() + composeTestRule + .onNodeWithText("Session timeout action") + .performScrollTo() + .performClick() + + composeTestRule + .onAllNodesWithText("Log out") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + .performClick() + + composeTestRule + .onAllNodesWithText("Warning") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + composeTestRule + .onAllNodesWithText("Cancel") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + .performClick() + + composeTestRule.assertNoDialogExists() + verify(exactly = 0) { viewModel.trySendAction(any()) } + } + + @Suppress("MaxLineLength") + @Test + fun `on session timeout action Logout confirmation dialog Yes click should dismiss the dialog and send VaultTimeoutActionSelect`() { + composeTestRule.assertNoDialogExists() + composeTestRule + .onNodeWithText("Session timeout action") + .performScrollTo() + .performClick() + + composeTestRule + .onAllNodesWithText("Log out") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + .performClick() + + composeTestRule + .onAllNodesWithText("Warning") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + composeTestRule + .onAllNodesWithText("Yes") + .filterToOne(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + .performClick() + + composeTestRule.assertNoDialogExists() + verify { + viewModel.trySendAction( + AccountSecurityAction.VaultTimeoutActionSelect( + VaultTimeoutAction.LOGOUT, + ), + ) + } + } + @Suppress("MaxLineLength") @Test fun `on session timeout action dialog cancel click should close the dialog`() {