BIT-1575: Collection Screen (#878)

This commit is contained in:
Ramsey Smith
2024-01-30 16:01:20 -07:00
committed by Álison Fernandes
parent 96401aba79
commit 034284fad4
14 changed files with 194 additions and 64 deletions

View File

@@ -108,8 +108,11 @@ fun NavGraphBuilder.vaultUnlockedGraph(
onNavigateBack = { navController.popBackStack() },
onNavigateToGeneratorModal = { navController.navigateToGeneratorModal(mode = it) },
onNavigateToAttachments = { navController.navigateToAttachment(it) },
onNavigateToMoveToOrganization = {
navController.navigateToVaultMoveToOrganization(it)
onNavigateToMoveToOrganization = { vaultItemId, showOnlyCollections ->
navController.navigateToVaultMoveToOrganization(
vaultItemId = vaultItemId,
showOnlyCollections = showOnlyCollections,
)
},
)
vaultMoveToOrganizationDestination(
@@ -126,8 +129,11 @@ fun NavGraphBuilder.vaultUnlockedGraph(
},
)
},
onNavigateToMoveToOrganization = {
navController.navigateToVaultMoveToOrganization(it)
onNavigateToMoveToOrganization = { vaultItemId, showOnlyCollections ->
navController.navigateToVaultMoveToOrganization(
vaultItemId = vaultItemId,
showOnlyCollections = showOnlyCollections,
)
},
onNavigateToAttachments = { navController.navigateToAttachment(it) },
)

View File

@@ -24,15 +24,19 @@ import com.x8bit.bitwarden.ui.vault.model.VaultCollection
fun LazyListScope.collectionItemsSelector(
collectionList: List<VaultCollection>?,
onCollectionSelect: (VaultCollection) -> Unit,
isCollectionsTitleVisible: Boolean = true,
) {
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenListHeaderText(
label = stringResource(id = R.string.collections),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
if (isCollectionsTitleVisible) {
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenListHeaderText(
label = stringResource(id = R.string.collections),
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
}
if (collectionList?.isNotEmpty() == true) {
@@ -54,14 +58,14 @@ fun LazyListScope.collectionItemsSelector(
Spacer(modifier = Modifier.height(8.dp))
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
) {
Text(
text = stringResource(id = R.string.no_collections_to_list),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp),
)
}
}

View File

@@ -49,7 +49,7 @@ fun NavGraphBuilder.vaultAddEditDestination(
onNavigateToQrCodeScanScreen: () -> Unit,
onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit,
onNavigateToAttachments: (cipherId: String) -> Unit,
onNavigateToMoveToOrganization: (cipherId: String) -> Unit,
onNavigateToMoveToOrganization: (cipherId: String, showOnlyCollections: Boolean) -> Unit,
) {
composableWithSlideTransitions(
route = ADD_EDIT_ITEM_ROUTE,

View File

@@ -57,7 +57,7 @@ fun VaultAddEditScreen(
onNavigateToManualCodeEntryScreen: () -> Unit,
onNavigateToGeneratorModal: (GeneratorMode.Modal) -> Unit,
onNavigateToAttachments: (cipherId: String) -> Unit,
onNavigateToMoveToOrganization: (cipherId: String) -> Unit,
onNavigateToMoveToOrganization: (cipherId: String, showOnlyCollections: Boolean) -> Unit,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val context = LocalContext.current
@@ -83,12 +83,11 @@ fun VaultAddEditScreen(
is VaultAddEditEvent.NavigateToAttachments -> onNavigateToAttachments(event.cipherId)
is VaultAddEditEvent.NavigateToMoveToOrganization -> {
onNavigateToMoveToOrganization(event.cipherId)
onNavigateToMoveToOrganization(event.cipherId, false)
}
is VaultAddEditEvent.NavigateToCollections -> {
// TODO implement Collections in BIT-1575
Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show()
onNavigateToMoveToOrganization(event.cipherId, true)
}
VaultAddEditEvent.NavigateBack -> onNavigateBack.invoke()

View File

@@ -29,7 +29,7 @@ data class VaultItemArgs(val vaultItemId: String) {
fun NavGraphBuilder.vaultItemDestination(
onNavigateBack: () -> Unit,
onNavigateToVaultEditItem: (vaultItemId: String, isClone: Boolean) -> Unit,
onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit,
onNavigateToMoveToOrganization: (vaultItemId: String, showOnlyCollections: Boolean) -> Unit,
onNavigateToAttachments: (vaultItemId: String) -> Unit,
) {
composableWithSlideTransitions(

View File

@@ -61,7 +61,7 @@ fun VaultItemScreen(
intentManager: IntentManager = LocalIntentManager.current,
onNavigateBack: () -> Unit,
onNavigateToVaultAddEditItem: (vaultItemId: String, isClone: Boolean) -> Unit,
onNavigateToMoveToOrganization: (vaultItemId: String) -> Unit,
onNavigateToMoveToOrganization: (vaultItemId: String, showOnlyCollections: Boolean) -> Unit,
onNavigateToAttachments: (vaultItemId: String) -> Unit,
) {
val state by viewModel.stateFlow.collectAsStateWithLifecycle()
@@ -94,12 +94,11 @@ fun VaultItemScreen(
is VaultItemEvent.NavigateToAttachments -> onNavigateToAttachments(event.itemId)
is VaultItemEvent.NavigateToMoveToOrganization -> {
onNavigateToMoveToOrganization(event.itemId)
onNavigateToMoveToOrganization(event.itemId, false)
}
is VaultItemEvent.NavigateToCollections -> {
// TODO implement Collections in BIT-1575
Toast.makeText(context, "Not yet implemented.", Toast.LENGTH_SHORT).show()
onNavigateToMoveToOrganization(event.itemId, true)
}
is VaultItemEvent.ShowToast -> {

View File

@@ -27,6 +27,7 @@ import kotlinx.collections.immutable.toImmutableList
@Composable
fun VaultMoveToOrganizationContent(
state: VaultMoveToOrganizationState.ViewState.Content,
showOnlyCollections: Boolean,
organizationSelect: (VaultMoveToOrganizationState.ViewState.Content.Organization) -> Unit,
collectionSelect: (VaultCollection) -> Unit,
modifier: Modifier = Modifier,
@@ -34,44 +35,47 @@ fun VaultMoveToOrganizationContent(
LazyColumn(
modifier = modifier,
) {
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenMultiSelectButton(
label = stringResource(id = R.string.organization),
options = state
.organizations
.map { it.name }
.toImmutableList(),
selectedOption = state.selectedOrganization.name,
onOptionSelected = { selectedString ->
organizationSelect(
state
.organizations
.first { it.name == selectedString },
)
},
modifier = Modifier.padding(horizontal = 16.dp),
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = stringResource(id = R.string.move_to_org_desc),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 12.sp,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
if (!showOnlyCollections) {
item {
Spacer(modifier = Modifier.height(8.dp))
BitwardenMultiSelectButton(
label = stringResource(id = R.string.organization),
options = state
.organizations
.map { it.name }
.toImmutableList(),
selectedOption = state.selectedOrganization.name,
onOptionSelected = { selectedString ->
organizationSelect(
state
.organizations
.first { it.name == selectedString },
)
},
modifier = Modifier.padding(horizontal = 16.dp),
)
}
item {
Spacer(modifier = Modifier.height(8.dp))
Column(horizontalAlignment = Alignment.CenterHorizontally) {
Text(
text = stringResource(id = R.string.move_to_org_desc),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
fontSize = 12.sp,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 32.dp),
)
}
}
}
collectionItemsSelector(
collectionList = state.selectedOrganization.collections,
onCollectionSelect = collectionSelect,
isCollectionsTitleVisible = !showOnlyCollections,
)
}
}

View File

@@ -11,16 +11,26 @@ import com.x8bit.bitwarden.ui.platform.base.util.composableWithSlideTransitions
private const val VAULT_MOVE_TO_ORGANIZATION_PREFIX = "vault_move_to_organization"
private const val VAULT_MOVE_TO_ORGANIZATION_ID = "vault_move_to_organization_id"
private const val VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS =
"vault_move_to_organization_only_collections"
private const val VAULT_MOVE_TO_ORGANIZATION_ROUTE =
"$VAULT_MOVE_TO_ORGANIZATION_PREFIX/{$VAULT_MOVE_TO_ORGANIZATION_ID}"
VAULT_MOVE_TO_ORGANIZATION_PREFIX +
"/{$VAULT_MOVE_TO_ORGANIZATION_ID}" +
"/{$VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS}"
/**
* Class to retrieve vault move to organization arguments from the [SavedStateHandle].
*/
@OmitFromCoverage
data class VaultMoveToOrganizationArgs(val vaultItemId: String) {
data class VaultMoveToOrganizationArgs(
val vaultItemId: String,
val showOnlyCollections: Boolean,
) {
constructor(savedStateHandle: SavedStateHandle) : this(
checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ID]) as String,
vaultItemId = checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ID]) as String,
showOnlyCollections =
(checkNotNull(savedStateHandle[VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS]) as String)
.toBoolean(),
)
}
@@ -34,6 +44,9 @@ fun NavGraphBuilder.vaultMoveToOrganizationDestination(
route = VAULT_MOVE_TO_ORGANIZATION_ROUTE,
arguments = listOf(
navArgument(VAULT_MOVE_TO_ORGANIZATION_ID) { type = NavType.StringType },
navArgument(VAULT_MOVE_TO_ORGANIZATION_ONLY_COLLECTIONS) {
type = NavType.StringType
},
),
) {
VaultMoveToOrganizationScreen(
@@ -47,10 +60,11 @@ fun NavGraphBuilder.vaultMoveToOrganizationDestination(
*/
fun NavController.navigateToVaultMoveToOrganization(
vaultItemId: String,
showOnlyCollections: Boolean,
navOptions: NavOptions? = null,
) {
navigate(
route = "$VAULT_MOVE_TO_ORGANIZATION_PREFIX/$vaultItemId",
route = "$VAULT_MOVE_TO_ORGANIZATION_PREFIX/$vaultItemId/$showOnlyCollections",
navOptions = navOptions,
)
}

View File

@@ -109,14 +109,14 @@ private fun VaultMoveToOrganizationScaffold(
.nestedScroll(scrollBehavior.nestedScrollConnection),
topBar = {
BitwardenTopAppBar(
title = stringResource(id = R.string.move_to_organization),
title = state.appBarText(),
scrollBehavior = scrollBehavior,
navigationIcon = painterResource(id = R.drawable.ic_close),
navigationIconContentDescription = stringResource(id = R.string.close),
onNavigationIconClick = closeClick,
actions = {
BitwardenTextButton(
label = stringResource(id = R.string.move),
label = state.appBarButtonText(),
onClick = moveClick,
isEnabled = state.viewState is
VaultMoveToOrganizationState.ViewState.Content,
@@ -134,6 +134,7 @@ private fun VaultMoveToOrganizationScaffold(
is VaultMoveToOrganizationState.ViewState.Content -> {
VaultMoveToOrganizationContent(
state = state.viewState,
showOnlyCollections = state.onlyShowCollections,
organizationSelect = organizationSelect,
collectionSelect = collectionSelect,
modifier = modifier,

View File

@@ -45,6 +45,7 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
?: run {
VaultMoveToOrganizationState(
vaultItemId = VaultMoveToOrganizationArgs(savedStateHandle).vaultItemId,
onlyShowCollections = VaultMoveToOrganizationArgs(savedStateHandle).showOnlyCollections,
viewState = VaultMoveToOrganizationState.ViewState.Loading,
dialogState = null,
)
@@ -306,10 +307,25 @@ class VaultMoveToOrganizationViewModel @Inject constructor(
@Parcelize
data class VaultMoveToOrganizationState(
val vaultItemId: String,
val onlyShowCollections: Boolean,
val viewState: ViewState,
val dialogState: DialogState?,
) : Parcelable {
val appBarText: Text
get() = if (onlyShowCollections) {
R.string.collections.asText()
} else {
R.string.move_to_organization.asText()
}
val appBarButtonText: Text
get() = if (onlyShowCollections) {
R.string.save.asText()
} else {
R.string.move.asText()
}
/**
* Represents the current state of any dialogs on the screen.
*/