Add ViewState VaultAddItemViewModel (#359)

This commit is contained in:
David Perez 2023-12-08 16:03:15 -06:00 committed by Álison Fernandes
parent d5a1592ef0
commit 1219aa20fd
11 changed files with 539 additions and 346 deletions

View file

@ -0,0 +1,104 @@
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
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 kotlinx.collections.immutable.toImmutableList
/**
* The top level content UI state for the [VaultAddItemScreen].
*/
@Composable
fun AddEditItemContent(
viewState: VaultAddItemState.ViewState.Content,
shouldShowTypeSelector: Boolean,
onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit,
loginItemTypeHandlers: VaultAddLoginItemTypeHandlers,
secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers,
modifier: Modifier = Modifier,
) {
LazyColumn(
modifier = modifier,
) {
item {
BitwardenListHeaderText(
label = stringResource(id = R.string.item_information),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
if (shouldShowTypeSelector) {
item {
Spacer(modifier = Modifier.height(8.dp))
TypeOptionsItem(
content = viewState,
onTypeOptionClicked = onTypeOptionClicked,
modifier = Modifier.padding(horizontal = 16.dp),
)
}
}
when (viewState) {
is VaultAddItemState.ViewState.Content.Login -> {
addEditLoginItems(
state = viewState,
loginItemTypeHandlers = loginItemTypeHandlers,
)
}
is VaultAddItemState.ViewState.Content.Card -> {
// TODO(BIT-507): Create UI for card-type item creation
}
is VaultAddItemState.ViewState.Content.Identity -> {
// TODO(BIT-667): Create UI for identity-type item creation
}
is VaultAddItemState.ViewState.Content.SecureNotes -> {
addEditSecureNotesItems(
state = viewState,
secureNotesTypeHandlers = secureNotesTypeHandlers,
)
}
}
item {
Spacer(modifier = Modifier.navigationBarsPadding())
}
}
}
@Composable
private fun TypeOptionsItem(
content: VaultAddItemState.ViewState.Content,
onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit,
modifier: Modifier,
) {
val possibleMainStates = VaultAddItemState.ItemTypeOption.entries.toList()
val optionsWithStrings = possibleMainStates.associateWith { stringResource(id = it.labelRes) }
BitwardenMultiSelectButton(
label = stringResource(id = R.string.type),
options = optionsWithStrings.values.toImmutableList(),
selectedOption = stringResource(id = content.displayStringResId),
onOptionSelected = { selectedOption ->
val selectedOptionId = optionsWithStrings
.entries
.first { it.value == selectedOption }
.key
onTypeOptionClicked(selectedOptionId)
},
modifier = modifier,
)
}

View file

@ -31,7 +31,7 @@ import kotlinx.collections.immutable.toImmutableList
*/
@Suppress("LongMethod")
fun LazyListScope.addEditLoginItems(
state: VaultAddItemState.ItemType.Login,
state: VaultAddItemState.ViewState.Content.Login,
loginItemTypeHandlers: VaultAddLoginItemTypeHandlers,
) {
item {

View file

@ -26,7 +26,7 @@ import kotlinx.collections.immutable.toImmutableList
*/
@Suppress("LongMethod")
fun LazyListScope.addEditSecureNotesItems(
state: VaultAddItemState.ItemType.SecureNotes,
state: VaultAddItemState.ViewState.Content.SecureNotes,
secureNotesTypeHandlers: VaultAddSecureNotesItemTypeHandlers,
) {
item {

View file

@ -0,0 +1,40 @@
package com.x8bit.bitwarden.ui.vault.feature.additem
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
/**
* The top level error UI state for the [VaultAddItemScreen].
*/
@Composable
fun VaultAddEditError(
viewState: VaultAddItemState.ViewState.Error,
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = viewState.message(),
style = MaterialTheme.typography.bodyMedium,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
Spacer(modifier = Modifier.navigationBarsPadding())
}
}

View file

@ -0,0 +1,27 @@
package com.x8bit.bitwarden.ui.vault.feature.additem
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
/**
* The top level loading UI state for the [VaultAddItemScreen].
*/
@Composable
fun VaultAddEditItemLoading(
modifier: Modifier = Modifier,
) {
Column(
modifier = modifier,
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
CircularProgressIndicator()
Spacer(modifier = Modifier.navigationBarsPadding())
}
}

View file

@ -1,14 +1,9 @@
package com.x8bit.bitwarden.ui.vault.feature.additem
import android.widget.Toast
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.imePadding
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
@ -20,7 +15,6 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll
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 androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.x8bit.bitwarden.R
@ -28,14 +22,11 @@ import com.x8bit.bitwarden.ui.platform.base.util.EventsEffect
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.platform.components.BasicDialogState
import com.x8bit.bitwarden.ui.platform.components.BitwardenBasicDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenListHeaderText
import com.x8bit.bitwarden.ui.platform.components.BitwardenLoadingDialog
import com.x8bit.bitwarden.ui.platform.components.BitwardenMultiSelectButton
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 kotlinx.collections.immutable.toImmutableList
/**
* Top level composable for the vault add item screen.
@ -68,25 +59,12 @@ fun VaultAddItemScreen(
VaultAddSecureNotesItemTypeHandlers.create(viewModel = viewModel)
}
when (val dialogState = state.dialog) {
is VaultAddItemState.DialogState.Loading -> {
BitwardenLoadingDialog(
visibilityState = LoadingDialogState.Shown(dialogState.label),
)
}
is VaultAddItemState.DialogState.Error -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = dialogState.message,
),
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(VaultAddItemAction.DismissDialog) }
},
)
null -> Unit
}
VaultAddEditItemDialogs(
dialogState = state.dialog,
onDismissRequest = remember(viewModel) {
{ viewModel.trySendAction(VaultAddItemAction.DismissDialog) }
},
)
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
BitwardenScaffold(
@ -113,88 +91,63 @@ fun VaultAddItemScreen(
)
},
) { innerPadding ->
LazyColumn(
modifier = Modifier
.imePadding()
.padding(innerPadding)
.fillMaxSize(),
) {
item {
BitwardenListHeaderText(
label = stringResource(id = R.string.item_information),
when (val viewState = state.viewState) {
is VaultAddItemState.ViewState.Content -> {
AddEditItemContent(
viewState = viewState,
shouldShowTypeSelector = state.shouldShowTypeSelector,
onTypeOptionClicked = remember(viewModel) {
{ viewModel.trySendAction(VaultAddItemAction.TypeOptionSelect(it)) }
},
loginItemTypeHandlers = loginItemTypeHandlers,
secureNotesTypeHandlers = secureNotesTypeHandlers,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
.imePadding()
.padding(innerPadding)
.fillMaxSize(),
)
}
if (state.shouldShowTypeSelector) {
item {
Spacer(modifier = Modifier.height(8.dp))
TypeOptionsItem(
selectedType = state.selectedType,
onTypeOptionClicked = remember(viewModel) {
{ typeOption: VaultAddItemState.ItemTypeOption ->
viewModel.trySendAction(
VaultAddItemAction.TypeOptionSelect(typeOption),
)
}
},
modifier = Modifier.padding(horizontal = 16.dp),
)
}
is VaultAddItemState.ViewState.Error -> {
VaultAddEditError(
viewState = viewState,
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
)
}
when (val selectedType = state.selectedType) {
is VaultAddItemState.ItemType.Login -> {
addEditLoginItems(
state = selectedType,
loginItemTypeHandlers = loginItemTypeHandlers,
)
}
is VaultAddItemState.ItemType.Card -> {
// TODO(BIT-507): Create UI for card-type item creation
}
is VaultAddItemState.ItemType.Identity -> {
// TODO(BIT-667): Create UI for identity-type item creation
}
is VaultAddItemState.ItemType.SecureNotes -> {
addEditSecureNotesItems(
state = selectedType,
secureNotesTypeHandlers = secureNotesTypeHandlers,
)
}
}
item {
Spacer(modifier = Modifier.navigationBarsPadding())
VaultAddItemState.ViewState.Loading -> {
VaultAddEditItemLoading(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
)
}
}
}
}
@Composable
private fun TypeOptionsItem(
selectedType: VaultAddItemState.ItemType,
onTypeOptionClicked: (VaultAddItemState.ItemTypeOption) -> Unit,
modifier: Modifier,
private fun VaultAddEditItemDialogs(
dialogState: VaultAddItemState.DialogState?,
onDismissRequest: () -> Unit,
) {
val possibleMainStates = VaultAddItemState.ItemTypeOption.values().toList()
when (dialogState) {
is VaultAddItemState.DialogState.Loading -> {
BitwardenLoadingDialog(
visibilityState = LoadingDialogState.Shown(dialogState.label),
)
}
val optionsWithStrings =
possibleMainStates.associateBy({ it }, { stringResource(id = it.labelRes) })
is VaultAddItemState.DialogState.Error -> BitwardenBasicDialog(
visibilityState = BasicDialogState.Shown(
title = R.string.an_error_has_occurred.asText(),
message = dialogState.message,
),
onDismissRequest = onDismissRequest,
)
BitwardenMultiSelectButton(
label = stringResource(id = R.string.type),
options = optionsWithStrings.values.toImmutableList(),
selectedOption = stringResource(id = selectedType.displayStringResId),
onOptionSelected = { selectedOption ->
val selectedOptionId =
optionsWithStrings.entries.first { it.value == selectedOption }.key
onTypeOptionClicked(selectedOptionId)
},
modifier = modifier,
)
null -> Unit
}
}

View file

@ -1,6 +1,7 @@
package com.x8bit.bitwarden.ui.vault.feature.additem
import android.os.Parcelable
import androidx.annotation.StringRes
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.x8bit.bitwarden.R
@ -37,11 +38,17 @@ class VaultAddItemViewModel @Inject constructor(
private val vaultRepository: VaultRepository,
) : BaseViewModel<VaultAddItemState, VaultAddItemEvent, VaultAddItemAction>(
initialState = savedStateHandle[KEY_STATE]
?: VaultAddItemState(
vaultAddEditType = VaultAddEditItemArgs(savedStateHandle).vaultAddEditType,
selectedType = VaultAddItemState.ItemType.Login(),
dialog = null,
),
?: run {
val vaultAddEditType = VaultAddEditItemArgs(savedStateHandle).vaultAddEditType
VaultAddItemState(
vaultAddEditType = vaultAddEditType,
viewState = when (vaultAddEditType) {
VaultAddEditType.AddItem -> VaultAddItemState.ViewState.Content.Login()
is VaultAddEditType.EditItem -> VaultAddItemState.ViewState.Loading
},
dialog = null,
)
},
) {
//region Initialization and Overrides
@ -86,8 +93,8 @@ class VaultAddItemViewModel @Inject constructor(
//region Top Level Handlers
private fun handleSaveClick() {
if (state.selectedType.name.isBlank()) {
private fun handleSaveClick() = onContent { content ->
if (content.name.isBlank()) {
mutableStateFlow.update {
it.copy(
dialog = VaultAddItemState.DialogState.Error(
@ -96,7 +103,7 @@ class VaultAddItemViewModel @Inject constructor(
),
)
}
return
return@onContent
}
mutableStateFlow.update {
@ -111,7 +118,7 @@ class VaultAddItemViewModel @Inject constructor(
sendAction(
action = VaultAddItemAction.Internal.CreateCipherResultReceive(
createCipherResult = vaultRepository.createCipher(
cipherView = stateFlow.value.selectedType.toCipherView(),
cipherView = content.toCipherView(),
),
),
)
@ -144,35 +151,19 @@ class VaultAddItemViewModel @Inject constructor(
}
private fun handleSwitchToAddLoginItem() {
mutableStateFlow.update { currentState ->
currentState.copy(
selectedType = VaultAddItemState.ItemType.Login(),
)
}
updateContent { VaultAddItemState.ViewState.Content.Login() }
}
private fun handleSwitchToAddSecureNotesItem() {
mutableStateFlow.update { currentState ->
currentState.copy(
selectedType = VaultAddItemState.ItemType.SecureNotes(),
)
}
updateContent { VaultAddItemState.ViewState.Content.SecureNotes() }
}
private fun handleSwitchToAddCardItem() {
mutableStateFlow.update { currentState ->
currentState.copy(
selectedType = VaultAddItemState.ItemType.Card(),
)
}
updateContent { VaultAddItemState.ViewState.Content.Card() }
}
private fun handleSwitchToAddIdentityItem() {
mutableStateFlow.update { currentState ->
currentState.copy(
selectedType = VaultAddItemState.ItemType.Identity(),
)
}
updateContent { VaultAddItemState.ViewState.Content.Identity() }
}
//endregion Type Option Handlers
@ -257,7 +248,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginNameTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.NameTextChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(name = action.name)
}
}
@ -265,7 +256,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginUsernameTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.UsernameTextChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(username = action.username)
}
}
@ -273,7 +264,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginPasswordTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.PasswordTextChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(password = action.password)
}
}
@ -281,7 +272,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginURITextInputChange(
action: VaultAddItemAction.ItemType.LoginType.UriTextChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(uri = action.uri)
}
}
@ -289,7 +280,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginFolderTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.FolderChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(folderName = action.folder)
}
}
@ -297,7 +288,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginToggleFavorite(
action: VaultAddItemAction.ItemType.LoginType.ToggleFavorite,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(favorite = action.isFavorite)
}
}
@ -305,7 +296,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginToggleMasterPasswordReprompt(
action: VaultAddItemAction.ItemType.LoginType.ToggleMasterPasswordReprompt,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(masterPasswordReprompt = action.isMasterPasswordReprompt)
}
}
@ -313,7 +304,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginNotesTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.NotesTextChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(notes = action.notes)
}
}
@ -321,7 +312,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleLoginOwnershipTextInputChange(
action: VaultAddItemAction.ItemType.LoginType.OwnershipChange,
) {
updateLoginType { loginType ->
updateLoginContent { loginType ->
loginType.copy(ownership = action.ownership)
}
}
@ -451,7 +442,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteNameTextInputChange(
action: VaultAddItemAction.ItemType.SecureNotesType.NameTextChange,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(name = action.name)
}
}
@ -459,7 +450,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteFolderTextInputChange(
action: VaultAddItemAction.ItemType.SecureNotesType.FolderChange,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(folderName = action.folderName)
}
}
@ -467,7 +458,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteToggleFavorite(
action: VaultAddItemAction.ItemType.SecureNotesType.ToggleFavorite,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(favorite = action.isFavorite)
}
}
@ -475,7 +466,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteToggleMasterPasswordReprompt(
action: VaultAddItemAction.ItemType.SecureNotesType.ToggleMasterPasswordReprompt,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(masterPasswordReprompt = action.isMasterPasswordReprompt)
}
}
@ -483,7 +474,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteNotesTextInputChange(
action: VaultAddItemAction.ItemType.SecureNotesType.NotesTextChange,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(notes = action.note)
}
}
@ -491,7 +482,7 @@ class VaultAddItemViewModel @Inject constructor(
private fun handleSecureNoteOwnershipTextInputChange(
action: VaultAddItemAction.ItemType.SecureNotesType.OwnershipChange,
) {
updateSecureNoteType { secureNoteType ->
updateSecureNoteContent { secureNoteType ->
secureNoteType.copy(ownership = action.ownership)
}
}
@ -546,34 +537,38 @@ class VaultAddItemViewModel @Inject constructor(
//region Utility Functions
private inline fun updateLoginType(
crossinline block: (VaultAddItemState.ItemType.Login) -> VaultAddItemState.ItemType.Login,
private inline fun onContent(
crossinline block: (VaultAddItemState.ViewState.Content) -> Unit,
) {
mutableStateFlow.update { currentState ->
val currentSelectedType = currentState.selectedType
if (currentSelectedType !is VaultAddItemState.ItemType.Login) return@update currentState
val updatedLogin = block(currentSelectedType)
currentState.copy(selectedType = updatedLogin)
}
(state.viewState as? VaultAddItemState.ViewState.Content)?.let(block)
}
private inline fun updateSecureNoteType(
private inline fun updateContent(
crossinline block: (
VaultAddItemState.ItemType.SecureNotes,
) -> VaultAddItemState.ItemType.SecureNotes,
VaultAddItemState.ViewState.Content,
) -> VaultAddItemState.ViewState.Content?,
) {
mutableStateFlow.update { currentState ->
val currentSelectedType = currentState.selectedType
if (currentSelectedType !is VaultAddItemState.ItemType.SecureNotes) {
return@update currentState
}
val currentViewState = state.viewState
val updatedContent = (currentViewState as? VaultAddItemState.ViewState.Content)
?.let(block)
?: return
mutableStateFlow.update { it.copy(viewState = updatedContent) }
}
val updatedSecureNote = block(currentSelectedType)
private inline fun updateLoginContent(
crossinline block: (
VaultAddItemState.ViewState.Content.Login,
) -> VaultAddItemState.ViewState.Content.Login,
) {
updateContent { (it as? VaultAddItemState.ViewState.Content.Login)?.let(block) }
}
currentState.copy(selectedType = updatedSecureNote)
}
private inline fun updateSecureNoteContent(
crossinline block: (
VaultAddItemState.ViewState.Content.SecureNotes,
) -> VaultAddItemState.ViewState.Content.SecureNotes,
) {
updateContent { (it as? VaultAddItemState.ViewState.Content.SecureNotes)?.let(block) }
}
//endregion Utility Functions
@ -582,14 +577,14 @@ class VaultAddItemViewModel @Inject constructor(
/**
* Represents the state for adding an item to the vault.
*
* @property selectedType The type of the item (e.g., Card, Identity, SecureNotes)
* that has been selected to be added to the vault.
* @property vaultAddEditType Indicates whether the VM is in add or edit mode.
* @property viewState indicates what view state the screen is in.
* @property dialog the state for the dialogs that can be displayed
*/
@Parcelize
data class VaultAddItemState(
val vaultAddEditType: VaultAddEditType,
val selectedType: ItemType,
val viewState: ViewState,
val dialog: DialogState?,
) : Parcelable {
@ -626,125 +621,137 @@ data class VaultAddItemState(
}
/**
* A sealed class representing the item types that can be selected in the vault,
* encapsulating the different configurations and properties each item type has.
* Represents the specific view states for the [VaultAddItemScreen].
*/
@Parcelize
sealed class ItemType : Parcelable {
sealed class ViewState : Parcelable {
/**
* Represents the resource ID for the display string. This is an abstract property
* that must be overridden by each subclass to provide the appropriate string resource ID
* for display purposes.
*/
abstract val displayStringResId: Int
/**
* Represents the name for the item type. This is an abstract property
* that must be overridden to save the item
*/
abstract val name: String
/**
* Represents the login item information.
*
* @property username The username required for the login item.
* @property password The password required for the login item.
* @property uri The URI associated with the login item.
* @property folderName The folder used for the login item
* @property favorite Indicates whether this login item is marked as a favorite.
* @property masterPasswordReprompt Indicates if a master password reprompt is required.
* @property notes Any additional notes or comments associated with the login item.
* @property ownership The ownership email associated with the login item.
* @property availableFolders Retrieves a list of available folders.
* @property availableOwners Retrieves a list of available owners.
* Represents an error state for the [VaultAddItemScreen].
*/
@Parcelize
data class Login(
override val name: String = "",
val username: String = "",
val password: String = "",
val uri: String = "",
val folderName: Text = DEFAULT_FOLDER,
val favorite: Boolean = false,
val masterPasswordReprompt: Boolean = false,
val notes: String = "",
val ownership: String = DEFAULT_OWNERSHIP,
// TODO: Update this property to pull available owners from the data layer. (BIT-501)
val availableFolders: List<Text> = listOf(
"Folder 1".asText(),
"Folder 2".asText(),
"Folder 3".asText(),
),
// TODO: Update this property to pull available owners from the data layer. (BIT-501)
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
) : ItemType() {
override val displayStringResId: Int
get() = ItemTypeOption.LOGIN.labelRes
data class Error(
val message: Text,
) : ViewState()
companion object {
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
/**
* Loading state for the [VaultAddItemScreen], signifying that the content is being
* processed.
*/
@Parcelize
data object Loading : ViewState()
/**
* Represents a loaded content state for the [VaultAddItemScreen].
*/
@Parcelize
sealed class Content : ViewState() {
/**
* Represents the resource ID for the display string. This is an abstract property
* that must be overridden by each subclass to provide the appropriate string resource
* ID for display purposes.
*/
@get:StringRes
abstract val displayStringResId: Int
/**
* Represents the name for the item type. This is an abstract property that must be
* overridden to save the item
*/
abstract val name: String
/**
* Represents the login item information.
*
* @property username The username required for the login item.
* @property password The password required for the login item.
* @property uri The URI associated with the login item.
* @property folderName The folder used for the login item
* @property favorite Indicates whether this login item is marked as a favorite.
* @property masterPasswordReprompt Indicates if a master password reprompt is required.
* @property notes Any additional notes or comments associated with the login item.
* @property ownership The ownership email associated with the login item.
* @property availableFolders Retrieves a list of available folders.
* @property availableOwners Retrieves a list of available owners.
*/
@Parcelize
data class Login(
override val name: String = "",
val username: String = "",
val password: String = "",
val uri: String = "",
val folderName: Text = DEFAULT_FOLDER,
val favorite: Boolean = false,
val masterPasswordReprompt: Boolean = false,
val notes: String = "",
val ownership: String = DEFAULT_OWNERSHIP,
// TODO: Update this property to get available owners from the data layer (BIT-501)
val availableFolders: List<Text> = listOf(
"Folder 1".asText(),
"Folder 2".asText(),
"Folder 3".asText(),
),
// TODO: Update this property to get available owners from the data layer (BIT-501)
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
) : Content() {
override val displayStringResId: Int get() = ItemTypeOption.LOGIN.labelRes
companion object {
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
}
}
}
/**
* Represents the `Card` item type.
*/
@Parcelize
data class Card(
// TODO create the Card Item (BIT-509)
override val name: String = "",
) : ItemType() {
override val displayStringResId: Int
get() = ItemTypeOption.CARD.labelRes
}
/**
* Represents the `Card` item type.
*/
@Parcelize
data class Card(
override val name: String = "",
) : Content() {
override val displayStringResId: Int get() = ItemTypeOption.CARD.labelRes
}
/**
* Represents the `Identity` item type.
*/
@Parcelize
data class Identity(
// TODO create the Identity Item (BIT-667)
override val name: String = "",
) : ItemType() {
override val displayStringResId: Int
get() = ItemTypeOption.IDENTITY.labelRes
}
/**
* Represents the `Identity` item type.
*/
@Parcelize
data class Identity(
override val name: String = "",
) : Content() {
override val displayStringResId: Int get() = ItemTypeOption.IDENTITY.labelRes
}
/**
* Represents the `SecureNotes` item type.
*
* @property folder The folder used for the SecureNotes item
* @property favorite Indicates whether this SecureNotes item is marked as a favorite.
* @property masterPasswordReprompt Indicates if a master password reprompt is required.
* @property notes Notes or comments associated with the SecureNotes item.
* @property ownership The ownership email associated with the SecureNotes item.
* @property availableFolders A list of available folders.
* @property availableOwners A list of available owners.
*/
@Parcelize
data class SecureNotes(
override val name: String = "",
val folderName: Text = DEFAULT_FOLDER,
val favorite: Boolean = false,
val masterPasswordReprompt: Boolean = false,
val notes: String = "",
val ownership: String = DEFAULT_OWNERSHIP,
val availableFolders: List<Text> = listOf(
"Folder 1".asText(),
"Folder 2".asText(),
"Folder 3".asText(),
),
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
) : ItemType() {
/**
* Represents the `SecureNotes` item type.
*
* @property folderName The folder used for the SecureNotes item
* @property favorite Indicates whether this SecureNotes item is marked as a favorite.
* @property masterPasswordReprompt Indicates if a master password reprompt is required.
* @property notes Notes or comments associated with the SecureNotes item.
* @property ownership The ownership email associated with the SecureNotes item.
* @property availableFolders A list of available folders.
* @property availableOwners A list of available owners.
*/
@Parcelize
data class SecureNotes(
override val name: String = "",
val folderName: Text = DEFAULT_FOLDER,
val favorite: Boolean = false,
val masterPasswordReprompt: Boolean = false,
val notes: String = "",
val ownership: String = DEFAULT_OWNERSHIP,
val availableFolders: List<Text> = listOf(
"Folder 1".asText(),
"Folder 2".asText(),
"Folder 3".asText(),
),
val availableOwners: List<String> = listOf("a@b.com", "c@d.com"),
) : Content() {
override val displayStringResId: Int get() = ItemTypeOption.SECURE_NOTES.labelRes
override val displayStringResId: Int
get() = ItemTypeOption.SECURE_NOTES.labelRes
companion object {
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
companion object {
private val DEFAULT_FOLDER: Text = R.string.folder_none.asText()
private const val DEFAULT_OWNERSHIP: String = "placeholder@email.com"
}
}
}
}

View file

@ -84,20 +84,20 @@ fun VaultData.toViewState(): VaultState.ViewState =
}
/**
* Transforms a [VaultAddItemState.ItemType] into [CipherView].
* Transforms a [VaultAddItemState.ViewState.Content] into [CipherView].
*/
fun VaultAddItemState.ItemType.toCipherView(): CipherView =
fun VaultAddItemState.ViewState.Content.toCipherView(): CipherView =
when (this) {
is VaultAddItemState.ItemType.Card -> toCardCipherView()
is VaultAddItemState.ItemType.Identity -> toIdentityCipherView()
is VaultAddItemState.ItemType.Login -> toLoginCipherView()
is VaultAddItemState.ItemType.SecureNotes -> toSecureNotesCipherView()
is VaultAddItemState.ViewState.Content.Card -> toCardCipherView()
is VaultAddItemState.ViewState.Content.Identity -> toIdentityCipherView()
is VaultAddItemState.ViewState.Content.Login -> toLoginCipherView()
is VaultAddItemState.ViewState.Content.SecureNotes -> toSecureNotesCipherView()
}
/**
* Transforms [VaultAddItemState.ItemType.Login] into [CipherView].
* Transforms [VaultAddItemState.ViewState.Content.Login] into [CipherView].
*/
private fun VaultAddItemState.ItemType.Login.toLoginCipherView(): CipherView =
private fun VaultAddItemState.ViewState.Content.Login.toLoginCipherView(): CipherView =
CipherView(
id = null,
// TODO use real organization id BIT-780
@ -149,9 +149,9 @@ private fun VaultAddItemState.ItemType.Login.toLoginCipherView(): CipherView =
)
/**
* Transforms [VaultAddItemState.ItemType.SecureNotes] into [CipherView].
* Transforms [VaultAddItemState.ViewState.Content.SecureNotes] into [CipherView].
*/
private fun VaultAddItemState.ItemType.SecureNotes.toSecureNotesCipherView(): CipherView =
private fun VaultAddItemState.ViewState.Content.SecureNotes.toSecureNotesCipherView(): CipherView =
CipherView(
id = null,
// TODO use real organization id BIT-780
@ -189,13 +189,13 @@ private fun VaultAddItemState.ItemType.SecureNotes.toSecureNotesCipherView(): Ci
)
/**
* Transforms [VaultAddItemState.ItemType.Identity] into [CipherView].
* Transforms [VaultAddItemState.ViewState.Content.Identity] into [CipherView].
*/
private fun VaultAddItemState.ItemType.Identity.toIdentityCipherView(): CipherView =
private fun VaultAddItemState.ViewState.Content.Identity.toIdentityCipherView(): CipherView =
TODO("create Identity CipherView BIT-508")
/**
* Transforms [VaultAddItemState.ItemType.Card] into [CipherView].
* Transforms [VaultAddItemState.ViewState.Content.Card] into [CipherView].
*/
private fun VaultAddItemState.ItemType.Card.toCardCipherView(): CipherView =
private fun VaultAddItemState.ViewState.Content.Card.toCardCipherView(): CipherView =
TODO("create Card CipherView BIT-668")

View file

@ -24,6 +24,7 @@ import androidx.compose.ui.test.performTextInput
import androidx.compose.ui.test.performTouchInput
import com.x8bit.bitwarden.ui.platform.base.BaseComposeTest
import com.x8bit.bitwarden.ui.platform.base.util.asText
import com.x8bit.bitwarden.ui.util.isProgressBar
import com.x8bit.bitwarden.ui.util.onAllNodesWithTextAfterScroll
import com.x8bit.bitwarden.ui.util.onNodeWithContentDescriptionAfterScroll
import com.x8bit.bitwarden.ui.util.onNodeWithTextAfterScroll
@ -126,6 +127,43 @@ class VaultAddItemScreenTest : BaseComposeTest() {
.assertIsDisplayed()
}
@Test
fun `error text and retry should be displayed according to state`() {
val message = "error_message"
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Loading)
}
composeTestRule.onNodeWithText(message).assertIsNotDisplayed()
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Content.Login())
}
composeTestRule.onNodeWithText(message).assertIsNotDisplayed()
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Error(message.asText()))
}
composeTestRule.onNodeWithText(message).assertIsDisplayed()
}
@Test
fun `progressbar should be displayed according to state`() {
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Loading)
}
composeTestRule.onNode(isProgressBar).assertIsDisplayed()
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Error("Fail".asText()))
}
composeTestRule.onNode(isProgressBar).assertDoesNotExist()
mutableStateFlow.update {
it.copy(viewState = VaultAddItemState.ViewState.Content.Login())
}
composeTestRule.onNode(isProgressBar).assertDoesNotExist()
}
@Test
fun `clicking a Type Option should send TypeOptionSelect action`() {
// Opens the menu
@ -153,7 +191,7 @@ class VaultAddItemScreenTest : BaseComposeTest() {
.onNodeWithContentDescriptionAfterScroll(label = "Type, Login")
.assertIsDisplayed()
mutableStateFlow.update { it.copy(selectedType = VaultAddItemState.ItemType.Card()) }
mutableStateFlow.update { it.copy(viewState = VaultAddItemState.ViewState.Content.Card()) }
composeTestRule
.onNodeWithContentDescriptionAfterScroll(label = "Type, Card")
@ -815,47 +853,48 @@ class VaultAddItemScreenTest : BaseComposeTest() {
//region Helper functions
@Suppress("MaxLineLength")
private fun updateLoginType(
currentState: VaultAddItemState,
transform: VaultAddItemState.ItemType.Login.() -> VaultAddItemState.ItemType.Login,
transform: VaultAddItemState.ViewState.Content.Login.() -> VaultAddItemState.ViewState.Content.Login,
): VaultAddItemState {
val updatedType = when (val currentType = currentState.selectedType) {
is VaultAddItemState.ItemType.Login -> currentType.transform()
else -> currentType
val updatedType = when (val viewState = currentState.viewState) {
is VaultAddItemState.ViewState.Content.Login -> viewState.transform()
else -> viewState
}
return currentState.copy(selectedType = updatedType)
return currentState.copy(viewState = updatedType)
}
@Suppress("MaxLineLength")
private fun updateSecureNotesType(
currentState: VaultAddItemState,
transform: VaultAddItemState.ItemType.SecureNotes.() -> VaultAddItemState.ItemType.SecureNotes,
transform: VaultAddItemState.ViewState.Content.SecureNotes.() -> VaultAddItemState.ViewState.Content.SecureNotes,
): VaultAddItemState {
val updatedType = when (val currentType = currentState.selectedType) {
is VaultAddItemState.ItemType.SecureNotes -> currentType.transform()
else -> currentType
val updatedType = when (val viewState = currentState.viewState) {
is VaultAddItemState.ViewState.Content.SecureNotes -> viewState.transform()
else -> viewState
}
return currentState.copy(selectedType = updatedType)
return currentState.copy(viewState = updatedType)
}
//endregion Helper functions
companion object {
private val DEFAULT_STATE_LOGIN_DIALOG = VaultAddItemState(
selectedType = VaultAddItemState.ItemType.Login(),
viewState = VaultAddItemState.ViewState.Content.Login(),
dialog = VaultAddItemState.DialogState.Error("test".asText()),
vaultAddEditType = VaultAddEditType.AddItem,
)
private val DEFAULT_STATE_LOGIN = VaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem,
selectedType = VaultAddItemState.ItemType.Login(),
viewState = VaultAddItemState.ViewState.Content.Login(),
dialog = null,
)
private val DEFAULT_STATE_SECURE_NOTES = VaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem,
selectedType = VaultAddItemState.ItemType.SecureNotes(),
viewState = VaultAddItemState.ViewState.Content.SecureNotes(),
dialog = null,
)
}

View file

@ -40,11 +40,29 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
}
@Test
fun `initial state should be correct`() = runTest {
val viewModel = createAddVaultItemViewModel()
viewModel.stateFlow.test {
assertEquals(initialState, awaitItem())
}
fun `initial add state should be correct`() = runTest {
val vaultAddEditType = VaultAddEditType.AddItem
val initState = createVaultAddLoginItemState(vaultAddEditType = vaultAddEditType)
val viewModel = createAddVaultItemViewModel(
savedStateHandle = createSavedStateHandleWithState(
state = initState,
vaultAddEditType = vaultAddEditType,
),
)
assertEquals(initState, viewModel.stateFlow.value)
}
@Test
fun `initial edit state should be correct`() = runTest {
val vaultAddEditType = VaultAddEditType.EditItem(DEFAULT_EDIT_ITEM_ID)
val initState = createVaultAddLoginItemState(vaultAddEditType = vaultAddEditType)
val viewModel = createAddVaultItemViewModel(
savedStateHandle = createSavedStateHandleWithState(
state = initState,
vaultAddEditType = vaultAddEditType,
),
)
assertEquals(initState, viewModel.stateFlow.value)
}
@Test
@ -188,7 +206,9 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedState = initialState.copy(selectedType = VaultAddItemState.ItemType.Login())
val expectedState = initialState.copy(
viewState = VaultAddItemState.ViewState.Content.Login(),
)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -210,10 +230,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(name = "newName")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -227,10 +247,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(username = "newUsername")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -244,10 +264,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(password = "newPassword")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -260,10 +280,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(uri = "newUri")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -276,10 +296,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(folderName = "newFolder".asText())
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -292,10 +312,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(favorite = true)
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -312,10 +332,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(masterPasswordReprompt = true)
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -328,10 +348,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(notes = "newNotes")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -346,10 +366,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedLoginItem =
(initialState.selectedType as VaultAddItemState.ItemType.Login)
(initialState.viewState as VaultAddItemState.ViewState.Content.Login)
.copy(ownership = "newOwner")
val expectedState = initialState.copy(selectedType = expectedLoginItem)
val expectedState = initialState.copy(viewState = expectedLoginItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -496,10 +516,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(name = "newName")
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -513,10 +533,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(folderName = "newFolder".asText())
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -528,10 +548,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(favorite = true)
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -548,10 +568,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(masterPasswordReprompt = true)
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -565,10 +585,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(notes = "newNotes")
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -582,10 +602,10 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
viewModel.actionChannel.trySend(action)
val expectedSecureNotesItem =
(initialState.selectedType as VaultAddItemState.ItemType.SecureNotes)
(initialState.viewState as VaultAddItemState.ViewState.Content.SecureNotes)
.copy(ownership = "newOwner")
val expectedState = initialState.copy(selectedType = expectedSecureNotesItem)
val expectedState = initialState.copy(viewState = expectedSecureNotesItem)
assertEquals(expectedState, viewModel.stateFlow.value)
}
@ -618,6 +638,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
@Suppress("LongParameterList")
private fun createVaultAddLoginItemState(
vaultAddEditType: VaultAddEditType = VaultAddEditType.AddItem,
name: String = "",
username: String = "",
password: String = "",
@ -630,8 +651,8 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
dialogState: VaultAddItemState.DialogState? = null,
): VaultAddItemState =
VaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem,
selectedType = VaultAddItemState.ItemType.Login(
vaultAddEditType = vaultAddEditType,
viewState = VaultAddItemState.ViewState.Content.Login(
name = name,
username = username,
password = password,
@ -657,7 +678,7 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
): VaultAddItemState =
VaultAddItemState(
vaultAddEditType = VaultAddEditType.AddItem,
selectedType = VaultAddItemState.ItemType.SecureNotes(
viewState = VaultAddItemState.ViewState.Content.SecureNotes(
name = name,
folderName = folder,
favorite = favorite,
@ -692,3 +713,5 @@ class VaultAddItemViewModelTest : BaseViewModelTest() {
vaultRepository = vaultRepo,
)
}
private const val DEFAULT_EDIT_ITEM_ID: String = "edit_item_id"

View file

@ -109,7 +109,7 @@ class VaultDataExtensionsTest {
fun `toCipherView should transform Login ItemType to CipherView`() {
mockkStatic(Instant::class)
every { Instant.now() } returns Instant.MIN
val loginItemType = VaultAddItemState.ItemType.Login(
val loginItemType = VaultAddItemState.ViewState.Content.Login(
name = "mockName-1",
username = "mockUsername-1",
password = "mockPassword-1",
@ -170,7 +170,7 @@ class VaultDataExtensionsTest {
fun `toCipherView should transform SecureNotes ItemType to CipherView`() {
mockkStatic(Instant::class)
every { Instant.now() } returns Instant.MIN
val secureNotesItemType = VaultAddItemState.ItemType.SecureNotes(
val secureNotesItemType = VaultAddItemState.ViewState.Content.SecureNotes(
name = "mockName-1",
folderName = "mockFolder-1".asText(),
favorite = false,