From b991acd0d0cef58182002b90d365f224be195ed2 Mon Sep 17 00:00:00 2001 From: Shannon Draeker <125921730+shannon-livefront@users.noreply.github.com> Date: Sat, 27 Jan 2024 07:06:10 -0700 Subject: [PATCH] BIT-1277 BIT-1279: Confirmation dialog (#805) --- .../settings/exportvault/ExportVaultScreen.kt | 30 +++++++++++-- .../exportvault/ExportVaultViewModel.kt | 29 ++++++------ .../exportvault/ExportVaultScreenTest.kt | 44 ++++++++++++++++++- .../exportvault/ExportVaultViewModelTest.kt | 13 ------ 4 files changed, 84 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt index c80082b42..8ee4258d1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreen.kt @@ -17,7 +17,9 @@ import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.rememberTopAppBarState import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier @@ -40,6 +42,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordField import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar +import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.model.ExportVaultFormat import com.x8bit.bitwarden.ui.platform.util.displayLabel @@ -67,6 +70,29 @@ fun ExportVaultScreen( } } + var shouldShowConfirmationDialog by remember { mutableStateOf(false) } + if (shouldShowConfirmationDialog) { + BitwardenTwoButtonDialog( + title = stringResource(id = R.string.export_vault_confirmation_title), + message = if (state.exportFormat == ExportVaultFormat.JSON_ENCRYPTED) { + stringResource(id = R.string.enc_export_key_warning) + .plus("\n\n") + .plus(stringResource(id = R.string.enc_export_account_warning)) + } else { + stringResource( + id = R.string.export_vault_warning, + ) + }, + confirmButtonText = stringResource(id = R.string.export_vault), + dismissButtonText = stringResource(id = R.string.cancel), + onConfirmClick = remember(viewModel) { + { viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) } + }, + onDismissClick = { shouldShowConfirmationDialog = false }, + onDismissRequest = { shouldShowConfirmationDialog = false }, + ) + } + when (val dialog = state.dialogState) { is ExportVaultState.DialogState.Error -> { BitwardenBasicDialog( @@ -116,9 +142,7 @@ fun ExportVaultScreen( onPasswordInputChanged = remember(viewModel) { { viewModel.trySendAction(ExportVaultAction.PasswordInputChanged(it)) } }, - onExportVaultClick = remember(viewModel) { - { viewModel.trySendAction(ExportVaultAction.ExportVaultClick) } - }, + onExportVaultClick = { shouldShowConfirmationDialog = true }, modifier = Modifier .padding(innerPadding) .fillMaxSize(), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt index 0f690b349..00cdab4ad 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModel.kt @@ -40,9 +40,9 @@ class ExportVaultViewModel @Inject constructor( override fun handleAction(action: ExportVaultAction) { when (action) { ExportVaultAction.CloseButtonClick -> handleCloseButtonClicked() + ExportVaultAction.ConfirmExportVaultClicked -> handleConfirmExportVaultClicked() ExportVaultAction.DialogDismiss -> handleDialogDismiss() is ExportVaultAction.ExportFormatOptionSelect -> handleExportFormatOptionSelect(action) - ExportVaultAction.ExportVaultClick -> handleExportVaultClick() is ExportVaultAction.PasswordInputChanged -> handlePasswordInputChanged(action) } } @@ -54,6 +54,15 @@ class ExportVaultViewModel @Inject constructor( sendEvent(ExportVaultEvent.NavigateBack) } + /** + * Verify the master password after confirming exporting the vault. + */ + private fun handleConfirmExportVaultClicked() { + // TODO: BIT-1273 + mutableStateFlow.update { it.copy(dialogState = null) } + sendEvent(ExportVaultEvent.ShowToast("Coming soon to a PR near you!".asText())) + } + /** * Dismiss the dialog. */ @@ -70,14 +79,6 @@ class ExportVaultViewModel @Inject constructor( } } - /** - * Show the confirmation dialog and export the vault. - */ - private fun handleExportVaultClick() { - // TODO: BIT-1273 - sendEvent(ExportVaultEvent.ShowToast(message = "Coming soon to an app near you!".asText())) - } - /** * Update the state with the new password input. */ @@ -145,6 +146,11 @@ sealed class ExportVaultAction { */ data object CloseButtonClick : ExportVaultAction() + /** + * Indicates that the confirm export vault button was clicked. + */ + data object ConfirmExportVaultClicked : ExportVaultAction() + /** * Indicates that the dialog has been dismissed. */ @@ -155,11 +161,6 @@ sealed class ExportVaultAction { */ data class ExportFormatOptionSelect(val option: ExportVaultFormat) : ExportVaultAction() - /** - * Indicates that the export vault button was clicked. - */ - data object ExportVaultClick : ExportVaultAction() - /** * Indicates that the password input has changed. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreenTest.kt index 845aac27b..33e188b1b 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultScreenTest.kt @@ -1,6 +1,10 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.exportvault +import androidx.compose.ui.test.assert import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.filterToOne +import androidx.compose.ui.test.hasAnyAncestor +import androidx.compose.ui.test.isDialog import androidx.compose.ui.test.isDisplayed import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onFirst @@ -13,6 +17,7 @@ import com.x8bit.bitwarden.data.platform.repository.util.bufferedMutableSharedFl import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.model.ExportVaultFormat +import com.x8bit.bitwarden.ui.util.assertNoDialogExists import io.mockk.every import io.mockk.mockk import io.mockk.verify @@ -65,13 +70,48 @@ class ExportVaultScreenTest : BaseComposeTest() { } @Test - fun `export vault button click should emit ExportVaultClick action`() { + fun `export vault button click should display confirmation dialog`() { + composeTestRule.onNodeWithText("Confirm vault export").assertDoesNotExist() + + // Click the export vault button shows the alert. composeTestRule .onAllNodesWithText("Export vault") .onFirst() .performClick() + composeTestRule + .onNodeWithText("Confirm vault export") + .assert(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + + // Clicking the cancel button dismisses the alert. + composeTestRule + .onNodeWithText("Cancel") + .assert(hasAnyAncestor(isDialog())) + .performClick() + composeTestRule.assertNoDialogExists() + } + + @Test + fun `confirm export vault button click should send ConfirmExportClick action`() { + composeTestRule.onNodeWithText("Confirm vault export").assertDoesNotExist() + + // Click the export vault button shows the alert. + composeTestRule + .onAllNodesWithText("Export vault") + .onFirst() + .performClick() + composeTestRule + .onNodeWithText("Confirm vault export") + .assert(hasAnyAncestor(isDialog())) + .assertIsDisplayed() + + // Clicking the confirm button sends the confirm action. + composeTestRule + .onAllNodesWithText("Export vault") + .filterToOne(hasAnyAncestor(isDialog())) + .performClick() verify { - viewModel.trySendAction(ExportVaultAction.ExportVaultClick) + viewModel.trySendAction(ExportVaultAction.ConfirmExportVaultClicked) } } diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt index 11bd738ec..03f0939f4 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/platform/feature/settings/exportvault/ExportVaultViewModelTest.kt @@ -3,7 +3,6 @@ package com.x8bit.bitwarden.ui.platform.feature.settings.exportvault import androidx.lifecycle.SavedStateHandle import app.cash.turbine.test import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest -import com.x8bit.bitwarden.ui.platform.base.util.asText import com.x8bit.bitwarden.ui.platform.feature.settings.exportvault.model.ExportVaultFormat import kotlinx.coroutines.test.runTest import org.junit.jupiter.api.Assertions.assertEquals @@ -50,18 +49,6 @@ class ExportVaultViewModelTest : BaseViewModelTest() { } } - @Test - fun `ExportVaultClick should emit ShowToast`() = runTest { - val viewModel = createViewModel() - viewModel.eventFlow.test { - viewModel.actionChannel.trySend(ExportVaultAction.ExportVaultClick) - assertEquals( - ExportVaultEvent.ShowToast(message = "Coming soon to an app near you!".asText()), - awaitItem(), - ) - } - } - @Test fun `PasswordInputChanged should update the password input in the state`() = runTest { val viewModel = createViewModel()