From 65e85b02f87471e8cbd197d68ec470685b3ab16f Mon Sep 17 00:00:00 2001 From: Joshua Queen <139182194+joshua-livefront@users.noreply.github.com> Date: Wed, 24 Jan 2024 18:14:33 -0500 Subject: [PATCH] BIT-1496: Generator Prompt Overwrite Confirmation (#755) --- .../feature/addedit/VaultAddEditLoginItems.kt | 170 +++++++++++++----- .../feature/addedit/VaultAddEditScreenTest.kt | 24 ++- 2 files changed, 145 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt index 522e62d27c..eb16dd3dc2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditLoginItems.kt @@ -9,6 +9,11 @@ import androidx.compose.foundation.lazy.items import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme +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.painterResource import androidx.compose.ui.res.stringResource @@ -25,6 +30,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions +import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.model.IconResource import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers @@ -58,58 +64,18 @@ fun LazyListScope.vaultAddEditLoginItems( item { Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextFieldWithActions( - label = stringResource(id = R.string.username), - value = loginState.username, - onValueChange = loginItemTypeHandlers.onUsernameTextChange, - actions = { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_generator), - contentDescription = stringResource(id = R.string.generate_username), - ), - onClick = loginItemTypeHandlers.onOpenUsernameGeneratorClick, - ) - }, - modifier = Modifier.padding(horizontal = 16.dp), + UsernameRow( + loginState = loginState, + loginItemTypeHandlers = loginItemTypeHandlers, ) } item { Spacer(modifier = Modifier.height(8.dp)) - if (loginState.canViewPassword) { - BitwardenPasswordFieldWithActions( - label = stringResource(id = R.string.password), - value = loginState.password, - onValueChange = loginItemTypeHandlers.onPasswordTextChange, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) { - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_check_mark), - contentDescription = stringResource(id = R.string.check_password), - ), - onClick = loginItemTypeHandlers.onPasswordCheckerClick, - ) - BitwardenIconButtonWithResource( - iconRes = IconResource( - iconPainter = painterResource(id = R.drawable.ic_generator), - contentDescription = stringResource(id = R.string.generate_password), - ), - onClick = loginItemTypeHandlers.onOpenPasswordGeneratorClick, - ) - } - } else { - BitwardenHiddenPasswordField( - label = stringResource(id = R.string.password), - value = loginState.password, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - } + PasswordRow( + loginState = loginState, + loginItemTypeHandlers = loginItemTypeHandlers, + ) } item { @@ -362,3 +328,113 @@ fun LazyListScope.vaultAddEditLoginItems( Spacer(modifier = Modifier.height(24.dp)) } } + +@Composable +private fun UsernameRow( + loginState: VaultAddEditState.ViewState.Content.ItemType.Login, + loginItemTypeHandlers: VaultAddEditLoginTypeHandlers, +) { + var shouldShowDialog by rememberSaveable { mutableStateOf(false) } + + BitwardenTextFieldWithActions( + label = stringResource(id = R.string.username), + value = loginState.username, + onValueChange = loginItemTypeHandlers.onUsernameTextChange, + actions = { + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_generator), + contentDescription = stringResource(id = R.string.generate_username), + ), + onClick = { shouldShowDialog = true }, + ) + }, + modifier = Modifier.padding(horizontal = 16.dp), + ) + + if (shouldShowDialog) { + BitwardenTwoButtonDialog( + title = stringResource(id = R.string.username), + message = stringResource( + id = + R.string.are_you_sure_you_want_to_overwrite_the_current_username, + ), + confirmButtonText = stringResource(id = R.string.yes), + dismissButtonText = stringResource(id = R.string.no), + onConfirmClick = { + shouldShowDialog = false + loginItemTypeHandlers.onOpenUsernameGeneratorClick() + }, + onDismissClick = { + shouldShowDialog = false + }, + onDismissRequest = { + shouldShowDialog = false + }, + ) + } +} + +@Composable +private fun PasswordRow( + loginState: VaultAddEditState.ViewState.Content.ItemType.Login, + loginItemTypeHandlers: VaultAddEditLoginTypeHandlers, +) { + var shouldShowDialog by rememberSaveable { mutableStateOf(false) } + + if (loginState.canViewPassword) { + BitwardenPasswordFieldWithActions( + label = stringResource(id = R.string.password), + value = loginState.password, + onValueChange = loginItemTypeHandlers.onPasswordTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) { + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_check_mark), + contentDescription = stringResource(id = R.string.check_password), + ), + onClick = loginItemTypeHandlers.onPasswordCheckerClick, + ) + BitwardenIconButtonWithResource( + iconRes = IconResource( + iconPainter = painterResource(id = R.drawable.ic_generator), + contentDescription = stringResource(id = R.string.generate_password), + ), + onClick = { shouldShowDialog = true }, + ) + + if (shouldShowDialog) { + BitwardenTwoButtonDialog( + title = stringResource(id = R.string.password), + message = stringResource( + id = + R.string.password_override_alert, + ), + confirmButtonText = stringResource(id = R.string.yes), + dismissButtonText = stringResource(id = R.string.no), + onConfirmClick = { + shouldShowDialog = false + loginItemTypeHandlers.onOpenPasswordGeneratorClick() + }, + onDismissClick = { + shouldShowDialog = false + }, + onDismissRequest = { + shouldShowDialog = false + }, + ) + } + } + } else { + BitwardenHiddenPasswordField( + label = stringResource(id = R.string.password), + value = loginState.password, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt index 589be2539a..0ec984fce6 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditScreenTest.kt @@ -1,6 +1,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed import androidx.compose.ui.test.assertIsEnabled import androidx.compose.ui.test.assertIsNotDisplayed @@ -34,6 +35,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode +import com.x8bit.bitwarden.ui.util.assertNoDialogExists import com.x8bit.bitwarden.ui.util.isProgressBar import com.x8bit.bitwarden.ui.util.onAllNodesWithContentDescriptionAfterScroll import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll @@ -406,11 +408,20 @@ class VaultAddEditScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_Login state clicking Username generator action should trigger OpenUsernameGeneratorClick`() { + fun `in ItemType_Login state clicking Username generator action should open dialog that triggers OpenUsernameGeneratorClick`() { + composeTestRule.assertNoDialogExists() + composeTestRule .onNodeWithContentDescriptionAfterScroll(label = "Generate username") .performClick() + composeTestRule + .onNodeWithText("Yes") + .assert(hasAnyAncestor(isDialog())) + .performClick() + + composeTestRule.assertNoDialogExists() + verify { viewModel.trySendAction( VaultAddEditAction.ItemType.LoginType.OpenUsernameGeneratorClick, @@ -434,13 +445,22 @@ class VaultAddEditScreenTest : BaseComposeTest() { @Suppress("MaxLineLength") @Test - fun `in ItemType_Login state click Password text field generator action should trigger OpenPasswordGeneratorClick`() { + fun `in ItemType_Login state click Password text field generator action should open dialog that triggers OpenPasswordGeneratorClick`() { + composeTestRule.assertNoDialogExists() + composeTestRule .onNodeWithTextAfterScroll(text = "Password") .onSiblings() .onLast() .performClick() + composeTestRule + .onNodeWithText("Yes") + .assert(hasAnyAncestor(isDialog())) + .performClick() + + composeTestRule.assertNoDialogExists() + verify { viewModel.trySendAction( VaultAddEditAction.ItemType.LoginType.OpenPasswordGeneratorClick,