From 2fa7851b420232b19d1929948e89cc3cde0b6d58 Mon Sep 17 00:00:00 2001 From: David Perez Date: Fri, 19 Jan 2024 09:55:51 -0600 Subject: [PATCH] BIT-1411, BIT-1414: Add delete send confirmation dialog (#680) --- .../itemlisting/VaultItemListingScreen.kt | 15 ++++++++++ .../itemlisting/VaultItemListingViewModel.kt | 29 ++++++++++++++++++ .../itemlisting/VaultItemListingScreenTest.kt | 30 +++++++++++++++++++ .../VaultItemListingViewModelTest.kt | 23 +++++++++++--- 4 files changed, 93 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt index c88439566..951c47c43 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreen.kt @@ -30,6 +30,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenSearchActionItem 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.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.platform.manager.intent.IntentManager @@ -92,6 +93,9 @@ fun VaultItemListingScreen( VaultItemListingDialogs( dialogState = state.dialogState, + onDeleteSendConfirm = remember(viewModel) { + { viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(it)) } + }, onDismissRequest = remember(viewModel) { { viewModel.trySendAction(VaultItemListingsAction.DismissDialogClick) } }, @@ -108,9 +112,20 @@ fun VaultItemListingScreen( @Composable private fun VaultItemListingDialogs( dialogState: VaultItemListingState.DialogState?, + onDeleteSendConfirm: (sendId: String) -> Unit, onDismissRequest: () -> Unit, ) { when (dialogState) { + is VaultItemListingState.DialogState.DeleteSendConfirmation -> BitwardenTwoButtonDialog( + title = stringResource(id = R.string.delete), + message = stringResource(id = R.string.are_you_sure_delete_send), + confirmButtonText = stringResource(id = R.string.yes), + dismissButtonText = stringResource(id = R.string.cancel), + onConfirmClick = { onDeleteSendConfirm(dialogState.sendId) }, + onDismissClick = onDismissRequest, + onDismissRequest = onDismissRequest, + ) + is VaultItemListingState.DialogState.Error -> BitwardenBasicDialog( visibilityState = BasicDialogState.Shown( title = dialogState.title, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt index 9e6039d19..eabd5f9df 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModel.kt @@ -86,6 +86,10 @@ class VaultItemListingViewModel @Inject constructor( is VaultItemListingsAction.RefreshClick -> handleRefreshClick() is VaultItemListingsAction.CopySendUrlClick -> handleCopySendUrlClick(action) is VaultItemListingsAction.DeleteSendClick -> handleDeleteSendClick(action) + is VaultItemListingsAction.DeleteSendConfirmClick -> { + handleDeleteSendConfirmClick(action) + } + is VaultItemListingsAction.ShareSendUrlClick -> handleShareSendUrlClick(action) is VaultItemListingsAction.RemoveSendPasswordClick -> { handleRemoveSendPasswordClick(action) @@ -105,6 +109,18 @@ class VaultItemListingViewModel @Inject constructor( } private fun handleDeleteSendClick(action: VaultItemListingsAction.DeleteSendClick) { + mutableStateFlow.update { + it.copy( + dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation( + sendId = action.sendId, + ), + ) + } + } + + private fun handleDeleteSendConfirmClick( + action: VaultItemListingsAction.DeleteSendConfirmClick, + ) { mutableStateFlow.update { it.copy( dialogState = VaultItemListingState.DialogState.Loading( @@ -395,6 +411,14 @@ data class VaultItemListingState( */ sealed class DialogState : Parcelable { + /** + * Represents a dismissible dialog with the given error [message]. + */ + @Parcelize + data class DeleteSendConfirmation( + val sendId: String, + ) : DialogState() + /** * Represents a dismissible dialog with the given error [message]. */ @@ -710,6 +734,11 @@ sealed class VaultItemListingsAction { */ data class DeleteSendClick(val sendId: String) : VaultItemListingsAction() + /** + * Click on the delete send confirmation button. + */ + data class DeleteSendConfirmClick(val sendId: String) : VaultItemListingsAction() + /** * Models actions that the [VaultItemListingViewModel] itself might send. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt index 7650dfcb5..e9cc106f3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingScreenTest.kt @@ -605,6 +605,36 @@ class VaultItemListingScreenTest : BaseComposeTest() { } } + @Suppress("MaxLineLength") + @Test + fun `delete send confirmation dialog should be displayed according to state and emits DeleteSendConfirmClick on confirmation`() { + val sendId = "sendId" + val message = "Are you sure you want to delete this Send?" + composeTestRule.onNode(isDialog()).assertDoesNotExist() + composeTestRule.onNodeWithText(message).assertDoesNotExist() + + mutableStateFlow.update { + it.copy( + dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation( + sendId = sendId, + ), + ) + } + + composeTestRule + .onNodeWithText(message) + .assertIsDisplayed() + .assert(hasAnyAncestor(isDialog())) + composeTestRule + .onNodeWithText("Yes") + .assert(hasAnyAncestor(isDialog())) + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId)) + } + } + @Test fun `error dialog should be displayed according to state`() { val errorMessage = "Fail" diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt index a833045ba..c15d5b273 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingViewModelTest.kt @@ -200,14 +200,29 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { } @Test - fun `DeleteSendClick with deleteSend error should display error dialog`() = runTest { + fun `DeleteSendClick should display delete confirmation dialog`() { + val sendId = "sendId" + val viewModel = createVaultItemListingViewModel() + viewModel.trySendAction(VaultItemListingsAction.DeleteSendClick(sendId)) + assertEquals( + initialState.copy( + dialogState = VaultItemListingState.DialogState.DeleteSendConfirmation( + sendId = sendId, + ), + ), + viewModel.stateFlow.value, + ) + } + + @Test + fun `DeleteSendConfirmClick with deleteSend error should display error dialog`() = runTest { val sendId = "sendId1234" coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Error val viewModel = createVaultItemListingViewModel() viewModel.stateFlow.test { assertEquals(initialState, awaitItem()) - viewModel.trySendAction(VaultItemListingsAction.DeleteSendClick(sendId)) + viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId)) assertEquals( initialState.copy( dialogState = VaultItemListingState.DialogState.Loading( @@ -229,13 +244,13 @@ class VaultItemListingViewModelTest : BaseViewModelTest() { } @Test - fun `DeleteSendClick with deleteSend success should emit ShowToast`() = runTest { + fun `DeleteSendConfirmClick with deleteSend success should emit ShowToast`() = runTest { val sendId = "sendId1234" coEvery { vaultRepository.deleteSend(sendId) } returns DeleteSendResult.Success val viewModel = createVaultItemListingViewModel() viewModel.eventFlow.test { - viewModel.trySendAction(VaultItemListingsAction.DeleteSendClick(sendId)) + viewModel.trySendAction(VaultItemListingsAction.DeleteSendConfirmClick(sendId)) assertEquals( VaultItemListingEvent.ShowToast(R.string.send_deleted.asText()), awaitItem(),