diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditIdentityItems.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditIdentityItems.kt new file mode 100644 index 000000000..f6e762442 --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditIdentityItems.kt @@ -0,0 +1,404 @@ +package com.x8bit.bitwarden.ui.vault.feature.additem + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyListScope +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +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.unit.dp +import com.x8bit.bitwarden.R +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.handlers.VaultAddIdentityItemTypeHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddItemCommonHandlers +import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType +import kotlinx.collections.immutable.persistentListOf +import kotlinx.collections.immutable.toImmutableList + +/** + * The UI for adding and editing an identity cipher. + */ +@Suppress("LongMethod") +fun LazyListScope.addEditIdentityItems( + commonState: VaultAddItemState.ViewState.Content.Common, + identityState: VaultAddItemState.ViewState.Content.ItemType.Identity, + isAddItemMode: Boolean, + commonTypeHandlers: VaultAddItemCommonHandlers, + identityItemTypeHandlers: VaultAddIdentityItemTypeHandlers, +) { + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.name), + value = commonState.name, + onValueChange = commonTypeHandlers.onNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + TitleMultiSelectButton( + selectedTitle = identityState.selectedTitle, + onTitleSelected = identityItemTypeHandlers.onTitleSelected, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.first_name), + value = identityState.firstName, + onValueChange = identityItemTypeHandlers.onFirstNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.middle_name), + value = identityState.middleName, + onValueChange = identityItemTypeHandlers.onMiddleNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.last_name), + value = identityState.lastName, + onValueChange = identityItemTypeHandlers.onLastNameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.username), + value = identityState.username, + onValueChange = identityItemTypeHandlers.onUsernameTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.company), + value = identityState.company, + onValueChange = identityItemTypeHandlers.onCompanyTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.ssn), + value = identityState.ssn, + onValueChange = identityItemTypeHandlers.onSsnTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.passport_number), + value = identityState.passportNumber, + onValueChange = identityItemTypeHandlers.onPassportNumberTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.license_number), + value = identityState.licenseNumber, + onValueChange = identityItemTypeHandlers.onLicenseNumberTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.email), + value = identityState.email, + onValueChange = identityItemTypeHandlers.onEmailTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.phone), + value = identityState.phone, + onValueChange = identityItemTypeHandlers.onPhoneTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.address1), + value = identityState.address1, + onValueChange = identityItemTypeHandlers.onAddress1TextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.address2), + value = identityState.address2, + onValueChange = identityItemTypeHandlers.onAddress2TextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.address3), + value = identityState.address3, + onValueChange = identityItemTypeHandlers.onAddress3TextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.city_town), + value = identityState.city, + onValueChange = identityItemTypeHandlers.onCityTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.zip_postal_code), + value = identityState.zip, + onValueChange = identityItemTypeHandlers.onZipTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + label = stringResource(id = R.string.country), + value = identityState.country, + onValueChange = identityItemTypeHandlers.onCountryTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.miscellaneous), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.folder), + options = commonState.availableFolders.map { it.invoke() }.toImmutableList(), + selectedOption = commonState.folderName.invoke(), + onOptionSelected = commonTypeHandlers.onFolderTextChange, + modifier = Modifier.padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitch( + label = stringResource( + id = R.string.favorite, + ), + isChecked = commonState.favorite, + onCheckedChange = commonTypeHandlers.onToggleFavorite, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + BitwardenSwitchWithActions( + label = stringResource(id = R.string.password_prompt), + isChecked = commonState.masterPasswordReprompt, + onCheckedChange = commonTypeHandlers.onToggleMasterPasswordReprompt, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + actions = { + IconButton(onClick = commonTypeHandlers.onTooltipClick) { + Icon( + painter = painterResource(id = R.drawable.ic_tooltip), + tint = MaterialTheme.colorScheme.onSurface, + contentDescription = stringResource( + id = R.string.master_password_re_prompt_help, + ), + ) + } + }, + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.notes), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenTextField( + singleLine = false, + label = stringResource(id = R.string.notes), + value = commonState.notes, + onValueChange = commonTypeHandlers.onNotesTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.custom_fields), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + items(commonState.customFieldData) { customItem -> + AddEditCustomField( + customItem, + onCustomFieldValueChange = commonTypeHandlers.onCustomFieldValueChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + supportedLinkedTypes = persistentListOf( + VaultLinkedFieldType.PASSWORD, + VaultLinkedFieldType.USERNAME, + ), + ) + } + + item { + Spacer(modifier = Modifier.height(16.dp)) + AddEditCustomFieldsButton( + onFinishNamingClick = commonTypeHandlers.onAddNewCustomFieldClick, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + if (isAddItemMode) { + item { + Spacer(modifier = Modifier.height(24.dp)) + BitwardenListHeaderText( + label = stringResource(id = R.string.ownership), + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + + item { + Spacer(modifier = Modifier.height(8.dp)) + BitwardenMultiSelectButton( + label = stringResource(id = R.string.who_owns_this_item), + options = commonState.availableOwners.toImmutableList(), + selectedOption = commonState.ownership, + onOptionSelected = commonTypeHandlers.onOwnershipTextChange, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + ) + } + } + item { + Spacer(modifier = Modifier.height(24.dp)) + } +} + +@Composable +private fun TitleMultiSelectButton( + selectedTitle: VaultAddItemState.ViewState.Content.ItemType.Identity.Title, + onTitleSelected: (VaultAddItemState.ViewState.Content.ItemType.Identity.Title) -> Unit, + modifier: Modifier = Modifier, +) { + val resources = LocalContext.current.resources + BitwardenMultiSelectButton( + label = stringResource(id = R.string.title), + options = VaultAddItemState.ViewState.Content.ItemType.Identity.Title + .entries + .map { it.value() } + .toImmutableList(), + selectedOption = selectedTitle.value(), + onOptionSelected = { selectedString -> + onTitleSelected( + VaultAddItemState.ViewState.Content.ItemType.Identity.Title + .entries + .first { it.value.toString(resources) == selectedString }, + ) + }, + modifier = modifier, + ) +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt index b7d13ac1e..f69965b4f 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/AddEditItemContent.kt @@ -15,6 +15,9 @@ import com.x8bit.bitwarden.R import com.x8bit.bitwarden.ui.platform.base.util.PermissionsManager import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddIdentityItemTypeHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddItemCommonHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddLoginItemTypeHandlers import kotlinx.collections.immutable.toImmutableList /** @@ -26,8 +29,9 @@ fun AddEditItemContent( state: VaultAddItemState.ViewState.Content, isAddItemMode: Boolean, onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit, - commonTypeHandlers: VaultAddItemCommonTypeHandlers, + commonTypeHandlers: VaultAddItemCommonHandlers, loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, + identityItemTypeHandlers: VaultAddIdentityItemTypeHandlers, modifier: Modifier = Modifier, permissionsManager: PermissionsManager, ) { @@ -91,7 +95,13 @@ fun AddEditItemContent( } is VaultAddItemState.ViewState.Content.ItemType.Identity -> { - // TODO(BIT-667): Create UI for identity-type item creation + addEditIdentityItems( + commonState = state.common, + identityState = state.type, + isAddItemMode = isAddItemMode, + commonTypeHandlers = commonTypeHandlers, + identityItemTypeHandlers = identityItemTypeHandlers, + ) } is VaultAddItemState.ViewState.Content.ItemType.SecureNotes -> { 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 babe9bdf2..ab78194b5 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 @@ -25,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.feature.additem.handlers.VaultAddItemCommonHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddLoginItemTypeHandlers import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -37,7 +39,7 @@ fun LazyListScope.addEditLoginItems( commonState: VaultAddItemState.ViewState.Content.Common, loginState: VaultAddItemState.ViewState.Content.ItemType.Login, isAddItemMode: Boolean, - commonActionHandler: VaultAddItemCommonTypeHandlers, + commonActionHandler: VaultAddItemCommonHandlers, loginItemTypeHandlers: VaultAddLoginItemTypeHandlers, onTotpSetupClick: () -> Unit, ) { 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 88714b885..e881bf6a3 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 @@ -19,6 +19,7 @@ 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.handlers.VaultAddItemCommonHandlers import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.toImmutableList @@ -30,7 +31,7 @@ import kotlinx.collections.immutable.toImmutableList fun LazyListScope.addEditSecureNotesItems( commonState: VaultAddItemState.ViewState.Content.Common, isAddItemMode: Boolean, - commonTypeHandlers: VaultAddItemCommonTypeHandlers, + commonTypeHandlers: VaultAddItemCommonHandlers, ) { item { Spacer(modifier = Modifier.height(8.dp)) diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt index 3633eb3ed..23f129d3e 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemScreen.kt @@ -28,6 +28,9 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenScaffold import com.x8bit.bitwarden.ui.platform.components.BitwardenTextButton import com.x8bit.bitwarden.ui.platform.components.BitwardenTopAppBar import com.x8bit.bitwarden.ui.platform.components.LoadingDialogState +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddIdentityItemTypeHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddItemCommonHandlers +import com.x8bit.bitwarden.ui.vault.feature.additem.handlers.VaultAddLoginItemTypeHandlers import com.x8bit.bitwarden.ui.platform.base.util.PermissionsManager import com.x8bit.bitwarden.ui.platform.base.util.PermissionsManagerImpl @@ -61,7 +64,11 @@ fun VaultAddItemScreen( } val commonTypeHandlers = remember(viewModel) { - VaultAddItemCommonTypeHandlers.create(viewModel = viewModel) + VaultAddItemCommonHandlers.create(viewModel = viewModel) + } + + val identityItemTypeHandlers = remember(viewModel) { + VaultAddIdentityItemTypeHandlers.create(viewModel = viewModel) } VaultAddEditItemDialogs( @@ -107,6 +114,7 @@ fun VaultAddItemScreen( loginItemTypeHandlers = loginItemTypeHandlers, commonTypeHandlers = commonTypeHandlers, permissionsManager = permissionsManager, + identityItemTypeHandlers = identityItemTypeHandlers, modifier = Modifier .imePadding() .padding(innerPadding) 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 6d073c95e..ea56e508d 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 @@ -42,7 +42,7 @@ private const val KEY_STATE = "state" * @param savedStateHandle Handles the navigation arguments of this ViewModel. */ @HiltViewModel -@Suppress("TooManyFunctions") +@Suppress("TooManyFunctions", "LargeClass") class VaultAddItemViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val vaultRepository: VaultRepository, @@ -87,6 +87,7 @@ class VaultAddItemViewModel @Inject constructor( when (action) { is VaultAddItemAction.Common -> handleCommonActions(action) is VaultAddItemAction.ItemType.LoginType -> handleAddLoginTypeAction(action) + is VaultAddItemAction.ItemType.IdentityType -> handleIdentityTypeActions(action) is VaultAddItemAction.Internal -> handleInternalActions(action) } } @@ -158,7 +159,7 @@ class VaultAddItemViewModel @Inject constructor( private fun handleSwitchToAddIdentityItem() { updateContent { currentContent -> currentContent.copy( - type = VaultAddItemState.ViewState.Content.ItemType.Identity, + type = VaultAddItemState.ViewState.Content.ItemType.Identity(), ) } } @@ -438,6 +439,182 @@ class VaultAddItemViewModel @Inject constructor( //endregion Add Login Item Type Handlers + //region Identity Type Handlers + private fun handleIdentityTypeActions(action: VaultAddItemAction.ItemType.IdentityType) { + when (action) { + is VaultAddItemAction.ItemType.IdentityType.FirstNameTextChange -> { + handleIdentityFirstNameTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.Address1TextChange -> { + handleIdentityAddress1TextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.Address2TextChange -> { + handleIdentityAddress2TextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.Address3TextChange -> { + handleIdentityAddress3TextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.CityTextChange -> { + handleIdentityCityTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.CompanyTextChange -> { + handleIdentityCompanyTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.CountryTextChange -> { + handleIdentityCountryTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.EmailTextChange -> { + handleIdentityEmailTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.LastNameTextChange -> { + handleIdentityLastNameTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.LicenseNumberTextChange -> { + handleIdentityLicenseNumberTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.MiddleNameTextChange -> { + handleIdentityMiddleNameTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.PassportNumberTextChange -> { + handleIdentityPassportNumberTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.PhoneTextChange -> { + handleIdentityPhoneTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.ZipTextChange -> { + handleIdentityZipTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.SsnTextChange -> { + handleIdentitySsnTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.UsernameTextChange -> { + handleIdentityUsernameTextChange(action) + } + + is VaultAddItemAction.ItemType.IdentityType.TitleSelected -> { + handleIdentityTitleSelected(action) + } + } + } + + private fun handleIdentityAddress1TextChange( + action: VaultAddItemAction.ItemType.IdentityType.Address1TextChange, + ) { + updateIdentityContent { it.copy(address1 = action.address1) } + } + + private fun handleIdentityAddress2TextChange( + action: VaultAddItemAction.ItemType.IdentityType.Address2TextChange, + ) { + updateIdentityContent { it.copy(address2 = action.address2) } + } + + private fun handleIdentityAddress3TextChange( + action: VaultAddItemAction.ItemType.IdentityType.Address3TextChange, + ) { + updateIdentityContent { it.copy(address3 = action.address3) } + } + + private fun handleIdentityCityTextChange( + action: VaultAddItemAction.ItemType.IdentityType.CityTextChange, + ) { + updateIdentityContent { it.copy(city = action.city) } + } + + private fun handleIdentityCompanyTextChange( + action: VaultAddItemAction.ItemType.IdentityType.CompanyTextChange, + ) { + updateIdentityContent { it.copy(company = action.company) } + } + + private fun handleIdentityCountryTextChange( + action: VaultAddItemAction.ItemType.IdentityType.CountryTextChange, + ) { + updateIdentityContent { it.copy(country = action.country) } + } + + private fun handleIdentityEmailTextChange( + action: VaultAddItemAction.ItemType.IdentityType.EmailTextChange, + ) { + updateIdentityContent { it.copy(email = action.email) } + } + + private fun handleIdentityLastNameTextChange( + action: VaultAddItemAction.ItemType.IdentityType.LastNameTextChange, + ) { + updateIdentityContent { it.copy(lastName = action.lastName) } + } + + private fun handleIdentityLicenseNumberTextChange( + action: VaultAddItemAction.ItemType.IdentityType.LicenseNumberTextChange, + ) { + updateIdentityContent { it.copy(licenseNumber = action.licenseNumber) } + } + + private fun handleIdentityMiddleNameTextChange( + action: VaultAddItemAction.ItemType.IdentityType.MiddleNameTextChange, + ) { + updateIdentityContent { it.copy(middleName = action.middleName) } + } + + private fun handleIdentityPassportNumberTextChange( + action: VaultAddItemAction.ItemType.IdentityType.PassportNumberTextChange, + ) { + updateIdentityContent { it.copy(passportNumber = action.passportNumber) } + } + + private fun handleIdentityPhoneTextChange( + action: VaultAddItemAction.ItemType.IdentityType.PhoneTextChange, + ) { + updateIdentityContent { it.copy(phone = action.phone) } + } + + private fun handleIdentityZipTextChange( + action: VaultAddItemAction.ItemType.IdentityType.ZipTextChange, + ) { + updateIdentityContent { it.copy(zip = action.zip) } + } + + private fun handleIdentitySsnTextChange( + action: VaultAddItemAction.ItemType.IdentityType.SsnTextChange, + ) { + updateIdentityContent { it.copy(ssn = action.ssn) } + } + + private fun handleIdentityUsernameTextChange( + action: VaultAddItemAction.ItemType.IdentityType.UsernameTextChange, + ) { + updateIdentityContent { it.copy(username = action.username) } + } + + private fun handleIdentityFirstNameTextChange( + action: VaultAddItemAction.ItemType.IdentityType.FirstNameTextChange, + ) { + updateIdentityContent { it.copy(firstName = action.firstName) } + } + + private fun handleIdentityTitleSelected( + action: VaultAddItemAction.ItemType.IdentityType.TitleSelected, + ) { + updateIdentityContent { it.copy(selectedTitle = action.title) } + } + //endregion Identity Type Handlers + //region Internal Type Handlers private fun handleInternalActions(action: VaultAddItemAction.Internal) { @@ -594,6 +771,16 @@ class VaultAddItemViewModel @Inject constructor( } } + private inline fun updateIdentityContent( + crossinline block: (VaultAddItemState.ViewState.Content.ItemType.Identity) -> + VaultAddItemState.ViewState.Content.ItemType.Identity, + ) { + updateContent { currentContent -> + (currentContent.type as? VaultAddItemState.ViewState.Content.ItemType.Identity) + ?.let { currentContent.copy(type = block(it)) } + } + } + //endregion Utility Functions } @@ -746,9 +933,57 @@ data class VaultAddItemState( /** * Represents the `Identity` item type. + * + * @property selectedTitle The selected title for the identity item. + * @property firstName The first name for the identity item. + * @property middleName The middle name for the identity item. + * @property lastName The last name for the identity item. + * @property username The username for the identity item. + * @property company The company for the identity item. + * @property ssn The SSN for the identity item. + * @property passportNumber The passport number for the identity item. + * @property licenseNumber The license number for the identity item. + * @property email The email for the identity item. + * @property phone The phone for the identity item. + * @property address1 The address1 for the identity item. + * @property address2 The address2 for the identity item. + * @property address3 The address3 for the identity item. + * @property city The city for the identity item. + * @property zip The zip for the identity item. + * @property country The country for the identity item. */ @Parcelize - data object Identity : ItemType() { + data class Identity( + val selectedTitle: Title = Title.MR, + val firstName: String = "", + val middleName: String = "", + val lastName: String = "", + val username: String = "", + val company: String = "", + val ssn: String = "", + val passportNumber: String = "", + val licenseNumber: String = "", + val email: String = "", + val phone: String = "", + val address1: String = "", + val address2: String = "", + val address3: String = "", + val city: String = "", + val zip: String = "", + val country: String = "", + ) : ItemType() { + + /** + * Defines all available title options for identities. + */ + enum class Title(val value: Text) { + MR(value = R.string.mr.asText()), + MRS(value = R.string.mrs.asText()), + MS(value = R.string.ms.asText()), + MX(value = R.string.mx.asText()), + DR(value = R.string.dr.asText()), + } + override val displayStringResId: Int get() = ItemTypeOption.IDENTITY.labelRes } @@ -1011,6 +1246,133 @@ sealed class VaultAddItemAction { */ data object AddNewUriClick : LoginType() } + + /** + * Represents actions specific to the Identity type. + */ + sealed class IdentityType : ItemType() { + + /** + * Fired when the first name text input is changed. + * + * @property firstName The new first name text. + */ + data class FirstNameTextChange(val firstName: String) : IdentityType() + + /** + * Fired when the middle name text input is changed. + * + * @property middleName The new middle name text. + */ + data class MiddleNameTextChange(val middleName: String) : IdentityType() + + /** + * Fired when the last name text input is changed. + * + * @property lastName The new last name text. + */ + data class LastNameTextChange(val lastName: String) : IdentityType() + + /** + * Fired when the username text input is changed. + * + * @property username The new username text. + */ + data class UsernameTextChange(val username: String) : IdentityType() + + /** + * Fired when the company text input is changed. + * + * @property company The new company text. + */ + data class CompanyTextChange(val company: String) : IdentityType() + + /** + * Fired when the SSN text input is changed. + * + * @property ssn The new SSN text. + */ + data class SsnTextChange(val ssn: String) : IdentityType() + + /** + * Fired when the passport number text input is changed. + * + * @property passportNumber The new passport number text. + */ + data class PassportNumberTextChange(val passportNumber: String) : IdentityType() + + /** + * Fired when the license number text input is changed. + * + * @property licenseNumber The new license number text. + */ + data class LicenseNumberTextChange(val licenseNumber: String) : IdentityType() + + /** + * Fired when the email text input is changed. + * + * @property email The new email text. + */ + data class EmailTextChange(val email: String) : IdentityType() + + /** + * Fired when the phone text input is changed. + * + * @property phone The new phone text. + */ + data class PhoneTextChange(val phone: String) : IdentityType() + + /** + * Fired when the address1 text input is changed. + * + * @property address1 The new address1 text. + */ + data class Address1TextChange(val address1: String) : IdentityType() + + /** + * Fired when the address2 text input is changed. + * + * @property address2 The new address2 text. + */ + data class Address2TextChange(val address2: String) : IdentityType() + + /** + * Fired when the address3 text input is changed. + * + * @property address3 The new address3 text. + */ + data class Address3TextChange(val address3: String) : IdentityType() + + /** + * Fired when the city text input is changed. + * + * @property city The new city text. + */ + data class CityTextChange(val city: String) : IdentityType() + + /** + * Fired when the zip text input is changed. + * + * @property zip The new postal text. + */ + data class ZipTextChange(val zip: String) : IdentityType() + + /** + * Fired when the country text input is changed. + * + * @property country The new country text. + */ + data class CountryTextChange(val country: String) : IdentityType() + + /** + * Fired when the title input is selected. + * + * @property title The selected title. + */ + data class TitleSelected( + val title: VaultAddItemState.ViewState.Content.ItemType.Identity.Title, + ) : IdentityType() + } } /** diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddIdentityItemTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddIdentityItemTypeHandlers.kt new file mode 100644 index 000000000..31cd9c10d --- /dev/null +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddIdentityItemTypeHandlers.kt @@ -0,0 +1,179 @@ +package com.x8bit.bitwarden.ui.vault.feature.additem.handlers + +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemAction +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemViewModel + +/** + * A collection of handler functions specifically tailored for managing actions + * within the context of adding identity items to a vault. + * + * @property onFirstNameTextChange Handles the action when the first name text is changed. + * @property onMiddleNameTextChange Handles the action when the middle name text is changed. + * @property onLastNameTextChange Handles the action when the last name text is changed. + * @property onUsernameTextChange Handles the action when the username text is changed. + * @property onCompanyTextChange Handles the action when the company text is changed. + * @property onSsnTextChange Handles the action when the SSN text is changed. + * @property onPassportNumberTextChange Handles the action when the passport number text is changed. + * @property onLicenseNumberTextChange Handles the action when the license number text is changed. + * @property onEmailTextChange Handles the action when the email text is changed. + * @property onPhoneTextChange Handles the action when the phone text is changed. + * @property onAddress1TextChange Handles the action when the address1 text is changed. + * @property onAddress2TextChange Handles the action when the address2 text is changed. + * @property onAddress3TextChange Handles the action when the address3 text is changed. + * @property onCityTextChange Handles the action when the city text is changed. + * @property onZipTextChange Handles the action when the zip text is changed. + * @property onCountryTextChange Handles the action when the country text is changed. + */ +@Suppress("LongParameterList") +class VaultAddIdentityItemTypeHandlers( + val onTitleSelected: (VaultAddItemState.ViewState.Content.ItemType.Identity.Title) -> Unit, + val onFirstNameTextChange: (String) -> Unit, + val onMiddleNameTextChange: (String) -> Unit, + val onLastNameTextChange: (String) -> Unit, + val onUsernameTextChange: (String) -> Unit, + val onCompanyTextChange: (String) -> Unit, + val onSsnTextChange: (String) -> Unit, + val onPassportNumberTextChange: (String) -> Unit, + val onLicenseNumberTextChange: (String) -> Unit, + val onEmailTextChange: (String) -> Unit, + val onPhoneTextChange: (String) -> Unit, + val onAddress1TextChange: (String) -> Unit, + val onAddress2TextChange: (String) -> Unit, + val onAddress3TextChange: (String) -> Unit, + val onCityTextChange: (String) -> Unit, + val onZipTextChange: (String) -> Unit, + val onCountryTextChange: (String) -> Unit, +) { + companion object { + + /** + * Creates an instance of [VaultAddIdentityItemTypeHandlers] by binding actions + * to the provided [VaultAddItemViewModel]. + */ + @Suppress("LongMethod") + fun create(viewModel: VaultAddItemViewModel): VaultAddIdentityItemTypeHandlers { + return VaultAddIdentityItemTypeHandlers( + onTitleSelected = { newTitle -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.TitleSelected( + title = newTitle, + ), + ) + }, + onFirstNameTextChange = { newFirstName -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.FirstNameTextChange( + firstName = newFirstName, + ), + ) + }, + onMiddleNameTextChange = { newMiddleName -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.MiddleNameTextChange( + middleName = newMiddleName, + ), + ) + }, + onLastNameTextChange = { newLastName -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.LastNameTextChange( + lastName = newLastName, + ), + ) + }, + onUsernameTextChange = { newUsername -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.UsernameTextChange( + username = newUsername, + ), + ) + }, + onCompanyTextChange = { newCompany -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.CompanyTextChange( + company = newCompany, + ), + ) + }, + onSsnTextChange = { newSsn -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.SsnTextChange( + ssn = newSsn, + ), + ) + }, + onPassportNumberTextChange = { newPassportNumber -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.PassportNumberTextChange( + passportNumber = newPassportNumber, + ), + ) + }, + onLicenseNumberTextChange = { newLicenseNumber -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.LicenseNumberTextChange( + licenseNumber = newLicenseNumber, + ), + ) + }, + onEmailTextChange = { newEmail -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.EmailTextChange( + email = newEmail, + ), + ) + }, + onPhoneTextChange = { newPhone -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.PhoneTextChange( + phone = newPhone, + ), + ) + }, + onAddress1TextChange = { newAddress1 -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.Address1TextChange( + address1 = newAddress1, + ), + ) + }, + onAddress2TextChange = { newAddress2 -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.Address2TextChange( + address2 = newAddress2, + ), + ) + }, + onAddress3TextChange = { newAddress3 -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.Address3TextChange( + address3 = newAddress3, + ), + ) + }, + onCityTextChange = { newCity -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.CityTextChange( + city = newCity, + ), + ) + }, + onZipTextChange = { newZip -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.ZipTextChange( + zip = newZip, + ), + ) + }, + onCountryTextChange = { newCountry -> + viewModel.trySendAction( + VaultAddItemAction.ItemType.IdentityType.CountryTextChange( + country = newCountry, + ), + ) + }, + ) + } + } +} diff --git a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddItemCommonHandlers.kt similarity index 89% rename from app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt rename to app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddItemCommonHandlers.kt index 7d06344ff..8c315c0ef 100644 --- a/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddItemCommonTypeHandlers.kt +++ b/app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddItemCommonHandlers.kt @@ -1,6 +1,9 @@ -package com.x8bit.bitwarden.ui.vault.feature.additem +package com.x8bit.bitwarden.ui.vault.feature.additem.handlers import com.x8bit.bitwarden.ui.platform.base.util.asText +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemAction +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemState +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemViewModel import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType /** @@ -20,7 +23,7 @@ import com.x8bit.bitwarden.ui.vault.feature.additem.model.CustomFieldType * @property onCustomFieldValueChange Handles the action when the field's value changes */ @Suppress("LongParameterList") -class VaultAddItemCommonTypeHandlers( +class VaultAddItemCommonHandlers( val onNameTextChange: (String) -> Unit, val onFolderTextChange: (String) -> Unit, val onToggleFavorite: (Boolean) -> Unit, @@ -34,12 +37,12 @@ class VaultAddItemCommonTypeHandlers( companion object { /** - * Creates an instance of [VaultAddItemCommonTypeHandlers] by binding actions + * Creates an instance of [VaultAddItemCommonHandlers] by binding actions * to the provided [VaultAddItemViewModel]. */ @Suppress("LongMethod") - fun create(viewModel: VaultAddItemViewModel): VaultAddItemCommonTypeHandlers { - return VaultAddItemCommonTypeHandlers( + fun create(viewModel: VaultAddItemViewModel): VaultAddItemCommonHandlers { + return VaultAddItemCommonHandlers( onNameTextChange = { newName -> viewModel.trySendAction( VaultAddItemAction.Common.NameTextChange(newName), 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/handlers/VaultAddLoginItemTypeHandlers.kt similarity index 95% rename from app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/VaultAddLoginItemTypeHandlers.kt rename to app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/additem/handlers/VaultAddLoginItemTypeHandlers.kt index 5a0a301ea..c8b2e05c6 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/handlers/VaultAddLoginItemTypeHandlers.kt @@ -1,4 +1,7 @@ -package com.x8bit.bitwarden.ui.vault.feature.additem +package com.x8bit.bitwarden.ui.vault.feature.additem.handlers + +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemAction +import com.x8bit.bitwarden.ui.vault.feature.additem.VaultAddItemViewModel /** * A collection of handler functions specifically tailored for managing actions 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 34b57dda4..9b30022ac 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 @@ -27,7 +27,24 @@ fun CipherView.toViewState(): VaultAddItemState.ViewState = CipherType.SECURE_NOTE -> VaultAddItemState.ViewState.Content.ItemType.SecureNotes CipherType.CARD -> VaultAddItemState.ViewState.Content.ItemType.Card - CipherType.IDENTITY -> VaultAddItemState.ViewState.Content.ItemType.Identity + CipherType.IDENTITY -> VaultAddItemState.ViewState.Content.ItemType.Identity( + firstName = identity?.firstName.orEmpty(), + middleName = identity?.middleName.orEmpty(), + lastName = identity?.lastName.orEmpty(), + username = identity?.username.orEmpty(), + company = identity?.company.orEmpty(), + ssn = identity?.ssn.orEmpty(), + passportNumber = identity?.passportNumber.orEmpty(), + licenseNumber = identity?.licenseNumber.orEmpty(), + email = identity?.email.orEmpty(), + phone = identity?.phone.orEmpty(), + address1 = identity?.address1.orEmpty(), + address2 = identity?.address2.orEmpty(), + address3 = identity?.address3.orEmpty(), + city = identity?.city.orEmpty(), + zip = identity?.postalCode.orEmpty(), + country = identity?.country.orEmpty(), + ) }, common = VaultAddItemState.ViewState.Content.Common( originalCipher = this, 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 62bbb4255..589f1166f 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 @@ -103,7 +103,17 @@ class CipherViewExtensionsTest { availableFolders = emptyList(), availableOwners = emptyList(), ), - type = VaultAddItemState.ViewState.Content.ItemType.Identity, + type = VaultAddItemState.ViewState.Content.ItemType.Identity( + firstName = "John", + middleName = "Richard", + lastName = "Smith", + username = "Dr. JSR", + company = "Bitwarden", + email = "placeholde@email.com", + phone = "555-555-5555", + city = "Minneapolis", + country = "USA", + ), ), result, )