BIT-667: UI for identity creation (#431)

This commit is contained in:
Ramsey Smith 2023-12-21 13:25:17 -07:00 committed by Álison Fernandes
parent d6909d5a53
commit c440c28a02
11 changed files with 1015 additions and 16 deletions

View file

@ -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,
)
}

View file

@ -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 -> {

View file

@ -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,
) {

View file

@ -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))

View file

@ -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)

View file

@ -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()
}
}
/**

View file

@ -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,
),
)
},
)
}
}
}

View file

@ -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),

View file

@ -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

View file

@ -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,

View file

@ -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,
)