From 36d49a62a6dc305b7a938755da41de9578ff82d0 Mon Sep 17 00:00:00 2001 From: Ramsey Smith <142836716+ramsey-livefront@users.noreply.github.com> Date: Mon, 8 Jan 2024 13:23:22 -0700 Subject: [PATCH] Add linked fields for identity (#536) --- .../addedit/VaultAddEditCustomField.kt | 18 ++--- .../addedit/VaultAddEditIdentityItems.kt | 23 ++++++- .../feature/addedit/VaultAddEditViewModel.kt | 30 ++++++-- .../feature/addedit/model/CustomFieldType.kt | 12 +++- .../vault/util/VaultAddItemStateExtensions.kt | 2 +- .../ui/vault/model/VaultLinkedFieldType.kt | 20 ++++++ .../addedit/model/CustomFieldTypeTests.kt | 69 +++++++++++++++++-- 7 files changed, 149 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt index 800c74ab0e..a24c0c0c81 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditCustomField.kt @@ -61,14 +61,16 @@ fun VaultAddEditCustomField( } is VaultAddEditState.Custom.LinkedField -> { - CustomFieldLinkedField( - selectedOption = customField.vaultLinkedFieldType, - supportedLinkedTypes = supportedLinkedTypes, - onValueChanged = { - onCustomFieldValueChange(customField.copy(vaultLinkedFieldType = it)) - }, - modifier = modifier, - ) + customField.vaultLinkedFieldType?.let { fieldType -> + CustomFieldLinkedField( + selectedOption = fieldType, + supportedLinkedTypes = supportedLinkedTypes, + onValueChanged = { + onCustomFieldValueChange(customField.copy(vaultLinkedFieldType = it)) + }, + modifier = modifier, + ) + } } is VaultAddEditState.Custom.TextField -> { diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt index 002b6dc03c..b1ab9ed822 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditIdentityItems.kt @@ -339,14 +339,31 @@ fun LazyListScope.vaultAddEditIdentityItems( items(commonState.customFieldData) { customItem -> VaultAddEditCustomField( - customItem, + customField = customItem, onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, modifier = Modifier .fillMaxWidth() .padding(horizontal = 16.dp), supportedLinkedTypes = persistentListOf( - VaultLinkedFieldType.PASSWORD, - VaultLinkedFieldType.USERNAME, + VaultLinkedFieldType.TITLE, + VaultLinkedFieldType.MIDDLE_NAME, + VaultLinkedFieldType.ADDRESS_1, + VaultLinkedFieldType.ADDRESS_2, + VaultLinkedFieldType.ADDRESS_3, + VaultLinkedFieldType.CITY, + VaultLinkedFieldType.STATE, + VaultLinkedFieldType.POSTAL_CODE, + VaultLinkedFieldType.COUNTRY, + VaultLinkedFieldType.COMPANY, + VaultLinkedFieldType.EMAIL, + VaultLinkedFieldType.PHONE, + VaultLinkedFieldType.SSN, + VaultLinkedFieldType.IDENTITY_USERNAME, + VaultLinkedFieldType.PASSPORT_NUMBER, + VaultLinkedFieldType.LICENSE_NUMBER, + VaultLinkedFieldType.FIRST_NAME, + VaultLinkedFieldType.LAST_NAME, + VaultLinkedFieldType.FULL_NAME, ), ) } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt index 972127d07d..36630b2787 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/VaultAddEditViewModel.kt @@ -147,6 +147,7 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddLoginItem() { updateContent { currentContent -> currentContent.copy( + common = currentContent.common.clearNonSharedData(), type = VaultAddEditState.ViewState.Content.ItemType.Login(), ) } @@ -155,6 +156,7 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddSecureNotesItem() { updateContent { currentContent -> currentContent.copy( + common = currentContent.common.clearNonSharedData(), type = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, ) } @@ -163,6 +165,7 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddCardItem() { updateContent { currentContent -> currentContent.copy( + common = currentContent.common.clearNonSharedData(), type = VaultAddEditState.ViewState.Content.ItemType.Card(), ) } @@ -171,6 +174,7 @@ class VaultAddEditViewModel @Inject constructor( private fun handleSwitchToAddIdentityItem() { updateContent { currentContent -> currentContent.copy( + common = currentContent.common.clearNonSharedData(), type = VaultAddEditState.ViewState.Content.ItemType.Identity(), ) } @@ -230,13 +234,17 @@ class VaultAddEditViewModel @Inject constructor( private fun handleAddNewCustomFieldClick( action: VaultAddEditAction.Common.AddNewCustomFieldClick, ) { - - updateCommonContent { common -> - + updateContent { val newCustomData: VaultAddEditState.Custom = - action.customFieldType.toCustomField(action.name) - - common.copy(customFieldData = common.customFieldData + newCustomData) + action.customFieldType.toCustomField( + name = action.name, + itemType = it.type, + ) + it.copy( + common = it + .common + .copy(customFieldData = it.common.customFieldData + newCustomData), + ) } } @@ -457,6 +465,7 @@ class VaultAddEditViewModel @Inject constructor( //endregion Add Login Item Type Handlers //region Identity Type Handlers + @Suppress("LongMethod") private fun handleIdentityTypeActions(action: VaultAddEditAction.ItemType.IdentityType) { when (action) { is VaultAddEditAction.ItemType.IdentityType.FirstNameTextChange -> { @@ -909,6 +918,13 @@ class VaultAddEditViewModel @Inject constructor( } } + private fun VaultAddEditState.ViewState.Content.Common.clearNonSharedData(): + VaultAddEditState.ViewState.Content.Common = + copy( + customFieldData = customFieldData + .filterNot { it is VaultAddEditState.Custom.LinkedField }, + ) + //endregion Utility Functions } @@ -1185,7 +1201,7 @@ data class VaultAddEditState( data class LinkedField( override val itemId: String, val name: String, - val vaultLinkedFieldType: VaultLinkedFieldType, + val vaultLinkedFieldType: VaultLinkedFieldType?, ) : Custom() } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt index 764bd08d33..70306669c6 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldType.kt @@ -22,6 +22,7 @@ enum class CustomFieldType(val typeText: Text) { */ fun CustomFieldType.toCustomField( name: String, + itemType: VaultAddEditState.ViewState.Content.ItemType, ): VaultAddEditState.Custom { return when (this) { CustomFieldType.BOOLEAN -> { @@ -36,7 +37,7 @@ fun CustomFieldType.toCustomField( VaultAddEditState.Custom.LinkedField( itemId = UUID.randomUUID().toString(), name = name, - vaultLinkedFieldType = VaultLinkedFieldType.USERNAME, + vaultLinkedFieldType = itemType.defaultLinkedFieldTypeOrNull, ) } @@ -57,3 +58,12 @@ fun CustomFieldType.toCustomField( } } } + +@Suppress("MaxLineLength") +private val VaultAddEditState.ViewState.Content.ItemType.defaultLinkedFieldTypeOrNull: VaultLinkedFieldType? + get() = when (this) { + is VaultAddEditState.ViewState.Content.ItemType.Card -> VaultLinkedFieldType.CARDHOLDER_NAME + is VaultAddEditState.ViewState.Content.ItemType.Identity -> VaultLinkedFieldType.TITLE + is VaultAddEditState.ViewState.Content.ItemType.Login -> VaultLinkedFieldType.USERNAME + is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> null + } diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt index f4a8a377e1..2014b41de8 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/vault/util/VaultAddItemStateExtensions.kt @@ -177,7 +177,7 @@ private fun VaultAddEditState.Custom.toFieldView(): FieldView = name = item.name, value = null, type = FieldType.LINKED, - linkedId = item.vaultLinkedFieldType.id, + linkedId = item.vaultLinkedFieldType?.id, ) } 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 index ff0937a4e0..3611622d99 100644 --- 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 @@ -23,6 +23,26 @@ enum class VaultLinkedFieldType( SECURITY_CODE(id = 303.toUInt(), label = R.string.security_code.asText()), BRAND(id = 304.toUInt(), label = R.string.brand.asText()), NUMBER(id = 305.toUInt(), label = R.string.number.asText()), + + TITLE(id = 400.toUInt(), label = R.string.title.asText()), + MIDDLE_NAME(id = 401.toUInt(), label = R.string.middle_name.asText()), + ADDRESS_1(id = 402.toUInt(), label = R.string.address1.asText()), + ADDRESS_2(id = 403.toUInt(), label = R.string.address2.asText()), + ADDRESS_3(id = 404.toUInt(), label = R.string.address3.asText()), + CITY(id = 405.toUInt(), label = R.string.city_town.asText()), + STATE(id = 406.toUInt(), label = R.string.state_province.asText()), + POSTAL_CODE(id = 407.toUInt(), label = R.string.zip_postal_code.asText()), + COUNTRY(id = 408.toUInt(), label = R.string.country.asText()), + COMPANY(id = 409.toUInt(), label = R.string.company.asText()), + EMAIL(id = 410.toUInt(), label = R.string.email.asText()), + PHONE(id = 411.toUInt(), label = R.string.phone.asText()), + SSN(id = 412.toUInt(), label = R.string.ssn.asText()), + IDENTITY_USERNAME(id = 413.toUInt(), label = R.string.username.asText()), + PASSPORT_NUMBER(id = 414.toUInt(), label = R.string.passport_number.asText()), + LICENSE_NUMBER(id = 415.toUInt(), label = R.string.license_number.asText()), + FIRST_NAME(id = 416.toUInt(), label = R.string.first_name.asText()), + LAST_NAME(id = 417.toUInt(), label = R.string.last_name.asText()), + FULL_NAME(id = 418.toUInt(), label = R.string.full_name.asText()), ; companion object { diff --git a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldTypeTests.kt b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldTypeTests.kt index abfb899306..00e3f4341e 100644 --- a/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldTypeTests.kt +++ b/app/src/test/java/com/x8bit/bitwarden/ui/vault/feature/addedit/model/CustomFieldTypeTests.kt @@ -31,19 +31,72 @@ class CustomFieldTypeTests { val type = CustomFieldType.BOOLEAN val expected = VaultAddEditState.Custom.BooleanField(TEST_ID, "test", false) - val actual = type.toCustomField(name) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + ) assertEquals(expected, actual) } @Test - fun `toCustomField should return a custom linked type when we pass in required linked type`() { + fun `toCustomField should return a custom linked type when we pass in Login type`() { val name = "test" val type = CustomFieldType.LINKED val expected = VaultAddEditState.Custom.LinkedField(TEST_ID, "test", VaultLinkedFieldType.USERNAME) - val actual = type.toCustomField(name) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.Login(), + ) + + assertEquals(expected, actual) + } + + @Test + fun `toCustomField should return a custom linked type when we pass in Identity type`() { + val name = "test" + val type = CustomFieldType.LINKED + + val expected = + VaultAddEditState.Custom.LinkedField(TEST_ID, "test", VaultLinkedFieldType.TITLE) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.Identity(), + ) + + assertEquals(expected, actual) + } + + @Suppress("MaxLineLength") + @Test + fun `toCustomField should return a custom linked type when we pass in Card type`() { + val name = "test" + val type = CustomFieldType.LINKED + + val expected = + VaultAddEditState.Custom.LinkedField(TEST_ID, "test", VaultLinkedFieldType.CARDHOLDER_NAME) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.Card(), + ) + + assertEquals(expected, actual) + } + + @Suppress("MaxLineLength") + @Test + fun `toCustomField should return a null custom linked type when we pass in Secure Note type`() { + val name = "test" + val type = CustomFieldType.LINKED + + val expected = + VaultAddEditState.Custom.LinkedField(TEST_ID, "test", null) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + ) assertEquals(expected, actual) } @@ -54,7 +107,10 @@ class CustomFieldTypeTests { val type = CustomFieldType.TEXT val expected = VaultAddEditState.Custom.TextField(TEST_ID, "test", "") - val actual = type.toCustomField(name) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + ) assertEquals(expected, actual) } @@ -65,7 +121,10 @@ class CustomFieldTypeTests { val type = CustomFieldType.HIDDEN val expected = VaultAddEditState.Custom.HiddenField(TEST_ID, "test", "") - val actual = type.toCustomField(name) + val actual = type.toCustomField( + name = name, + itemType = VaultAddEditState.ViewState.Content.ItemType.SecureNotes, + ) assertEquals(expected, actual) }