diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt index b07e80f5ce2..c2f78bc84e8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/header/BitwardenExpandingHeader.kt @@ -1,6 +1,9 @@ package com.x8bit.bitwarden.ui.platform.components.header +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.AnimationConstants import androidx.compose.animation.core.animateFloatAsState +import androidx.compose.animation.core.tween import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.padding @@ -50,23 +53,46 @@ fun BitwardenExpandingHeader( .semantics(mergeDescendants = true) {}, verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = if (isExpanded) expandedText else collapsedText, - color = BitwardenTheme.colorScheme.text.interaction, - style = BitwardenTheme.typography.labelLarge, - modifier = Modifier.padding(end = 8.dp), - ) - if (showExpansionIndicator) { - val iconRotationDegrees = animateFloatAsState( - targetValue = if (isExpanded) 0f else 180f, - label = "expanderIconRotationAnimation", - ) - Icon( - painter = rememberVectorPainter(id = R.drawable.ic_chevron_up_small), - contentDescription = null, - tint = BitwardenTheme.colorScheme.icon.secondary, - modifier = Modifier.rotate(degrees = iconRotationDegrees.value), - ) + Crossfade( + targetState = isExpanded, + label = "BitwardenExpandingHeaderTitle_animation", + // Make the animation shorter when the text is the same to avoid crossfading the same + // text. + animationSpec = tween( + durationMillis = if (expandedText != collapsedText) { + AnimationConstants.DefaultDurationMillis + } else { + 0 + }, + ), + ) { expanded -> + if (expanded) { + Text( + text = expandedText, + color = BitwardenTheme.colorScheme.text.interaction, + style = BitwardenTheme.typography.labelLarge, + modifier = Modifier.padding(end = 8.dp), + ) + } else { + Text( + text = collapsedText, + color = BitwardenTheme.colorScheme.text.interaction, + style = BitwardenTheme.typography.labelLarge, + modifier = Modifier.padding(end = 8.dp), + ) + } + if (showExpansionIndicator) { + val iconRotationDegrees = animateFloatAsState( + targetValue = if (isExpanded) 0f else 180f, + label = "expanderIconRotationAnimation", + ) + Icon( + painter = rememberVectorPainter(id = R.drawable.ic_chevron_up_small), + contentDescription = null, + tint = BitwardenTheme.colorScheme.icon.secondary, + modifier = Modifier.rotate(degrees = iconRotationDegrees.value), + ) + } } } } 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 ab08298838f..b0bcdd3b1b4 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.item +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -41,253 +42,254 @@ fun VaultItemCardContent( vaultCardItemTypeHandlers: VaultCardItemTypeHandlers, modifier: Modifier = Modifier, ) { - LazyColumn(modifier = modifier) { - item { - Spacer(modifier = Modifier.height(height = 12.dp)) - ItemHeader( - value = commonState.name, - isFavorite = commonState.favorite, - iconData = commonState.iconData, - relatedLocations = commonState.relatedLocations, - iconTestTag = "CardItemNameIcon", - textFieldTestTag = "CardItemNameEntry", - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - cardState.cardholderName?.let { cardholderName -> - item { - BitwardenTextField( - label = stringResource(id = R.string.cardholder_name), - value = cardholderName, - onValueChange = {}, - readOnly = true, - singleLine = false, - textFieldTestTag = "CardholderNameEntry", - cardStyle = cardState - .propertyList - .toListItemCardStyle( - index = cardState.propertyList.indexOf(element = cardholderName), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + Column(modifier = modifier) { + Spacer(Modifier.height(height = 12.dp)) + ItemHeader( + value = commonState.name, + isFavorite = commonState.favorite, + iconData = commonState.iconData, + relatedLocations = commonState.relatedLocations, + iconTestTag = "CardItemNameIcon", + textFieldTestTag = "CardItemNameEntry", + ) + LazyColumn(modifier = modifier) { + cardState.cardholderName?.let { cardholderName -> + item { + BitwardenTextField( + label = stringResource(id = R.string.cardholder_name), + value = cardholderName, + onValueChange = {}, + readOnly = true, + singleLine = false, + textFieldTestTag = "CardholderNameEntry", + cardStyle = cardState + .propertyList + .toListItemCardStyle( + index = cardState.propertyList.indexOf(element = cardholderName), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - cardState.number?.let { numberData -> - item { - BitwardenPasswordField( - label = stringResource(id = R.string.number), - value = numberData.number, - onValueChange = {}, - showPassword = numberData.isVisible, - showPasswordChange = vaultCardItemTypeHandlers.onShowNumberClick, - readOnly = true, - singleLine = false, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_number), - onClick = vaultCardItemTypeHandlers.onCopyNumberClick, - modifier = Modifier.testTag(tag = "CardCopyNumberButton"), - ) - }, - passwordFieldTestTag = "CardNumberEntry", - showPasswordTestTag = "CardViewNumberButton", - cardStyle = cardState - .propertyList - .toListItemCardStyle( - index = cardState.propertyList.indexOf(element = numberData), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + cardState.number?.let { numberData -> + item { + BitwardenPasswordField( + label = stringResource(id = R.string.number), + value = numberData.number, + onValueChange = {}, + showPassword = numberData.isVisible, + showPasswordChange = vaultCardItemTypeHandlers.onShowNumberClick, + readOnly = true, + singleLine = false, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_number), + onClick = vaultCardItemTypeHandlers.onCopyNumberClick, + modifier = Modifier.testTag(tag = "CardCopyNumberButton"), + ) + }, + passwordFieldTestTag = "CardNumberEntry", + showPasswordTestTag = "CardViewNumberButton", + cardStyle = cardState + .propertyList + .toListItemCardStyle( + index = cardState.propertyList.indexOf(element = numberData), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - if (cardState.brand != null && cardState.brand != VaultCardBrand.SELECT) { - item { - BitwardenTextField( - label = stringResource(id = R.string.brand), - value = cardState.brand.shortName(), - onValueChange = {}, - readOnly = true, - singleLine = false, - textFieldTestTag = "CardBrandEntry", - cardStyle = cardState - .propertyList - .toListItemCardStyle( - index = cardState.propertyList.indexOf(element = cardState.brand), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + if (cardState.brand != null && cardState.brand != VaultCardBrand.SELECT) { + item { + BitwardenTextField( + label = stringResource(id = R.string.brand), + value = cardState.brand.shortName(), + onValueChange = {}, + readOnly = true, + singleLine = false, + textFieldTestTag = "CardBrandEntry", + cardStyle = cardState + .propertyList + .toListItemCardStyle( + index = cardState.propertyList.indexOf(element = cardState.brand), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - cardState.expiration?.let { expiration -> - item { - BitwardenTextField( - label = stringResource(id = R.string.expiration), - value = expiration, - onValueChange = {}, - readOnly = true, - singleLine = false, - textFieldTestTag = "CardExpirationEntry", - cardStyle = cardState - .propertyList - .toListItemCardStyle( - index = cardState.propertyList.indexOf(element = expiration), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + cardState.expiration?.let { expiration -> + item { + BitwardenTextField( + label = stringResource(id = R.string.expiration), + value = expiration, + onValueChange = {}, + readOnly = true, + singleLine = false, + textFieldTestTag = "CardExpirationEntry", + cardStyle = cardState + .propertyList + .toListItemCardStyle( + index = cardState.propertyList.indexOf(element = expiration), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - cardState.securityCode?.let { securityCodeData -> - item { - BitwardenPasswordField( - label = stringResource(id = R.string.security_code), - value = securityCodeData.code, - onValueChange = {}, - showPassword = securityCodeData.isVisible, - showPasswordChange = vaultCardItemTypeHandlers.onShowSecurityCodeClick, - readOnly = true, - singleLine = false, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_security_code), - onClick = vaultCardItemTypeHandlers.onCopySecurityCodeClick, - modifier = Modifier.testTag(tag = "CardCopySecurityCodeButton"), - ) - }, - showPasswordTestTag = "CardViewSecurityCodeButton", - passwordFieldTestTag = "CardSecurityCodeEntry", - cardStyle = cardState - .propertyList - .toListItemCardStyle( - index = cardState.propertyList.indexOf(element = securityCodeData), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + cardState.securityCode?.let { securityCodeData -> + item { + BitwardenPasswordField( + label = stringResource(id = R.string.security_code), + value = securityCodeData.code, + onValueChange = {}, + showPassword = securityCodeData.isVisible, + showPasswordChange = vaultCardItemTypeHandlers.onShowSecurityCodeClick, + readOnly = true, + singleLine = false, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource( + id = R.string.copy_security_code, + ), + onClick = vaultCardItemTypeHandlers.onCopySecurityCodeClick, + modifier = Modifier.testTag(tag = "CardCopySecurityCodeButton"), + ) + }, + showPasswordTestTag = "CardViewSecurityCodeButton", + passwordFieldTestTag = "CardSecurityCodeEntry", + cardStyle = cardState + .propertyList + .toListItemCardStyle( + index = cardState.propertyList.indexOf(element = securityCodeData), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - commonState.notes?.let { notes -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.additional_options), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - label = stringResource(id = R.string.notes), - value = notes, - onValueChange = { }, - readOnly = true, - singleLine = false, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_notes), - onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, - modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), - ) - }, - textFieldTestTag = "CipherNotesLabel", - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.additional_options), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.notes), + value = notes, + onValueChange = { }, + readOnly = true, + singleLine = false, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_notes), + onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, + modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), + ) + }, + textFieldTestTag = "CipherNotesLabel", + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + } + items(customFields) { customField -> + Spacer(modifier = Modifier.height(height = 8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = + vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - items(customFields) { customField -> - Spacer(modifier = Modifier.height(height = 8.dp)) - CustomField( - customField = customField, - onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, - onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, - onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } + itemsIndexed(attachments) { index, attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .testTag("CipherAttachment") + .fillMaxWidth() + .standardHorizontalMargin(), + attachmentItem = attachmentItem, + onAttachmentDownloadClick = vaultCommonItemTypeHandlers + .onAttachmentDownloadClick, + cardStyle = attachments.toListItemCardStyle(index = index), + ) + } } - } - commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> item { Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.attachments), + VaultItemUpdateText( + header = "${stringResource(id = R.string.date_updated)}: ", + text = commonState.lastUpdated, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 12.dp), ) - Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> - AttachmentItemContent( - modifier = Modifier - .testTag("CipherAttachment") - .fillMaxWidth() - .standardHorizontalMargin(), - attachmentItem = attachmentItem, - onAttachmentDownloadClick = vaultCommonItemTypeHandlers - .onAttachmentDownloadClick, - cardStyle = attachments.toListItemCardStyle(index = index), - ) + item { + Spacer(modifier = Modifier.height(88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } - - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - VaultItemUpdateText( - header = "${stringResource(id = R.string.date_updated)}: ", - text = commonState.lastUpdated, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp), - ) - } - item { - Spacer(modifier = Modifier.height(88.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } } } 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 5aca1a9beda..cabc7f601f1 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.item +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -38,304 +39,303 @@ fun VaultItemIdentityContent( vaultIdentityItemTypeHandlers: VaultIdentityItemTypeHandlers, modifier: Modifier = Modifier, ) { - LazyColumn(modifier = modifier) { - item { - Spacer(modifier = Modifier.height(height = 12.dp)) - ItemHeader( - value = commonState.name, - isFavorite = commonState.favorite, - iconData = commonState.iconData, - relatedLocations = commonState.relatedLocations, - iconTestTag = "IdentityItemNameIcon", - textFieldTestTag = "IdentityItemNameEntry", - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - identityState.identityName?.let { identityName -> - item { - IdentityCopyField( - label = stringResource(id = R.string.identity_name), - value = identityName, - copyContentDescription = stringResource(id = R.string.copy_identity_name), - textFieldTestTag = "IdentityNameEntry", - copyActionTestTag = "IdentityCopyNameButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyIdentityNameClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = identityName), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + Column(modifier = modifier) { + Spacer(Modifier.height(height = 12.dp)) + ItemHeader( + value = commonState.name, + isFavorite = commonState.favorite, + iconData = commonState.iconData, + relatedLocations = commonState.relatedLocations, + iconTestTag = "IdentityItemNameIcon", + textFieldTestTag = "IdentityItemNameEntry", + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + LazyColumn { + identityState.identityName?.let { identityName -> + item { + IdentityCopyField( + label = stringResource(id = R.string.identity_name), + value = identityName, + copyContentDescription = stringResource(id = R.string.copy_identity_name), + textFieldTestTag = "IdentityNameEntry", + copyActionTestTag = "IdentityCopyNameButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyIdentityNameClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = identityName), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.username?.let { username -> - item { - IdentityCopyField( - label = stringResource(id = R.string.username), - value = username, - copyContentDescription = stringResource(id = R.string.copy_username), - textFieldTestTag = "IdentityUsernameEntry", - copyActionTestTag = "IdentityCopyUsernameButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyUsernameClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = username), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.username?.let { username -> + item { + IdentityCopyField( + label = stringResource(id = R.string.username), + value = username, + copyContentDescription = stringResource(id = R.string.copy_username), + textFieldTestTag = "IdentityUsernameEntry", + copyActionTestTag = "IdentityCopyUsernameButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyUsernameClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = username), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.company?.let { company -> - item { - IdentityCopyField( - label = stringResource(id = R.string.company), - value = company, - copyContentDescription = stringResource(id = R.string.copy_company), - textFieldTestTag = "IdentityCompanyEntry", - copyActionTestTag = "IdentityCopyCompanyButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyCompanyClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = company), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.company?.let { company -> + item { + IdentityCopyField( + label = stringResource(id = R.string.company), + value = company, + copyContentDescription = stringResource(id = R.string.copy_company), + textFieldTestTag = "IdentityCompanyEntry", + copyActionTestTag = "IdentityCopyCompanyButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyCompanyClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = company), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.ssn?.let { ssn -> - item { - IdentityCopyField( - label = stringResource(id = R.string.ssn), - value = ssn, - copyContentDescription = stringResource(id = R.string.copy_ssn), - textFieldTestTag = "IdentitySsnEntry", - copyActionTestTag = "IdentityCopySsnButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopySsnClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = ssn), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.ssn?.let { ssn -> + item { + IdentityCopyField( + label = stringResource(id = R.string.ssn), + value = ssn, + copyContentDescription = stringResource(id = R.string.copy_ssn), + textFieldTestTag = "IdentitySsnEntry", + copyActionTestTag = "IdentityCopySsnButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopySsnClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = ssn), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.passportNumber?.let { passportNumber -> - item { - IdentityCopyField( - label = stringResource(id = R.string.passport_number), - value = passportNumber, - copyContentDescription = stringResource(id = R.string.copy_passport_number), - textFieldTestTag = "IdentityPassportNumberEntry", - copyActionTestTag = "IdentityCopyPassportNumberButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyPassportNumberClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = passportNumber), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.passportNumber?.let { passportNumber -> + item { + IdentityCopyField( + label = stringResource(id = R.string.passport_number), + value = passportNumber, + copyContentDescription = stringResource(id = R.string.copy_passport_number), + textFieldTestTag = "IdentityPassportNumberEntry", + copyActionTestTag = "IdentityCopyPassportNumberButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyPassportNumberClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = passportNumber), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.licenseNumber?.let { licenseNumber -> - item { - IdentityCopyField( - label = stringResource(id = R.string.license_number), - value = licenseNumber, - copyContentDescription = stringResource(id = R.string.copy_license_number), - textFieldTestTag = "IdentityLicenseNumberEntry", - copyActionTestTag = "IdentityCopyLicenseNumberButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyLicenseNumberClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = licenseNumber), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.licenseNumber?.let { licenseNumber -> + item { + IdentityCopyField( + label = stringResource(id = R.string.license_number), + value = licenseNumber, + copyContentDescription = stringResource(id = R.string.copy_license_number), + textFieldTestTag = "IdentityLicenseNumberEntry", + copyActionTestTag = "IdentityCopyLicenseNumberButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyLicenseNumberClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = licenseNumber), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.email?.let { email -> - item { - IdentityCopyField( - label = stringResource(id = R.string.email), - value = email, - copyContentDescription = stringResource(id = R.string.copy_email), - textFieldTestTag = "IdentityEmailEntry", - copyActionTestTag = "IdentityCopyEmailButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyEmailClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = email), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.email?.let { email -> + item { + IdentityCopyField( + label = stringResource(id = R.string.email), + value = email, + copyContentDescription = stringResource(id = R.string.copy_email), + textFieldTestTag = "IdentityEmailEntry", + copyActionTestTag = "IdentityCopyEmailButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyEmailClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = email), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.phone?.let { phone -> - item { - IdentityCopyField( - label = stringResource(id = R.string.phone), - value = phone, - copyContentDescription = stringResource(id = R.string.copy_phone), - textFieldTestTag = "IdentityPhoneEntry", - copyActionTestTag = "IdentityCopyPhoneButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyPhoneClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = phone), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.phone?.let { phone -> + item { + IdentityCopyField( + label = stringResource(id = R.string.phone), + value = phone, + copyContentDescription = stringResource(id = R.string.copy_phone), + textFieldTestTag = "IdentityPhoneEntry", + copyActionTestTag = "IdentityCopyPhoneButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyPhoneClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = phone), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - identityState.address?.let { address -> - item { - IdentityCopyField( - label = stringResource(id = R.string.address), - value = address, - copyContentDescription = stringResource(id = R.string.copy_address), - textFieldTestTag = "IdentityAddressEntry", - copyActionTestTag = "IdentityCopyAddressButton", - onCopyClick = vaultIdentityItemTypeHandlers.onCopyAddressClick, - cardStyle = identityState - .propertyList - .toListItemCardStyle( - index = identityState.propertyList.indexOf(element = address), - dividerPadding = 0.dp, - ), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + identityState.address?.let { address -> + item { + IdentityCopyField( + label = stringResource(id = R.string.address), + value = address, + copyContentDescription = stringResource(id = R.string.copy_address), + textFieldTestTag = "IdentityAddressEntry", + copyActionTestTag = "IdentityCopyAddressButton", + onCopyClick = vaultIdentityItemTypeHandlers.onCopyAddressClick, + cardStyle = identityState + .propertyList + .toListItemCardStyle( + index = identityState.propertyList.indexOf(element = address), + dividerPadding = 0.dp, + ), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - commonState.notes?.let { notes -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.additional_options), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(8.dp)) - IdentityCopyField( - label = stringResource(id = R.string.notes), - value = notes, - copyContentDescription = stringResource(id = R.string.copy_notes), - textFieldTestTag = "CipherNotesLabel", - copyActionTestTag = "CipherNotesCopyButton", - onCopyClick = vaultCommonItemTypeHandlers.onCopyNotesClick, - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.additional_options), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + IdentityCopyField( + label = stringResource(id = R.string.notes), + value = notes, + copyContentDescription = stringResource(id = R.string.copy_notes), + textFieldTestTag = "CipherNotesLabel", + copyActionTestTag = "CipherNotesCopyButton", + onCopyClick = vaultCommonItemTypeHandlers.onCopyNotesClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + } + items(customFields) { customField -> + Spacer(modifier = Modifier.height(height = 8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - items(customFields) { customField -> - Spacer(modifier = Modifier.height(height = 8.dp)) - CustomField( - customField = customField, - onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, - onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, - onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } + itemsIndexed(attachments) { index, attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .testTag("CipherAttachment") + .fillMaxWidth() + .standardHorizontalMargin(), + attachmentItem = attachmentItem, + onAttachmentDownloadClick = vaultCommonItemTypeHandlers + .onAttachmentDownloadClick, + cardStyle = attachments.toListItemCardStyle(index = index), + ) + } } - } - commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> item { Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.attachments), + VaultItemUpdateText( + header = "${stringResource(id = R.string.date_updated)}: ", + text = commonState.lastUpdated, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .padding(horizontal = 12.dp), ) - Spacer(modifier = Modifier.height(height = 8.dp)) } - itemsIndexed(attachments) { index, attachmentItem -> - AttachmentItemContent( - modifier = Modifier - .testTag("CipherAttachment") - .fillMaxWidth() - .standardHorizontalMargin(), - attachmentItem = attachmentItem, - onAttachmentDownloadClick = vaultCommonItemTypeHandlers - .onAttachmentDownloadClick, - cardStyle = attachments.toListItemCardStyle(index = index), - ) + item { + Spacer(modifier = Modifier.height(88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } - - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - VaultItemUpdateText( - header = "${stringResource(id = R.string.date_updated)}: ", - text = commonState.lastUpdated, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp), - ) - } - item { - Spacer(modifier = Modifier.height(88.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } } } 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 02cb83d7d4c..89ea43d819c 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.item +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -50,246 +51,229 @@ fun VaultItemLoginContent( vaultLoginItemTypeHandlers: VaultLoginItemTypeHandlers, modifier: Modifier = Modifier, ) { - LazyColumn( - modifier = modifier, + Column( + modifier = modifier + .fillMaxWidth() + .standardHorizontalMargin(), ) { - item { - Spacer(Modifier.height(12.dp)) - ItemHeader( - value = commonState.name, - isFavorite = commonState.favorite, - iconData = commonState.iconData, - relatedLocations = commonState.relatedLocations, - iconTestTag = "LoginItemNameIcon", - textFieldTestTag = "LoginItemNameEntry", - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } - - if (loginItemState.hasLoginCredentials) { - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.login_credentials), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) + Spacer(Modifier.height(height = 12.dp)) + ItemHeader( + value = commonState.name, + isFavorite = commonState.favorite, + iconData = commonState.iconData, + relatedLocations = commonState.relatedLocations, + iconTestTag = "LoginItemNameIcon", + textFieldTestTag = "LoginItemNameEntry", + ) + LazyColumn( + modifier = modifier, + ) { + if (loginItemState.hasLoginCredentials) { + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.login_credentials), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } } - } - loginItemState.username?.let { username -> - item { - UsernameField( - username = username, - onCopyUsernameClick = vaultLoginItemTypeHandlers.onCopyUsernameClick, - cardStyle = loginItemState - .passwordData - ?.let { CardStyle.Top(dividerPadding = 0.dp) } - ?: CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + loginItemState.username?.let { username -> + item { + UsernameField( + username = username, + onCopyUsernameClick = vaultLoginItemTypeHandlers.onCopyUsernameClick, + cardStyle = loginItemState + .passwordData + ?.let { CardStyle.Top(dividerPadding = 0.dp) } + ?: CardStyle.Full, + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - loginItemState.passwordData?.let { passwordData -> - item { - PasswordField( - passwordData = passwordData, - onShowPasswordClick = vaultLoginItemTypeHandlers.onShowPasswordClick, - onCheckForBreachClick = vaultLoginItemTypeHandlers.onCheckForBreachClick, - onCopyPasswordClick = vaultLoginItemTypeHandlers.onCopyPasswordClick, - cardStyle = loginItemState - .username - ?.let { CardStyle.Bottom } - ?: CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + loginItemState.passwordData?.let { passwordData -> + item { + PasswordField( + passwordData = passwordData, + onShowPasswordClick = vaultLoginItemTypeHandlers.onShowPasswordClick, + onCheckForBreachClick = vaultLoginItemTypeHandlers.onCheckForBreachClick, + onCopyPasswordClick = vaultLoginItemTypeHandlers.onCopyPasswordClick, + cardStyle = loginItemState + .username + ?.let { CardStyle.Bottom } + ?: CardStyle.Full, + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - loginItemState.fido2CredentialCreationDateText?.let { creationDate -> - item { - Spacer(modifier = Modifier.height(8.dp)) - Fido2CredentialField( - creationDate = creationDate(), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + loginItemState.fido2CredentialCreationDateText?.let { creationDate -> + item { + Spacer(modifier = Modifier.height(8.dp)) + Fido2CredentialField( + creationDate = creationDate(), + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - loginItemState.totpCodeItemData?.let { totpCodeItemData -> - item { - Spacer(modifier = Modifier.height(8.dp)) - TotpField( - totpCodeItemData = totpCodeItemData, - enabled = loginItemState.canViewTotpCode, - onCopyTotpClick = vaultLoginItemTypeHandlers.onCopyTotpCodeClick, - onAuthenticatorHelpToolTipClick = vaultLoginItemTypeHandlers - .onAuthenticatorHelpToolTipClick, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + loginItemState.totpCodeItemData?.let { totpCodeItemData -> + item { + Spacer(modifier = Modifier.height(8.dp)) + TotpField( + totpCodeItemData = totpCodeItemData, + enabled = loginItemState.canViewTotpCode, + onCopyTotpClick = vaultLoginItemTypeHandlers.onCopyTotpCodeClick, + onAuthenticatorHelpToolTipClick = vaultLoginItemTypeHandlers + .onAuthenticatorHelpToolTipClick, + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - loginItemState.uris.takeUnless { it.isEmpty() }?.let { uris -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.autofill_options), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } + loginItemState.uris.takeUnless { it.isEmpty() }?.let { uris -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.autofill_options), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } - itemsIndexed(uris) { index, uriData -> - UriField( - uriData = uriData, - onCopyUriClick = vaultLoginItemTypeHandlers.onCopyUriClick, - onLaunchUriClick = vaultLoginItemTypeHandlers.onLaunchUriClick, - cardStyle = uris.toListItemCardStyle(index = index, dividerPadding = 0.dp), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + itemsIndexed(uris) { index, uriData -> + UriField( + uriData = uriData, + onCopyUriClick = vaultLoginItemTypeHandlers.onCopyUriClick, + onLaunchUriClick = vaultLoginItemTypeHandlers.onLaunchUriClick, + cardStyle = uris.toListItemCardStyle(index = index, dividerPadding = 0.dp), + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - commonState.notes?.let { notes -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.additional_options), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(8.dp)) - NotesField( - notes = notes, - onCopyAction = vaultCommonItemTypeHandlers.onCopyNotesClick, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.additional_options), + modifier = Modifier + .fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(8.dp)) + NotesField( + notes = notes, + onCopyAction = vaultCommonItemTypeHandlers.onCopyNotesClick, + modifier = Modifier + .fillMaxWidth(), + ) + } } - } - commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth(), + ) + } + items(customFields) { customField -> + Spacer(modifier = Modifier.height(height = 8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = + vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth(), + ) + } } - items(customFields) { customField -> - Spacer(modifier = Modifier.height(height = 8.dp)) - CustomField( - customField = customField, - onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, - onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, - onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth(), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } + itemsIndexed(attachments) { index, attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .fillMaxWidth(), + attachmentItem = attachmentItem, + cardStyle = attachments.toListItemCardStyle(index = index), + onAttachmentDownloadClick = vaultCommonItemTypeHandlers + .onAttachmentDownloadClick, + ) + } } - } - commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.attachments), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - itemsIndexed(attachments) { index, attachmentItem -> - AttachmentItemContent( + Spacer(modifier = Modifier.height(16.dp)) + VaultItemUpdateText( + header = "${stringResource(id = R.string.date_updated)}: ", + text = commonState.lastUpdated, modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - attachmentItem = attachmentItem, - cardStyle = attachments.toListItemCardStyle(index = index), - onAttachmentDownloadClick = vaultCommonItemTypeHandlers - .onAttachmentDownloadClick, + .fillMaxWidth(), ) } - } - item { - Spacer(modifier = Modifier.height(16.dp)) - VaultItemUpdateText( - header = "${stringResource(id = R.string.date_updated)}: ", - text = commonState.lastUpdated, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp), - ) - } + loginItemState.passwordRevisionDate?.let { revisionDate -> + item { + Spacer(modifier = Modifier.height(height = 4.dp)) + VaultItemUpdateText( + header = "${stringResource(id = R.string.date_password_updated)}: ", + text = revisionDate, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp), + ) + } + } - loginItemState.passwordRevisionDate?.let { revisionDate -> - item { - Spacer(modifier = Modifier.height(height = 4.dp)) - VaultItemUpdateText( - header = "${stringResource(id = R.string.date_password_updated)}: ", - text = revisionDate, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp), - ) + loginItemState.passwordHistoryCount?.let { passwordHistoryCount -> + item { + Spacer(modifier = Modifier.height(height = 4.dp)) + BitwardenHyperTextLink( + annotatedResId = R.string.password_history_count, + args = arrayOf(passwordHistoryCount.toString()), + annotationKey = "passwordHistory", + accessibilityString = stringResource(id = R.string.password_history), + onClick = vaultLoginItemTypeHandlers.onPasswordHistoryClick, + style = BitwardenTheme.typography.labelMedium, + modifier = Modifier + .wrapContentWidth() + .padding(horizontal = 12.dp), + ) + } } - } - loginItemState.passwordHistoryCount?.let { passwordHistoryCount -> item { - Spacer(modifier = Modifier.height(height = 4.dp)) - BitwardenHyperTextLink( - annotatedResId = R.string.password_history_count, - args = arrayOf(passwordHistoryCount.toString()), - annotationKey = "passwordHistory", - accessibilityString = stringResource(id = R.string.password_history), - onClick = vaultLoginItemTypeHandlers.onPasswordHistoryClick, - style = BitwardenTheme.typography.labelMedium, - modifier = Modifier - .wrapContentWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp), - ) + Spacer(modifier = Modifier.height(88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } - - item { - Spacer(modifier = Modifier.height(88.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } } } 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 ed420e4293e..3655c59aa74 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.item +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -38,126 +39,129 @@ fun VaultItemSecureNoteContent( vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers, modifier: Modifier = Modifier, ) { - LazyColumn(modifier = modifier) { - item { - Spacer(modifier = Modifier.height(height = 12.dp)) - ItemHeader( - value = commonState.name, - isFavorite = commonState.favorite, - iconData = commonState.iconData, - relatedLocations = commonState.relatedLocations, - iconTestTag = "SecureNoteItemNameIcon", - textFieldTestTag = "SecureNoteItemNameEntry", - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } + Column(modifier = modifier) { + Spacer(Modifier.height(height = 12.dp)) + ItemHeader( + value = commonState.name, + isFavorite = commonState.favorite, + iconData = commonState.iconData, + relatedLocations = commonState.relatedLocations, + iconTestTag = "SecureNoteItemNameIcon", + textFieldTestTag = "SecureNoteItemNameEntry", + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + LazyColumn { - commonState.notes?.let { notes -> - item { - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - label = stringResource(id = R.string.notes), - value = notes, - onValueChange = { }, - readOnly = true, - singleLine = false, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_notes), - onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, - modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), - ) - }, - textFieldTestTag = "CipherNotesLabel", - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.notes), + value = notes, + onValueChange = { }, + readOnly = true, + singleLine = false, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_notes), + onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, + modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), + ) + }, + textFieldTestTag = "CipherNotesLabel", + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - } - commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + } + + items(customFields) { customField -> + Spacer(modifier = Modifier.height(height = 8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = + vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } } - items(customFields) { customField -> - Spacer(modifier = Modifier.height(height = 8.dp)) - CustomField( - customField = customField, - onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, - onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, - onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, - cardStyle = CardStyle.Full, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } + itemsIndexed(attachments) { index, attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .testTag("CipherAttachment") + .fillMaxWidth() + .standardHorizontalMargin(), + attachmentItem = attachmentItem, + onAttachmentDownloadClick = vaultCommonItemTypeHandlers + .onAttachmentDownloadClick, + cardStyle = attachments.toListItemCardStyle(index = index), + ) + } } - } - commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> item { Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.attachments), + Row( modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - itemsIndexed(attachments) { index, attachmentItem -> - AttachmentItemContent( - modifier = Modifier - .testTag("CipherAttachment") - .fillMaxWidth() - .standardHorizontalMargin(), - attachmentItem = attachmentItem, - onAttachmentDownloadClick = vaultCommonItemTypeHandlers - .onAttachmentDownloadClick, - cardStyle = attachments.toListItemCardStyle(index = index), - ) + .padding(horizontal = 12.dp) + .semantics(mergeDescendants = true) { }, + ) { + Text( + text = "${stringResource(id = R.string.date_updated)}: ", + style = BitwardenTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.primary, + ) + Text( + text = commonState.lastUpdated, + style = BitwardenTheme.typography.bodySmall, + color = BitwardenTheme.colorScheme.text.primary, + ) + } } - } - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - Row( - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp) - .semantics(mergeDescendants = true) { }, - ) { - Text( - text = "${stringResource(id = R.string.date_updated)}: ", - style = BitwardenTheme.typography.bodySmall, - color = BitwardenTheme.colorScheme.text.primary, - ) - Text( - text = commonState.lastUpdated, - style = BitwardenTheme.typography.bodySmall, - color = BitwardenTheme.colorScheme.text.primary, - ) + item { + Spacer(modifier = Modifier.height(88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) } } - - item { - Spacer(modifier = Modifier.height(88.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) - } } } 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 55363451830..5cd6a633ec6 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 @@ -1,5 +1,6 @@ package com.x8bit.bitwarden.ui.vault.feature.item +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -39,207 +40,195 @@ fun VaultItemSshKeyContent( vaultCommonItemTypeHandlers: VaultCommonItemTypeHandlers, modifier: Modifier = Modifier, ) { - LazyColumn(modifier = modifier) { - item { - Spacer(modifier = Modifier.height(height = 12.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.item_information), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - - item { - ItemHeader( - value = commonState.name, - isFavorite = commonState.favorite, - iconData = commonState.iconData, - relatedLocations = commonState.relatedLocations, - iconTestTag = "SshKeyItemNameIcon", - textFieldTestTag = "SshKeyItemNameEntry", - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } - - item { - Spacer(modifier = Modifier.height(8.dp)) - BitwardenTextField( - label = stringResource(id = R.string.public_key), - value = sshKeyItemState.publicKey, - onValueChange = { }, - singleLine = false, - readOnly = true, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_public_key), - onClick = vaultSshKeyItemTypeHandlers.onCopyPublicKeyClick, - modifier = Modifier.testTag(tag = "SshKeyCopyPublicKeyButton"), - ) - }, - cardStyle = CardStyle.Top(), - modifier = Modifier - .testTag("SshKeyItemPublicKeyEntry") - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } - - item { - BitwardenPasswordField( - label = stringResource(id = R.string.private_key), - value = sshKeyItemState.privateKey, - onValueChange = { }, - singleLine = false, - readOnly = true, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_private_key), - onClick = vaultSshKeyItemTypeHandlers.onCopyPrivateKeyClick, - modifier = Modifier.testTag(tag = "SshKeyCopyPrivateKeyButton"), - ) - }, - showPassword = sshKeyItemState.showPrivateKey, - showPasswordTestTag = "ViewPrivateKeyButton", - showPasswordChange = vaultSshKeyItemTypeHandlers.onShowPrivateKeyClick, - cardStyle = CardStyle.Middle(), - modifier = Modifier - .testTag("SshKeyItemPrivateKeyEntry") - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } - - item { - BitwardenTextField( - label = stringResource(id = R.string.fingerprint), - value = sshKeyItemState.fingerprint, - onValueChange = { }, - singleLine = false, - readOnly = true, - actions = { - BitwardenStandardIconButton( - vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_fingerprint), - onClick = vaultSshKeyItemTypeHandlers.onCopyFingerprintClick, - modifier = Modifier.testTag(tag = "SshKeyCopyFingerprintButton"), - ) - }, - cardStyle = CardStyle.Bottom, - modifier = Modifier - .testTag("SshKeyItemFingerprintEntry") - .fillMaxWidth() - .standardHorizontalMargin(), - ) - } - - commonState.notes?.let { notes -> + Column(modifier = modifier) { + Spacer(Modifier.height(height = 12.dp)) + ItemHeader( + value = commonState.name, + isFavorite = commonState.favorite, + iconData = commonState.iconData, + relatedLocations = commonState.relatedLocations, + iconTestTag = "SshKeyItemNameIcon", + textFieldTestTag = "SshKeyItemNameEntry", + ) + LazyColumn(modifier = modifier) { item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.additional_options), - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) Spacer(modifier = Modifier.height(8.dp)) BitwardenTextField( - label = stringResource(id = R.string.notes), - value = notes, + label = stringResource(id = R.string.public_key), + value = sshKeyItemState.publicKey, onValueChange = { }, - readOnly = true, singleLine = false, + readOnly = true, actions = { BitwardenStandardIconButton( vectorIconRes = R.drawable.ic_copy, - contentDescription = stringResource(id = R.string.copy_notes), - onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, - modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), + contentDescription = stringResource(id = R.string.copy_public_key), + onClick = vaultSshKeyItemTypeHandlers.onCopyPublicKeyClick, + modifier = Modifier.testTag(tag = "SshKeyCopyPublicKeyButton"), ) }, - textFieldTestTag = "CipherNotesLabel", - cardStyle = CardStyle.Full, + cardStyle = CardStyle.Top(), modifier = Modifier + .testTag("SshKeyItemPublicKeyEntry") .fillMaxWidth() .standardHorizontalMargin(), ) } - } - commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> item { - Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.custom_fields), + BitwardenPasswordField( + label = stringResource(id = R.string.private_key), + value = sshKeyItemState.privateKey, + onValueChange = { }, + singleLine = false, + readOnly = true, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_private_key), + onClick = vaultSshKeyItemTypeHandlers.onCopyPrivateKeyClick, + modifier = Modifier.testTag(tag = "SshKeyCopyPrivateKeyButton"), + ) + }, + showPassword = sshKeyItemState.showPrivateKey, + showPasswordTestTag = "ViewPrivateKeyButton", + showPasswordChange = vaultSshKeyItemTypeHandlers.onShowPrivateKeyClick, + cardStyle = CardStyle.Middle(), modifier = Modifier + .testTag("SshKeyItemPrivateKeyEntry") .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 16.dp), + .standardHorizontalMargin(), ) } - items(customFields) { customField -> - Spacer(modifier = Modifier.height(height = 8.dp)) - CustomField( - customField = customField, - onCopyCustomHiddenField = vaultCommonItemTypeHandlers.onCopyCustomHiddenField, - onCopyCustomTextField = vaultCommonItemTypeHandlers.onCopyCustomTextField, - onShowHiddenFieldClick = vaultCommonItemTypeHandlers.onShowHiddenFieldClick, - cardStyle = CardStyle.Full, + + item { + BitwardenTextField( + label = stringResource(id = R.string.fingerprint), + value = sshKeyItemState.fingerprint, + onValueChange = { }, + singleLine = false, + readOnly = true, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_fingerprint), + onClick = vaultSshKeyItemTypeHandlers.onCopyFingerprintClick, + modifier = Modifier.testTag(tag = "SshKeyCopyFingerprintButton"), + ) + }, + cardStyle = CardStyle.Bottom, modifier = Modifier + .testTag("SshKeyItemFingerprintEntry") .fillMaxWidth() .standardHorizontalMargin(), ) } - } - commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + commonState.notes?.let { notes -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.additional_options), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.notes), + value = notes, + onValueChange = { }, + readOnly = true, + singleLine = false, + actions = { + BitwardenStandardIconButton( + vectorIconRes = R.drawable.ic_copy, + contentDescription = stringResource(id = R.string.copy_notes), + onClick = vaultCommonItemTypeHandlers.onCopyNotesClick, + modifier = Modifier.testTag(tag = "CipherNotesCopyButton"), + ) + }, + textFieldTestTag = "CipherNotesLabel", + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } + } + + commonState.customFields.takeUnless { it.isEmpty() }?.let { customFields -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + } + items(customFields) { customField -> + Spacer(modifier = Modifier.height(height = 8.dp)) + CustomField( + customField = customField, + onCopyCustomHiddenField = + vaultCommonItemTypeHandlers.onCopyCustomHiddenField, + onCopyCustomTextField = + vaultCommonItemTypeHandlers.onCopyCustomTextField, + onShowHiddenFieldClick = + vaultCommonItemTypeHandlers.onShowHiddenFieldClick, + cardStyle = CardStyle.Full, + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + ) + } + } + + commonState.attachments.takeUnless { it?.isEmpty() == true }?.let { attachments -> + item { + Spacer(modifier = Modifier.height(height = 16.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.attachments), + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin() + .padding(horizontal = 16.dp), + ) + Spacer(modifier = Modifier.height(height = 8.dp)) + } + itemsIndexed(attachments) { index, attachmentItem -> + AttachmentItemContent( + modifier = Modifier + .fillMaxWidth() + .standardHorizontalMargin(), + attachmentItem = attachmentItem, + onAttachmentDownloadClick = vaultCommonItemTypeHandlers + .onAttachmentDownloadClick, + cardStyle = attachments.toListItemCardStyle(index = index), + ) + } + } + item { Spacer(modifier = Modifier.height(height = 16.dp)) - BitwardenListHeaderText( - label = stringResource(id = R.string.attachments), + VaultItemUpdateText( + header = "${stringResource(id = R.string.date_updated)}: ", + text = commonState.lastUpdated, modifier = Modifier .fillMaxWidth() .standardHorizontalMargin() - .padding(horizontal = 16.dp), - ) - Spacer(modifier = Modifier.height(height = 8.dp)) - } - itemsIndexed(attachments) { index, attachmentItem -> - AttachmentItemContent( - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin(), - attachmentItem = attachmentItem, - onAttachmentDownloadClick = vaultCommonItemTypeHandlers - .onAttachmentDownloadClick, - cardStyle = attachments.toListItemCardStyle(index = index), + .padding(horizontal = 12.dp) + .testTag("SshKeyItemLastUpdated"), ) } - } - item { - Spacer(modifier = Modifier.height(height = 16.dp)) - VaultItemUpdateText( - header = "${stringResource(id = R.string.date_updated)}: ", - text = commonState.lastUpdated, - modifier = Modifier - .fillMaxWidth() - .standardHorizontalMargin() - .padding(horizontal = 12.dp) - .testTag("SshKeyItemLastUpdated"), - ) - } - - item { - Spacer(modifier = Modifier.height(88.dp)) - Spacer(modifier = Modifier.navigationBarsPadding()) + item { + Spacer(modifier = Modifier.height(88.dp)) + Spacer(modifier = Modifier.navigationBarsPadding()) + } } } } 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 03cc7839d00..0d6368fcde1 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 @@ -1,23 +1,18 @@ package com.x8bit.bitwarden.ui.vault.feature.item.component import android.content.res.Configuration -import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyItemScope import androidx.compose.foundation.lazy.items import androidx.compose.material3.Icon import androidx.compose.material3.Text @@ -28,7 +23,6 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clipToBounds import androidx.compose.ui.graphics.vector.VectorPainter import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource @@ -36,7 +30,6 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.x8bit.bitwarden.R -import com.x8bit.bitwarden.data.platform.annotation.OmitFromCoverage import com.x8bit.bitwarden.ui.platform.base.util.cardStyle import com.x8bit.bitwarden.ui.platform.base.util.nullableTestTag import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider @@ -50,7 +43,6 @@ import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme import com.x8bit.bitwarden.ui.vault.feature.item.model.VaultItemLocation import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -import kotlinx.collections.immutable.toImmutableList /** * The max number of items that can be displayed before the "show more" text is visible. @@ -58,9 +50,8 @@ import kotlinx.collections.immutable.toImmutableList private const val EXPANDABLE_THRESHOLD = 2 /** - * Reusable composable for displaying the cipher name and favorite status. + * Reusable composable for displaying the cipher name, favorite status, and related locations. */ -@OmitFromCoverage @Suppress("LongMethod") @Composable fun ItemHeader( @@ -72,119 +63,107 @@ fun ItemHeader( iconTestTag: String? = null, textFieldTestTag: String? = null, ) { - Column( + var isExpanded by rememberSaveable { mutableStateOf(false) } + LazyColumn( modifier = modifier .cardStyle(CardStyle.Full) .fillMaxWidth(), ) { - Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) { - ItemHeaderIcon( - iconData = iconData, - testTag = iconTestTag, - modifier = Modifier.size(36.dp), - ) - BitwardenTextField( - label = null, - value = value, - onValueChange = { }, - readOnly = true, - singleLine = false, - actions = { - Icon( - painter = painterResource( - id = if (isFavorite) { - R.drawable.ic_favorite_full - } else { - R.drawable.ic_favorite_empty - }, - ), - contentDescription = stringResource( - id = if (isFavorite) R.string.favorite else R.string.unfavorite, - ), - modifier = Modifier.padding(all = 12.dp), - ) - }, - textFieldTestTag = textFieldTestTag, - cardStyle = null, - textStyle = BitwardenTheme.typography.titleMedium, - ) - } - - BitwardenHorizontalDivider(Modifier.padding(start = 16.dp)) - - Spacer(Modifier.height(8.dp)) - - if (relatedLocations.isEmpty()) { - ItemLocationListItem( - vectorPainter = rememberVectorPainter(R.drawable.ic_folder), - text = stringResource(R.string.no_folder), - iconTestTag = "NoFolderIcon", + item { + Row( + verticalAlignment = Alignment.CenterVertically, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), - ) - return@Column + ) { + ItemHeaderIcon( + iconData = iconData, + testTag = iconTestTag, + modifier = Modifier.size(36.dp), + ) + BitwardenTextField( + label = null, + value = value, + onValueChange = { }, + readOnly = true, + singleLine = false, + actions = { + Icon( + painter = painterResource( + id = if (isFavorite) { + R.drawable.ic_favorite_full + } else { + R.drawable.ic_favorite_empty + }, + ), + contentDescription = stringResource( + id = if (isFavorite) R.string.favorite else R.string.unfavorite, + ), + modifier = Modifier.padding(all = 12.dp), + ) + }, + textFieldTestTag = textFieldTestTag, + cardStyle = null, + textStyle = BitwardenTheme.typography.titleMedium, + ) + } + BitwardenHorizontalDivider(Modifier.padding(start = 16.dp)) + + Spacer(Modifier.height(8.dp)) } - relatedLocations - .take(EXPANDABLE_THRESHOLD) - .forEach { + if (relatedLocations.isEmpty()) { + item { ItemLocationListItem( - vectorPainter = rememberVectorPainter(it.icon), - iconTestTag = "ItemLocationIcon", - text = it.name, + vectorPainter = rememberVectorPainter(R.drawable.ic_folder), + text = stringResource(R.string.no_folder), + iconTestTag = "NoFolderIcon", modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), ) } + return@LazyColumn + } - ExpandingItemLocationContent( - overflowLocations = relatedLocations - .drop(EXPANDABLE_THRESHOLD) - .toImmutableList(), - ) - } -} + items(relatedLocations.take(EXPANDABLE_THRESHOLD)) { + ItemLocationListItem( + vectorPainter = rememberVectorPainter(it.icon), + iconTestTag = "ItemLocationIcon", + text = it.name, + modifier = Modifier + .animateItem() + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } -@Composable -private fun ColumnScope.ExpandingItemLocationContent( - overflowLocations: ImmutableList, -) { - var isExpanded by rememberSaveable { mutableStateOf(false) } - AnimatedVisibility( - visible = isExpanded, - enter = fadeIn() + slideInVertically(), - exit = fadeOut() + slideOutVertically(), - modifier = Modifier.clipToBounds(), - ) { - LazyColumn { - items(overflowLocations) { + if (isExpanded) { + items(relatedLocations.drop(EXPANDABLE_THRESHOLD)) { ItemLocationListItem( vectorPainter = rememberVectorPainter(it.icon), text = it.name, iconTestTag = "ItemLocationIcon", modifier = Modifier .padding(horizontal = 16.dp) + .animateItem() .fillMaxWidth(), ) } } - } - if (overflowLocations.isNotEmpty()) { - BitwardenExpandingHeader( - collapsedText = stringResource(R.string.show_more), - expandedText = stringResource(R.string.show_less), - isExpanded = isExpanded, - onClick = { isExpanded = !isExpanded }, - showExpansionIndicator = false, - ) + if (relatedLocations.size > EXPANDABLE_THRESHOLD) { + item { + BitwardenExpandingHeader( + collapsedText = stringResource(R.string.show_more), + expandedText = stringResource(R.string.show_less), + isExpanded = isExpanded, + onClick = { isExpanded = !isExpanded }, + showExpansionIndicator = false, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + } } } @@ -194,31 +173,35 @@ private fun ItemHeaderIcon( modifier: Modifier = Modifier, testTag: String? = null, ) { + val isLocalIcon = iconData is IconData.Local Box( contentAlignment = Alignment.Center, - modifier = if (iconData is IconData.Local) { - modifier.then( + modifier = modifier.then( + if (isLocalIcon) { Modifier.background( color = BitwardenTheme.colorScheme.illustration.backgroundPrimary, shape = BitwardenTheme.shapes.favicon, - ), - ) - } else { - modifier - }, + ) + } else { + Modifier + }, + ), ) { BitwardenIcon( iconData = iconData, contentDescription = null, tint = BitwardenTheme.colorScheme.illustration.outline, modifier = Modifier - .nullableTestTag(testTag), + .nullableTestTag(testTag) + .then( + if (!isLocalIcon) Modifier.fillMaxSize() else Modifier, + ), ) } } @Composable -private fun ItemLocationListItem( +private fun LazyItemScope.ItemLocationListItem( vectorPainter: VectorPainter, iconTestTag: String?, text: String, @@ -254,18 +237,14 @@ private fun ItemLocationListItem( @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) private fun ItemHeader_LocalIcon_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Login without favicon", - isFavorite = true, - iconData = IconData.Local( - iconRes = R.drawable.ic_globe, - ), - relatedLocations = persistentListOf(), - ) - } - } + ItemHeader( + value = "Login without favicon", + isFavorite = true, + iconData = IconData.Local( + iconRes = R.drawable.ic_globe, + ), + relatedLocations = persistentListOf(), + ) } } @@ -273,19 +252,15 @@ private fun ItemHeader_LocalIcon_Preview() { @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) private fun ItemHeader_NetworkIcon_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Login with favicon", - isFavorite = true, - iconData = IconData.Network( - uri = "mockuri", - fallbackIconRes = R.drawable.ic_globe, - ), - relatedLocations = persistentListOf(), - ) - } - } + ItemHeader( + value = "Login with favicon", + isFavorite = true, + iconData = IconData.Network( + uri = "mockuri", + fallbackIconRes = R.drawable.ic_globe, + ), + relatedLocations = persistentListOf(), + ) } } @@ -293,20 +268,16 @@ private fun ItemHeader_NetworkIcon_Preview() { @Preview(uiMode = Configuration.UI_MODE_NIGHT_YES) private fun ItemHeader_Organization_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Login without favicon", - isFavorite = true, - iconData = IconData.Local( - iconRes = R.drawable.ic_globe, - ), - relatedLocations = persistentListOf( - VaultItemLocation.Organization("Stark Industries"), - ), - ) - } - } + ItemHeader( + value = "Login without favicon", + isFavorite = true, + iconData = IconData.Local( + iconRes = R.drawable.ic_globe, + ), + relatedLocations = persistentListOf( + VaultItemLocation.Organization("Stark Industries"), + ), + ) } } @@ -314,21 +285,17 @@ private fun ItemHeader_Organization_Preview() { @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) private fun ItemNameField_Org_SingleCollection_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Login without favicon", - isFavorite = true, - iconData = IconData.Local( - iconRes = R.drawable.ic_globe, - ), - relatedLocations = persistentListOf( - VaultItemLocation.Organization("Stark Industries"), - VaultItemLocation.Collection("Marketing"), - ), - ) - } - } + ItemHeader( + value = "Login without favicon", + isFavorite = true, + iconData = IconData.Local( + iconRes = R.drawable.ic_globe, + ), + relatedLocations = persistentListOf( + VaultItemLocation.Organization("Stark Industries"), + VaultItemLocation.Collection("Marketing"), + ), + ) } } @@ -336,22 +303,18 @@ private fun ItemNameField_Org_SingleCollection_Preview() { @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) private fun ItemNameField_Org_MultiCollection_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Login without favicon", - isFavorite = true, - iconData = IconData.Local( - iconRes = R.drawable.ic_globe, - ), - relatedLocations = persistentListOf( - VaultItemLocation.Organization("Stark Industries"), - VaultItemLocation.Collection("Marketing"), - VaultItemLocation.Collection("Product"), - ), - ) - } - } + ItemHeader( + value = "Login without favicon", + isFavorite = true, + iconData = IconData.Local( + iconRes = R.drawable.ic_globe, + ), + relatedLocations = persistentListOf( + VaultItemLocation.Organization("Stark Industries"), + VaultItemLocation.Collection("Marketing"), + VaultItemLocation.Collection("Product"), + ), + ) } } @@ -359,22 +322,18 @@ private fun ItemNameField_Org_MultiCollection_Preview() { @Preview(uiMode = Configuration.UI_MODE_NIGHT_NO) private fun ItemNameField_Org_SingleCollection_Folder_Preview() { BitwardenTheme { - LazyColumn { - item { - ItemHeader( - value = "Note without favicon", - isFavorite = true, - iconData = IconData.Local( - iconRes = R.drawable.ic_note, - ), - relatedLocations = persistentListOf( - VaultItemLocation.Organization("Stark Industries"), - VaultItemLocation.Collection("Marketing"), - VaultItemLocation.Folder("Competition"), - ), - ) - } - } + ItemHeader( + value = "Note without favicon", + isFavorite = true, + iconData = IconData.Local( + iconRes = R.drawable.ic_note, + ), + relatedLocations = persistentListOf( + VaultItemLocation.Organization("Stark Industries"), + VaultItemLocation.Collection("Marketing"), + VaultItemLocation.Folder("Competition"), + ), + ) } } //endregion Previews