mirror of
https://github.com/bitwarden/android.git
synced 2026-04-27 11:28:41 -05:00
PM-17841: Hide additional options behind expandable section (#4687)
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
package com.x8bit.bitwarden.ui.platform.components.header
|
||||
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
|
||||
/**
|
||||
* Reusable header element that is clickable for expanding or collapsing content.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenExpandingHeader(
|
||||
isExpanded: Boolean,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
title: String = stringResource(id = R.string.additional_options),
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(shape = BitwardenTheme.shapes.content)
|
||||
.clickable(
|
||||
onClickLabel = stringResource(
|
||||
id = if (isExpanded) R.string.options_expanded else R.string.options_collapsed,
|
||||
),
|
||||
onClick = onClick,
|
||||
)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(top = 16.dp, bottom = 8.dp)
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics(mergeDescendants = true) {},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = title,
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
style = BitwardenTheme.typography.labelLarge,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
@@ -18,9 +17,7 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -31,7 +28,6 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clipToBounds
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
@@ -43,11 +39,11 @@ import com.x8bit.bitwarden.ui.platform.components.card.BitwardenInfoCalloutCard
|
||||
import com.x8bit.bitwarden.ui.platform.components.divider.BitwardenHorizontalDivider
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.stepper.BitwardenStepper
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.tools.feature.send.addsend.handlers.AddSendHandlers
|
||||
@@ -324,43 +320,14 @@ private fun AddSendOptions(
|
||||
addSendHandlers: AddSendHandlers,
|
||||
) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
Row(
|
||||
BitwardenExpandingHeader(
|
||||
isExpanded = isExpanded,
|
||||
onClick = { isExpanded = !isExpanded },
|
||||
modifier = Modifier
|
||||
.testTag("SendShowHideOptionsButton")
|
||||
.fillMaxWidth()
|
||||
.clickable(
|
||||
onClickLabel = if (isExpanded) {
|
||||
stringResource(id = R.string.options_expanded)
|
||||
} else {
|
||||
stringResource(id = R.string.options_collapsed)
|
||||
},
|
||||
onClick = { isExpanded = !isExpanded },
|
||||
)
|
||||
.minimumInteractiveComponentSize()
|
||||
.padding(top = 16.dp, bottom = 8.dp)
|
||||
.testTag(tag = "SendShowHideOptionsButton")
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp)
|
||||
.semantics(mergeDescendants = true) {},
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.additional_options),
|
||||
color = BitwardenTheme.colorScheme.text.interaction,
|
||||
style = BitwardenTheme.typography.labelLarge,
|
||||
modifier = Modifier.padding(end = 8.dp),
|
||||
)
|
||||
Icon(
|
||||
painter = rememberVectorPainter(
|
||||
if (isExpanded) {
|
||||
R.drawable.ic_chevron_up_small
|
||||
} else {
|
||||
R.drawable.ic_chevron_down_small
|
||||
},
|
||||
),
|
||||
contentDescription = null,
|
||||
tint = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
}
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
// Hide all content if not expanded:
|
||||
AnimatedVisibility(
|
||||
visible = isExpanded,
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenExpandingHeader
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.platform.util.persistentListOfNotNull
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
|
||||
/**
|
||||
* The collapsable UI for additional options when adding or editing a cipher.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditAdditionalOptions(
|
||||
itemType: VaultAddEditState.ViewState.Content.ItemType,
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||
isAdditionalOptionsExpanded: Boolean,
|
||||
onAdditionalOptionsClick: () -> Unit,
|
||||
) {
|
||||
item {
|
||||
BitwardenExpandingHeader(
|
||||
isExpanded = isAdditionalOptionsExpanded,
|
||||
onClick = onAdditionalOptionsClick,
|
||||
modifier = Modifier
|
||||
.standardHorizontalMargin()
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
if (isAdditionalOptionsExpanded) {
|
||||
val isNotes = itemType is VaultAddEditState.ViewState.Content.ItemType.SecureNotes
|
||||
if (!isNotes) {
|
||||
item(key = "optionalNotes") {
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = commonState.notes,
|
||||
onValueChange = commonTypeHandlers.onNotesTextChange,
|
||||
textFieldTestTag = "ItemNotesEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item(key = "MasterPasswordRepromptToggle") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
) {
|
||||
if (!isNotes) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle_small,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonTypeHandlers.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag(tag = "MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "customFieldsHeader") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.custom_fields),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
items(
|
||||
items = commonState.customFieldData,
|
||||
key = { "customField_${it.itemId}" },
|
||||
) { customItem ->
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
|
||||
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
|
||||
supportedLinkedTypes = itemType.vaultLinkedFieldTypes,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item(key = "addCustomFieldButton") {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.animateItem()
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
) {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick,
|
||||
options = persistentListOfNotNull(
|
||||
CustomFieldType.TEXT,
|
||||
CustomFieldType.HIDDEN,
|
||||
CustomFieldType.BOOLEAN,
|
||||
CustomFieldType.LINKED.takeIf {
|
||||
itemType.vaultLinkedFieldTypes.isNotEmpty()
|
||||
},
|
||||
),
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
@@ -18,21 +16,14 @@ import androidx.compose.ui.text.input.KeyboardType
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCardTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.util.longName
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
@@ -40,9 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditCardItems(
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
cardState: VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
commonHandlers: VaultAddEditCommonHandlers,
|
||||
cardHandlers: VaultAddEditCardTypeHandlers,
|
||||
) {
|
||||
item {
|
||||
@@ -156,97 +145,4 @@ fun LazyListScope.vaultAddEditCardItems(
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
item {
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = commonState.notes,
|
||||
onValueChange = commonHandlers.onNotesTextChange,
|
||||
textFieldTestTag = "ItemNotesEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonHandlers.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonHandlers.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag("MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(commonState.customFieldData) { customItem ->
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonHandlers.onCustomFieldActionSelect,
|
||||
supportedLinkedTypes = persistentListOf(
|
||||
VaultLinkedFieldType.CARDHOLDER_NAME,
|
||||
VaultLinkedFieldType.EXPIRATION_MONTH,
|
||||
VaultLinkedFieldType.EXPIRATION_YEAR,
|
||||
VaultLinkedFieldType.SECURITY_CODE,
|
||||
VaultLinkedFieldType.BRAND,
|
||||
VaultLinkedFieldType.NUMBER,
|
||||
),
|
||||
onHiddenVisibilityChanged = commonHandlers.onHiddenFieldVisibilityChange,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonHandlers.onAddNewCustomFieldClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,6 @@ import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenTextEntryDialo
|
||||
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenBasicDialogRow
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* A UI element that is used by the user to add a custom field item.
|
||||
@@ -24,17 +23,11 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
* @param options The types that are to be chosen by the user.
|
||||
* @param onFinishNamingClick Invoked when the user finishes naming the item.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick: (CustomFieldType, String) -> Unit,
|
||||
options: ImmutableList<CustomFieldType>,
|
||||
modifier: Modifier = Modifier,
|
||||
options: ImmutableList<CustomFieldType> = persistentListOf(
|
||||
CustomFieldType.TEXT,
|
||||
CustomFieldType.HIDDEN,
|
||||
CustomFieldType.BOOLEAN,
|
||||
CustomFieldType.LINKED,
|
||||
),
|
||||
) {
|
||||
var shouldShowChooserDialog by remember { mutableStateOf(false) }
|
||||
var shouldShowNameDialog by remember { mutableStateOf(false) }
|
||||
|
||||
@@ -3,9 +3,7 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
@@ -14,18 +12,11 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.dropdown.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
@@ -33,9 +24,7 @@ import kotlinx.collections.immutable.toImmutableList
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditIdentityItems(
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
identityState: VaultAddEditState.ViewState.Content.ItemType.Identity,
|
||||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||
identityItemTypeHandlers: VaultAddEditIdentityTypeHandlers,
|
||||
) {
|
||||
item {
|
||||
@@ -253,112 +242,6 @@ fun LazyListScope.vaultAddEditIdentityItems(
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
item {
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = commonState.notes,
|
||||
onValueChange = commonTypeHandlers.onNotesTextChange,
|
||||
textFieldTestTag = "ItemNotesEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonTypeHandlers.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag("MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(commonState.customFieldData) { customItem ->
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
|
||||
supportedLinkedTypes = persistentListOf(
|
||||
VaultLinkedFieldType.TITLE,
|
||||
VaultLinkedFieldType.MIDDLE_NAME,
|
||||
VaultLinkedFieldType.ADDRESS_1,
|
||||
VaultLinkedFieldType.ADDRESS_2,
|
||||
VaultLinkedFieldType.ADDRESS_3,
|
||||
VaultLinkedFieldType.CITY,
|
||||
VaultLinkedFieldType.STATE,
|
||||
VaultLinkedFieldType.POSTAL_CODE,
|
||||
VaultLinkedFieldType.COUNTRY,
|
||||
VaultLinkedFieldType.COMPANY,
|
||||
VaultLinkedFieldType.EMAIL,
|
||||
VaultLinkedFieldType.PHONE,
|
||||
VaultLinkedFieldType.SSN,
|
||||
VaultLinkedFieldType.IDENTITY_USERNAME,
|
||||
VaultLinkedFieldType.PASSPORT_NUMBER,
|
||||
VaultLinkedFieldType.LICENSE_NUMBER,
|
||||
VaultLinkedFieldType.FIRST_NAME,
|
||||
VaultLinkedFieldType.LAST_NAME,
|
||||
VaultLinkedFieldType.FULL_NAME,
|
||||
),
|
||||
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
@@ -68,6 +70,7 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
|
||||
},
|
||||
)
|
||||
|
||||
var isAdditionalOptionsExpanded = rememberSaveable { mutableStateOf(value = false) }
|
||||
LazyColumn(modifier = modifier, state = lazyListState) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 12.dp))
|
||||
@@ -203,9 +206,7 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
|
||||
when (state.type) {
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Login -> {
|
||||
vaultAddEditLoginItems(
|
||||
commonState = state.common,
|
||||
loginState = state.type,
|
||||
commonActionHandler = commonTypeHandlers,
|
||||
loginItemTypeHandlers = loginItemTypeHandlers,
|
||||
onTotpSetupClick = {
|
||||
if (permissionsManager.checkPermission(Manifest.permission.CAMERA)) {
|
||||
@@ -224,18 +225,14 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
|
||||
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Card -> {
|
||||
vaultAddEditCardItems(
|
||||
commonState = state.common,
|
||||
cardState = state.type,
|
||||
commonHandlers = commonTypeHandlers,
|
||||
cardHandlers = cardItemTypeHandlers,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> {
|
||||
vaultAddEditIdentityItems(
|
||||
commonState = state.common,
|
||||
identityState = state.type,
|
||||
commonTypeHandlers = commonTypeHandlers,
|
||||
identityItemTypeHandlers = identityItemTypeHandlers,
|
||||
)
|
||||
}
|
||||
@@ -249,14 +246,22 @@ fun CoachMarkScope<AddEditItemCoachMark>.VaultAddEditContent(
|
||||
|
||||
is VaultAddEditState.ViewState.Content.ItemType.SshKey -> {
|
||||
vaultAddEditSshKeyItems(
|
||||
commonState = state.common,
|
||||
sshKeyState = state.type,
|
||||
commonTypeHandlers = commonTypeHandlers,
|
||||
sshKeyTypeHandlers = sshKeyItemTypeHandlers,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
vaultAddEditAdditionalOptions(
|
||||
itemType = state.type,
|
||||
commonState = state.common,
|
||||
commonTypeHandlers = commonTypeHandlers,
|
||||
isAdditionalOptionsExpanded = isAdditionalOptionsExpanded.value,
|
||||
onAdditionalOptionsClick = {
|
||||
isAdditionalOptionsExpanded.value = !isAdditionalOptionsExpanded.value
|
||||
},
|
||||
)
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
Spacer(modifier = Modifier.navigationBarsPadding())
|
||||
|
||||
@@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -36,13 +35,9 @@ import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.text.BitwardenClickableText
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.rememberVectorPainter
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* The UI for adding and editing a login cipher.
|
||||
@@ -50,9 +45,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||
@Suppress("LongMethod", "LongParameterList")
|
||||
fun LazyListScope.vaultAddEditLoginItems(
|
||||
coachMarkScope: CoachMarkScope<AddEditItemCoachMark>,
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
loginState: VaultAddEditState.ViewState.Content.ItemType.Login,
|
||||
commonActionHandler: VaultAddEditCommonHandlers,
|
||||
loginItemTypeHandlers: VaultAddEditLoginTypeHandlers,
|
||||
onTotpSetupClick: () -> Unit,
|
||||
onNextCoachMark: () -> Unit,
|
||||
@@ -201,97 +194,6 @@ fun LazyListScope.vaultAddEditLoginItems(
|
||||
.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
|
||||
item {
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = commonState.notes,
|
||||
onValueChange = commonActionHandler.onNotesTextChange,
|
||||
textFieldTestTag = "ItemNotesEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonActionHandler.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle_small,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonActionHandler.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag("MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(commonState.customFieldData) { customItem ->
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonActionHandler.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonActionHandler.onCustomFieldActionSelect,
|
||||
supportedLinkedTypes = persistentListOf(
|
||||
VaultLinkedFieldType.PASSWORD,
|
||||
VaultLinkedFieldType.USERNAME,
|
||||
),
|
||||
onHiddenVisibilityChanged = commonActionHandler.onHiddenFieldVisibilityChange,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonActionHandler.onAddNewCustomFieldClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
||||
@@ -3,29 +3,19 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* The UI for adding and editing a secure notes cipher.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditSecureNotesItems(
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||
@@ -44,79 +34,4 @@ fun LazyListScope.vaultAddEditSecureNotesItems(
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonTypeHandlers.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag("MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.custom_fields),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
items(commonState.customFieldData) { customItem ->
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
|
||||
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick,
|
||||
options = persistentListOf(
|
||||
CustomFieldType.TEXT,
|
||||
CustomFieldType.HIDDEN,
|
||||
CustomFieldType.BOOLEAN,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,38 +3,26 @@ package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.testTag
|
||||
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.ui.platform.base.util.standardHorizontalMargin
|
||||
import com.x8bit.bitwarden.ui.platform.components.button.BitwardenStandardIconButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenPasswordField
|
||||
import com.x8bit.bitwarden.ui.platform.components.field.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.header.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.CardStyle
|
||||
import com.x8bit.bitwarden.ui.platform.components.toggle.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.theme.BitwardenTheme
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditSshKeyTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
/**
|
||||
* The UI for adding and editing a SSH key cipher.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditSshKeyItems(
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
sshKeyState: VaultAddEditState.ViewState.Content.ItemType.SshKey,
|
||||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||
sshKeyTypeHandlers: VaultAddEditSshKeyTypeHandlers,
|
||||
) {
|
||||
item {
|
||||
@@ -82,109 +70,14 @@ fun LazyListScope.vaultAddEditSshKeyItems(
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 16.dp))
|
||||
BitwardenListHeaderText(
|
||||
label = stringResource(id = R.string.miscellaneous),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
}
|
||||
|
||||
item {
|
||||
BitwardenTextField(
|
||||
singleLine = false,
|
||||
label = stringResource(id = R.string.notes),
|
||||
value = commonState.notes,
|
||||
onValueChange = commonTypeHandlers.onNotesTextChange,
|
||||
textFieldTestTag = "ItemNotesEntry",
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
if (commonState.isUnlockWithPasswordEnabled) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
BitwardenSwitch(
|
||||
label = stringResource(id = R.string.password_prompt),
|
||||
isChecked = commonState.masterPasswordReprompt,
|
||||
onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt,
|
||||
actions = {
|
||||
BitwardenStandardIconButton(
|
||||
vectorIconRes = R.drawable.ic_question_circle_small,
|
||||
contentDescription = stringResource(
|
||||
id = R.string.master_password_re_prompt_help,
|
||||
),
|
||||
onClick = commonTypeHandlers.onTooltipClick,
|
||||
contentColor = BitwardenTheme.colorScheme.icon.secondary,
|
||||
)
|
||||
},
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.testTag("MasterPasswordRepromptToggle")
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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(commonState.customFieldData) { customItem ->
|
||||
Spacer(modifier = Modifier.height(height = 8.dp))
|
||||
VaultAddEditCustomField(
|
||||
customField = customItem,
|
||||
onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange,
|
||||
onCustomFieldAction = commonTypeHandlers.onCustomFieldActionSelect,
|
||||
onHiddenVisibilityChanged = commonTypeHandlers.onHiddenFieldVisibilityChange,
|
||||
cardStyle = CardStyle.Full,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick,
|
||||
options = persistentListOf(
|
||||
CustomFieldType.TEXT,
|
||||
CustomFieldType.HIDDEN,
|
||||
CustomFieldType.BOOLEAN,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.standardHorizontalMargin(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true)
|
||||
@Preview
|
||||
@Composable
|
||||
private fun VaultAddEditSshKeyItems_preview() {
|
||||
BitwardenTheme {
|
||||
LazyColumn {
|
||||
vaultAddEditSshKeyItems(
|
||||
commonState = VaultAddEditState.ViewState.Content.Common(
|
||||
name = "SSH Key",
|
||||
),
|
||||
sshKeyState = VaultAddEditState.ViewState.Content.ItemType.SshKey(
|
||||
publicKey = "public key",
|
||||
privateKey = "private key",
|
||||
@@ -193,20 +86,6 @@ private fun VaultAddEditSshKeyItems_preview() {
|
||||
showPrivateKey = false,
|
||||
showFingerprint = false,
|
||||
),
|
||||
commonTypeHandlers = VaultAddEditCommonHandlers(
|
||||
onNameTextChange = { },
|
||||
onFolderSelected = { },
|
||||
onToggleFavorite = { },
|
||||
onToggleMasterPasswordReprompt = { },
|
||||
onNotesTextChange = { },
|
||||
onOwnerSelected = { },
|
||||
onTooltipClick = { },
|
||||
onAddNewCustomFieldClick = { _, _ -> },
|
||||
onCustomFieldValueChange = { },
|
||||
onCustomFieldActionSelect = { _, _ -> },
|
||||
onCollectionSelect = { },
|
||||
onHiddenFieldVisibilityChange = { },
|
||||
),
|
||||
sshKeyTypeHandlers = VaultAddEditSshKeyTypeHandlers(
|
||||
onPrivateKeyVisibilityChange = { },
|
||||
),
|
||||
|
||||
@@ -65,6 +65,8 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCollection
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
@@ -2208,6 +2210,11 @@ data class VaultAddEditState(
|
||||
*/
|
||||
abstract val itemTypeOption: ItemTypeOption
|
||||
|
||||
/**
|
||||
* A list of all the linked field types supported by this [ItemType].
|
||||
*/
|
||||
abstract val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
|
||||
/**
|
||||
* Represents the login item information.
|
||||
*
|
||||
@@ -2240,6 +2247,12 @@ data class VaultAddEditState(
|
||||
) : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.LOGIN
|
||||
|
||||
override val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
get() = persistentListOf(
|
||||
VaultLinkedFieldType.PASSWORD,
|
||||
VaultLinkedFieldType.USERNAME,
|
||||
)
|
||||
|
||||
/**
|
||||
* Indicates whether the passkey can or cannot be removed.
|
||||
*/
|
||||
@@ -2266,6 +2279,16 @@ data class VaultAddEditState(
|
||||
val securityCode: String = "",
|
||||
) : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.CARD
|
||||
|
||||
override val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
get() = persistentListOf(
|
||||
VaultLinkedFieldType.CARDHOLDER_NAME,
|
||||
VaultLinkedFieldType.EXPIRATION_MONTH,
|
||||
VaultLinkedFieldType.EXPIRATION_YEAR,
|
||||
VaultLinkedFieldType.SECURITY_CODE,
|
||||
VaultLinkedFieldType.BRAND,
|
||||
VaultLinkedFieldType.NUMBER,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2311,8 +2334,30 @@ data class VaultAddEditState(
|
||||
val zip: String = "",
|
||||
val country: String = "",
|
||||
) : ItemType() {
|
||||
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.IDENTITY
|
||||
|
||||
override val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
get() = persistentListOf(
|
||||
VaultLinkedFieldType.TITLE,
|
||||
VaultLinkedFieldType.MIDDLE_NAME,
|
||||
VaultLinkedFieldType.ADDRESS_1,
|
||||
VaultLinkedFieldType.ADDRESS_2,
|
||||
VaultLinkedFieldType.ADDRESS_3,
|
||||
VaultLinkedFieldType.CITY,
|
||||
VaultLinkedFieldType.STATE,
|
||||
VaultLinkedFieldType.POSTAL_CODE,
|
||||
VaultLinkedFieldType.COUNTRY,
|
||||
VaultLinkedFieldType.COMPANY,
|
||||
VaultLinkedFieldType.EMAIL,
|
||||
VaultLinkedFieldType.PHONE,
|
||||
VaultLinkedFieldType.SSN,
|
||||
VaultLinkedFieldType.IDENTITY_USERNAME,
|
||||
VaultLinkedFieldType.PASSPORT_NUMBER,
|
||||
VaultLinkedFieldType.LICENSE_NUMBER,
|
||||
VaultLinkedFieldType.FIRST_NAME,
|
||||
VaultLinkedFieldType.LAST_NAME,
|
||||
VaultLinkedFieldType.FULL_NAME,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2321,6 +2366,8 @@ data class VaultAddEditState(
|
||||
@Parcelize
|
||||
data object SecureNotes : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SECURE_NOTES
|
||||
override val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
get() = persistentListOf()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2340,6 +2387,8 @@ data class VaultAddEditState(
|
||||
val showFingerprint: Boolean = false,
|
||||
) : ItemType() {
|
||||
override val itemTypeOption: ItemTypeOption get() = ItemTypeOption.SSH_KEYS
|
||||
override val vaultLinkedFieldTypes: ImmutableList<VaultLinkedFieldType>
|
||||
get() = persistentListOf()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2302,6 +2302,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking New Custom Field button should allow creation of Linked type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
@@ -2564,6 +2569,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `toggling the Master password re-prompt toggle should send ToggleMasterPasswordReprompt action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Master password re-prompt")
|
||||
.performTouchInput {
|
||||
@@ -2583,6 +2593,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `re-prompt toggle should display according to state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Master password re-prompt")
|
||||
.assertIsDisplayed()
|
||||
@@ -2599,6 +2614,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `the master password re-prompt toggle should be enabled or disabled according to state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("Master password re-prompt")
|
||||
.assertIsOff()
|
||||
@@ -2616,6 +2636,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `toggling the Master password re-prompt tooltip button should send TooltipClick action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Master password re-prompt help")
|
||||
.performClick()
|
||||
@@ -2721,6 +2746,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking New Custom Field button should allow creation of Text type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
@@ -2757,6 +2787,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking New Custom Field button should not display linked type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
@@ -2775,6 +2810,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking New Custom Field button should allow creation of Boolean type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
@@ -2811,6 +2851,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking New Custom Field button should allow creation of Hidden type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
@@ -2863,6 +2908,10 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
),
|
||||
)
|
||||
}
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Hidden item")
|
||||
.assertExists()
|
||||
@@ -2882,6 +2931,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking and changing the custom text field will send a CustomFieldValueChange event`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("TestText")
|
||||
.performTextClearance()
|
||||
@@ -2899,6 +2953,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking and changing the custom hidden field will send a CustomFieldValueChange event`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("TestHidden")
|
||||
.performTextClearance()
|
||||
@@ -2917,6 +2976,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking and changing the custom boolean field will send a CustomFieldValueChange event`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll("TestBoolean")
|
||||
.performClick()
|
||||
@@ -2934,6 +2998,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking custom field edit icon and Edit option sends a CustomFieldValueChange action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithContentDescriptionAfterScroll("Edit")
|
||||
.onFirst()
|
||||
@@ -2974,6 +3043,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking custom field edit icon and Remove option sends a CustomFieldActionSelect remove action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithContentDescriptionAfterScroll("Edit")
|
||||
.onFirst()
|
||||
@@ -3002,6 +3076,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking custom field edit icon and Move down option sends a CustomFieldActionSelect move down action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithContentDescriptionAfterScroll("Edit")
|
||||
.onFirst()
|
||||
@@ -3030,6 +3109,11 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
||||
fun `clicking custom field edit icon and Move Up options sends a CustomFieldActionSelect move up action`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS
|
||||
|
||||
// Expand the additional options UI before interacting with it
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Additional options")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithContentDescriptionAfterScroll("Edit")
|
||||
.onFirst()
|
||||
|
||||
Reference in New Issue
Block a user