Skip to content

Commit 00f30c9

Browse files
PM-18058 and PM-18059 Choose which type of vault item to add from Vault screen and inside a Folder. (#4703)
1 parent a68b370 commit 00f30c9

29 files changed

+728
-87
lines changed

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/addedit/FolderAddEditNavigation.kt

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,40 +13,51 @@ import com.x8bit.bitwarden.ui.platform.feature.settings.folders.model.FolderAddE
1313
private const val ADD_TYPE: String = "add"
1414
private const val EDIT_TYPE: String = "edit"
1515
private const val EDIT_ITEM_ID: String = "folder_edit_id"
16+
private const val PARENT_FOLDER_NAME: String = "parent_folder_name"
1617

1718
private const val ADD_EDIT_ITEM_PREFIX: String = "folder_add_edit_item"
1819
private const val ADD_EDIT_ITEM_TYPE: String = "folder_add_edit_type"
1920

2021
private const val ADD_EDIT_ITEM_ROUTE: String =
21-
"$ADD_EDIT_ITEM_PREFIX/{$ADD_EDIT_ITEM_TYPE}?$EDIT_ITEM_ID={$EDIT_ITEM_ID}"
22+
"$ADD_EDIT_ITEM_PREFIX/{$ADD_EDIT_ITEM_TYPE}" +
23+
"?$EDIT_ITEM_ID={$EDIT_ITEM_ID}&$PARENT_FOLDER_NAME={$PARENT_FOLDER_NAME}"
2224

2325
/**
2426
* Class to retrieve folder add & edit arguments from the [SavedStateHandle].
2527
*/
2628
@OmitFromCoverage
2729
data class FolderAddEditArgs(
2830
val folderAddEditType: FolderAddEditType,
31+
val parentFolderName: String?,
2932
) {
3033
constructor(savedStateHandle: SavedStateHandle) : this(
3134
folderAddEditType = when (requireNotNull(savedStateHandle[ADD_EDIT_ITEM_TYPE])) {
3235
ADD_TYPE -> FolderAddEditType.AddItem
3336
EDIT_TYPE -> FolderAddEditType.EditItem(requireNotNull(savedStateHandle[EDIT_ITEM_ID]))
3437
else -> throw IllegalStateException("Unknown FolderAddEditType.")
3538
},
39+
parentFolderName = savedStateHandle[PARENT_FOLDER_NAME],
3640
)
3741
}
3842

3943
/**
4044
* Add the folder add & edit screen to the nav graph.
4145
*/
42-
@Suppress("LongParameterList")
4346
fun NavGraphBuilder.folderAddEditDestination(
4447
onNavigateBack: () -> Unit,
4548
) {
4649
composableWithSlideTransitions(
4750
route = ADD_EDIT_ITEM_ROUTE,
4851
arguments = listOf(
4952
navArgument(ADD_EDIT_ITEM_TYPE) { type = NavType.StringType },
53+
navArgument(EDIT_ITEM_ID) {
54+
nullable = true
55+
type = NavType.StringType
56+
},
57+
navArgument(PARENT_FOLDER_NAME) {
58+
nullable = true
59+
type = NavType.StringType
60+
},
5061
),
5162
) {
5263
FolderAddEditScreen(onNavigateBack = onNavigateBack)
@@ -58,11 +69,13 @@ fun NavGraphBuilder.folderAddEditDestination(
5869
*/
5970
fun NavController.navigateToFolderAddEdit(
6071
folderAddEditType: FolderAddEditType,
72+
parentFolderName: String? = null,
6173
navOptions: NavOptions? = null,
6274
) {
6375
navigate(
6476
route = "$ADD_EDIT_ITEM_PREFIX/${folderAddEditType.toTypeString()}" +
65-
"?$EDIT_ITEM_ID=${folderAddEditType.toIdOrNull()}",
77+
"?$EDIT_ITEM_ID=${folderAddEditType.toIdOrNull()}" +
78+
"&$PARENT_FOLDER_NAME=$parentFolderName",
6679
navOptions = navOptions,
6780
)
6881
}

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/settings/folders/addedit/FolderAddEditViewModel.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,15 @@ class FolderAddEditViewModel @Inject constructor(
3939
// We load the state from the savedStateHandle for testing purposes.
4040
initialState = savedStateHandle[KEY_STATE]
4141
?: run {
42-
val folderAddEditType = FolderAddEditArgs(savedStateHandle).folderAddEditType
42+
val folderAddEditArgs = FolderAddEditArgs(savedStateHandle)
4343
FolderAddEditState(
44-
folderAddEditType = folderAddEditType,
45-
viewState = when (folderAddEditType) {
44+
folderAddEditType = folderAddEditArgs.folderAddEditType,
45+
viewState = when (folderAddEditArgs.folderAddEditType) {
4646
is FolderAddEditType.AddItem -> FolderAddEditState.ViewState.Content("")
4747
is FolderAddEditType.EditItem -> FolderAddEditState.ViewState.Loading
4848
},
4949
dialog = null,
50+
parentFolderName = folderAddEditArgs.parentFolderName?.takeUnless { it.isEmpty() },
5051
)
5152
},
5253
) {
@@ -111,7 +112,13 @@ class FolderAddEditViewModel @Inject constructor(
111112
FolderAddEditType.AddItem -> {
112113
val result = vaultRepository.createFolder(
113114
FolderView(
114-
name = content.folderName,
115+
name = state
116+
.parentFolderName
117+
?.let {
118+
"$it/"
119+
}
120+
.orEmpty() +
121+
content.folderName,
115122
id = folderAddEditType.folderId,
116123
revisionDate = DateTime.now(),
117124
),
@@ -327,6 +334,7 @@ data class FolderAddEditState(
327334
val folderAddEditType: FolderAddEditType,
328335
val viewState: ViewState,
329336
val dialog: DialogState?,
337+
val parentFolderName: String?,
330338
) : Parcelable {
331339

332340
/**

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlocked/VaultUnlockedNavigation.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ fun NavGraphBuilder.vaultUnlockedGraph(
8989
onNavigateToVaultEditItemScreen = {
9090
navController.navigateToVaultAddEdit(VaultAddEditType.EditItem(it))
9191
},
92+
onNavigateToAddFolderScreen = {
93+
navController.navigateToFolderAddEdit(
94+
folderAddEditType = FolderAddEditType.AddItem,
95+
parentFolderName = it,
96+
)
97+
},
9298
)
9399
vaultUnlockedNavBarDestination(
94100
onNavigateToExportVault = { navController.navigateToExportVault() },
@@ -120,6 +126,12 @@ fun NavGraphBuilder.vaultUnlockedGraph(
120126
onNavigateToImportLogins = {
121127
navController.navigateToImportLoginsScreen(snackbarRelay = it)
122128
},
129+
onNavigateToAddFolderScreen = {
130+
navController.navigateToFolderAddEdit(
131+
folderAddEditType = FolderAddEditType.AddItem,
132+
parentFolderName = it,
133+
)
134+
},
123135
)
124136
deleteAccountDestination(
125137
onNavigateBack = { navController.popBackStack() },

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarNavigation.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
4040
onNavigateToSetupUnlockScreen: () -> Unit,
4141
onNavigateToSetupAutoFillScreen: () -> Unit,
4242
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
43+
onNavigateToAddFolderScreen: (selectedFolderName: String?) -> Unit,
4344
) {
4445
composableWithStayTransitions(
4546
route = VAULT_UNLOCKED_NAV_BAR_ROUTE,
@@ -60,6 +61,7 @@ fun NavGraphBuilder.vaultUnlockedNavBarDestination(
6061
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
6162
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
6263
onNavigateToImportLogins = onNavigateToImportLogins,
64+
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
6365
)
6466
}
6567
}

app/src/main/java/com/x8bit/bitwarden/ui/platform/feature/vaultunlockednavbar/VaultUnlockedNavBarScreen.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ fun VaultUnlockedNavBarScreen(
7878
onNavigateToSetupUnlockScreen: () -> Unit,
7979
onNavigateToSetupAutoFillScreen: () -> Unit,
8080
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
81+
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
8182
) {
8283
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
8384

@@ -144,6 +145,7 @@ fun VaultUnlockedNavBarScreen(
144145
onNavigateToSetupUnlockScreen = onNavigateToSetupUnlockScreen,
145146
onNavigateToSetupAutoFillScreen = onNavigateToSetupAutoFillScreen,
146147
onNavigateToImportLogins = onNavigateToImportLogins,
148+
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
147149
)
148150
}
149151

@@ -174,6 +176,7 @@ private fun VaultUnlockedNavBarScaffold(
174176
onNavigateToSetupUnlockScreen: () -> Unit,
175177
onNavigateToSetupAutoFillScreen: () -> Unit,
176178
onNavigateToImportLogins: (SnackbarRelay) -> Unit,
179+
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
177180
) {
178181
var shouldDimNavBar by rememberSaveable { mutableStateOf(false) }
179182

@@ -233,6 +236,7 @@ private fun VaultUnlockedNavBarScaffold(
233236
shouldDimNavBar = shouldDim
234237
},
235238
onNavigateToImportLogins = onNavigateToImportLogins,
239+
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
236240
)
237241
sendGraph(
238242
navController = navController,
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.x8bit.bitwarden.ui.vault.components
2+
3+
import androidx.compose.runtime.Composable
4+
import androidx.compose.ui.res.stringResource
5+
import com.x8bit.bitwarden.R
6+
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenSelectionDialog
7+
import com.x8bit.bitwarden.ui.platform.components.dialog.row.BitwardenBasicDialogRow
8+
import com.x8bit.bitwarden.ui.vault.components.model.CreateVaultItemType
9+
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
10+
import kotlinx.collections.immutable.ImmutableList
11+
import kotlinx.collections.immutable.persistentListOf
12+
13+
/**
14+
* Reusable dialog for making selections between supported [VaultItemCipherType]s
15+
*
16+
* @param onOptionSelected Lambda to be called when an item is selected.
17+
* @param onDismissRequest Lambda to call when the dialog is dismissed by user. Is automatically
18+
* called when a selection is made.
19+
* @param excludedOptions List of [VaultItemCipherType] to exclude from the presented list.
20+
*/
21+
@Composable
22+
fun VaultItemSelectionDialog(
23+
onOptionSelected: (option: CreateVaultItemType) -> Unit,
24+
onDismissRequest: () -> Unit,
25+
// TODO: PM-TBD possibly remove SSH_KEY as default exclusion once added SSH_KEY is enabled.
26+
excludedOptions: ImmutableList<CreateVaultItemType> = persistentListOf(
27+
CreateVaultItemType.SSH_KEY,
28+
),
29+
) {
30+
val supportedEntries = CreateVaultItemType
31+
.entries
32+
.filterNot { excludedOptions.contains(it) }
33+
BitwardenSelectionDialog(
34+
title = stringResource(R.string.type),
35+
onDismissRequest = onDismissRequest,
36+
) {
37+
supportedEntries.forEach {
38+
BitwardenBasicDialogRow(
39+
text = stringResource(it.selectionText),
40+
onClick = {
41+
onDismissRequest()
42+
onOptionSelected(it)
43+
},
44+
)
45+
}
46+
}
47+
}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package com.x8bit.bitwarden.ui.vault.components.model
2+
3+
import androidx.annotation.StringRes
4+
import com.x8bit.bitwarden.R
5+
6+
/**
7+
* Enumerated values to represent a create vault item option.
8+
*/
9+
enum class CreateVaultItemType(
10+
@StringRes val selectionText: Int,
11+
) {
12+
/**
13+
* A login cipher.
14+
*/
15+
LOGIN(R.string.log_in_noun),
16+
17+
/**
18+
* A card cipher.
19+
*/
20+
CARD(R.string.type_card),
21+
22+
/**
23+
* A identity cipher.
24+
*/
25+
IDENTITY(R.string.type_identity),
26+
27+
/**
28+
* A secure note cipher.
29+
*/
30+
SECURE_NOTE(R.string.type_secure_note),
31+
32+
/**
33+
* A SSH key cipher.
34+
*/
35+
SSH_KEY(R.string.type_ssh_key),
36+
37+
/**
38+
* A cipher item folder
39+
*/
40+
FOLDER(R.string.folder),
41+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.x8bit.bitwarden.ui.vault.components.util
2+
3+
import com.x8bit.bitwarden.ui.vault.components.model.CreateVaultItemType
4+
import com.x8bit.bitwarden.ui.vault.model.VaultItemCipherType
5+
6+
/**
7+
* Extension function to map [CreateVaultItemType] to a matching [VaultItemCipherType] or
8+
* `null` if there is no matching type.
9+
*/
10+
fun CreateVaultItemType.toVaultItemCipherTypeOrNull(): VaultItemCipherType? = when (this) {
11+
CreateVaultItemType.LOGIN -> VaultItemCipherType.LOGIN
12+
CreateVaultItemType.CARD -> VaultItemCipherType.CARD
13+
CreateVaultItemType.IDENTITY -> VaultItemCipherType.IDENTITY
14+
CreateVaultItemType.SECURE_NOTE -> VaultItemCipherType.SECURE_NOTE
15+
CreateVaultItemType.SSH_KEY -> VaultItemCipherType.SSH_KEY
16+
CreateVaultItemType.FOLDER -> null
17+
}

app/src/main/java/com/x8bit/bitwarden/ui/vault/feature/itemlisting/VaultItemListingNavigation.kt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ fun NavGraphBuilder.vaultItemListingDestination(
6969
selectedFolderId: String?,
7070
selectedCollectionId: String?,
7171
) -> Unit,
72+
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
7273
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
7374
) {
7475
internalVaultItemListingDestination(
@@ -81,12 +82,14 @@ fun NavGraphBuilder.vaultItemListingDestination(
8182
onNavigateToVaultItemScreen = onNavigateToVaultItemScreen,
8283
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
8384
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
85+
onNavigateToAddFolderScreen = onNavigateToAddFolderScreen,
8486
)
8587
}
8688

8789
/**
8890
* Add the [VaultItemListingScreen] to the nav graph.
8991
*/
92+
@Suppress("LongParameterList")
9093
fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
9194
onNavigateBack: () -> Unit,
9295
onNavigateToVaultItemScreen: (id: String) -> Unit,
@@ -96,6 +99,7 @@ fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
9699
selectedFolderId: String?,
97100
selectedCollectionId: String?,
98101
) -> Unit,
102+
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
99103
onNavigateToSearchVault: (searchType: SearchType.Vault) -> Unit,
100104
) {
101105
composableWithStayTransitions(
@@ -115,6 +119,7 @@ fun NavGraphBuilder.vaultItemListingDestinationAsRoot(
115119
onNavigateToVaultEditItemScreen = onNavigateToVaultEditItemScreen,
116120
onNavigateToVaultAddItemScreen = onNavigateToVaultAddItemScreen,
117121
onNavigateToSearch = { onNavigateToSearchVault(it as SearchType.Vault) },
122+
onNavigateToAddFolder = onNavigateToAddFolderScreen,
118123
onNavigateToVaultItemListing = {},
119124
onNavigateToAddSendItem = {},
120125
onNavigateToEditSendItem = {},
@@ -137,6 +142,7 @@ fun NavGraphBuilder.sendItemListingDestination(
137142
onNavigateToAddSendItem = onNavigateToAddSendItem,
138143
onNavigateToEditSendItem = onNavigateToEditSendItem,
139144
onNavigateToVaultAddItemScreen = { _, _, _ -> },
145+
onNavigateToAddFolderScreen = { _ -> },
140146
onNavigateToVaultItemScreen = { },
141147
onNavigateToVaultEditItemScreen = { },
142148
onNavigateToVaultItemListing = { },
@@ -159,6 +165,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
159165
selectedFolderId: String?,
160166
selectedCollectionId: String?,
161167
) -> Unit,
168+
onNavigateToAddFolderScreen: (selectedFolderId: String?) -> Unit,
162169
onNavigateToAddSendItem: () -> Unit,
163170
onNavigateToEditSendItem: (sendId: String) -> Unit,
164171
onNavigateToSearch: (searchType: SearchType) -> Unit,
@@ -190,6 +197,7 @@ private fun NavGraphBuilder.internalVaultItemListingDestination(
190197
onNavigateToEditSendItem = onNavigateToEditSendItem,
191198
onNavigateToVaultItemListing = onNavigateToVaultItemListing,
192199
onNavigateToSearch = onNavigateToSearch,
200+
onNavigateToAddFolder = onNavigateToAddFolderScreen,
193201
)
194202
}
195203
}

0 commit comments

Comments
 (0)