From 0aa24d73d9977bf70a5c30958f98be1fcdaedade Mon Sep 17 00:00:00 2001 From: David Perez Date: Wed, 10 Jan 2024 22:17:36 -0600 Subject: [PATCH] Add the UI for the overflow menu (#570) --- .../feature/send/addsend/AddSendScreen.kt | 37 ++++++ .../feature/send/addsend/AddSendViewModel.kt | 49 ++++++++ .../feature/send/addsend/AddSendScreenTest.kt | 106 ++++++++++++++++++ .../send/addsend/AddSendViewModelTest.kt | 52 +++++++++ 4 files changed, 244 insertions(+) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt index 118d190b63..7e79303528 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreen.kt @@ -25,11 +25,14 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog import com.x8bit.bitwarden.ui.platform.components.BitwardenErrorContent import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingContent import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog +import com.x8bit.bitwarden.ui.platform.components.BitwardenOverflowActionItem import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState +import com.x8bit.bitwarden.ui.platform.components.OverflowMenuItemData import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers +import kotlinx.collections.immutable.persistentListOf /** * Displays new send UX. @@ -86,6 +89,40 @@ fun AddSendScreen( { viewModel.trySendAction(AddSendAction.SaveClick) } }, ) + if (!state.isAddMode) { + BitwardenOverflowActionItem( + menuItemDataList = persistentListOf( + OverflowMenuItemData( + text = stringResource(id = R.string.remove_password), + onClick = remember(viewModel) { + { + viewModel.trySendAction( + AddSendAction.RemovePasswordClick, + ) + } + }, + ), + OverflowMenuItemData( + text = stringResource(id = R.string.copy_link), + onClick = remember(viewModel) { + { viewModel.trySendAction(AddSendAction.CopyLinkClick) } + }, + ), + OverflowMenuItemData( + text = stringResource(id = R.string.share_link), + onClick = remember(viewModel) { + { viewModel.trySendAction(AddSendAction.ShareLinkClick) } + }, + ), + OverflowMenuItemData( + text = stringResource(id = R.string.delete), + onClick = remember(viewModel) { + { viewModel.trySendAction(AddSendAction.DeleteClick) } + }, + ), + ), + ) + } }, ) }, diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt index ff25c3497d..64754c7b2d 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModel.kt @@ -93,6 +93,10 @@ class AddSendViewModel @Inject constructor( } override fun handleAction(action: AddSendAction): Unit = when (action) { + AddSendAction.CopyLinkClick -> handleCopyLinkClick() + AddSendAction.DeleteClick -> handleDeleteClick() + AddSendAction.RemovePasswordClick -> handleRemovePasswordClick() + AddSendAction.ShareLinkClick -> handleShareLinkClick() is AddSendAction.CloseClick -> handleCloseClick() is AddSendAction.DeletionDateChange -> handleDeletionDateChange(action) is AddSendAction.ExpirationDateChange -> handleExpirationDateChange(action) @@ -150,6 +154,26 @@ class AddSendViewModel @Inject constructor( } } + private fun handleCopyLinkClick() { + // TODO Add copy link support (BIT-1435) + sendEvent(AddSendEvent.ShowToast("Not yet implemented")) + } + + private fun handleDeleteClick() { + // TODO Add deletion support (BIT-1435) + sendEvent(AddSendEvent.ShowToast("Not yet implemented")) + } + + private fun handleRemovePasswordClick() { + // TODO Add remove password support (BIT-1435) + sendEvent(AddSendEvent.ShowToast("Not yet implemented")) + } + + private fun handleShareLinkClick() { + // TODO Add share link support (BIT-1435) + sendEvent(AddSendEvent.ShowToast("Not yet implemented")) + } + private fun handlePasswordChange(action: AddSendAction.PasswordChange) { updateCommonContent { it.copy(passwordInput = action.input) @@ -348,6 +372,11 @@ data class AddSendState( is AddSendType.EditItem -> R.string.edit_send.asText() } + /** + * Helper to determine if the UI should display the content in add send mode. + */ + val isAddMode: Boolean get() = addSendType is AddSendType.AddItem + /** * Represents the specific view states for the [AddSendScreen]. */ @@ -463,6 +492,26 @@ sealed class AddSendEvent { */ sealed class AddSendAction { + /** + * User clicked the remove password button. + */ + data object RemovePasswordClick : AddSendAction() + + /** + * User clicked the copy link button. + */ + data object CopyLinkClick : AddSendAction() + + /** + * User clicked the share link button. + */ + data object ShareLinkClick : AddSendAction() + + /** + * User clicked the delete button. + */ + data object DeleteClick : AddSendAction() + /** * User clicked the close button. */ diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt index db4d014c20..62507ff81e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendScreenTest.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.test.filterToOne import androidx.compose.ui.test.hasAnyAncestor import androidx.compose.ui.test.hasSetTextAction import androidx.compose.ui.test.isDialog +import androidx.compose.ui.test.isDisplayed +import androidx.compose.ui.test.isPopup import androidx.compose.ui.test.onAllNodesWithText import androidx.compose.ui.test.onNodeWithContentDescription import androidx.compose.ui.test.onNodeWithText @@ -89,6 +91,110 @@ class AddSendScreenTest : BaseComposeTest() { verify { viewModel.trySendAction(AddSendAction.SaveClick) } } + @Test + fun `on overflow button click should display overflow menu`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Remove password") + .assert(hasAnyAncestor(isPopup())) + .isDisplayed() + composeTestRule + .onNodeWithText("Copy link") + .assert(hasAnyAncestor(isPopup())) + .isDisplayed() + composeTestRule + .onNodeWithText("Share link") + .assert(hasAnyAncestor(isPopup())) + .isDisplayed() + composeTestRule + .onNodeWithText("Delete") + .assert(hasAnyAncestor(isPopup())) + .isDisplayed() + } + + @Test + fun `on overflow remove password button click should send RemovePasswordClick`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Remove password") + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(AddSendAction.RemovePasswordClick) + } + } + + @Test + fun `on overflow remove Share link button click should send ShareLinkClick`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Share link") + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(AddSendAction.ShareLinkClick) + } + } + + @Test + fun `on overflow remove Delete button click should send DeleteClick`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Delete") + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(AddSendAction.DeleteClick) + } + } + + @Test + fun `on overflow remove Copy link button click should send CopyLinkClick`() { + mutableStateFlow.value = DEFAULT_STATE.copy( + addSendType = AddSendType.EditItem(sendItemId = "sendId"), + ) + + composeTestRule + .onNodeWithContentDescription("More") + .performClick() + + composeTestRule + .onNodeWithText("Copy link") + .performClick() + + verify(exactly = 1) { + viewModel.trySendAction(AddSendAction.CopyLinkClick) + } + } + @Test fun `on name input change should send NameChange`() { composeTestRule diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt index 8fc1808ebd..66ae11fcd1 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/tools/feature/send/addsend/AddSendViewModelTest.kt @@ -168,6 +168,58 @@ class AddSendViewModelTest : BaseViewModelTest() { ) } + @Test + fun `CopyLinkClick should send ShowToast`() = runTest { + val viewModel = createViewModel( + state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem("sendId")), + addSendType = AddSendType.EditItem("sendId"), + ) + + viewModel.eventFlow.test { + viewModel.trySendAction(AddSendAction.CopyLinkClick) + assertEquals(AddSendEvent.ShowToast("Not yet implemented"), awaitItem()) + } + } + + @Test + fun `DeleteClick should send ShowToast`() = runTest { + val viewModel = createViewModel( + state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem("sendId")), + addSendType = AddSendType.EditItem("sendId"), + ) + + viewModel.eventFlow.test { + viewModel.trySendAction(AddSendAction.DeleteClick) + assertEquals(AddSendEvent.ShowToast("Not yet implemented"), awaitItem()) + } + } + + @Test + fun `RemovePasswordClick should send ShowToast`() = runTest { + val viewModel = createViewModel( + state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem("sendId")), + addSendType = AddSendType.EditItem("sendId"), + ) + + viewModel.eventFlow.test { + viewModel.trySendAction(AddSendAction.RemovePasswordClick) + assertEquals(AddSendEvent.ShowToast("Not yet implemented"), awaitItem()) + } + } + + @Test + fun `ShareLinkClick should send ShowToast`() = runTest { + val viewModel = createViewModel( + state = DEFAULT_STATE.copy(addSendType = AddSendType.EditItem("sendId")), + addSendType = AddSendType.EditItem("sendId"), + ) + + viewModel.eventFlow.test { + viewModel.trySendAction(AddSendAction.ShareLinkClick) + assertEquals(AddSendEvent.ShowToast("Not yet implemented"), awaitItem()) + } + } + @Test fun `DismissDialogClick should clear the dialog state`() { val viewModel = createViewModel(