From 07d3849c4bbe71ab4080d288b13b7636450e7329 Mon Sep 17 00:00:00 2001 From: Patrick Honkonen Date: Wed, 26 Feb 2025 17:10:10 -0500 Subject: [PATCH] Add keys and animation to all content items --- .../feature/item/VaultItemCardContent.kt | 70 ++++++++------ .../feature/item/VaultItemIdentityContent.kt | 84 ++++++++++------- .../feature/item/VaultItemLoginContent.kt | 93 ++++++++++++------- .../item/VaultItemSecureNoteContent.kt | 43 +++++---- .../feature/item/VaultItemSshKeyContent.kt | 59 +++++++----- .../feature/item/component/ItemHeader.kt | 50 +++++----- .../feature/item/VaultItemViewModelTest.kt | 1 - 7 files changed, 245 insertions(+), 155 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt index ef8ecc220a..0c0f3db4a1 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemCardContent.kt @@ -61,7 +61,7 @@ fun VaultItemCardContent( onExpandClick = { isExpanded = !isExpanded }, ) cardState.cardholderName?.let { cardholderName -> - item { + item(key = "cardholderName") { BitwardenTextField( label = stringResource(id = R.string.cardholder_name), value = cardholderName, @@ -77,12 +77,13 @@ fun VaultItemCardContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } cardState.number?.let { numberData -> - item { + item(key = "cardNumber") { BitwardenPasswordField( label = stringResource(id = R.string.number), value = numberData.number, @@ -109,13 +110,14 @@ fun VaultItemCardContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } if (cardState.brand != null && cardState.brand != VaultCardBrand.SELECT) { - item { + item(key = "cardBrand") { BitwardenTextField( label = stringResource(id = R.string.brand), value = cardState.brand.shortName(), @@ -131,13 +133,14 @@ fun VaultItemCardContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } cardState.expiration?.let { expiration -> - item { + item(key = "expiration") { BitwardenTextField( label = stringResource(id = R.string.expiration), value = expiration, @@ -153,13 +156,14 @@ fun VaultItemCardContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } cardState.securityCode?.let { securityCodeData -> - item { + item(key = "securityCodeData_$securityCodeData") { BitwardenPasswordField( label = stringResource(id = R.string.security_code), value = securityCodeData.code, @@ -188,20 +192,22 @@ fun VaultItemCardContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.notes?.let { notes -> - item { + item(key = "notes") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.additional_options), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( @@ -222,58 +228,69 @@ fun VaultItemCardContent( cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { + item(key = "customFieldsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.custom_fields), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) } - items(customFields) { customField -> + itemsIndexed( + items = customFields, + key = { index, _ -> "customField_$index" }, + ) { _, customField -> Spacer(modifier = Modifier.height(height = 8.dp)) CustomField( customField = customField, onCopyCustomHiddenField = - vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, onCopyCustomTextField = - vaultCommonItemTypeHandlers.onCopyCustomTextField, + vaultCommonItemTypeHandlers.onCopyCustomTextField, onShowHiddenFieldClick = - vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> - item { + item(key = "attachmentsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.attachments), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> + itemsIndexed( + items = attachments, + key = { index, _ -> "attachment_$index" }, + ) { index, attachmentItem -> AttachmentItemContent( modifier = Modifier .testTag("CipherAttachment") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), attachmentItem = attachmentItem, onAttachmentDownloadClick = vaultCommonItemTypeHandlers .onAttachmentDownloadClick, @@ -282,7 +299,7 @@ fun VaultItemCardContent( } } - item { + item(key = "lastUpdated") { Spacer(modifier = Modifier.height(height = 16.dp)) VaultItemUpdateText( header = "${stringResource(id = R.string.date_updated)}: ", @@ -290,7 +307,8 @@ fun VaultItemCardContent( modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 12.dp), + .padding(horizontal = 12.dp) + .animateItem(), ) } item { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt index 1501b079e4..72069334f8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemIdentityContent.kt @@ -60,7 +60,7 @@ fun VaultItemIdentityContent( onExpandClick = { isExpanded = !isExpanded }, ) identityState.identityName?.let { identityName -> - item { + item(key = "identityName") { IdentityCopyField( label = stringResource(id = R.string.identity_name), value = identityName, @@ -76,12 +76,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.username?.let { username -> - item { + item(key = "username") { IdentityCopyField( label = stringResource(id = R.string.username), value = username, @@ -97,12 +98,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.company?.let { company -> - item { + item(key = "company") { IdentityCopyField( label = stringResource(id = R.string.company), value = company, @@ -118,12 +120,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.ssn?.let { ssn -> - item { + item(key = "ssn") { IdentityCopyField( label = stringResource(id = R.string.ssn), value = ssn, @@ -139,12 +142,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.passportNumber?.let { passportNumber -> - item { + item(key = "passportNumber") { IdentityCopyField( label = stringResource(id = R.string.passport_number), value = passportNumber, @@ -160,12 +164,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.licenseNumber?.let { licenseNumber -> - item { + item(key = "licenseNumber") { IdentityCopyField( label = stringResource(id = R.string.license_number), value = licenseNumber, @@ -181,12 +186,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.email?.let { email -> - item { + item(key = "email") { IdentityCopyField( label = stringResource(id = R.string.email), value = email, @@ -202,12 +208,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.phone?.let { phone -> - item { + item(key = "phone") { IdentityCopyField( label = stringResource(id = R.string.phone), value = phone, @@ -223,12 +230,13 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } identityState.address?.let { address -> - item { + item(key = "address") { IdentityCopyField( label = stringResource(id = R.string.address), value = address, @@ -244,19 +252,21 @@ fun VaultItemIdentityContent( ), modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.notes?.let { notes -> - item { + item(key = "notes") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.additional_options), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(8.dp)) IdentityCopyField( @@ -269,23 +279,28 @@ fun VaultItemIdentityContent( cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { + item(key = "customFieldsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.custom_fields), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) } - items(customFields) { customField -> + items( + items = customFields, + key = { "customField_$it" }, + ) { customField -> Spacer(modifier = Modifier.height(height = 8.dp)) CustomField( customField = customField, @@ -295,29 +310,35 @@ fun VaultItemIdentityContent( cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> - item { + item(key = "attachmentsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.attachments), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> + itemsIndexed( + items = attachments, + key = { index, _ -> "attachment_$index" }, + ) { index, attachmentItem -> AttachmentItemContent( modifier = Modifier .testTag("CipherAttachment") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), attachmentItem = attachmentItem, onAttachmentDownloadClick = vaultCommonItemTypeHandlers .onAttachmentDownloadClick, @@ -326,7 +347,7 @@ fun VaultItemIdentityContent( } } - item { + item(key = "lastUpdated") { Spacer(modifier = Modifier.height(height = 16.dp)) VaultItemUpdateText( header = "${stringResource(id = R.string.date_updated)}: ", @@ -334,7 +355,8 @@ fun VaultItemIdentityContent( modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 12.dp), + .padding(horizontal = 12.dp) + .animateItem(), ) } item { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt index fea1811028..ee43beb1e3 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt @@ -72,21 +72,22 @@ fun VaultItemLoginContent( onExpandClick = { isExpanded = !isExpanded }, ) if (loginItemState.hasLoginCredentials) { - item { + item(key = "loginCredentialsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.login_credentials), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } } loginItemState.username?.let { username -> - item { + item(key = "username") { UsernameField( username = username, onCopyUsernameClick = vaultLoginItemTypeHandlers.onCopyUsernameClick, @@ -96,13 +97,14 @@ fun VaultItemLoginContent( ?: CardStyle.Full, modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } loginItemState.passwordData?.let { passwordData -> - item { + item(key = "passwordData") { PasswordField( passwordData = passwordData, onShowPasswordClick = vaultLoginItemTypeHandlers.onShowPasswordClick, @@ -114,25 +116,27 @@ fun VaultItemLoginContent( ?: CardStyle.Full, modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } loginItemState.fido2CredentialCreationDateText?.let { creationDate -> - item { + item(key = "creationDate") { Spacer(modifier = Modifier.height(8.dp)) Fido2CredentialField( creationDate = creationDate(), modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } loginItemState.totpCodeItemData?.let { totpCodeItemData -> - item { + item(key = "totpCode") { Spacer(modifier = Modifier.height(8.dp)) TotpField( totpCodeItemData = totpCodeItemData, @@ -142,25 +146,30 @@ fun VaultItemLoginContent( .onAuthenticatorHelpToolTipClick, modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } loginItemState.uris.takeUnless { it.isEmpty() }?.let { uris -> - item { + item(key = "urisHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.autofill_options), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(uris) { index, uriData -> + itemsIndexed( + items = uris, + key = { index, _ -> "uri_$index" }, + ) { index, uriData -> UriField( uriData = uriData, onCopyUriClick = vaultLoginItemTypeHandlers.onCopyUriClick, @@ -168,20 +177,22 @@ fun VaultItemLoginContent( cardStyle = uris.toListItemCardStyle(index = index, dividerPadding = 0.dp), modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } commonState.notes?.let { notes -> - item { + item(key = "notes") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.additional_options), modifier = Modifier .standardHorizontalMargin() .padding(horizontal = 16.dp) - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) Spacer(modifier = Modifier.height(8.dp)) NotesField( @@ -189,57 +200,68 @@ fun VaultItemLoginContent( onCopyAction = vaultCommonItemTypeHandlers.onCopyNotesClick, modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { + item(key = "customFieldsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.custom_fields), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) } - items(customFields) { customField -> + itemsIndexed( + items = customFields, + key = { index, _ -> "customField_$index" }, + ) { _, customField -> Spacer(modifier = Modifier.height(height = 8.dp)) CustomField( customField = customField, onCopyCustomHiddenField = - vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, onCopyCustomTextField = - vaultCommonItemTypeHandlers.onCopyCustomTextField, + vaultCommonItemTypeHandlers.onCopyCustomTextField, onShowHiddenFieldClick = - vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, cardStyle = CardStyle.Full, modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } } commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> - item { + item(key = "attachmentsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.attachments), modifier = Modifier .standardHorizontalMargin() .padding(horizontal = 16.dp) - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> + itemsIndexed( + items = attachments, + key = { index, _ -> "attachment_$index" }, + ) { index, attachmentItem -> AttachmentItemContent( modifier = Modifier .standardHorizontalMargin() - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), attachmentItem = attachmentItem, cardStyle = attachments.toListItemCardStyle(index = index), onAttachmentDownloadClick = vaultCommonItemTypeHandlers @@ -248,7 +270,7 @@ fun VaultItemLoginContent( } } - item { + item(key = "lastUpdated") { Spacer(modifier = Modifier.height(16.dp)) VaultItemUpdateText( header = "${stringResource(id = R.string.date_updated)}: ", @@ -256,12 +278,13 @@ fun VaultItemLoginContent( modifier = Modifier .standardHorizontalMargin() .padding(horizontal = 12.dp) - .fillMaxWidth(), + .fillMaxWidth() + .animateItem(), ) } loginItemState.passwordRevisionDate?.let { revisionDate -> - item { + item(key = "revisionDate") { Spacer(modifier = Modifier.height(height = 4.dp)) VaultItemUpdateText( header = "${stringResource(id = R.string.date_password_updated)}: ", @@ -269,13 +292,14 @@ fun VaultItemLoginContent( modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 12.dp), + .padding(horizontal = 12.dp) + .animateItem(), ) } } loginItemState.passwordHistoryCount?.let { passwordHistoryCount -> - item { + item(key = "passwordHistoryCount") { Spacer(modifier = Modifier.height(height = 4.dp)) BitwardenHyperTextLink( annotatedResId = R.string.password_history_count, @@ -287,7 +311,8 @@ fun VaultItemLoginContent( modifier = Modifier .wrapContentWidth() .standardHorizontalMargin() - .padding(horizontal = 12.dp), + .padding(horizontal = 12.dp) + .animateItem(), ) } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt index ea32ae974f..99039af9bc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemSecureNoteContent.kt @@ -7,7 +7,6 @@ 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.foundation.lazy.itemsIndexed import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -58,7 +57,7 @@ fun VaultItemSecureNoteContent( onExpandClick = { isExpanded = !isExpanded }, ) commonState.notes?.let { notes -> - item { + item(key = "notes") { Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( label = stringResource(id = R.string.notes), @@ -78,59 +77,70 @@ fun VaultItemSecureNoteContent( cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { + item(key = "customFieldsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.custom_fields), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) } - items(customFields) { customField -> + itemsIndexed( + items = customFields, + key = { index, _ -> "customField_$index" }, + ) { _, customField -> Spacer(modifier = Modifier.height(height = 8.dp)) CustomField( customField = customField, onCopyCustomHiddenField = - vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, onCopyCustomTextField = - vaultCommonItemTypeHandlers.onCopyCustomTextField, + vaultCommonItemTypeHandlers.onCopyCustomTextField, onShowHiddenFieldClick = - vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> - item { + item(key = "attachmentsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.attachments), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> + itemsIndexed( + items = attachments, + key = { index, _ -> "attachment_$index" }, + ) { index, attachmentItem -> AttachmentItemContent( modifier = Modifier .testTag("CipherAttachment") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), attachmentItem = attachmentItem, onAttachmentDownloadClick = vaultCommonItemTypeHandlers .onAttachmentDownloadClick, @@ -139,14 +149,15 @@ fun VaultItemSecureNoteContent( } } - item { + item(key = "lastUpdated") { Spacer(modifier = Modifier.height(height = 16.dp)) Row( modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() .padding(horizontal = 12.dp) - .semantics(mergeDescendants = true) { }, + .semantics(mergeDescendants = true) { } + .animateItem(), ) { Text( text = "${stringResource(id = R.string.date_updated)}: ", 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 729a043a2f..f9dc346f1e 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,7 +6,6 @@ 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.foundation.lazy.itemsIndexed import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -58,7 +57,7 @@ fun VaultItemSshKeyContent( isExpanded = isExpanded, onExpandClick = { isExpanded = !isExpanded }, ) - item { + item(key = "publicKey") { Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( label = stringResource(id = R.string.public_key), @@ -78,11 +77,12 @@ fun VaultItemSshKeyContent( modifier = Modifier .testTag("SshKeyItemPublicKeyEntry") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } - item { + item(key = "privateKey") { BitwardenPasswordField( label = stringResource(id = R.string.private_key), value = sshKeyItemState.privateKey, @@ -104,11 +104,12 @@ fun VaultItemSshKeyContent( modifier = Modifier .testTag("SshKeyItemPrivateKeyEntry") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } - item { + item(key = "fingerprint") { BitwardenTextField( label = stringResource(id = R.string.fingerprint), value = sshKeyItemState.fingerprint, @@ -127,19 +128,21 @@ fun VaultItemSshKeyContent( modifier = Modifier .testTag("SshKeyItemFingerprintEntry") .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } commonState.notes?.let { notes -> - item { + item(key = "notes") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.additional_options), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( @@ -160,57 +163,68 @@ fun VaultItemSshKeyContent( cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { + item(key = "customFieldsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.custom_fields), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) } - items(customFields) { customField -> + itemsIndexed( + items = customFields, + key = { index, _ -> "customField_$index" }, + ) { _, customField -> Spacer(modifier = Modifier.height(height = 8.dp)) CustomField( customField = customField, onCopyCustomHiddenField = - vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, onCopyCustomTextField = - vaultCommonItemTypeHandlers.onCopyCustomTextField, + vaultCommonItemTypeHandlers.onCopyCustomTextField, onShowHiddenFieldClick = - vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, cardStyle = CardStyle.Full, modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), ) } } commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> - item { + item(key = "attachmentsHeader") { Spacer(modifier = Modifier.height(height = 16.dp)) BitwardenListHeaderText( label = stringResource(id = R.string.attachments), modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 16.dp) + .animateItem(), ) Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> + itemsIndexed( + items = attachments, + key = { index, _ -> "attachment_$index" }, + ) { index, attachmentItem -> AttachmentItemContent( modifier = Modifier .fillMaxWidth() - .standardHorizontalMargin(), + .standardHorizontalMargin() + .animateItem(), attachmentItem = attachmentItem, onAttachmentDownloadClick = vaultCommonItemTypeHandlers .onAttachmentDownloadClick, @@ -219,7 +233,7 @@ fun VaultItemSshKeyContent( } } - item { + item(key = "lastUpdated") { Spacer(modifier = Modifier.height(height = 16.dp)) VaultItemUpdateText( header = "${stringResource(id = R.string.date_updated)}: ", @@ -228,6 +242,7 @@ fun VaultItemSshKeyContent( .fillMaxWidth() .standardHorizontalMargin() .padding(horizontal = 12.dp) + .animateItem() .testTag("SshKeyItemLastUpdated"), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/component/ItemHeader.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/component/ItemHeader.kt index ed3d999357..ad1f8d1fcb 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/component/ItemHeader.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/component/ItemHeader.kt @@ -46,7 +46,7 @@ private const val EXPANDABLE_THRESHOLD = 2 /** * Reusable composable for displaying the cipher name, favorite status, and related locations. */ -@Suppress("LongMethod") +@Suppress("LongMethod", "LongParameterList") fun LazyListScope.itemHeader( value: String, isFavorite: Boolean, @@ -256,9 +256,9 @@ private fun LazyItemScope.ItemLocationListItem( } //region Previews -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -//private fun ItemHeader_LocalIcon_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +// private fun ItemHeader_LocalIcon_Preview() { // BitwardenTheme { // ItemHeader( // value = "Login without favicon", @@ -269,11 +269,11 @@ private fun LazyItemScope.ItemLocationListItem( // relatedLocations = persistentListOf(), // ) // } -//} +// } // -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -//private fun ItemHeader_NetworkIcon_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +// private fun ItemHeader_NetworkIcon_Preview() { // BitwardenTheme { // ItemHeader( // value = "Login with favicon", @@ -285,11 +285,11 @@ private fun LazyItemScope.ItemLocationListItem( // relatedLocations = persistentListOf(), // ) // } -//} +// } // -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) -//private fun ItemHeader_Organization_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) +// private fun ItemHeader_Organization_Preview() { // BitwardenTheme { // ItemHeader( // value = "Login without favicon", @@ -302,11 +302,11 @@ private fun LazyItemScope.ItemLocationListItem( // ), // ) // } -//} +// } // -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -//private fun ItemNameField_Org_SingleCollection_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +// private fun ItemNameField_Org_SingleCollection_Preview() { // BitwardenTheme { // ItemHeader( // value = "Login without favicon", @@ -320,11 +320,11 @@ private fun LazyItemScope.ItemLocationListItem( // ), // ) // } -//} +// } // -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -//private fun ItemNameField_Org_MultiCollection_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +// private fun ItemNameField_Org_MultiCollection_Preview() { // BitwardenTheme { // ItemHeader( // value = "Login without favicon", @@ -339,11 +339,11 @@ private fun LazyItemScope.ItemLocationListItem( // ), // ) // } -//} +// } // -//@Composable -//@Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) -//private fun ItemNameField_Org_SingleCollection_Folder_Preview() { +// @Composable +// @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) +// private fun ItemNameField_Org_SingleCollection_Folder_Preview() { // BitwardenTheme { // ItemHeader( // value = "Note without favicon", @@ -358,5 +358,5 @@ private fun LazyItemScope.ItemLocationListItem( // ), // ) // } -//} +// } //endregion Previews diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt index 24aaefef43..816324e229 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt @@ -3341,7 +3341,6 @@ class VaultItemViewModelTest : BaseViewModelTest() { shouldManageResetPassword = false, shouldUseKeyConnector = false, role = OrganizationType.OWNER, - shouldManagePolicies = false, ), ), ),