mirror of
https://github.com/bitwarden/android.git
synced 2026-03-22 12:32:53 -05:00
Refactor ItemHeader to use LazyColumn and Crossfade for smoother transitions
- Migrates `ItemHeader` to `LazyColumn` to improve performance. - Introduces `Crossfade` for animating title changes in `BitwardenExpandingHeader`. - Adjusts icon sizing in `ItemHeaderIcon`. - Removes unnecessary column scope and animated visibility from `ExpandingItemLocationContent`. - Refactors to use `LazyItemScope` and adds `animateItem()` to `ItemLocationListItem`. - Adds conditional handling for expanding the item locations list.
This commit is contained in:
committed by
David Perez
parent
0ba240852f
commit
d7d099477f
@@ -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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.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
|
||||
.testTag("CipherAttachment")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
attachmentItem = attachmentItem,
|
||||
onAttachmentDownloadClick = vaultCommonItemTypeHandlers
|
||||
.onAttachmentDownloadClick,
|
||||
cardStyle = attachments.toListItemCardStyle(index = index),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
commonState.notes?.let { notes ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.additional_options),
|
||||
VaultItemUpdateText(
|
||||
header = "${stringResource(id = R.string.date_updated)}: ",
|
||||
text = commonState.lastUpdated,
|
||||
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(),
|
||||
.padding(horizontal = 12.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),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,303 +39,302 @@ 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 ->
|
||||
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),
|
||||
)
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.additional_options),
|
||||
VaultItemUpdateText(
|
||||
header = "${stringResource(id = R.string.date_updated)}: ",
|
||||
text = commonState.lastUpdated,
|
||||
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(),
|
||||
.padding(horizontal = 12.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),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
)
|
||||
}
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
.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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
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.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.fido2CredentialCreationDateText?.let { creationDate ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
Fido2CredentialField(
|
||||
creationDate = creationDate(),
|
||||
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()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
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(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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.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,
|
||||
cardStyle = attachments.toListItemCardStyle(index = index),
|
||||
onAttachmentDownloadClick = vaultCommonItemTypeHandlers
|
||||
.onAttachmentDownloadClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultItemUpdateText(
|
||||
header = "${stringResource(id = R.string.date_password_updated)}: ",
|
||||
text = revisionDate,
|
||||
header = "${stringResource(id = R.string.date_updated)}: ",
|
||||
text = commonState.lastUpdated,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 12.dp),
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
loginItemState.passwordHistoryCount?.let { passwordHistoryCount ->
|
||||
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.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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
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.customFields.takeUnless { it.isEmpty() }?.let { customFields ->
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.custom_fields),
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
.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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
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(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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
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 ->
|
||||
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.custom_fields),
|
||||
VaultItemUpdateText(
|
||||
header = "${stringResource(id = R.string.date_updated)}: ",
|
||||
text = commonState.lastUpdated,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
.padding(horizontal = 12.dp)
|
||||
.testTag("SshKeyItemLastUpdated"),
|
||||
)
|
||||
}
|
||||
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))
|
||||
Spacer(modifier = Modifier.height(88.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
}
|
||||
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))
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<VaultItemLocation>,
|
||||
) {
|
||||
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
|
||||
|
||||
Reference in New Issue
Block a user