mirror of
https://github.com/bitwarden/android.git
synced 2024-11-22 09:25:58 +03:00
BIT-529 Added the ability to create custom type fields (#374)
This commit is contained in:
parent
4e686fcc2e
commit
0148512bf8
22 changed files with 782 additions and 189 deletions
|
@ -0,0 +1,62 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components
|
||||
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.x8bit.bitwarden.R
|
||||
|
||||
/**
|
||||
* Represents a Bitwarden-styled dialog that is used to enter text.
|
||||
*
|
||||
* @param title The optional title to show.
|
||||
* @param textFieldLabel Label for the text field.
|
||||
* @param onConfirmClick Called when the confirm button is clicked.
|
||||
* @param onDismissRequest Called when the user attempts to dismiss the dialog.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenTextEntryDialog(
|
||||
title: String?,
|
||||
textFieldLabel: String,
|
||||
onConfirmClick: (String) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
var text by remember { mutableStateOf("") }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
dismissButton = {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.cancel),
|
||||
onClick = onDismissRequest,
|
||||
)
|
||||
},
|
||||
confirmButton = {
|
||||
BitwardenTextButton(
|
||||
label = stringResource(id = R.string.ok),
|
||||
onClick = { onConfirmClick(text) },
|
||||
)
|
||||
},
|
||||
title = title?.let {
|
||||
{
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.headlineSmall,
|
||||
)
|
||||
}
|
||||
},
|
||||
text = {
|
||||
BitwardenTextField(
|
||||
label = textFieldLabel,
|
||||
value = text,
|
||||
onValueChange = { text = it },
|
||||
)
|
||||
},
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
)
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem
|
||||
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.semantics.semantics
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.showNotYetImplementedToast
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenIconButtonWithResource
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenPasswordFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenRowOfActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenWideSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* The UI element used to display custom field items.
|
||||
*
|
||||
* @param customField The field that is to be displayed.
|
||||
* @param onCustomFieldValueChange Invoked when the user changes the value.
|
||||
* @param modifier Modifier for the UI elements.
|
||||
* @param supportedLinkedTypes The supported linked types for the vault item.
|
||||
*/
|
||||
@Composable
|
||||
@Suppress("LongMethod")
|
||||
fun AddEditCustomField(
|
||||
customField: VaultAddItemState.Custom,
|
||||
onCustomFieldValueChange: (VaultAddItemState.Custom) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
supportedLinkedTypes: ImmutableList<VaultLinkedFieldType> = persistentListOf(),
|
||||
) {
|
||||
when (customField) {
|
||||
is VaultAddItemState.Custom.BooleanField -> {
|
||||
CustomFieldBoolean(
|
||||
label = customField.name,
|
||||
value = customField.value,
|
||||
onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.HiddenField -> {
|
||||
CustomFieldHiddenField(
|
||||
customField.name,
|
||||
customField.value,
|
||||
onValueChanged = {
|
||||
onCustomFieldValueChange(customField.copy(value = it))
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.LinkedField -> {
|
||||
CustomFieldLinkedField(
|
||||
selectedOption = customField.vaultLinkedFieldType,
|
||||
supportedLinkedTypes = supportedLinkedTypes,
|
||||
onValueChanged = {
|
||||
onCustomFieldValueChange(customField.copy(vaultLinkedFieldType = it))
|
||||
},
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.TextField -> {
|
||||
CustomFieldTextField(
|
||||
label = customField.name,
|
||||
value = customField.value,
|
||||
onValueChanged = { onCustomFieldValueChange(customField.copy(value = it)) },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A UI element that is used to display custom field boolean fields.
|
||||
*/
|
||||
@Composable
|
||||
private fun CustomFieldBoolean(
|
||||
label: String,
|
||||
value: Boolean,
|
||||
onValueChanged: (Boolean) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.semantics(mergeDescendants = true) {}
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
BitwardenWideSwitch(
|
||||
label = label,
|
||||
isChecked = value,
|
||||
onCheckedChange = onValueChanged,
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
BitwardenRowOfActions(
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.edit),
|
||||
),
|
||||
onClick = {
|
||||
// TODO add support for custom field actions (BIT-540)
|
||||
showNotYetImplementedToast(context = context)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A UI element that is used to display custom field hidden fields.
|
||||
*/
|
||||
@Composable
|
||||
private fun CustomFieldHiddenField(
|
||||
label: String,
|
||||
value: String,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
BitwardenPasswordFieldWithActions(
|
||||
label = label,
|
||||
value = value,
|
||||
onValueChange = onValueChanged,
|
||||
singleLine = true,
|
||||
modifier = modifier,
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.edit),
|
||||
),
|
||||
onClick = {
|
||||
// TODO Add support for custom field actions (BIT-540)
|
||||
showNotYetImplementedToast(context = context)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A UI element that is used to display custom field text fields.
|
||||
*/
|
||||
@Composable
|
||||
private fun CustomFieldTextField(
|
||||
label: String,
|
||||
value: String,
|
||||
onValueChanged: (String) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
BitwardenTextFieldWithActions(
|
||||
label = label,
|
||||
value = value,
|
||||
onValueChange = onValueChanged,
|
||||
singleLine = true,
|
||||
modifier = modifier,
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.edit),
|
||||
),
|
||||
onClick = {
|
||||
// TODO add support for custom field actions (BIT-540)
|
||||
showNotYetImplementedToast(context = context)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* A UI element that is used to display custom field linked fields.
|
||||
*/
|
||||
@Composable
|
||||
private fun CustomFieldLinkedField(
|
||||
selectedOption: VaultLinkedFieldType,
|
||||
onValueChanged: (VaultLinkedFieldType) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
label: String = "",
|
||||
supportedLinkedTypes: ImmutableList<VaultLinkedFieldType> = persistentListOf(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val possibleTypesWithStrings = supportedLinkedTypes.associateWith { it.label.invoke() }
|
||||
|
||||
Row(
|
||||
modifier = modifier
|
||||
.semantics(mergeDescendants = true) {}
|
||||
.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
BitwardenMultiSelectButton(
|
||||
label = label,
|
||||
options = supportedLinkedTypes.map { it.label.invoke() }.toImmutableList(),
|
||||
selectedOption = selectedOption.label.invoke(),
|
||||
onOptionSelected = { selectedType ->
|
||||
possibleTypesWithStrings.forEach {
|
||||
if (it.value == selectedType) {
|
||||
onValueChanged(it.key)
|
||||
}
|
||||
}
|
||||
},
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
|
||||
BitwardenRowOfActions(
|
||||
actions = {
|
||||
BitwardenIconButtonWithResource(
|
||||
iconRes = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_settings),
|
||||
contentDescription = stringResource(id = R.string.edit),
|
||||
),
|
||||
onClick = {
|
||||
// TODO add support for custom field actions (BIT-540)
|
||||
showNotYetImplementedToast(context = context)
|
||||
},
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialogRow
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSelectionDialog
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextEntryDialog
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.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.
|
||||
*
|
||||
* @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 AddEditCustomFieldsButton(
|
||||
onFinishNamingClick: (CustomFieldType, String) -> Unit,
|
||||
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) }
|
||||
|
||||
var customFieldType: CustomFieldType by remember { mutableStateOf(CustomFieldType.TEXT) }
|
||||
var customFieldName: String by remember { mutableStateOf("") }
|
||||
|
||||
if (shouldShowChooserDialog) {
|
||||
BitwardenSelectionDialog(
|
||||
title = stringResource(id = R.string.select_type_field),
|
||||
onDismissRequest = { shouldShowChooserDialog = false },
|
||||
) {
|
||||
options.forEach { type ->
|
||||
BitwardenBasicDialogRow(
|
||||
text = type.typeText.invoke(),
|
||||
onClick = {
|
||||
shouldShowChooserDialog = false
|
||||
shouldShowNameDialog = true
|
||||
customFieldType = type
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldShowNameDialog) {
|
||||
BitwardenTextEntryDialog(
|
||||
title = stringResource(id = R.string.custom_field_name),
|
||||
textFieldLabel = stringResource(id = R.string.name),
|
||||
onDismissRequest = { shouldShowNameDialog = false },
|
||||
onConfirmClick = {
|
||||
shouldShowNameDialog = false
|
||||
customFieldName = it
|
||||
onFinishNamingClick(customFieldType, customFieldName)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.new_custom_field),
|
||||
onClick = { shouldShowChooserDialog = true },
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
|
@ -5,6 +5,7 @@ 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.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -24,6 +25,8 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextFieldWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.model.IconResource
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
|
@ -245,11 +248,24 @@ fun LazyListScope.addEditLoginItems(
|
|||
)
|
||||
}
|
||||
|
||||
items(state.customFieldData) { customItem ->
|
||||
AddEditCustomField(
|
||||
customItem,
|
||||
onCustomFieldValueChange = loginItemTypeHandlers.onCustomFieldValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
supportedLinkedTypes = persistentListOf(
|
||||
VaultLinkedFieldType.PASSWORD,
|
||||
VaultLinkedFieldType.USERNAME,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.new_custom_field),
|
||||
onClick = loginItemTypeHandlers.onAddNewCustomFieldClick,
|
||||
AddEditCustomFieldsButton(
|
||||
onFinishNamingClick = loginItemTypeHandlers.onAddNewCustomFieldClick,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
|
|
@ -5,6 +5,7 @@ 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.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
|
@ -13,12 +14,13 @@ import androidx.compose.ui.res.painterResource
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenFilledTonalButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitch
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
||||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
|
@ -132,11 +134,25 @@ fun LazyListScope.addEditSecureNotesItems(
|
|||
)
|
||||
}
|
||||
|
||||
items(state.customFieldData) { customItem ->
|
||||
AddEditCustomField(
|
||||
customItem,
|
||||
onCustomFieldValueChange = secureNotesTypeHandlers.onCustomFieldValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
BitwardenFilledTonalButton(
|
||||
label = stringResource(id = R.string.new_custom_field),
|
||||
onClick = secureNotesTypeHandlers.onAddNewCustomFieldClick,
|
||||
AddEditCustomFieldsButton(
|
||||
onFinishNamingClick = secureNotesTypeHandlers.onAddNewCustomFieldClick,
|
||||
options = persistentListOf(
|
||||
CustomFieldType.TEXT,
|
||||
CustomFieldType.HIDDEN,
|
||||
CustomFieldType.BOOLEAN,
|
||||
),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
|
|
|
@ -16,8 +16,11 @@ import com.x8bit.bitwarden.ui.platform.base.util.Text
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemAction.ItemType.SecureNotesType.TooltipClick.toCustomField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
@ -26,6 +29,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
||||
private const val KEY_STATE = "state"
|
||||
|
@ -279,7 +283,11 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick -> {
|
||||
handleLoginAddNewCustomFieldClick()
|
||||
handleLoginAddNewCustomFieldClick(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange -> {
|
||||
handleLoginCustomFieldValueChange(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -426,12 +434,29 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleLoginAddNewCustomFieldClick() {
|
||||
viewModelScope.launch {
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Add New Custom Field",
|
||||
),
|
||||
private fun handleLoginAddNewCustomFieldClick(
|
||||
action: VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick,
|
||||
) {
|
||||
val newCustomData: VaultAddItemState.Custom =
|
||||
action.customFieldType.toCustomField(action.name)
|
||||
|
||||
updateLoginContent { loginType ->
|
||||
loginType.copy(customFieldData = loginType.customFieldData + newCustomData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoginCustomFieldValueChange(
|
||||
action: VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange,
|
||||
) {
|
||||
updateLoginContent { login ->
|
||||
login.copy(
|
||||
customFieldData = login.customFieldData.map { customField ->
|
||||
if (customField.itemId == action.customField.itemId) {
|
||||
action.customField
|
||||
} else {
|
||||
customField
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -473,7 +498,11 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick -> {
|
||||
handleSecureNoteAddNewCustomFieldClick()
|
||||
handleSecureNoteAddNewCustomFieldClick(action)
|
||||
}
|
||||
|
||||
is VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange -> {
|
||||
handleSecureNoteCustomFieldValueChange(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -535,13 +564,31 @@ class VaultAddItemViewModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun handleSecureNoteAddNewCustomFieldClick() {
|
||||
// TODO Implement custom text fields (BIT-529)
|
||||
sendEvent(
|
||||
event = VaultAddItemEvent.ShowToast(
|
||||
message = "Not yet implemented",
|
||||
),
|
||||
)
|
||||
private fun handleSecureNoteAddNewCustomFieldClick(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
) {
|
||||
val newCustomData: VaultAddItemState.Custom =
|
||||
action.customFieldType.toCustomField(action.name)
|
||||
|
||||
updateSecureNoteContent { secureNotesType ->
|
||||
secureNotesType.copy(customFieldData = secureNotesType.customFieldData + newCustomData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSecureNoteCustomFieldValueChange(
|
||||
action: VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange,
|
||||
) {
|
||||
updateSecureNoteContent { secureNote ->
|
||||
secureNote.copy(
|
||||
customFieldData = secureNote.customFieldData.map { customField ->
|
||||
if (customField.itemId == action.customField.itemId) {
|
||||
action.customField
|
||||
} else {
|
||||
customField
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Secure Notes Item Type Handlers
|
||||
|
@ -810,6 +857,7 @@ data class VaultAddItemState(
|
|||
val folderName: Text = DEFAULT_FOLDER,
|
||||
val favorite: Boolean = false,
|
||||
override val masterPasswordReprompt: Boolean = false,
|
||||
val customFieldData: List<Custom> = emptyList(),
|
||||
val notes: String = "",
|
||||
// TODO: Update this property to get available owners from the data layer (BIT-501)
|
||||
val availableFolders: List<Text> = listOf(
|
||||
|
@ -876,6 +924,7 @@ data class VaultAddItemState(
|
|||
"Folder 2".asText(),
|
||||
"Folder 3".asText(),
|
||||
),
|
||||
val customFieldData: List<Custom> = emptyList(),
|
||||
override val ownership: String = DEFAULT_OWNERSHIP,
|
||||
override val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
|
||||
) : Content() {
|
||||
|
@ -889,6 +938,58 @@ data class VaultAddItemState(
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This Models the Custom field type chosen by the user.
|
||||
*/
|
||||
@Parcelize
|
||||
sealed class Custom : Parcelable {
|
||||
|
||||
/**
|
||||
* The itemId that is used to identify the Custom item on updates.
|
||||
*/
|
||||
abstract val itemId: String
|
||||
|
||||
/**
|
||||
* Represents the data for displaying a custom text field.
|
||||
*/
|
||||
@Parcelize
|
||||
data class TextField(
|
||||
override val itemId: String,
|
||||
val name: String,
|
||||
val value: String,
|
||||
) : Custom()
|
||||
|
||||
/**
|
||||
* Represents the data for displaying a custom hidden text field.
|
||||
*/
|
||||
@Parcelize
|
||||
data class HiddenField(
|
||||
override val itemId: String,
|
||||
val name: String,
|
||||
val value: String,
|
||||
) : Custom()
|
||||
|
||||
/**
|
||||
* Represents the data for displaying a custom boolean property field.
|
||||
*/
|
||||
@Parcelize
|
||||
data class BooleanField(
|
||||
override val itemId: String,
|
||||
val name: String,
|
||||
val value: Boolean,
|
||||
) : Custom()
|
||||
|
||||
/**
|
||||
* Represents the data for displaying a custom linked field.
|
||||
*/
|
||||
@Parcelize
|
||||
data class LinkedField(
|
||||
override val itemId: String,
|
||||
val name: String,
|
||||
val vaultLinkedFieldType: VaultLinkedFieldType,
|
||||
) : Custom()
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a dialog.
|
||||
*/
|
||||
|
@ -1029,6 +1130,21 @@ sealed class VaultAddItemAction {
|
|||
*/
|
||||
data class OwnershipChange(val ownership: String) : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to add a new custom field.
|
||||
*/
|
||||
data class AddNewCustomFieldClick(
|
||||
val customFieldType: CustomFieldType,
|
||||
val name: String,
|
||||
) : LoginType()
|
||||
|
||||
/**
|
||||
* Fired when the custom field data is changed.
|
||||
*/
|
||||
data class CustomFieldValueChange(
|
||||
val customField: VaultAddItemState.Custom,
|
||||
) : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to open the username generator.
|
||||
*/
|
||||
|
@ -1063,11 +1179,6 @@ sealed class VaultAddItemAction {
|
|||
* Represents the action to open tooltip
|
||||
*/
|
||||
data object TooltipClick : LoginType()
|
||||
|
||||
/**
|
||||
* Represents the action to add a new custom field.
|
||||
*/
|
||||
data object AddNewCustomFieldClick : LoginType()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1127,7 +1238,17 @@ sealed class VaultAddItemAction {
|
|||
/**
|
||||
* Represents the action to add a new custom field.
|
||||
*/
|
||||
data object AddNewCustomFieldClick : SecureNotesType()
|
||||
data class AddNewCustomFieldClick(
|
||||
val customFieldType: CustomFieldType,
|
||||
val name: String,
|
||||
) : SecureNotesType()
|
||||
|
||||
/**
|
||||
* Fired when the custom field data is changed.
|
||||
*/
|
||||
data class CustomFieldValueChange(
|
||||
val customField: VaultAddItemState.Custom,
|
||||
) : SecureNotesType()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1156,4 +1277,43 @@ sealed class VaultAddItemAction {
|
|||
val updateCipherResult: UpdateCipherResult,
|
||||
) : Internal()
|
||||
}
|
||||
|
||||
/**
|
||||
* An extension function for adding custom field types.
|
||||
*/
|
||||
fun CustomFieldType.toCustomField(name: String): VaultAddItemState.Custom {
|
||||
return when (this) {
|
||||
CustomFieldType.BOOLEAN -> {
|
||||
VaultAddItemState.Custom.BooleanField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = name,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
|
||||
CustomFieldType.LINKED -> {
|
||||
VaultAddItemState.Custom.LinkedField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = name,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
|
||||
)
|
||||
}
|
||||
|
||||
CustomFieldType.HIDDEN -> {
|
||||
VaultAddItemState.Custom.HiddenField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = name,
|
||||
value = "",
|
||||
)
|
||||
}
|
||||
|
||||
CustomFieldType.TEXT -> {
|
||||
VaultAddItemState.Custom.TextField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = name,
|
||||
value = "",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
|
@ -28,6 +29,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
|
|||
* @property onTooltipClick Handles the action when the tooltip button is clicked.
|
||||
* @property onAddNewCustomFieldClick Handles the action when the add new custom field
|
||||
* button is clicked.
|
||||
* @property onCustomFieldValueChange Handles the action when the field's value changes
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class VaultAddLoginItemTypeHandlers(
|
||||
|
@ -47,7 +49,8 @@ class VaultAddLoginItemTypeHandlers(
|
|||
val onUriSettingsClick: () -> Unit,
|
||||
val onAddNewUriClick: () -> Unit,
|
||||
val onTooltipClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit,
|
||||
val onCustomFieldValueChange: (VaultAddItemState.Custom) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
||||
|
@ -135,9 +138,19 @@ class VaultAddLoginItemTypeHandlers(
|
|||
onTooltipClick = {
|
||||
viewModel.trySendAction(VaultAddItemAction.ItemType.LoginType.TooltipClick)
|
||||
},
|
||||
onAddNewCustomFieldClick = {
|
||||
onAddNewCustomFieldClick = { customFieldType, name ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick,
|
||||
VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick(
|
||||
customFieldType = customFieldType,
|
||||
name = name,
|
||||
),
|
||||
)
|
||||
},
|
||||
onCustomFieldValueChange = { customField ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.LoginType.CustomFieldValueChange(
|
||||
customField = customField,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem
|
||||
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
|
@ -16,6 +17,7 @@ import com.x8bit.bitwarden.ui.platform.base.util.asText
|
|||
* @property onTooltipClick Handles the action when the tooltip button is clicked.
|
||||
* @property onAddNewCustomFieldClick Handles the action when the add new custom field
|
||||
* button is clicked.
|
||||
* @property onCustomFieldValueChange Handles the action when the field's value changes
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
class VaultAddSecureNotesItemTypeHandlers(
|
||||
|
@ -26,7 +28,8 @@ class VaultAddSecureNotesItemTypeHandlers(
|
|||
val onNotesTextChange: (String) -> Unit,
|
||||
val onOwnershipTextChange: (String) -> Unit,
|
||||
val onTooltipClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: () -> Unit,
|
||||
val onAddNewCustomFieldClick: (CustomFieldType, String) -> Unit,
|
||||
val onCustomFieldValueChange: (VaultAddItemState.Custom) -> Unit,
|
||||
) {
|
||||
companion object {
|
||||
|
||||
|
@ -76,9 +79,19 @@ class VaultAddSecureNotesItemTypeHandlers(
|
|||
VaultAddItemAction.ItemType.SecureNotesType.TooltipClick,
|
||||
)
|
||||
},
|
||||
onAddNewCustomFieldClick = {
|
||||
onAddNewCustomFieldClick = { newCustomFieldType, name ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick(
|
||||
newCustomFieldType,
|
||||
name,
|
||||
),
|
||||
)
|
||||
},
|
||||
onCustomFieldValueChange = { newValue ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.CustomFieldValueChange(
|
||||
newValue,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.additem.model
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* The Enum representing the Custom Field type that is being added by the user.
|
||||
*/
|
||||
enum class CustomFieldType(val typeText: Text) {
|
||||
LINKED(R.string.field_type_linked.asText()),
|
||||
HIDDEN(R.string.field_type_hidden.asText()),
|
||||
BOOLEAN(R.string.field_type_boolean.asText()),
|
||||
TEXT(R.string.field_type_text.asText()),
|
||||
}
|
|
@ -3,9 +3,13 @@ package com.x8bit.bitwarden.ui.vault.feature.additem.util
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType.Companion.fromId
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Transforms [CipherView] into [VaultAddItemState.ViewState].
|
||||
|
@ -30,6 +34,7 @@ fun CipherView.toViewState(): VaultAddItemState.ViewState =
|
|||
ownership = "",
|
||||
// TODO: Update this property to pull available owners from data layer (BIT-501)
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -47,6 +52,7 @@ fun CipherView.toViewState(): VaultAddItemState.ViewState =
|
|||
ownership = "",
|
||||
// TODO: Update this property to pull available owners from data layer (BIT-501)
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = this.fields.orEmpty().map { it.toCustomField() },
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -58,3 +64,30 @@ fun CipherView.toViewState(): VaultAddItemState.ViewState =
|
|||
message = "Not yet implemented.".asText(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun FieldView.toCustomField() =
|
||||
when (this.type) {
|
||||
FieldType.TEXT -> VaultAddItemState.Custom.TextField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = this.name.orEmpty(),
|
||||
value = this.value.orEmpty(),
|
||||
)
|
||||
|
||||
FieldType.HIDDEN -> VaultAddItemState.Custom.HiddenField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = this.name.orEmpty(),
|
||||
value = this.value.orEmpty(),
|
||||
)
|
||||
|
||||
FieldType.BOOLEAN -> VaultAddItemState.Custom.BooleanField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = this.name.orEmpty(),
|
||||
value = this.value.toBoolean(),
|
||||
)
|
||||
|
||||
FieldType.LINKED -> VaultAddItemState.Custom.LinkedField(
|
||||
itemId = UUID.randomUUID().toString(),
|
||||
name = this.name.orEmpty(),
|
||||
vaultLinkedFieldType = fromId(requireNotNull(this.linkedId)),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -257,7 +257,7 @@ private fun CustomField(
|
|||
is VaultItemState.ViewState.Content.Custom.LinkedField -> {
|
||||
BitwardenTextField(
|
||||
label = customField.name,
|
||||
value = customField.type.label(),
|
||||
value = customField.vaultLinkedFieldType.label.invoke(),
|
||||
leadingIconResource = IconResource(
|
||||
iconPainter = painterResource(id = R.drawable.ic_linked),
|
||||
contentDescription = stringResource(id = R.string.field_type_linked),
|
||||
|
|
|
@ -15,6 +15,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModel
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.util.toViewState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -550,22 +551,9 @@ data class VaultItemState(
|
|||
*/
|
||||
@Parcelize
|
||||
data class LinkedField(
|
||||
private val id: UInt,
|
||||
val vaultLinkedFieldType: VaultLinkedFieldType,
|
||||
val name: String,
|
||||
) : Custom() {
|
||||
val type: Type get() = Type.values().first { it.id == id }
|
||||
|
||||
/**
|
||||
* Represents the types linked fields.
|
||||
*/
|
||||
enum class Type(
|
||||
val id: UInt,
|
||||
val label: Text,
|
||||
) {
|
||||
USERNAME(id = 100.toUInt(), label = R.string.username.asText()),
|
||||
PASSWORD(id = 101.toUInt(), label = R.string.password.asText()),
|
||||
}
|
||||
}
|
||||
) : Custom()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.bitwarden.core.LoginUriView
|
|||
import com.x8bit.bitwarden.data.vault.repository.model.VaultData
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.orZeroWidthSpace
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.VaultState
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
@ -82,7 +83,7 @@ private fun FieldView.toCustomField(): VaultItemState.ViewState.Content.Custom =
|
|||
)
|
||||
|
||||
FieldType.LINKED -> VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
id = requireNotNull(linkedId),
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.fromId(requireNotNull(linkedId)),
|
||||
name = name.orEmpty(),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,8 @@ package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.SecureNoteType
|
||||
|
@ -144,8 +146,7 @@ private fun VaultAddItemState.ViewState.Content.Login.toLoginCipherView(): Ciphe
|
|||
// TODO Use real organization ID (BIT-780)
|
||||
organizationId = this.originalCipher?.organizationId,
|
||||
reprompt = this.toCipherRepromptType(),
|
||||
// TODO Implement custom fields (BIT-529)
|
||||
fields = null,
|
||||
fields = this.customFieldData.map { it.toFieldView() },
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -183,8 +184,7 @@ private fun VaultAddItemState.ViewState.Content.SecureNotes.toSecureNotesCipherV
|
|||
// TODO Use real organization ID (BIT-780)
|
||||
organizationId = this.originalCipher?.organizationId,
|
||||
reprompt = this.toCipherRepromptType(),
|
||||
// TODO Implement custom fields (BIT-529)
|
||||
fields = null,
|
||||
fields = this.customFieldData.map { it.toFieldView() },
|
||||
)
|
||||
|
||||
/**
|
||||
|
@ -205,3 +205,45 @@ private fun VaultAddItemState.ViewState.Content.toCipherRepromptType(): CipherRe
|
|||
} else {
|
||||
CipherRepromptType.NONE
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms [VaultAddItemState.Custom into [FieldView].
|
||||
*/
|
||||
private fun VaultAddItemState.Custom.toFieldView(): FieldView =
|
||||
when (val item = this) {
|
||||
is VaultAddItemState.Custom.BooleanField -> {
|
||||
FieldView(
|
||||
name = item.name,
|
||||
value = item.value.toString(),
|
||||
type = FieldType.BOOLEAN,
|
||||
linkedId = null,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.HiddenField -> {
|
||||
FieldView(
|
||||
name = item.name,
|
||||
value = item.value,
|
||||
type = FieldType.HIDDEN,
|
||||
linkedId = null,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.LinkedField -> {
|
||||
FieldView(
|
||||
name = item.name,
|
||||
value = null,
|
||||
type = FieldType.LINKED,
|
||||
linkedId = item.vaultLinkedFieldType.id,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddItemState.Custom.TextField -> {
|
||||
FieldView(
|
||||
name = item.name,
|
||||
value = item.value,
|
||||
type = FieldType.TEXT,
|
||||
linkedId = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* Represents the types for linked fields.
|
||||
*
|
||||
* @param id The ID for the linked field.
|
||||
* @param label A human-readable label for the linked field.
|
||||
*/
|
||||
enum class VaultLinkedFieldType(
|
||||
val id: UInt,
|
||||
val label: Text,
|
||||
) {
|
||||
USERNAME(id = 100.toUInt(), label = R.string.username.asText()),
|
||||
PASSWORD(id = 101.toUInt(), label = R.string.password.asText()),
|
||||
;
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Helper function to get the LinkedCustomFieldType from the id
|
||||
*/
|
||||
fun fromId(id: UInt): VaultLinkedFieldType =
|
||||
VaultLinkedFieldType.entries.first { it.id == id }
|
||||
}
|
||||
}
|
|
@ -542,18 +542,6 @@ class VaultAddItemScreenTest : BaseComposeTest() {
|
|||
.assertTextContains("NewNote")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Login state clicking New Custom Field button should trigger AddNewCustomFieldClick`() {
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Login state clicking a Ownership option should send OwnershipChange action`() {
|
||||
// Opens the menu
|
||||
|
@ -793,22 +781,6 @@ class VaultAddItemScreenTest : BaseComposeTest() {
|
|||
.assertTextContains("NewNote")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state clicking New Custom Field button should trigger AddNewCustomFieldClick`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_SECURE_NOTES
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "New custom field")
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_SecureNotes state clicking a Ownership option should send OwnershipChange action`() {
|
||||
|
|
|
@ -587,21 +587,6 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(VaultAddItemEvent.ShowToast("Tooltip"), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddNewCustomFieldClick should emit ShowToast with 'Add New Custom Field' message`() =
|
||||
runTest {
|
||||
val viewModel = createAddVaultItemViewModel()
|
||||
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
.actionChannel
|
||||
.trySend(
|
||||
VaultAddItemAction.ItemType.LoginType.AddNewCustomFieldClick,
|
||||
)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Add New Custom Field"), awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
@ -734,19 +719,6 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
|
|||
assertEquals(VaultAddItemEvent.ShowToast("Not yet implemented"), awaitItem())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `AddNewCustomFieldClick should emit ShowToast with 'Add New Custom Field' message`() =
|
||||
runTest {
|
||||
viewModel.eventFlow.test {
|
||||
viewModel
|
||||
.actionChannel
|
||||
.trySend(
|
||||
VaultAddItemAction.ItemType.SecureNotesType.AddNewCustomFieldClick,
|
||||
)
|
||||
assertEquals(VaultAddItemEvent.ShowToast("Not yet implemented"), awaitItem())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
|
|
|
@ -4,8 +4,6 @@ import com.bitwarden.core.CardView
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.IdentityView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
|
@ -65,6 +63,7 @@ class CipherViewExtensionsTest {
|
|||
ownership = "",
|
||||
availableFolders = emptyList(),
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = emptyList(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
|
@ -87,6 +86,7 @@ class CipherViewExtensionsTest {
|
|||
ownership = "",
|
||||
availableFolders = emptyList(),
|
||||
availableOwners = emptyList(),
|
||||
customFieldData = emptyList(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
|
@ -113,38 +113,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
|||
viewPassword = false,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
fields = listOf(
|
||||
FieldView(
|
||||
name = "text",
|
||||
value = "value",
|
||||
type = FieldType.TEXT,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "hidden",
|
||||
value = "value",
|
||||
type = FieldType.HIDDEN,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "boolean",
|
||||
value = "true",
|
||||
type = FieldType.BOOLEAN,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "linked username",
|
||||
value = null,
|
||||
type = FieldType.LINKED,
|
||||
linkedId = 100U,
|
||||
),
|
||||
FieldView(
|
||||
name = "linked password",
|
||||
value = null,
|
||||
type = FieldType.LINKED,
|
||||
linkedId = 101U,
|
||||
),
|
||||
),
|
||||
fields = emptyList(),
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
|
|
|
@ -23,6 +23,7 @@ import com.x8bit.bitwarden.ui.util.assertScrollableNodeDoesNotExist
|
|||
import com.x8bit.bitwarden.ui.util.isProgressBar
|
||||
import com.x8bit.bitwarden.ui.util.onFirstNodeWithTextAfterScroll
|
||||
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
|
@ -712,11 +713,11 @@ private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
|
|||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked username",
|
||||
id = 100U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
|
||||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked password",
|
||||
id = 101U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
|
||||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.x8bit.bitwarden.ui.platform.base.BaseViewModelTest
|
|||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.util.DEFAULT_EMPTY_LOGIN_VIEW_STATE
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.every
|
||||
|
@ -626,11 +627,11 @@ private val DEFAULT_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
|
|||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked username",
|
||||
id = 100U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
|
||||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked password",
|
||||
id = 101U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
|
||||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
|
|
|
@ -9,6 +9,7 @@ import com.bitwarden.core.LoginUriView
|
|||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.PasswordHistoryView
|
||||
import com.x8bit.bitwarden.ui.vault.feature.item.VaultItemState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
@ -192,11 +193,11 @@ val DEFAULT_FULL_LOGIN_VIEW_STATE: VaultItemState.ViewState.Content.Login =
|
|||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked username",
|
||||
id = 100U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.USERNAME,
|
||||
),
|
||||
VaultItemState.ViewState.Content.Custom.LinkedField(
|
||||
name = "linked password",
|
||||
id = 101U,
|
||||
vaultLinkedFieldType = VaultLinkedFieldType.PASSWORD,
|
||||
),
|
||||
),
|
||||
requiresReprompt = true,
|
||||
|
|
|
@ -3,8 +3,6 @@ package com.x8bit.bitwarden.ui.vault.feature.vault.util
|
|||
import com.bitwarden.core.CipherRepromptType
|
||||
import com.bitwarden.core.CipherType
|
||||
import com.bitwarden.core.CipherView
|
||||
import com.bitwarden.core.FieldType
|
||||
import com.bitwarden.core.FieldView
|
||||
import com.bitwarden.core.LoginUriView
|
||||
import com.bitwarden.core.LoginView
|
||||
import com.bitwarden.core.PasswordHistoryView
|
||||
|
@ -159,7 +157,7 @@ class VaultDataExtensionsTest {
|
|||
viewPassword = true,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
fields = null,
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
creationDate = Instant.MIN,
|
||||
deletedDate = null,
|
||||
|
@ -183,6 +181,7 @@ class VaultDataExtensionsTest {
|
|||
masterPasswordReprompt = false,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
customFieldData = emptyList(),
|
||||
)
|
||||
|
||||
val result = loginItemType.toCipherView()
|
||||
|
@ -208,7 +207,7 @@ class VaultDataExtensionsTest {
|
|||
),
|
||||
favorite = true,
|
||||
reprompt = CipherRepromptType.NONE,
|
||||
fields = null,
|
||||
fields = emptyList(),
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
|
@ -256,7 +255,7 @@ class VaultDataExtensionsTest {
|
|||
viewPassword = true,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
fields = null,
|
||||
fields = emptyList(),
|
||||
passwordHistory = null,
|
||||
creationDate = Instant.MIN,
|
||||
deletedDate = null,
|
||||
|
@ -277,6 +276,7 @@ class VaultDataExtensionsTest {
|
|||
masterPasswordReprompt = true,
|
||||
notes = "mockNotes-1",
|
||||
ownership = "mockOwnership-1",
|
||||
customFieldData = emptyList(),
|
||||
)
|
||||
|
||||
val result = secureNotesItemType.toCipherView()
|
||||
|
@ -288,7 +288,7 @@ class VaultDataExtensionsTest {
|
|||
type = CipherType.SECURE_NOTE,
|
||||
secureNote = SecureNoteView(SecureNoteType.GENERIC),
|
||||
reprompt = CipherRepromptType.PASSWORD,
|
||||
fields = null,
|
||||
fields = emptyList(),
|
||||
),
|
||||
result,
|
||||
)
|
||||
|
@ -315,38 +315,7 @@ private val DEFAULT_BASE_CIPHER_VIEW: CipherView = CipherView(
|
|||
viewPassword = false,
|
||||
localData = null,
|
||||
attachments = null,
|
||||
fields = listOf(
|
||||
FieldView(
|
||||
name = "text",
|
||||
value = "value",
|
||||
type = FieldType.TEXT,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "hidden",
|
||||
value = "value",
|
||||
type = FieldType.HIDDEN,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "boolean",
|
||||
value = "true",
|
||||
type = FieldType.BOOLEAN,
|
||||
linkedId = null,
|
||||
),
|
||||
FieldView(
|
||||
name = "linked username",
|
||||
value = null,
|
||||
type = FieldType.LINKED,
|
||||
linkedId = 100U,
|
||||
),
|
||||
FieldView(
|
||||
name = "linked password",
|
||||
value = null,
|
||||
type = FieldType.LINKED,
|
||||
linkedId = 101U,
|
||||
),
|
||||
),
|
||||
fields = emptyList(),
|
||||
passwordHistory = listOf(
|
||||
PasswordHistoryView(
|
||||
password = "old_password",
|
||||
|
|
Loading…
Reference in a new issue