diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextEntryDialog.kt b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextEntryDialog.kt new file mode 100644 index 000000000..369e06245 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/platform/components/BitwardenTextEntryDialog.kt @@ -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, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomField.kt new file mode 100644 index 000000000..d190c8789 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomField.kt @@ -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 = 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 = 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) + }, + ) + }, + ) + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomFieldsButton.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomFieldsButton.kt new file mode 100644 index 000000000..43b464f7a --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditCustomFieldsButton.kt @@ -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 = 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, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt index bd2ce3c28..36c555af4 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditLoginItems.kt @@ -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), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt index 3a1b7d87a..6f9ca7438 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditSecureNotesItems.kt @@ -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), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt index 84962bed4..e5ff76435 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModel.kt @@ -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 = emptyList(), val notes: String = "", // TODO: Update this property to get available owners from the data layer (BIT-501) val availableFolders: List = listOf( @@ -876,6 +924,7 @@ data class VaultAddItemState( "Folder 2".asText(), "Folder 3".asText(), ), + val customFieldData: List = emptyList(), override val ownership: String = DEFAULT_OWNERSHIP, override val availableOwners: List = 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 = "", + ) + } + } + } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt index fe3515b8d..f27c71ddc 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt @@ -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, + ), ) }, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt index b75408901..7fc680552 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddSecureNotesItemTypeHandlers.kt @@ -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, + ), ) }, ) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt new file mode 100644 index 000000000..4b992c5b2 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/model/CustomFieldType.kt @@ -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()), +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt index 7743b43f0..210a11448 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensions.kt @@ -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)), + ) + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt index f29398182..d7a331606 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemLoginContent.kt @@ -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), diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt index 3188470f0..809191019 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModel.kt @@ -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() } } } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt index c1fff94f4..eb94f07da 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensions.kt @@ -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(), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt index 22769391b..88e15b05a 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensions.kt @@ -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, + ) + } + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultLinkedFieldType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultLinkedFieldType.kt new file mode 100644 index 000000000..a326dc7b9 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/model/VaultLinkedFieldType.kt @@ -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 } + } +} diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt index 9d435e063..ad659bc90 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreenTest.kt @@ -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`() { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt index c1f801dc0..ce8caad81 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemViewModelTest.kt @@ -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") diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt index 0f742e1db..5c497bf74 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/additem/util/CipherViewExtensionsTest.kt @@ -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", diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt index ae6e6a103..09eac2db5 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemScreenTest.kt @@ -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, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt index 1a3fbdae9..fad950a9e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/VaultItemViewModelTest.kt @@ -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, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt index f61a5cbc9..4edf8a713 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/item/util/CipherViewExtensionsTest.kt @@ -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, diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt index 1d3a73ff9..eaebdd07e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultDataExtensionsTest.kt @@ -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",