Add ViewState VaultAddItemViewModel (#359)

This commit is contained in:
David Perez
2024-06-20 17:08:07 +01:00
committed by Álison Fernandes
parent d5a1592ef0
commit 1219aa20fd
11 changed files with 539 additions and 346 deletions
@@ -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,
)
}
@@ -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 {
@@ -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 {
@@ -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())
}
}
@@ -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())
}
}
@@ -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
}
}
@@ -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"
}
}
}
}
@@ -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")