PM-21445: Update the Send delete buttons (#5195)

This commit is contained in:
David Perez
2025-05-14 16:45:59 -05:00
committed by GitHub
parent f14a1404e3
commit dfd58822b7
6 changed files with 138 additions and 99 deletions

View File

@@ -2,10 +2,10 @@ package com.x8bit.bitwarden.ui.tools.feature.send.addsend
import android.Manifest
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
@@ -38,7 +38,9 @@ import com.x8bit.bitwarden.R
import com.x8bit.bitwarden.ui.platform.base.util.cardStyle
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenOutlinedErrorButton
import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
@@ -46,6 +48,7 @@ import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeade
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.stepper.BitwardenStepper
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
@@ -185,11 +188,49 @@ fun AddSendContent(
isAddMode = isAddMode,
addSendHandlers = addSendHandlers,
)
if (!isAddMode) {
DeleteButton(
onDeleteClick = addSendHandlers.onDeleteClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
)
}
Spacer(modifier = Modifier.height(height = 12.dp))
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
@Composable
private fun DeleteButton(
onDeleteClick: () -> Unit,
modifier: Modifier = Modifier,
) {
var shouldShowDeleteConfirmationDialog by rememberSaveable { mutableStateOf(value = false) }
if (shouldShowDeleteConfirmationDialog) {
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 = {
onDeleteClick()
shouldShowDeleteConfirmationDialog = false
},
onDismissClick = { shouldShowDeleteConfirmationDialog = false },
onDismissRequest = { shouldShowDeleteConfirmationDialog = false },
)
}
BitwardenOutlinedErrorButton(
label = stringResource(id = R.string.delete_send),
onClick = { shouldShowDeleteConfirmationDialog = true },
icon = rememberVectorPainter(id = R.drawable.ic_trash_small),
modifier = modifier,
)
}
@Composable
private fun ColumnScope.TextTypeContent(
textType: AddSendState.ViewState.Content.SendType.Text,
@@ -357,11 +398,10 @@ private fun AddSendOptions(
.standardHorizontalMargin()
.fillMaxWidth(),
)
// Hide all content if not expanded:
AnimatedVisibility(
visible = isExpanded,
enter = fadeIn() + slideInVertically(),
exit = fadeOut() + slideOutVertically(),
enter = fadeIn() + expandVertically(expandFrom = Alignment.Top),
exit = fadeOut() + shrinkVertically(shrinkTowards = Alignment.Top),
modifier = Modifier.clipToBounds(),
) {
Column {
@@ -434,6 +474,7 @@ private fun AddSendOptions(
.fillMaxWidth()
.standardHorizontalMargin(),
)
Spacer(modifier = Modifier.height(height = 16.dp))
}
}
}

View File

@@ -9,10 +9,7 @@ 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.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalContext
@@ -32,7 +29,6 @@ import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent
import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingContent
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.model.TopAppBarDividerStyle
import com.x8bit.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.x8bit.bitwarden.ui.platform.components.segment.BitwardenSegmentedButton
@@ -109,24 +105,6 @@ fun AddSendScreen(
{ viewModel.trySendAction(AddSendAction.DismissDialogClick) }
},
)
var shouldShowDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
if (shouldShowDeleteConfirmationDialog) {
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 = remember(viewModel) {
{
viewModel.trySendAction(AddSendAction.DeleteClick)
shouldShowDeleteConfirmationDialog = false
}
},
onDismissClick = { shouldShowDeleteConfirmationDialog = false },
onDismissRequest = { shouldShowDeleteConfirmationDialog = false },
)
}
BitwardenScaffold(
modifier = Modifier
.fillMaxSize()
@@ -181,10 +159,6 @@ fun AddSendScreen(
},
)
.takeIf { !state.policyDisablesSend },
OverflowMenuItemData(
text = stringResource(id = R.string.delete),
onClick = { shouldShowDeleteConfirmationDialog = true },
),
),
)
}

View File

@@ -23,6 +23,7 @@ data class AddSendHandlers(
val onHideEmailToggle: (Boolean) -> Unit,
val onDeactivateSendToggle: (Boolean) -> Unit,
val onDeletionDateChange: (ZonedDateTime) -> Unit,
val onDeleteClick: () -> Unit,
) {
@Suppress("UndocumentedPublicClass")
companion object {
@@ -57,6 +58,7 @@ data class AddSendHandlers(
onDeletionDateChange = {
viewModel.trySendAction(AddSendAction.DeletionDateChange(it))
},
onDeleteClick = { viewModel.trySendAction(AddSendAction.DeleteClick) },
)
}
}

View File

@@ -58,6 +58,7 @@ import com.x8bit.bitwarden.ui.platform.components.content.BitwardenErrorContent
import com.x8bit.bitwarden.ui.platform.components.content.BitwardenLoadingContent
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
import com.x8bit.bitwarden.ui.platform.components.fab.BitwardenFloatingActionButton
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
@@ -306,10 +307,8 @@ private fun ViewStateContent(
AdditionalOptions(state = state)
BitwardenOutlinedErrorButton(
label = stringResource(id = R.string.delete_send),
onClick = onDeleteClick,
icon = rememberVectorPainter(id = R.drawable.ic_trash_small),
DeleteButton(
onDeleteClick = onDeleteClick,
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin(),
@@ -319,6 +318,34 @@ private fun ViewStateContent(
}
}
@Composable
private fun DeleteButton(
onDeleteClick: () -> Unit,
modifier: Modifier = Modifier,
) {
var shouldShowDeleteConfirmationDialog by rememberSaveable { mutableStateOf(value = false) }
if (shouldShowDeleteConfirmationDialog) {
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 = {
onDeleteClick()
shouldShowDeleteConfirmationDialog = false
},
onDismissClick = { shouldShowDeleteConfirmationDialog = false },
onDismissRequest = { shouldShowDeleteConfirmationDialog = false },
)
}
BitwardenOutlinedErrorButton(
label = stringResource(id = R.string.delete_send),
onClick = { shouldShowDeleteConfirmationDialog = true },
icon = rememberVectorPainter(id = R.drawable.ic_trash_small),
modifier = modifier,
)
}
/**
* A default content block which displays a header with an optional subtitle and an icon.
* Implemented to match design component.

View File

@@ -162,14 +162,10 @@ class AddSendScreenTest : BaseComposeTest() {
.onNodeWithText("Share link")
.assert(hasAnyAncestor(isPopup()))
.isDisplayed()
composeTestRule
.onNodeWithText("Delete")
.assert(hasAnyAncestor(isPopup()))
.isDisplayed()
}
@Test
fun `on overflow button click should only display delete when policy disables send`() {
fun `on overflow button should not be present when policy disables send`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
policyDisablesSend = true,
@@ -177,21 +173,7 @@ class AddSendScreenTest : BaseComposeTest() {
composeTestRule
.onNodeWithContentDescription("More")
.performClick()
composeTestRule
.onNodeWithText("Remove password")
.assertDoesNotExist()
composeTestRule
.onNodeWithText("Copy link")
.assertDoesNotExist()
composeTestRule
.onNodeWithText("Share link")
.assertDoesNotExist()
composeTestRule
.onNodeWithText("Delete")
.assert(hasAnyAncestor(isPopup()))
.isDisplayed()
}
@Test
@@ -250,50 +232,6 @@ class AddSendScreenTest : BaseComposeTest() {
}
}
@Test
fun `on overflow Delete button click should Display delete confirmation dialog`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
)
composeTestRule
.onNodeWithContentDescription("More")
.performClick()
composeTestRule
.onNodeWithText("Delete")
.performClick()
composeTestRule
.onNodeWithText("Are you sure you want to delete this Send?")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on delete confirmation dialog yes click should send DeleteClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
)
composeTestRule
.onNodeWithContentDescription("More")
.performClick()
composeTestRule
.onNodeWithText("Delete")
.performClick()
composeTestRule
.onNodeWithText("Yes")
.assert(hasAnyAncestor(isDialog()))
.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(
@@ -313,6 +251,44 @@ class AddSendScreenTest : BaseComposeTest() {
}
}
@Test
fun `on Delete button click should Display delete confirmation dialog`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
)
composeTestRule
.onNodeWithText(text = "Delete send")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText(text = "Are you sure you want to delete this Send?")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `on delete confirmation dialog yes click should send DeleteClick`() {
mutableStateFlow.value = DEFAULT_STATE.copy(
addSendType = AddSendType.EditItem(sendItemId = "sendId"),
)
composeTestRule
.onNodeWithText(text = "Delete send")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText(text = "Yes")
.assert(hasAnyAncestor(isDialog()))
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(AddSendAction.DeleteClick)
}
}
@Test
fun `policy warning should update according to state`() {
val policyText = "Due to an enterprise policy, you are only " +

View File

@@ -125,11 +125,30 @@ class ViewSendScreenTest : BaseComposeTest() {
}
@Test
fun `on delete click should send DeleteClick`() {
fun `on Delete button click should Display delete confirmation dialog`() {
composeTestRule
.onNodeWithText(text = "Delete send")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText(text = "Are you sure you want to delete this Send?")
.assert(hasAnyAncestor(isDialog()))
.assertIsDisplayed()
}
@Test
fun `oon delete confirmation dialog yes click should send DeleteClick`() {
composeTestRule
.onNodeWithText(text = "Delete send")
.performScrollTo()
.performClick()
composeTestRule
.onNodeWithText(text = "Yes")
.assert(hasAnyAncestor(isDialog()))
.performClick()
verify(exactly = 1) {
viewModel.trySendAction(ViewSendAction.DeleteClick)
}