From f09bfec2e1af46f5153cf120122de2684752eba4 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Thu, 21 Nov 2024 23:59:43 -0500 Subject: [PATCH] [PM-15116] Add common vault item content to SSH keys Add common vault item content (notes, custom fields, and attachments) to the SSH key content screen. --- .../ui/vault/feature/item/VaultItemScreen.kt | 1 + .../feature/item/VaultItemSshKeyContent.kt | 82 +++++++++++++++++++ .../vault/feature/item/VaultItemScreenTest.kt | 76 +++++++++++------ 3 files changed, 136 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt index 1189bc38a16..c411e9e9f24 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreen.kt @@ -404,6 +404,7 @@ private fun VaultItemContent( VaultItemSshKeyContent( commonState = viewState.common, sshKeyItemState = viewState.type, + vaultCommonItemTypeHandlers = vaultCommonItemTypeHandlers, vaultSshKeyItemTypeHandlers = vaultSshKeyItemTypeHandlers, modifier = modifier, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt index fcd744aaae6..c270087bdf2 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSshKeyContent.kt @@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.platform.testTag @@ -18,6 +19,7 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextFieldWithActions import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText +import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultCommonItemTypeHandlers import com.x8bit.bitwarden.ui.vault.feature.item.handlers.VaultSshKeyItemTypeHandlers /** @@ -29,6 +31,7 @@ fun VaultItemSshKeyContent( commonState: VaultItemState.ViewState.Content.Common, sshKeyItemState: VaultItemState.ViewState.Content.ItemType.SshKey, vaultSshKeyItemTypeHandlers: VaultSshKeyItemTypeHandlers, + vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers, modifier: Modifier = Modifier, ) { LazyColumn(modifier = modifier) { @@ -120,6 +123,85 @@ fun VaultItemSshKeyContent( ) } + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(4.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.notes), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextFieldWithActions( + label = stringResource(id = R.string.notes), + value = notes, + onValueChange = { }, + readOnly = true, + singleLine = false, + actions = { + BitwardenTonalIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_notes), + onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, + modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), + ) + }, + textFieldTestTag = "CipherNotesLabel", + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } + + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(4.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + items(customFields) { customField -> + Spacer(modifier = Modifier.height(8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } + + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(4.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + items(attachments) { attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .fillMaxWidth() + .padding(start = 16.dp), + attachmentItem = attachmentItem, + onAttachmentDownloadClick = + vaultCommonItemTypeHandlers.onAttachmentDownloadClick, + ) + } + item { Spacer(modifier = Modifier.height(8.dp)) } + } + item { Spacer(modifier = Modifier.height(24.dp)) VaultItemUpdateText( diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index f63b9056168..3cfe234bea3 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -1366,6 +1366,42 @@ class VaultItemScreenTest : BaseComposeTest() { viewModel.trySendAction(VaultItemAction.Common.CopyNotesClick) } } + + @Test + fun `on ssh key copy notes field click should send CopyNotesClick`() { + // Adding a custom field so that we can scroll to it + // So we can see the Copy notes button but not have it covered by the FAB + val textField = VaultItemState.ViewState.Content.Common.Custom.TextField( + name = "text", + value = "value", + isCopyable = true, + ) + + EMPTY_VIEW_STATES + .forEach { typeState -> + mutableStateFlow.update { currentState -> + currentState.copy( + viewState = typeState.copy( + type = DEFAULT_SSH_KEY, + common = EMPTY_COMMON.copy( + notes = "this is a note", + customFields = listOf(textField), + ), + ), + ) + } + } + + composeTestRule.onNodeWithTextAfterScroll(textField.name) + + composeTestRule + .onNodeWithTag("CipherNotesCopyButton") + .performClick() + + verify { + viewModel.trySendAction(VaultItemAction.Common.CopyNotesClick) + } + } //endregion common //region login @@ -2602,29 +2638,6 @@ private fun updateCardType( return currentState.copy(viewState = updatedType) } -private fun updateSshKeyType( - currentState: VaultItemState, - transform: VaultItemState.ViewState.Content.ItemType.SshKey.() -> - VaultItemState.ViewState.Content.ItemType.SshKey, -): VaultItemState { - val updatedType = when (val viewState = currentState.viewState) { - is VaultItemState.ViewState.Content -> { - when (val type = viewState.type) { - is VaultItemState.ViewState.Content.ItemType.SshKey -> { - viewState.copy( - type = type.transform(), - ) - } - - else -> viewState - } - } - - else -> viewState - } - return currentState.copy(viewState = updatedType) -} - private fun updateCommonContent( currentState: VaultItemState, transform: VaultItemState.ViewState.Content.Common.() @@ -2811,6 +2824,15 @@ private val EMPTY_CARD_TYPE: VaultItemState.ViewState.Content.ItemType.Card = ), ) +private val EMPTY_SSH_KEY_TYPE: VaultItemState.ViewState.Content.ItemType.SshKey = + VaultItemState.ViewState.Content.ItemType.SshKey( + name = "", + publicKey = "", + privateKey = "", + fingerprint = "", + showPrivateKey = false, + ) + private val EMPTY_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content = VaultItemState.ViewState.Content( common = EMPTY_COMMON, @@ -2835,6 +2857,12 @@ private val EMPTY_SECURE_NOTE_VIEW_STATE = type = VaultItemState.ViewState.Content.ItemType.SecureNote, ) +private val EMPTY_SSH_KEY_VIEW_STATE = + VaultItemState.ViewState.Content( + common = EMPTY_COMMON, + type = EMPTY_SSH_KEY_TYPE, + ) + private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content = VaultItemState.ViewState.Content( type = DEFAULT_LOGIN, @@ -2869,10 +2897,12 @@ private val EMPTY_VIEW_STATES = listOf( EMPTY_LOGIN_VIEW_STATE, EMPTY_IDENTITY_VIEW_STATE, EMPTY_SECURE_NOTE_VIEW_STATE, + EMPTY_SSH_KEY_VIEW_STATE, ) private val DEFAULT_VIEW_STATES = listOf( DEFAULT_LOGIN_VIEW_STATE, DEFAULT_IDENTITY_VIEW_STATE, DEFAULT_SECURE_NOTE_VIEW_STATE, + DEFAULT_SSH_KEY_VIEW_STATE, )