mirror of
https://github.com/bitwarden/android.git
synced 2025-03-15 18:58:59 +03:00
BIT-507: Create Card UI (#497)
This commit is contained in:
parent
c964d8c830
commit
8d5de22c72
22 changed files with 1135 additions and 62 deletions
|
@ -47,6 +47,8 @@ import com.x8bit.bitwarden.ui.platform.components.util.nonLetterColorVisualTrans
|
|||
* @param showPasswordTestTag The test tag to be used on the show password button (testing tool).
|
||||
* @param autoFocus When set to true, the view will request focus after the first recomposition.
|
||||
* Setting this to true on multiple fields at once may have unexpected consequences.
|
||||
* @param keyboardType The type of keyboard the user has access to when inputting values into
|
||||
* the password field.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenPasswordField(
|
||||
|
@ -61,6 +63,7 @@ fun BitwardenPasswordField(
|
|||
hint: String? = null,
|
||||
showPasswordTestTag: String? = null,
|
||||
autoFocus: Boolean = false,
|
||||
keyboardType: KeyboardType = KeyboardType.Password,
|
||||
) {
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
OutlinedTextField(
|
||||
|
@ -76,7 +79,7 @@ fun BitwardenPasswordField(
|
|||
},
|
||||
singleLine = singleLine,
|
||||
readOnly = readOnly,
|
||||
keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password),
|
||||
keyboardOptions = KeyboardOptions(keyboardType = keyboardType),
|
||||
supportingText = hint?.let {
|
||||
{
|
||||
Text(
|
||||
|
@ -129,6 +132,8 @@ fun BitwardenPasswordField(
|
|||
* @param showPasswordTestTag The test tag to be used on the show password button (testing tool).
|
||||
* @param autoFocus When set to true, the view will request focus after the first recomposition.
|
||||
* Setting this to true on multiple fields at once may have unexpected consequences.
|
||||
* @param keyboardType The type of keyboard the user has access to when inputting values into
|
||||
* the password field.
|
||||
*/
|
||||
@Composable
|
||||
fun BitwardenPasswordField(
|
||||
|
@ -142,6 +147,7 @@ fun BitwardenPasswordField(
|
|||
initialShowPassword: Boolean = false,
|
||||
showPasswordTestTag: String? = null,
|
||||
autoFocus: Boolean = false,
|
||||
keyboardType: KeyboardType = KeyboardType.Password,
|
||||
) {
|
||||
var showPassword by rememberSaveable { mutableStateOf(initialShowPassword) }
|
||||
BitwardenPasswordField(
|
||||
|
@ -156,6 +162,7 @@ fun BitwardenPasswordField(
|
|||
hint = hint,
|
||||
showPasswordTestTag = showPasswordTestTag,
|
||||
autoFocus = autoFocus,
|
||||
keyboardType = keyboardType,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.ColumnScope
|
|||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredHeightIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
|
@ -15,10 +16,12 @@ import androidx.compose.material3.Text
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.components.util.maxDialogHeight
|
||||
|
||||
/**
|
||||
* Displays a dialog with a title and "Cancel" button.
|
||||
|
@ -28,6 +31,7 @@ import com.x8bit.bitwarden.R
|
|||
* @param selectionItems Lambda containing selection items to show to the user. See
|
||||
* [BitwardenSelectionRow].
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
fun BitwardenSelectionDialog(
|
||||
title: String,
|
||||
|
@ -37,12 +41,18 @@ fun BitwardenSelectionDialog(
|
|||
Dialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
) {
|
||||
val configuration = LocalConfiguration.current
|
||||
val scrollState = rememberScrollState()
|
||||
Column(
|
||||
modifier = Modifier.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = RoundedCornerShape(28.dp),
|
||||
),
|
||||
modifier = Modifier
|
||||
.requiredHeightIn(
|
||||
max = configuration.maxDialogHeight,
|
||||
)
|
||||
// This background is necessary for the dialog to not be transparent.
|
||||
.background(
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh,
|
||||
shape = RoundedCornerShape(28.dp),
|
||||
),
|
||||
horizontalAlignment = Alignment.End,
|
||||
) {
|
||||
Text(
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
package com.x8bit.bitwarden.ui.platform.components.util
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* Provides the maximum height [Dp] common for all dialogs with a given [Configuration].
|
||||
*/
|
||||
val Configuration.maxDialogHeight: Dp
|
||||
get() = when (orientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE -> 312.dp
|
||||
Configuration.ORIENTATION_PORTRAIT -> 542.dp
|
||||
Configuration.ORIENTATION_UNDEFINED -> Dp.Unspecified
|
||||
Configuration.ORIENTATION_SQUARE -> Dp.Unspecified
|
||||
else -> Dp.Unspecified
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit
|
||||
|
||||
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.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.KeyboardType
|
||||
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.BitwardenPasswordField
|
||||
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.addedit.handlers.VaultAddEditCardTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* The UI for adding and editing a card cipher.
|
||||
*/
|
||||
@Suppress("LongMethod")
|
||||
fun LazyListScope.vaultAddEditCardItems(
|
||||
commonState: VaultAddEditState.ViewState.Content.Common,
|
||||
cardState: VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
commonHandlers: VaultAddEditCommonHandlers,
|
||||
cardHandlers: VaultAddEditCardTypeHandlers,
|
||||
isAddItemMode: Boolean,
|
||||
) {
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.name),
|
||||
value = commonState.name,
|
||||
onValueChange = commonHandlers.onNameTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.cardholder_name),
|
||||
value = cardState.cardHolderName,
|
||||
onValueChange = cardHandlers.onCardHolderNameTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenPasswordField(
|
||||
label = stringResource(id = R.string.number),
|
||||
value = cardState.number,
|
||||
onValueChange = cardHandlers.onNumberTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
val resources = LocalContext.current.resources
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.brand),
|
||||
options = VaultCardBrand
|
||||
.entries
|
||||
.map { it.value() }
|
||||
.toImmutableList(),
|
||||
selectedOption = cardState.brand.value(),
|
||||
onOptionSelected = { selectedString ->
|
||||
cardHandlers.onBrandSelected(
|
||||
VaultCardBrand
|
||||
.entries
|
||||
.first { it.value.toString(resources) == selectedString },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
val resources = LocalContext.current.resources
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.expiration_month),
|
||||
options = VaultCardExpirationMonth
|
||||
.entries
|
||||
.map { it.value() }
|
||||
.toImmutableList(),
|
||||
selectedOption = cardState.expirationMonth.value(),
|
||||
onOptionSelected = { selectedString ->
|
||||
cardHandlers.onExpirationMonthSelected(
|
||||
VaultCardExpirationMonth
|
||||
.entries
|
||||
.first { it.value.toString(resources) == selectedString },
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenTextField(
|
||||
label = stringResource(id = R.string.expiration_year),
|
||||
value = cardState.expirationYear,
|
||||
onValueChange = cardHandlers.onExpirationYearTextChange,
|
||||
keyboardType = KeyboardType.Number,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(8.dp))
|
||||
BitwardenPasswordField(
|
||||
label = stringResource(id = R.string.security_code),
|
||||
value = cardState.securityCode,
|
||||
onValueChange = cardHandlers.onSecurityCodeTextChange,
|
||||
keyboardType = KeyboardType.NumberPassword,
|
||||
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 = commonHandlers.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 = commonHandlers.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 = commonHandlers.onToggleMasterPasswordReprompt,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
actions = {
|
||||
IconButton(onClick = commonHandlers.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 = commonHandlers.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 ->
|
||||
VaultAddEditCustomField(
|
||||
customItem,
|
||||
onCustomFieldValueChange = commonHandlers.onCustomFieldValueChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
supportedLinkedTypes = persistentListOf(
|
||||
VaultLinkedFieldType.CARDHOLDER_NAME,
|
||||
VaultLinkedFieldType.EXPIRATION_MONTH,
|
||||
VaultLinkedFieldType.EXPIRATION_YEAR,
|
||||
VaultLinkedFieldType.SECURITY_CODE,
|
||||
VaultLinkedFieldType.BRAND,
|
||||
VaultLinkedFieldType.NUMBER,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(16.dp))
|
||||
VaultAddEditCustomFieldsButton(
|
||||
onFinishNamingClick = commonHandlers.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 = commonHandlers.onOwnershipTextChange,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
Spacer(modifier = Modifier.height(24.dp))
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ import com.x8bit.bitwarden.ui.platform.components.BitwardenSwitchWithActions
|
|||
import com.x8bit.bitwarden.ui.platform.components.BitwardenTextField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
@ -391,21 +392,21 @@ fun LazyListScope.vaultAddEditIdentityItems(
|
|||
|
||||
@Composable
|
||||
private fun TitleMultiSelectButton(
|
||||
selectedTitle: VaultAddEditState.ViewState.Content.ItemType.Identity.Title,
|
||||
onTitleSelected: (VaultAddEditState.ViewState.Content.ItemType.Identity.Title) -> Unit,
|
||||
selectedTitle: VaultIdentityTitle,
|
||||
onTitleSelected: (VaultIdentityTitle) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
BitwardenMultiSelectButton(
|
||||
label = stringResource(id = R.string.title),
|
||||
options = VaultAddEditState.ViewState.Content.ItemType.Identity.Title
|
||||
options = VaultIdentityTitle
|
||||
.entries
|
||||
.map { it.value() }
|
||||
.toImmutableList(),
|
||||
selectedOption = selectedTitle.value(),
|
||||
onOptionSelected = { selectedString ->
|
||||
onTitleSelected(
|
||||
VaultAddEditState.ViewState.Content.ItemType.Identity.Title
|
||||
VaultIdentityTitle
|
||||
.entries
|
||||
.first { it.value.toString(resources) == selectedString },
|
||||
)
|
||||
|
|
|
@ -15,6 +15,7 @@ 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.addedit.handlers.VaultAddEditCardTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
|
||||
|
@ -32,6 +33,7 @@ fun VaultAddEditContent(
|
|||
commonTypeHandlers: VaultAddEditCommonHandlers,
|
||||
loginItemTypeHandlers: VaultAddEditLoginTypeHandlers,
|
||||
identityItemTypeHandlers: VaultAddEditIdentityTypeHandlers,
|
||||
cardItemTypeHandlers: VaultAddEditCardTypeHandlers,
|
||||
modifier: Modifier = Modifier,
|
||||
permissionsManager: PermissionsManager,
|
||||
) {
|
||||
|
@ -39,9 +41,7 @@ fun VaultAddEditContent(
|
|||
onResult = { isGranted ->
|
||||
when (state.type) {
|
||||
is VaultAddEditState.ViewState.Content.ItemType.SecureNotes -> Unit
|
||||
// TODO: Create UI for card-type item creation BIT-507
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Card -> Unit
|
||||
// TODO: Create UI for identity-type item creation BIT-667
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> Unit
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Login -> {
|
||||
loginItemTypeHandlers.onSetupTotpClick(isGranted)
|
||||
|
@ -91,7 +91,13 @@ fun VaultAddEditContent(
|
|||
}
|
||||
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Card -> {
|
||||
// TODO(BIT-507): Create UI for card-type item creation
|
||||
vaultAddEditCardItems(
|
||||
commonState = state.common,
|
||||
cardState = state.type,
|
||||
commonHandlers = commonTypeHandlers,
|
||||
cardHandlers = cardItemTypeHandlers,
|
||||
isAddItemMode = isAddItemMode,
|
||||
)
|
||||
}
|
||||
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Identity -> {
|
||||
|
|
|
@ -35,6 +35,7 @@ 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.addedit.handlers.VaultAddEditCardTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditCommonHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditIdentityTypeHandlers
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.handlers.VaultAddEditLoginTypeHandlers
|
||||
|
@ -87,6 +88,10 @@ fun VaultAddEditScreen(
|
|||
VaultAddEditIdentityTypeHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
val cardItemTypeHandlers = remember(viewModel) {
|
||||
VaultAddEditCardTypeHandlers.create(viewModel = viewModel)
|
||||
}
|
||||
|
||||
VaultAddEditItemDialogs(
|
||||
dialogState = state.dialog,
|
||||
onDismissRequest = remember(viewModel) {
|
||||
|
@ -131,6 +136,7 @@ fun VaultAddEditScreen(
|
|||
commonTypeHandlers = commonTypeHandlers,
|
||||
permissionsManager = permissionsManager,
|
||||
identityItemTypeHandlers = identityItemTypeHandlers,
|
||||
cardItemTypeHandlers = cardItemTypeHandlers,
|
||||
modifier = Modifier
|
||||
.imePadding()
|
||||
.padding(innerPadding)
|
||||
|
|
|
@ -19,6 +19,9 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
|||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.vault.util.toCipherView
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
|
@ -94,6 +97,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
is VaultAddEditAction.Common -> handleCommonActions(action)
|
||||
is VaultAddEditAction.ItemType.LoginType -> handleAddLoginTypeAction(action)
|
||||
is VaultAddEditAction.ItemType.IdentityType -> handleIdentityTypeActions(action)
|
||||
is VaultAddEditAction.ItemType.CardType -> handleCardTypeActions(action)
|
||||
is VaultAddEditAction.Internal -> handleInternalActions(action)
|
||||
}
|
||||
}
|
||||
|
@ -157,7 +161,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
private fun handleSwitchToAddCardItem() {
|
||||
updateContent { currentContent ->
|
||||
currentContent.copy(
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -224,11 +228,13 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
private fun handleAddNewCustomFieldClick(
|
||||
action: VaultAddEditAction.Common.AddNewCustomFieldClick,
|
||||
) {
|
||||
val newCustomData: VaultAddEditState.Custom =
|
||||
action.customFieldType.toCustomField(action.name)
|
||||
|
||||
updateCommonContent { loginType ->
|
||||
loginType.copy(customFieldData = loginType.customFieldData + newCustomData)
|
||||
updateCommonContent { common ->
|
||||
|
||||
val newCustomData: VaultAddEditState.Custom =
|
||||
action.customFieldType.toCustomField(action.name)
|
||||
|
||||
common.copy(customFieldData = common.customFieldData + newCustomData)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -529,7 +535,7 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
handleIdentityUsernameTextChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.IdentityType.TitleSelected -> {
|
||||
is VaultAddEditAction.ItemType.IdentityType.TitleSelect -> {
|
||||
handleIdentityTitleSelected(action)
|
||||
}
|
||||
}
|
||||
|
@ -638,12 +644,79 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun handleIdentityTitleSelected(
|
||||
action: VaultAddEditAction.ItemType.IdentityType.TitleSelected,
|
||||
action: VaultAddEditAction.ItemType.IdentityType.TitleSelect,
|
||||
) {
|
||||
updateIdentityContent { it.copy(selectedTitle = action.title) }
|
||||
}
|
||||
//endregion Identity Type Handlers
|
||||
|
||||
//region Card Type Handlers
|
||||
private fun handleCardTypeActions(action: VaultAddEditAction.ItemType.CardType) {
|
||||
when (action) {
|
||||
is VaultAddEditAction.ItemType.CardType.BrandSelect -> {
|
||||
handleCardBrandSelected(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.CardType.CardHolderNameTextChange -> {
|
||||
handleCardCardHolderNameTextChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.CardType.ExpirationMonthSelect -> {
|
||||
handleCardExpirationMonthSelected(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.CardType.ExpirationYearTextChange -> {
|
||||
handleCardExpirationYearTextChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.CardType.NumberTextChange -> {
|
||||
handleCardNumberTextChange(action)
|
||||
}
|
||||
|
||||
is VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange -> {
|
||||
handleCardSecurityCodeTextChange(action)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCardBrandSelected(
|
||||
action: VaultAddEditAction.ItemType.CardType.BrandSelect,
|
||||
) {
|
||||
updateCardContent { it.copy(brand = action.brand) }
|
||||
}
|
||||
|
||||
private fun handleCardCardHolderNameTextChange(
|
||||
action: VaultAddEditAction.ItemType.CardType.CardHolderNameTextChange,
|
||||
) {
|
||||
updateCardContent { it.copy(cardHolderName = action.cardHolderName) }
|
||||
}
|
||||
|
||||
private fun handleCardExpirationMonthSelected(
|
||||
action: VaultAddEditAction.ItemType.CardType.ExpirationMonthSelect,
|
||||
) {
|
||||
updateCardContent { it.copy(expirationMonth = action.expirationMonth) }
|
||||
}
|
||||
|
||||
private fun handleCardExpirationYearTextChange(
|
||||
action: VaultAddEditAction.ItemType.CardType.ExpirationYearTextChange,
|
||||
) {
|
||||
updateCardContent { it.copy(expirationYear = action.expirationYear) }
|
||||
}
|
||||
|
||||
private fun handleCardNumberTextChange(
|
||||
action: VaultAddEditAction.ItemType.CardType.NumberTextChange,
|
||||
) {
|
||||
updateCardContent { it.copy(number = action.number) }
|
||||
}
|
||||
|
||||
private fun handleCardSecurityCodeTextChange(
|
||||
action: VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange,
|
||||
) {
|
||||
updateCardContent { it.copy(securityCode = action.securityCode) }
|
||||
}
|
||||
|
||||
//endregion Card Type Handlers
|
||||
|
||||
//region Internal Type Handlers
|
||||
|
||||
private fun handleInternalActions(action: VaultAddEditAction.Internal) {
|
||||
|
@ -831,6 +904,19 @@ class VaultAddEditViewModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private inline fun updateCardContent(
|
||||
crossinline block: (VaultAddEditState.ViewState.Content.ItemType.Card) ->
|
||||
VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
) {
|
||||
updateContent { currentContent ->
|
||||
(currentContent.type as? VaultAddEditState.ViewState.Content.ItemType.Card)?.let {
|
||||
currentContent.copy(
|
||||
type = block(it),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//endregion Utility Functions
|
||||
}
|
||||
|
||||
|
@ -980,9 +1066,23 @@ data class VaultAddEditState(
|
|||
|
||||
/**
|
||||
* Represents the `Card` item type.
|
||||
*
|
||||
* @property cardHolderName The card holder name for the card item.
|
||||
* @property number The number for the card item.
|
||||
* @property brand The brand for the card item.
|
||||
* @property expirationMonth The expiration month for the card item.
|
||||
* @property expirationYear The expiration year for the card item.
|
||||
* @property securityCode The security code for the card item.
|
||||
*/
|
||||
@Parcelize
|
||||
data object Card : ItemType() {
|
||||
data class Card(
|
||||
val cardHolderName: String = "",
|
||||
val number: String = "",
|
||||
val brand: VaultCardBrand = VaultCardBrand.SELECT,
|
||||
val expirationMonth: VaultCardExpirationMonth = VaultCardExpirationMonth.SELECT,
|
||||
val expirationYear: String = "",
|
||||
val securityCode: String = "",
|
||||
) : ItemType() {
|
||||
override val displayStringResId: Int get() = ItemTypeOption.CARD.labelRes
|
||||
}
|
||||
|
||||
|
@ -1010,7 +1110,7 @@ data class VaultAddEditState(
|
|||
*/
|
||||
@Parcelize
|
||||
data class Identity(
|
||||
val selectedTitle: Title = Title.MR,
|
||||
val selectedTitle: VaultIdentityTitle = VaultIdentityTitle.MR,
|
||||
val firstName: String = "",
|
||||
val middleName: String = "",
|
||||
val lastName: String = "",
|
||||
|
@ -1030,17 +1130,6 @@ data class VaultAddEditState(
|
|||
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
|
||||
}
|
||||
|
||||
|
@ -1450,10 +1539,63 @@ sealed class VaultAddEditAction {
|
|||
*
|
||||
* @property title The selected title.
|
||||
*/
|
||||
data class TitleSelected(
|
||||
val title: VaultAddEditState.ViewState.Content.ItemType.Identity.Title,
|
||||
data class TitleSelect(
|
||||
val title: VaultIdentityTitle,
|
||||
) : IdentityType()
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents actions specific to the Card type.
|
||||
*/
|
||||
sealed class CardType : ItemType() {
|
||||
|
||||
/**
|
||||
* Fired when the card holder name text input is changed.
|
||||
*
|
||||
* @property cardHolderName The new card holder name text.
|
||||
*/
|
||||
data class CardHolderNameTextChange(val cardHolderName: String) : CardType()
|
||||
|
||||
/**
|
||||
* Fired when the number text input is changed.
|
||||
*
|
||||
* @property number The new number text.
|
||||
*/
|
||||
data class NumberTextChange(val number: String) : CardType()
|
||||
|
||||
/**
|
||||
* Fired when the brand input is selected.
|
||||
*
|
||||
* @property brand The selected brand.
|
||||
*/
|
||||
data class BrandSelect(
|
||||
val brand: VaultCardBrand,
|
||||
) : CardType()
|
||||
|
||||
/**
|
||||
* Fired when the expiration month input is selected.
|
||||
*
|
||||
* @property expirationMonth The selected expiration month.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
data class ExpirationMonthSelect(
|
||||
val expirationMonth: VaultCardExpirationMonth,
|
||||
) : CardType()
|
||||
|
||||
/**
|
||||
* Fired when the expiration year text input is changed.
|
||||
*
|
||||
* @property expirationYear The new expiration year text.
|
||||
*/
|
||||
data class ExpirationYearTextChange(val expirationYear: String) : CardType()
|
||||
|
||||
/**
|
||||
* Fired when the security code text input is changed.
|
||||
*
|
||||
* @property securityCode The new security code text.
|
||||
*/
|
||||
data class SecurityCodeTextChange(val securityCode: String) : CardType()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
* within the context of adding card items to a vault.
|
||||
*
|
||||
* @property onCardHolderNameTextChange Handles the action when the card holder name text is changed.
|
||||
* @property onNumberTextChange Handles the action when the number text is changed.
|
||||
* @property onBrandSelected Handles the action when a brand is selected.
|
||||
* @property onExpirationMonthSelected Handles the action when an expiration month is selected.
|
||||
* @property onExpirationYearTextChange Handles the action when the expiration year text is changed.
|
||||
* @property onSecurityCodeTextChange Handles the action when the expiration year text is changed.
|
||||
*/
|
||||
@Suppress("MaxLineLength")
|
||||
data class VaultAddEditCardTypeHandlers(
|
||||
val onCardHolderNameTextChange: (String) -> Unit,
|
||||
val onNumberTextChange: (String) -> Unit,
|
||||
val onBrandSelected: (VaultCardBrand) -> Unit,
|
||||
val onExpirationMonthSelected: (VaultCardExpirationMonth) -> Unit,
|
||||
val onExpirationYearTextChange: (String) -> Unit,
|
||||
val onSecurityCodeTextChange: (String) -> Unit,
|
||||
|
||||
) {
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Creates an instance of [VaultAddEditCardTypeHandlers] by binding actions
|
||||
* to the provided [VaultAddEditViewModel].
|
||||
*/
|
||||
fun create(viewModel: VaultAddEditViewModel): VaultAddEditCardTypeHandlers =
|
||||
VaultAddEditCardTypeHandlers(
|
||||
onCardHolderNameTextChange = { newCardHolderName ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.CardHolderNameTextChange(
|
||||
cardHolderName = newCardHolderName,
|
||||
),
|
||||
)
|
||||
},
|
||||
onNumberTextChange = { newNumber ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.NumberTextChange(
|
||||
number = newNumber,
|
||||
),
|
||||
)
|
||||
},
|
||||
onBrandSelected = { newBrand ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.BrandSelect(
|
||||
brand = newBrand,
|
||||
),
|
||||
)
|
||||
},
|
||||
onExpirationMonthSelected = { newExpirationMonth ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.ExpirationMonthSelect(
|
||||
expirationMonth = newExpirationMonth,
|
||||
),
|
||||
)
|
||||
},
|
||||
onExpirationYearTextChange = { newExpirationYear ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.ExpirationYearTextChange(
|
||||
expirationYear = newExpirationYear,
|
||||
),
|
||||
)
|
||||
},
|
||||
onSecurityCodeTextChange = { newSecurityCode ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange(
|
||||
securityCode = newSecurityCode,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,8 +1,8 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.handlers
|
||||
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditAction
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
|
||||
/**
|
||||
* A collection of handler functions specifically tailored for managing actions
|
||||
|
@ -27,7 +27,7 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditViewModel
|
|||
*/
|
||||
@Suppress("LongParameterList")
|
||||
data class VaultAddEditIdentityTypeHandlers(
|
||||
val onTitleSelected: (VaultAddEditState.ViewState.Content.ItemType.Identity.Title) -> Unit,
|
||||
val onTitleSelected: (VaultIdentityTitle) -> Unit,
|
||||
val onFirstNameTextChange: (String) -> Unit,
|
||||
val onMiddleNameTextChange: (String) -> Unit,
|
||||
val onLastNameTextChange: (String) -> Unit,
|
||||
|
@ -57,7 +57,7 @@ data class VaultAddEditIdentityTypeHandlers(
|
|||
return VaultAddEditIdentityTypeHandlers(
|
||||
onTitleSelected = { newTitle ->
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.IdentityType.TitleSelected(
|
||||
VaultAddEditAction.ItemType.IdentityType.TitleSelect(
|
||||
title = newTitle,
|
||||
),
|
||||
)
|
||||
|
|
|
@ -8,6 +8,9 @@ import com.bitwarden.core.FieldView
|
|||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType.Companion.fromId
|
||||
import java.util.UUID
|
||||
|
||||
|
@ -28,7 +31,13 @@ fun CipherView.toViewState(): VaultAddEditState.ViewState =
|
|||
}
|
||||
|
||||
CipherType.SECURE_NOTE -> VaultAddEditState.ViewState.Content.ItemType.SecureNotes
|
||||
CipherType.CARD -> VaultAddEditState.ViewState.Content.ItemType.Card
|
||||
CipherType.CARD -> VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
cardHolderName = card?.cardholderName.orEmpty(),
|
||||
number = card?.number.orEmpty(),
|
||||
brand = card?.brand.toBrandOrDefault(),
|
||||
expirationMonth = card?.expMonth.toExpirationMonthOrDefault(),
|
||||
securityCode = card?.code.orEmpty(),
|
||||
)
|
||||
CipherType.IDENTITY -> VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = identity?.title.toTitleOrDefault(),
|
||||
firstName = identity?.firstName.orEmpty(),
|
||||
|
@ -93,9 +102,20 @@ private fun FieldView.toCustomField() =
|
|||
)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun String?.toTitleOrDefault(): VaultAddEditState.ViewState.Content.ItemType.Identity.Title =
|
||||
VaultAddEditState.ViewState.Content.ItemType.Identity.Title
|
||||
private fun String?.toTitleOrDefault(): VaultIdentityTitle =
|
||||
VaultIdentityTitle
|
||||
.entries
|
||||
.find { it.name == this }
|
||||
?: VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MR
|
||||
?: VaultIdentityTitle.MR
|
||||
|
||||
private fun String?.toBrandOrDefault(): VaultCardBrand =
|
||||
VaultCardBrand
|
||||
.entries
|
||||
.find { it.name == this }
|
||||
?: VaultCardBrand.SELECT
|
||||
|
||||
private fun String?.toExpirationMonthOrDefault(): VaultCardExpirationMonth =
|
||||
VaultCardExpirationMonth
|
||||
.entries
|
||||
.find { it.name == this }
|
||||
?: VaultCardExpirationMonth.SELECT
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package com.x8bit.bitwarden.ui.vault.feature.addedit.util
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
|
||||
/**
|
||||
* Default, "select" Text to show on multi select buttons in the VaultAddEdit package.
|
||||
*/
|
||||
val SELECT_TEXT: Text
|
||||
get() = "-- "
|
||||
.asText()
|
||||
.concat(R.string.select.asText())
|
||||
.concat(" --".asText())
|
|
@ -201,7 +201,9 @@ fun VaultItemIdentityContent(
|
|||
onValueChange = { },
|
||||
readOnly = true,
|
||||
singleLine = false,
|
||||
modifier = modifier,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 16.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,10 +14,12 @@ import com.bitwarden.core.SecureNoteView
|
|||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.orNullIfBlank
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Transforms a [VaultAddEditState.ViewState.ItemType] into [CipherView].
|
||||
* Transforms [VaultAddEditState.ViewState.Content] into [CipherView].
|
||||
*/
|
||||
fun VaultAddEditState.ViewState.Content.toCipherView(): CipherView =
|
||||
CipherView(
|
||||
|
@ -64,14 +66,23 @@ private fun VaultAddEditState.ViewState.Content.ItemType.toCipherType(): CipherT
|
|||
|
||||
private fun VaultAddEditState.ViewState.Content.ItemType.toCardView(): CardView? =
|
||||
(this as? VaultAddEditState.ViewState.Content.ItemType.Card)?.let {
|
||||
// TODO Create real CardView from Content (BIT-668)
|
||||
CardView(
|
||||
cardholderName = null,
|
||||
expMonth = null,
|
||||
expYear = null,
|
||||
code = null,
|
||||
brand = null,
|
||||
number = null,
|
||||
cardholderName = it.cardHolderName.orNullIfBlank(),
|
||||
expMonth = it
|
||||
.expirationMonth
|
||||
.takeUnless { month ->
|
||||
month == VaultCardExpirationMonth.SELECT
|
||||
}
|
||||
?.name,
|
||||
expYear = it.expirationYear.orNullIfBlank(),
|
||||
code = it.securityCode.orNullIfBlank(),
|
||||
brand = it
|
||||
.brand
|
||||
.takeUnless { brand ->
|
||||
brand == VaultCardBrand.SELECT
|
||||
}
|
||||
?.name,
|
||||
number = it.number.orNullIfBlank(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.SELECT_TEXT
|
||||
|
||||
/**
|
||||
* Defines all available brand options for cards.
|
||||
*/
|
||||
enum class VaultCardBrand(val value: Text) {
|
||||
SELECT(value = SELECT_TEXT),
|
||||
VISA(value = "Visa".asText()),
|
||||
MASTERCARD(value = "Mastercard".asText()),
|
||||
AMERICAN_EXPRESS(value = "American Express".asText()),
|
||||
DISCOVER(value = "Discover".asText()),
|
||||
DINERS_CLUB(value = "Diners Club".asText()),
|
||||
JCB(value = "JCB".asText()),
|
||||
MAESTRO(value = "Maestro".asText()),
|
||||
UNIONPAY(value = "UnionPay".asText()),
|
||||
RUPAY(value = "RuPay".asText()),
|
||||
OTHER(value = R.string.other.asText()),
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import androidx.annotation.StringRes
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.concat
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.SELECT_TEXT
|
||||
|
||||
/**
|
||||
* Defines all available expiration month options for cards.
|
||||
*/
|
||||
enum class VaultCardExpirationMonth(
|
||||
val value: Text,
|
||||
) {
|
||||
SELECT(value = SELECT_TEXT),
|
||||
JANUARY(value = R.string.january.dateText("01 - ")),
|
||||
FEBRUARY(value = R.string.february.dateText("02 - ")),
|
||||
MARCH(value = R.string.march.dateText("03 - ")),
|
||||
APRIL(value = R.string.april.dateText("04 - ")),
|
||||
MAY(value = R.string.may.dateText("05 - ")),
|
||||
JUNE(value = R.string.june.dateText("06 - ")),
|
||||
JULY(value = R.string.july.dateText("07 - ")),
|
||||
AUGUST(value = R.string.august.dateText("08 - ")),
|
||||
SEPTEMBER(value = R.string.september.dateText("09 - ")),
|
||||
OCTOBER(value = R.string.october.dateText("10 - ")),
|
||||
NOVEMBER(value = R.string.november.dateText("11 - ")),
|
||||
DECEMBER(value = R.string.december.dateText("12 - ")),
|
||||
}
|
||||
|
||||
private fun @receiver:StringRes Int.dateText(prefix: String): Text =
|
||||
prefix
|
||||
.asText()
|
||||
.concat(asText())
|
|
@ -0,0 +1,16 @@
|
|||
package com.x8bit.bitwarden.ui.vault.model
|
||||
|
||||
import com.x8bit.bitwarden.R
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.Text
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
|
||||
/**
|
||||
* Defines all available title options for identities.
|
||||
*/
|
||||
enum class VaultIdentityTitle(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()),
|
||||
}
|
|
@ -16,6 +16,13 @@ enum class VaultLinkedFieldType(
|
|||
) {
|
||||
USERNAME(id = 100.toUInt(), label = R.string.username.asText()),
|
||||
PASSWORD(id = 101.toUInt(), label = R.string.password.asText()),
|
||||
|
||||
CARDHOLDER_NAME(id = 300.toUInt(), label = R.string.cardholder_name.asText()),
|
||||
EXPIRATION_MONTH(id = 301.toUInt(), label = R.string.expiration_month.asText()),
|
||||
EXPIRATION_YEAR(id = 302.toUInt(), label = R.string.expiration_year.asText()),
|
||||
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()),
|
||||
;
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -16,6 +16,7 @@ import androidx.compose.ui.test.hasAnyAncestor
|
|||
import androidx.compose.ui.test.hasContentDescription
|
||||
import androidx.compose.ui.test.hasSetTextAction
|
||||
import androidx.compose.ui.test.isDialog
|
||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||
import androidx.compose.ui.test.onAllNodesWithText
|
||||
import androidx.compose.ui.test.onFirst
|
||||
import androidx.compose.ui.test.onLast
|
||||
|
@ -38,6 +39,9 @@ import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
|
|||
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import io.mockk.every
|
||||
import io.mockk.just
|
||||
import io.mockk.mockk
|
||||
|
@ -245,7 +249,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
it.copy(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -653,8 +657,8 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.IdentityType.TitleSelected(
|
||||
title = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MX,
|
||||
VaultAddEditAction.ItemType.IdentityType.TitleSelect(
|
||||
title = VaultIdentityTitle.MX,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
@ -670,7 +674,7 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
mutableStateFlow.update { currentState ->
|
||||
updateIdentityType(currentState) {
|
||||
copy(
|
||||
selectedTitle = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MX,
|
||||
selectedTitle = VaultIdentityTitle.MX,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -1212,6 +1216,239 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
.assertTextContains("NewCountry")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card changing the card holder name text field should trigger CardHolderNameTextChange`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Cardholder name")
|
||||
.performTextInput(text = "TestCardHolderName")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.CardHolderNameTextChange(
|
||||
cardHolderName = "TestCardHolderName",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card the card holder name text field should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Cardholder name")
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) { copy(cardHolderName = "NewCardHolderName") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Cardholder name")
|
||||
.assertTextContains("NewCardHolderName")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Card changing the number text field should trigger NumberTextChange`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Number")
|
||||
.performTextInput(text = "TestNumber")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.NumberTextChange(
|
||||
number = "TestNumber",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Card the number text field should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Number")
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) { copy(number = "123") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll("Show")
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Number")
|
||||
.assertTextContains("123")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `in ItemType_Card selecting a brand should trigger BrandSelected`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Brand, -- Select --")
|
||||
.performClick()
|
||||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "Visa")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.BrandSelect(
|
||||
brand = VaultCardBrand.VISA,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card the Brand should display the selected brand from the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Brand, -- Select --")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) {
|
||||
copy(
|
||||
brand = VaultCardBrand.AMERICAN_EXPRESS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Brand, American Express")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card selecting an expiration month should trigger ExpirationMonthSelected`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
// Opens the menu
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Expiration month, -- Select --")
|
||||
.performClick()
|
||||
|
||||
// Choose the option from the menu
|
||||
composeTestRule
|
||||
.onAllNodesWithText(text = "02 - February")
|
||||
.onLast()
|
||||
.performScrollTo()
|
||||
.performClick()
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.ExpirationMonthSelect(
|
||||
expirationMonth = VaultCardExpirationMonth.FEBRUARY,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card the Expiration month should display the selected expiration month from the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Expiration month, -- Select --")
|
||||
.assertIsDisplayed()
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) {
|
||||
copy(
|
||||
expirationMonth = VaultCardExpirationMonth.MARCH,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithContentDescriptionAfterScroll(label = "Expiration month, 03 - March")
|
||||
.assertIsDisplayed()
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card changing the expiration year text field should trigger ExpirationYearTextChange`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Expiration year")
|
||||
.performTextInput(text = "TestExpirationYear")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.ExpirationYearTextChange(
|
||||
expirationYear = "TestExpirationYear",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card the expiration year text field should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Expiration year")
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) { copy(expirationYear = "NewExpirationYear") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Expiration year")
|
||||
.assertTextContains("NewExpirationYear")
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card changing the security code text field should trigger SecurityCodeTextChange`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Security code")
|
||||
.performTextInput(text = "TestSecurityCode")
|
||||
|
||||
verify {
|
||||
viewModel.trySendAction(
|
||||
VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange(
|
||||
securityCode = "TestSecurityCode",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `in ItemType_Card the security code text field should display the text provided by the state`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_CARD
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Security code")
|
||||
.assertTextContains("")
|
||||
|
||||
mutableStateFlow.update { currentState ->
|
||||
updateCardType(currentState) { copy(securityCode = "123") }
|
||||
}
|
||||
|
||||
composeTestRule
|
||||
.onAllNodesWithContentDescription("Show")
|
||||
.onLast()
|
||||
.performClick()
|
||||
|
||||
composeTestRule
|
||||
.onNodeWithTextAfterScroll(text = "Security code")
|
||||
.assertTextContains("123")
|
||||
}
|
||||
@Test
|
||||
fun `clicking New Custom Field button should allow creation of Linked type`() {
|
||||
mutableStateFlow.value = DEFAULT_STATE_LOGIN
|
||||
|
@ -1754,6 +1991,29 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
return currentState.copy(viewState = updatedType)
|
||||
}
|
||||
|
||||
private fun updateCardType(
|
||||
currentState: VaultAddEditState,
|
||||
transform: VaultAddEditState.ViewState.Content.ItemType.Card.() ->
|
||||
VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
): VaultAddEditState {
|
||||
val updatedType = when (val viewState = currentState.viewState) {
|
||||
is VaultAddEditState.ViewState.Content -> {
|
||||
when (val type = viewState.type) {
|
||||
is VaultAddEditState.ViewState.Content.ItemType.Card -> {
|
||||
viewState.copy(
|
||||
type = type.transform(),
|
||||
)
|
||||
}
|
||||
|
||||
else -> viewState
|
||||
}
|
||||
}
|
||||
|
||||
else -> viewState
|
||||
}
|
||||
return currentState.copy(viewState = updatedType)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private fun updateCommonContent(
|
||||
currentState: VaultAddEditState,
|
||||
|
@ -1799,6 +2059,15 @@ class VaultAddEditScreenTest : BaseComposeTest() {
|
|||
dialog = null,
|
||||
)
|
||||
|
||||
private val DEFAULT_STATE_CARD = VaultAddEditState(
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
common = VaultAddEditState.ViewState.Content.Common(),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card(),
|
||||
),
|
||||
dialog = null,
|
||||
)
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
private val DEFAULT_STATE_SECURE_NOTES_CUSTOM_FIELDS = VaultAddEditState(
|
||||
viewState = VaultAddEditState.ViewState.Content(
|
||||
|
|
|
@ -16,6 +16,9 @@ import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType
|
|||
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.toCustomField
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.util.toViewState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultAddEditType
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardBrand
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultCardExpirationMonth
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
|
@ -923,13 +926,125 @@ class VaultAddEditViewModelTest : BaseViewModelTest() {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `TitleSelected should update title`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.IdentityType.TitleSelected(
|
||||
title = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MX,
|
||||
fun `TitleSelect should update title`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.IdentityType.TitleSelect(
|
||||
title = VaultIdentityTitle.MX,
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MX,
|
||||
selectedTitle = VaultIdentityTitle.MX,
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
inner class VaultAddCardTypeItemActions {
|
||||
private lateinit var viewModel: VaultAddEditViewModel
|
||||
private lateinit var vaultAddItemInitialState: VaultAddEditState
|
||||
private lateinit var identityInitialSavedStateHandle: SavedStateHandle
|
||||
|
||||
@BeforeEach
|
||||
fun setup() {
|
||||
vaultAddItemInitialState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(),
|
||||
)
|
||||
identityInitialSavedStateHandle = createSavedStateHandleWithState(
|
||||
state = vaultAddItemInitialState,
|
||||
vaultAddEditType = VaultAddEditType.AddItem,
|
||||
)
|
||||
viewModel = createAddVaultItemViewModel(
|
||||
savedStateHandle = identityInitialSavedStateHandle,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CardHolderNameTextChange should update card holder name`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.CardHolderNameTextChange(
|
||||
cardHolderName = "newCardHolderName",
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
cardHolderName = "newCardHolderName",
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `NumberTextChange should update number`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.NumberTextChange(
|
||||
number = "newNumber",
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
number = "newNumber",
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `BrandSelect should update brand`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.BrandSelect(
|
||||
brand = VaultCardBrand.VISA,
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
brand = VaultCardBrand.VISA,
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Suppress("MaxLineLength")
|
||||
@Test
|
||||
fun `ExpirationMonthSelect should update expiration month`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.ExpirationMonthSelect(
|
||||
expirationMonth = VaultCardExpirationMonth.JUNE,
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
expirationMonth = VaultCardExpirationMonth.JUNE,
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `ExpirationYearTextChange should update expiration year`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.ExpirationYearTextChange(
|
||||
expirationYear = "newExpirationYear",
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
expirationYear = "newExpirationYear",
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
||||
assertEquals(expectedState, viewModel.stateFlow.value)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `SecurityCodeTextChange should update security code`() = runTest {
|
||||
val action = VaultAddEditAction.ItemType.CardType.SecurityCodeTextChange(
|
||||
securityCode = "newSecurityCode",
|
||||
)
|
||||
val expectedState = createVaultAddItemState(
|
||||
typeContentViewState = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
securityCode = "newSecurityCode",
|
||||
),
|
||||
)
|
||||
viewModel.actionChannel.trySend(action)
|
||||
|
|
|
@ -68,7 +68,11 @@ class CipherViewExtensionsTest {
|
|||
availableFolders = emptyList(),
|
||||
availableOwners = emptyList(),
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card,
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Card(
|
||||
cardHolderName = "Bit Warden",
|
||||
number = "4012888888881881",
|
||||
securityCode = "123",
|
||||
),
|
||||
),
|
||||
result,
|
||||
)
|
||||
|
|
|
@ -14,6 +14,7 @@ import com.bitwarden.core.SecureNoteView
|
|||
import com.bitwarden.core.UriMatchType
|
||||
import com.x8bit.bitwarden.ui.platform.base.util.asText
|
||||
import com.x8bit.bitwarden.ui.vault.feature.addedit.VaultAddEditState
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultIdentityTitle
|
||||
import com.x8bit.bitwarden.ui.vault.model.VaultLinkedFieldType
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkStatic
|
||||
|
@ -310,7 +311,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
ownership = "mockOwnership-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MR,
|
||||
selectedTitle = VaultIdentityTitle.MR,
|
||||
firstName = "mockFirstName",
|
||||
lastName = "mockLastName",
|
||||
middleName = "mockMiddleName",
|
||||
|
@ -407,7 +408,7 @@ class VaultAddItemStateExtensionsTest {
|
|||
ownership = "mockOwnership-1",
|
||||
),
|
||||
type = VaultAddEditState.ViewState.Content.ItemType.Identity(
|
||||
selectedTitle = VaultAddEditState.ViewState.Content.ItemType.Identity.Title.MR,
|
||||
selectedTitle = VaultIdentityTitle.MR,
|
||||
firstName = "mockFirstName",
|
||||
lastName = "mockLastName",
|
||||
middleName = "mockMiddleName",
|
||||
|
|
Loading…
Add table
Reference in a new issue