mirror of
https://github.com/bitwarden/android.git
synced 2024-10-31 15:15:34 +03:00
Add delete and remove password options items for send (#591)
This commit is contained in:
parent
197feea56a
commit
c06be2b8de
8 changed files with 197 additions and 2 deletions
|
@ -83,6 +83,12 @@ fun SendContent(
|
|||
onCopyClick = { sendHandlers.onCopySendClick(it) },
|
||||
onEditClick = { sendHandlers.onEditSendClick(it) },
|
||||
onShareClick = { sendHandlers.onShareSendClick(it) },
|
||||
onDeleteClick = { sendHandlers.onDeleteSendClick(it) },
|
||||
onRemovePasswordClick = if (it.hasPassword) {
|
||||
{ sendHandlers.onRemovePasswordClick(it) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
package com.x8bit.bitwarden.ui.tools.feature.send
|
||||
|
||||
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.graphics.painter.Painter
|
||||
import androidx.compose.ui.res.painterResource
|
||||
|
@ -8,11 +12,12 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListItem
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTwoButtonDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.SelectionItemData
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.model.SendStatusIcon
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* A Composable function that displays a row send item.
|
||||
|
@ -24,6 +29,9 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
* @param onEditClick The lambda to be invoked when the edit option is clicked from the menu.
|
||||
* @param onCopyClick The lambda to be invoked when the copy option is clicked from the menu.
|
||||
* @param onShareClick The lambda to be invoked when the share option is clicked from the menu.
|
||||
* @param onDeleteClick The lambda to be invoked when the delete option is clicked from the menu.
|
||||
* @param onRemovePasswordClick The lambda to be invoked when the remove password option is clicked
|
||||
* from the menu, if `null` the remove password button is not displayed.
|
||||
* @param modifier An optional [Modifier] for this Composable, defaulting to an empty Modifier.
|
||||
* This allows the caller to specify things like padding, size, etc.
|
||||
*/
|
||||
|
@ -38,8 +46,11 @@ fun SendListItem(
|
|||
onEditClick: () -> Unit,
|
||||
onCopyClick: () -> Unit,
|
||||
onShareClick: () -> Unit,
|
||||
onDeleteClick: () -> Unit,
|
||||
onRemovePasswordClick: (() -> Unit)?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
var shouldShowDeleteConfirmationDialog by rememberSaveable { mutableStateOf(false) }
|
||||
BitwardenListItem(
|
||||
label = label,
|
||||
supportingLabel = supportingLabel,
|
||||
|
@ -51,7 +62,7 @@ fun SendListItem(
|
|||
)
|
||||
},
|
||||
onClick = onClick,
|
||||
selectionDataList = persistentListOf(
|
||||
selectionDataList = persistentListOfNotNull(
|
||||
SelectionItemData(
|
||||
text = stringResource(id = R.string.edit),
|
||||
onClick = onEditClick,
|
||||
|
@ -64,9 +75,33 @@ fun SendListItem(
|
|||
text = stringResource(id = R.string.share_link),
|
||||
onClick = onShareClick,
|
||||
),
|
||||
onRemovePasswordClick?.let {
|
||||
SelectionItemData(
|
||||
text = stringResource(id = R.string.remove_password),
|
||||
onClick = it,
|
||||
)
|
||||
},
|
||||
SelectionItemData(
|
||||
text = stringResource(id = R.string.delete),
|
||||
onClick = { shouldShowDeleteConfirmationDialog = true },
|
||||
),
|
||||
),
|
||||
modifier = modifier,
|
||||
)
|
||||
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 = {
|
||||
shouldShowDeleteConfirmationDialog = false
|
||||
onDeleteClick()
|
||||
},
|
||||
onDismissClick = { shouldShowDeleteConfirmationDialog = false },
|
||||
onDismissRequest = { shouldShowDeleteConfirmationDialog = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
|
@ -82,6 +117,8 @@ private fun SendListItem_preview() {
|
|||
onCopyClick = {},
|
||||
onEditClick = {},
|
||||
onShareClick = {},
|
||||
onDeleteClick = {},
|
||||
onRemovePasswordClick = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -67,6 +67,8 @@ class SendViewModel @Inject constructor(
|
|||
is SendAction.SendClick -> handleSendClick(action)
|
||||
is SendAction.ShareClick -> handleShareClick(action)
|
||||
SendAction.TextTypeClick -> handleTextTypeClick()
|
||||
is SendAction.DeleteSendClick -> handleDeleteSendClick(action)
|
||||
is SendAction.RemovePasswordClick -> handleRemovePasswordClick(action)
|
||||
is SendAction.Internal -> handleInternalAction(action)
|
||||
}
|
||||
|
||||
|
@ -185,6 +187,16 @@ class SendViewModel @Inject constructor(
|
|||
// TODO: Navigate to the text type send list screen (BIT-1388)
|
||||
sendEvent(SendEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleDeleteSendClick(action: SendAction.DeleteSendClick) {
|
||||
// TODO: Navigate to the text type send list screen (BIT-1388)
|
||||
sendEvent(SendEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
|
||||
private fun handleRemovePasswordClick(action: SendAction.RemovePasswordClick) {
|
||||
// TODO: Navigate to the text type send list screen (BIT-1388)
|
||||
sendEvent(SendEvent.ShowToast("Not yet implemented".asText()))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -227,6 +239,7 @@ data class SendState(
|
|||
val type: Type,
|
||||
val iconList: List<SendStatusIcon>,
|
||||
val shareUrl: String,
|
||||
val hasPassword: Boolean,
|
||||
) : Parcelable {
|
||||
/**
|
||||
* Indicates the type of send this, a text or file.
|
||||
|
@ -345,6 +358,20 @@ sealed class SendAction {
|
|||
val sendItem: SendState.ViewState.Content.SendItem,
|
||||
) : SendAction()
|
||||
|
||||
/**
|
||||
* User clicked the delete item button.
|
||||
*/
|
||||
data class DeleteSendClick(
|
||||
val sendItem: SendState.ViewState.Content.SendItem,
|
||||
) : SendAction()
|
||||
|
||||
/**
|
||||
* User clicked the remove password item button.
|
||||
*/
|
||||
data class RemovePasswordClick(
|
||||
val sendItem: SendState.ViewState.Content.SendItem,
|
||||
) : SendAction()
|
||||
|
||||
/**
|
||||
* Models actions that the [SendViewModel] itself will send.
|
||||
*/
|
||||
|
|
|
@ -15,6 +15,8 @@ data class SendHandlers(
|
|||
val onEditSendClick: (SendState.ViewState.Content.SendItem) -> Unit,
|
||||
val onCopySendClick: (SendState.ViewState.Content.SendItem) -> Unit,
|
||||
val onShareSendClick: (SendState.ViewState.Content.SendItem) -> Unit,
|
||||
val onDeleteSendClick: (SendState.ViewState.Content.SendItem) -> Unit,
|
||||
val onRemovePasswordClick: (SendState.ViewState.Content.SendItem) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
/**
|
||||
|
@ -30,6 +32,10 @@ data class SendHandlers(
|
|||
onEditSendClick = { viewModel.trySendAction(SendAction.SendClick(it)) },
|
||||
onCopySendClick = { viewModel.trySendAction(SendAction.CopyClick(it)) },
|
||||
onShareSendClick = { viewModel.trySendAction(SendAction.ShareClick(it)) },
|
||||
onDeleteSendClick = { viewModel.trySendAction(SendAction.DeleteSendClick(it)) },
|
||||
onRemovePasswordClick = {
|
||||
viewModel.trySendAction(SendAction.RemovePasswordClick(it))
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,6 +54,7 @@ private fun List<SendView>.toSendContent(
|
|||
},
|
||||
),
|
||||
shareUrl = sendView.toSendUrl(baseWebSendUrl),
|
||||
hasPassword = sendView.hasPassword,
|
||||
)
|
||||
}
|
||||
.sortedBy { it.name },
|
||||
|
|
|
@ -470,6 +470,100 @@ class SendScreenTest : BaseComposeTest() {
|
|||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on send item overflow dialog remove password click should send RemovePasswordClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = SendState.ViewState.Content(
|
||||
textTypeCount = 0,
|
||||
fileTypeCount = 1,
|
||||
sendItems = listOf(DEFAULT_SEND_ITEM),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Options")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Remove password")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(SendAction.RemovePasswordClick(DEFAULT_SEND_ITEM))
|
||||
}
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on send item overflow dialog delete click should show confirmation dialog`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = SendState.ViewState.Content(
|
||||
textTypeCount = 0,
|
||||
fileTypeCount = 1,
|
||||
sendItems = listOf(DEFAULT_SEND_ITEM),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Options")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Delete")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.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 DeleteSendClick`() {
|
||||
mutableStateFlow.update {
|
||||
it.copy(
|
||||
viewState = SendState.ViewState.Content(
|
||||
textTypeCount = 0,
|
||||
fileTypeCount = 1,
|
||||
sendItems = listOf(DEFAULT_SEND_ITEM),
|
||||
),
|
||||
)
|
||||
}
|
||||
composeTestRule.assertNoDialogExists()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescription("Options")
|
||||
.assertIsDisplayed()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Delete")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithText("Yes")
|
||||
.assert(hasAnyAncestor(isDialog()))
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(SendAction.DeleteSendClick(DEFAULT_SEND_ITEM))
|
||||
}
|
||||
|
||||
composeTestRule.assertNoDialogExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `on send item overflow dialog cancel click should close the dialog`() {
|
||||
mutableStateFlow.update {
|
||||
|
@ -525,6 +619,7 @@ private val DEFAULT_SEND_ITEM: SendState.ViewState.Content.SendItem =
|
|||
type = SendState.ViewState.Content.SendItem.Type.FILE,
|
||||
iconList = emptyList(),
|
||||
shareUrl = "www.test.com/#/send/mockAccessId-1/mockKey-1",
|
||||
hasPassword = true,
|
||||
)
|
||||
|
||||
private val DEFAULT_CONTENT_VIEW_STATE: SendState.ViewState.Content = SendState.ViewState.Content(
|
||||
|
@ -539,6 +634,7 @@ private val DEFAULT_CONTENT_VIEW_STATE: SendState.ViewState.Content = SendState.
|
|||
type = SendState.ViewState.Content.SendItem.Type.TEXT,
|
||||
iconList = emptyList(),
|
||||
shareUrl = "www.test.com/#/send/mockAccessId-1/mockKey-1",
|
||||
hasPassword = true,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -107,6 +107,26 @@ class SendViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `DeleteSendClick should emit ShowToast`() = runTest {
|
||||
val sendItem = mockk<SendState.ViewState.Content.SendItem>()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SendAction.DeleteSendClick(sendItem))
|
||||
assertEquals(SendEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `RemovePasswordClick should emit ShowToast`() = runTest {
|
||||
val sendItem = mockk<SendState.ViewState.Content.SendItem>()
|
||||
val viewModel = createViewModel()
|
||||
viewModel.eventFlow.test {
|
||||
viewModel.trySendAction(SendAction.RemovePasswordClick(sendItem))
|
||||
assertEquals(SendEvent.ShowToast("Not yet implemented".asText()), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SyncClick should call sync`() {
|
||||
val viewModel = createViewModel()
|
||||
|
|
|
@ -72,6 +72,7 @@ class SendDataExtensionsTest {
|
|||
SendStatusIcon.PENDING_DELETE,
|
||||
),
|
||||
shareUrl = "www.test.com/#/send/mockAccessId-1/mockKey-1",
|
||||
hasPassword = true,
|
||||
),
|
||||
SendState.ViewState.Content.SendItem(
|
||||
id = "mockId-2",
|
||||
|
@ -85,6 +86,7 @@ class SendDataExtensionsTest {
|
|||
SendStatusIcon.PENDING_DELETE,
|
||||
),
|
||||
shareUrl = "www.test.com/#/send/mockAccessId-2/mockKey-2",
|
||||
hasPassword = true,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
Loading…
Reference in a new issue