PM-26579: Remove duplicated BitwardenScaffold from ItemListingScreen (#5978)

This commit is contained in:
David Perez 2025-10-07 12:04:32 -05:00 committed by GitHub
parent 7849bbbb0a
commit 202dd65229
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -27,7 +27,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.SnackbarHostState import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.material3.rememberTopAppBarState import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -41,6 +40,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalResources
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
@ -63,12 +63,12 @@ import com.bitwarden.ui.platform.base.util.EventsEffect
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.base.util.toListItemCardStyle import com.bitwarden.ui.platform.base.util.toListItemCardStyle
import com.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar import com.bitwarden.ui.platform.components.appbar.BitwardenMediumTopAppBar
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.appbar.action.BitwardenSearchActionItem import com.bitwarden.ui.platform.components.appbar.action.BitwardenSearchActionItem
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
import com.bitwarden.ui.platform.components.button.BitwardenTextButton import com.bitwarden.ui.platform.components.button.BitwardenTextButton
import com.bitwarden.ui.platform.components.card.BitwardenActionCard import com.bitwarden.ui.platform.components.card.BitwardenActionCard
import com.bitwarden.ui.platform.components.card.color.bitwardenCardColors import com.bitwarden.ui.platform.components.card.color.bitwardenCardColors
import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent
import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog import com.bitwarden.ui.platform.components.dialog.BitwardenBasicDialog
import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog import com.bitwarden.ui.platform.components.dialog.BitwardenLoadingDialog
import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog import com.bitwarden.ui.platform.components.dialog.BitwardenTwoButtonDialog
@ -107,6 +107,7 @@ fun ItemListingScreen(
val state by viewModel.stateFlow.collectAsStateWithLifecycle() val state by viewModel.stateFlow.collectAsStateWithLifecycle()
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
val context = LocalContext.current val context = LocalContext.current
val resources = LocalResources.current
val launcher = permissionsManager.getLauncher { isGranted -> val launcher = permissionsManager.getLauncher { isGranted ->
if (isGranted) { if (isGranted) {
viewModel.trySendAction(ItemListingAction.ScanQrCodeClick) viewModel.trySendAction(ItemListingAction.ScanQrCodeClick)
@ -124,13 +125,7 @@ fun ItemListingScreen(
is ItemListingEvent.NavigateToQrCodeScanner -> onNavigateToQrCodeScanner() is ItemListingEvent.NavigateToQrCodeScanner -> onNavigateToQrCodeScanner()
is ItemListingEvent.NavigateToManualAddItem -> onNavigateToManualKeyEntry() is ItemListingEvent.NavigateToManualAddItem -> onNavigateToManualKeyEntry()
is ItemListingEvent.ShowToast -> { is ItemListingEvent.ShowToast -> {
Toast Toast.makeText(context, event.message(resources), Toast.LENGTH_LONG).show()
.makeText(
context,
event.message(context.resources),
Toast.LENGTH_LONG,
)
.show()
} }
is ItemListingEvent.NavigateToEditItem -> onNavigateToEditItemScreen(event.id) is ItemListingEvent.NavigateToEditItem -> onNavigateToEditItemScreen(event.id)
@ -164,136 +159,136 @@ fun ItemListingScreen(
ItemListingDialogs( ItemListingDialogs(
dialog = state.dialog, dialog = state.dialog,
onDismissRequest = remember(viewModel) { onDismissRequest = remember(viewModel) {
{ { viewModel.trySendAction(ItemListingAction.DialogDismiss) }
viewModel.trySendAction(
ItemListingAction.DialogDismiss,
)
}
}, },
onConfirmDeleteClick = remember(viewModel) { onConfirmDeleteClick = remember(viewModel) {
{ itemId -> { viewModel.trySendAction(ItemListingAction.ConfirmDeleteClick(it)) }
viewModel.trySendAction(
ItemListingAction.ConfirmDeleteClick(itemId = itemId),
)
}
}, },
) )
when (val currentState = state.viewState) { BitwardenScaffold(
is ItemListingState.ViewState.Content -> { modifier = Modifier
ItemListingContent( .fillMaxSize()
state = currentState, .nestedScroll(scrollBehavior.nestedScrollConnection),
snackbarHostState = snackbarHostState, topBar = {
BitwardenMediumTopAppBar(
title = stringResource(id = BitwardenString.verification_codes),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
onNavigateToSearch = remember(viewModel) { actions = {
{ if (state.viewState is ItemListingState.ViewState.Content) {
viewModel.trySendAction( BitwardenSearchActionItem(
ItemListingAction.SearchClick, contentDescription = stringResource(id = BitwardenString.search_codes),
onClick = onNavigateToSearch,
) )
} }
}, },
onScanQrCodeClick = remember(viewModel) {
{
launcher.launch(Manifest.permission.CAMERA)
}
},
onEnterSetupKeyClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick)
}
},
onItemClick = remember(viewModel) {
{
viewModel.trySendAction(
ItemListingAction.ItemClick(it),
)
}
},
onDropdownMenuClick = remember(viewModel) {
{ action, item ->
viewModel.trySendAction(
ItemListingAction.DropdownMenuClick(
menuAction = action,
item = item,
),
)
}
},
onDownloadBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick)
}
},
onDismissDownloadBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss)
}
},
onSyncWithBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick)
}
},
onDismissSyncWithBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
}
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
},
onSectionExpandedClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SectionExpandedClick(it)) }
},
) )
} },
floatingActionButton = {
BitwardenExpandableFloatingActionButton(
modifier = Modifier.testTag("AddItemButton"),
items = persistentListOf(
ItemListingExpandableFabAction.ScanQrCode(
label = BitwardenString.scan_a_qr_code.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_camera_small,
contentDescription = BitwardenString.scan_a_qr_code.asText(),
testTag = "ScanQRCodeButton",
),
onScanQrCodeClick = remember(viewModel) {
{ launcher.launch(Manifest.permission.CAMERA) }
},
),
ItemListingExpandableFabAction.EnterSetupKey(
label = BitwardenString.enter_key_manually.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_lock_encrypted_small,
contentDescription = BitwardenString.enter_key_manually.asText(),
testTag = "EnterSetupKeyButton",
),
onEnterSetupKeyClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) }
},
),
),
expandableFabIcon = ExpandableFabIcon(
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_plus,
contentDescription = BitwardenString.add_item.asText(),
testTag = "AddItemButton",
),
iconRotation = 45f,
),
)
},
snackbarHost = { FirstTimeSyncSnackbarHost(state = snackbarHostState) },
) {
when (val currentState = state.viewState) {
is ItemListingState.ViewState.Content -> {
ItemListingContent(
state = currentState,
onItemClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.ItemClick(it)) }
},
onDropdownMenuClick = remember(viewModel) {
{ action, item ->
viewModel.trySendAction(
ItemListingAction.DropdownMenuClick(
menuAction = action,
item = item,
),
)
}
},
onDownloadBitwardenClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
},
onDismissDownloadBitwardenClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
},
onSyncWithBitwardenClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
},
onDismissSyncWithBitwardenClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss) }
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
},
onSectionExpandedClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SectionExpandedClick(it)) }
},
)
}
ItemListingState.ViewState.Loading -> Unit ItemListingState.ViewState.Loading -> {
is ItemListingState.ViewState.NoItems, BitwardenLoadingContent(modifier = Modifier.fillMaxSize())
-> { }
EmptyItemListingContent(
actionCardState = currentState.actionCard, is ItemListingState.ViewState.NoItems -> {
appTheme = state.appTheme, EmptyItemListingContent(
scrollBehavior = scrollBehavior, actionCardState = currentState.actionCard,
onAddCodeClick = remember(viewModel) { appTheme = state.appTheme,
{ onAddCodeClick = remember(viewModel) {
launcher.launch(Manifest.permission.CAMERA) { launcher.launch(Manifest.permission.CAMERA) }
} },
}, onDownloadBitwardenClick = remember(viewModel) {
onScanQrCodeClick = remember(viewModel) { { viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) }
{ },
launcher.launch(Manifest.permission.CAMERA) onDismissDownloadBitwardenClick = remember(viewModel) {
} { viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss) }
}, },
onEnterSetupKeyClick = remember(viewModel) { onSyncWithBitwardenClick = remember(viewModel) {
{ { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick) }
viewModel.trySendAction(ItemListingAction.EnterSetupKeyClick) },
} onSyncLearnMoreClick = remember(viewModel) {
}, { viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
onDownloadBitwardenClick = remember(viewModel) { },
{ onDismissSyncWithBitwardenClick = remember(viewModel) {
viewModel.trySendAction(ItemListingAction.DownloadBitwardenClick) { viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss) }
} },
}, )
onDismissDownloadBitwardenClick = remember(viewModel) { }
{
viewModel.trySendAction(ItemListingAction.DownloadBitwardenDismiss)
}
},
onSyncWithBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenClick)
}
},
onSyncLearnMoreClick = remember(viewModel) {
{ viewModel.trySendAction(ItemListingAction.SyncLearnMoreClick) }
},
onDismissSyncWithBitwardenClick = remember(viewModel) {
{
viewModel.trySendAction(ItemListingAction.SyncWithBitwardenDismiss)
}
},
)
} }
} }
} }
@ -338,15 +333,9 @@ private fun ItemListingDialogs(
} }
@Suppress("LongMethod") @Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
private fun ItemListingContent( private fun ItemListingContent(
state: ItemListingState.ViewState.Content, state: ItemListingState.ViewState.Content,
snackbarHostState: SnackbarHostState,
scrollBehavior: TopAppBarScrollBehavior,
onNavigateToSearch: () -> Unit,
onScanQrCodeClick: () -> Unit,
onEnterSetupKeyClick: () -> Unit,
onItemClick: (String) -> Unit, onItemClick: (String) -> Unit,
onDropdownMenuClick: (VaultDropdownMenuAction, VerificationCodeDisplayItem) -> Unit, onDropdownMenuClick: (VaultDropdownMenuAction, VerificationCodeDisplayItem) -> Unit,
onDownloadBitwardenClick: () -> Unit, onDownloadBitwardenClick: () -> Unit,
@ -355,244 +344,189 @@ private fun ItemListingContent(
onDismissSyncWithBitwardenClick: () -> Unit, onDismissSyncWithBitwardenClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit, onSyncLearnMoreClick: () -> Unit,
onSectionExpandedClick: (SharedCodesDisplayState.SharedCodesAccountSection) -> Unit, onSectionExpandedClick: (SharedCodesDisplayState.SharedCodesAccountSection) -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenScaffold( var isLocalHeaderExpanded by rememberSaveable { mutableStateOf(value = true) }
modifier = Modifier LazyColumn(modifier = modifier.fillMaxSize()) {
.fillMaxSize() item(key = "action_card") {
.nestedScroll(scrollBehavior.nestedScrollConnection), ActionCard(
topBar = { actionCardState = state.actionCard,
BitwardenMediumTopAppBar( onDownloadBitwardenClick = onDownloadBitwardenClick,
title = stringResource(id = BitwardenString.verification_codes), onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
scrollBehavior = scrollBehavior, onSyncWithBitwardenClick = onSyncWithBitwardenClick,
actions = { onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
BitwardenSearchActionItem( onSyncLearnMoreClick = onSyncLearnMoreClick,
contentDescription = stringResource(id = BitwardenString.search_codes), modifier = Modifier
onClick = onNavigateToSearch, .standardHorizontalMargin()
) .padding(top = 12.dp, bottom = 16.dp)
}, .animateItem(),
) )
}, }
floatingActionButton = { if (state.favoriteItems.isNotEmpty()) {
BitwardenExpandableFloatingActionButton( item(key = "favorites_header") {
modifier = Modifier.testTag("AddItemButton"), BitwardenListHeaderText(
items = persistentListOf( label = stringResource(id = BitwardenString.favorites),
ItemListingExpandableFabAction.ScanQrCode( supportingLabel = state.favoriteItems.count().toString(),
label = BitwardenString.scan_a_qr_code.asText(), modifier = Modifier
icon = IconData.Local( .fillMaxWidth()
iconRes = BitwardenDrawable.ic_camera_small, .standardHorizontalMargin()
contentDescription = BitwardenString.scan_a_qr_code.asText(), .padding(horizontal = 16.dp)
testTag = "ScanQRCodeButton", .animateItem(),
), )
onScanQrCodeClick = onScanQrCodeClick, Spacer(modifier = Modifier.height(height = 8.dp))
), }
ItemListingExpandableFabAction.EnterSetupKey(
label = BitwardenString.enter_key_manually.asText(), itemsIndexed(
icon = IconData.Local( items = state.favoriteItems,
iconRes = BitwardenDrawable.ic_lock_encrypted_small, key = { _, it -> "favorite_item_${it.id}" },
contentDescription = BitwardenString.enter_key_manually.asText(), ) { index, it ->
testTag = "EnterSetupKeyButton", VaultVerificationCodeItem(
), authCode = it.authCode,
onEnterSetupKeyClick = onEnterSetupKeyClick, primaryLabel = it.title,
), secondaryLabel = it.subtitle,
), periodSeconds = it.periodSeconds,
expandableFabIcon = ExpandableFabIcon( timeLeftSeconds = it.timeLeftSeconds,
icon = IconData.Local( alertThresholdSeconds = it.alertThresholdSeconds,
iconRes = BitwardenDrawable.ic_plus, startIcon = it.startIcon,
contentDescription = BitwardenString.add_item.asText(), onItemClick = { onItemClick(it.authCode) },
testTag = "AddItemButton", onDropdownMenuClick = { action ->
), onDropdownMenuClick(action, it)
iconRotation = 45f, },
), showMoveToBitwarden = it.showMoveToBitwarden,
) allowLongPress = it.allowLongPressActions,
}, cardStyle = state.favoriteItems.toListItemCardStyle(index = index),
snackbarHost = { FirstTimeSyncSnackbarHost(state = snackbarHostState) },
) {
var isLocalHeaderExpanded by rememberSaveable { mutableStateOf(true) }
LazyColumn(
modifier = Modifier.fillMaxSize(),
) {
item(key = "action_card") {
ActionCard(
actionCardState = state.actionCard,
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
onSyncLearnMoreClick = onSyncLearnMoreClick,
modifier = Modifier modifier = Modifier
.standardHorizontalMargin() .standardHorizontalMargin()
.padding(top = 12.dp, bottom = 16.dp) .fillMaxWidth(),
)
}
}
if (state.shouldShowLocalHeader) {
item(key = "local_items_header") {
AuthenticatorExpandingHeader(
label = stringResource(
id = BitwardenString.local_codes,
state.itemList.size,
),
isExpanded = isLocalHeaderExpanded,
onClick = { isLocalHeaderExpanded = !isLocalHeaderExpanded },
onClickLabel = stringResource(
id = if (isLocalHeaderExpanded) {
BitwardenString.local_items_are_expanded_click_to_collapse
} else {
BitwardenString.local_items_are_collapsed_click_to_expand
},
),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.animateItem(), .animateItem(),
) )
} }
if (state.favoriteItems.isNotEmpty()) { }
item(key = "favorites_header") {
BitwardenListHeaderText( if (isLocalHeaderExpanded) {
label = stringResource(id = BitwardenString.favorites), itemsIndexed(
supportingLabel = state.favoriteItems.count().toString(), items = state.itemList,
key = { _, it -> "local_item_${it.id}" },
) { index, it ->
VaultVerificationCodeItem(
authCode = it.authCode,
primaryLabel = it.title,
secondaryLabel = it.subtitle,
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = { onItemClick(it.authCode) },
onDropdownMenuClick = { action ->
onDropdownMenuClick(action, it)
},
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
cardStyle = state.itemList.toListItemCardStyle(index = index),
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth()
.animateItem(),
)
}
}
when (state.sharedItems) {
is SharedCodesDisplayState.Codes -> {
state.sharedItems.sections.forEachIndexed { index, section ->
item(key = "sharedSection_${section.label}") {
AuthenticatorExpandingHeader(
label = section.label(),
isExpanded = section.isExpanded,
onClick = {
onSectionExpandedClick(section)
},
onClickLabel = stringResource(
id = if (section.isExpanded) {
BitwardenString.items_expanded_click_to_collapse
} else {
BitwardenString.items_are_collapsed_click_to_expand
},
),
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.animateItem(),
)
}
if (section.isExpanded) {
itemsIndexed(
items = section.codes,
key = { _, code -> "code_${code.id}" },
) { index, it ->
VaultVerificationCodeItem(
authCode = it.authCode,
primaryLabel = it.title,
secondaryLabel = it.subtitle,
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = { onItemClick(it.authCode) },
onDropdownMenuClick = { action ->
onDropdownMenuClick(action, it)
},
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
cardStyle = section.codes.toListItemCardStyle(index = index),
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth()
.animateItem(),
)
}
}
}
}
SharedCodesDisplayState.Error -> {
item(key = "shared_codes_error") {
Text(
text = stringResource(BitwardenString.shared_codes_error),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin() .standardHorizontalMargin()
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
.animateItem(), .animateItem(),
) )
Spacer(modifier = Modifier.height(height = 8.dp))
}
itemsIndexed(
items = state.favoriteItems,
key = { _, it -> "favorite_item_${it.id}" },
) { index, it ->
VaultVerificationCodeItem(
authCode = it.authCode,
primaryLabel = it.title,
secondaryLabel = it.subtitle,
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = { onItemClick(it.authCode) },
onDropdownMenuClick = { action ->
onDropdownMenuClick(action, it)
},
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
cardStyle = state.favoriteItems.toListItemCardStyle(index = index),
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth(),
)
} }
} }
}
if (state.shouldShowLocalHeader) { // Add a spacer item to prevent the FAB from hiding verification codes at the
item(key = "local_items_header") { // bottom of the list
AuthenticatorExpandingHeader( item {
label = stringResource( Spacer(modifier = Modifier.height(height = 88.dp))
id = BitwardenString.local_codes, Spacer(modifier = Modifier.navigationBarsPadding())
state.itemList.size,
),
isExpanded = isLocalHeaderExpanded,
onClick = { isLocalHeaderExpanded = !isLocalHeaderExpanded },
onClickLabel = if (isLocalHeaderExpanded) {
stringResource(
BitwardenString.local_items_are_expanded_click_to_collapse,
)
} else {
stringResource(
BitwardenString.local_items_are_collapsed_click_to_expand,
)
},
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.animateItem(),
)
}
}
if (isLocalHeaderExpanded) {
itemsIndexed(
items = state.itemList,
key = { _, it -> "local_item_${it.id}" },
) { index, it ->
VaultVerificationCodeItem(
authCode = it.authCode,
primaryLabel = it.title,
secondaryLabel = it.subtitle,
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = { onItemClick(it.authCode) },
onDropdownMenuClick = { action ->
onDropdownMenuClick(action, it)
},
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
cardStyle = state.itemList.toListItemCardStyle(index = index),
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth()
.animateItem(),
)
}
}
when (state.sharedItems) {
is SharedCodesDisplayState.Codes -> {
state.sharedItems.sections.forEachIndexed { index, section ->
item(key = "sharedSection_${section.label}") {
AuthenticatorExpandingHeader(
label = section.label(),
isExpanded = section.isExpanded,
onClick = {
onSectionExpandedClick(section)
},
onClickLabel = if (section.isExpanded) {
stringResource(BitwardenString.items_expanded_click_to_collapse)
} else {
stringResource(
BitwardenString.items_are_collapsed_click_to_expand,
)
},
modifier = Modifier
.fillMaxWidth()
.standardHorizontalMargin()
.animateItem(),
)
}
if (section.isExpanded) {
itemsIndexed(
items = section.codes,
key = { _, code -> "code_${code.id}" },
) { index, it ->
VaultVerificationCodeItem(
authCode = it.authCode,
primaryLabel = it.title,
secondaryLabel = it.subtitle,
periodSeconds = it.periodSeconds,
timeLeftSeconds = it.timeLeftSeconds,
alertThresholdSeconds = it.alertThresholdSeconds,
startIcon = it.startIcon,
onItemClick = { onItemClick(it.authCode) },
onDropdownMenuClick = { action ->
onDropdownMenuClick(action, it)
},
showMoveToBitwarden = it.showMoveToBitwarden,
allowLongPress = it.allowLongPressActions,
cardStyle = section.codes.toListItemCardStyle(index = index),
modifier = Modifier
.standardHorizontalMargin()
.fillMaxWidth()
.animateItem(),
)
}
}
}
}
SharedCodesDisplayState.Error -> {
item(key = "shared_codes_error") {
Text(
text = stringResource(BitwardenString.shared_codes_error),
color = BitwardenTheme.colorScheme.text.secondary,
style = BitwardenTheme.typography.bodySmall,
modifier = Modifier
.standardHorizontalMargin()
.padding(horizontal = 16.dp)
.animateItem(),
)
}
}
}
// Add a spacer item to prevent the FAB from hiding verification codes at the
// bottom of the list
item {
Spacer(modifier = Modifier.height(height = 88.dp))
Spacer(modifier = Modifier.navigationBarsPadding())
}
} }
} }
} }
@ -601,147 +535,95 @@ private fun ItemListingContent(
* Displays the item listing screen with no existing items. * Displays the item listing screen with no existing items.
*/ */
@Suppress("LongMethod") @Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun EmptyItemListingContent( fun EmptyItemListingContent(
modifier: Modifier = Modifier,
actionCardState: ItemListingState.ActionCardState, actionCardState: ItemListingState.ActionCardState,
appTheme: AppTheme, appTheme: AppTheme,
scrollBehavior: TopAppBarScrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(
rememberTopAppBarState(),
),
onAddCodeClick: () -> Unit, onAddCodeClick: () -> Unit,
onScanQrCodeClick: () -> Unit,
onEnterSetupKeyClick: () -> Unit,
onDownloadBitwardenClick: () -> Unit, onDownloadBitwardenClick: () -> Unit,
onDismissDownloadBitwardenClick: () -> Unit, onDismissDownloadBitwardenClick: () -> Unit,
onSyncWithBitwardenClick: () -> Unit, onSyncWithBitwardenClick: () -> Unit,
onSyncLearnMoreClick: () -> Unit, onSyncLearnMoreClick: () -> Unit,
onDismissSyncWithBitwardenClick: () -> Unit, onDismissSyncWithBitwardenClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
BitwardenScaffold( Column(
modifier = Modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.nestedScroll(scrollBehavior.nestedScrollConnection), .verticalScroll(rememberScrollState()),
topBar = { verticalArrangement = when (actionCardState) {
BitwardenTopAppBar( ItemListingState.ActionCardState.None -> Arrangement.Center
title = stringResource(id = BitwardenString.verification_codes), ItemListingState.ActionCardState.DownloadBitwardenApp -> Arrangement.Top
scrollBehavior = scrollBehavior, ItemListingState.ActionCardState.SyncWithBitwarden -> Arrangement.Top
navigationIcon = null,
)
},
floatingActionButton = {
BitwardenExpandableFloatingActionButton(
modifier = Modifier.testTag("AddItemButton"),
items = persistentListOf(
ItemListingExpandableFabAction.ScanQrCode(
label = BitwardenString.scan_a_qr_code.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_camera_small,
contentDescription = BitwardenString.scan_a_qr_code.asText(),
testTag = "ScanQRCodeButton",
),
onScanQrCodeClick = onScanQrCodeClick,
),
ItemListingExpandableFabAction.EnterSetupKey(
label = BitwardenString.enter_key_manually.asText(),
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_lock_encrypted_small,
contentDescription = BitwardenString.enter_key_manually.asText(),
testTag = "EnterSetupKeyButton",
),
onEnterSetupKeyClick = onEnterSetupKeyClick,
),
),
expandableFabIcon = ExpandableFabIcon(
icon = IconData.Local(
iconRes = BitwardenDrawable.ic_plus,
contentDescription = BitwardenString.add_item.asText(),
testTag = "AddItemButton",
),
iconRotation = 45f,
),
)
}, },
) { ) {
ActionCard(
actionCardState = actionCardState,
onDownloadBitwardenClick = onDownloadBitwardenClick,
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick,
onSyncWithBitwardenClick = onSyncWithBitwardenClick,
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick,
onSyncLearnMoreClick = onSyncLearnMoreClick,
modifier = Modifier
.standardHorizontalMargin()
.padding(top = 12.dp, bottom = 16.dp),
)
Column( Column(
modifier = modifier modifier = modifier
.fillMaxSize() .fillMaxSize()
.verticalScroll(rememberScrollState()), .standardHorizontalMargin(),
verticalArrangement = when (actionCardState) { verticalArrangement = Arrangement.Center,
ItemListingState.ActionCardState.None -> Arrangement.Center horizontalAlignment = Alignment.CenterHorizontally,
ItemListingState.ActionCardState.DownloadBitwardenApp -> Arrangement.Top
ItemListingState.ActionCardState.SyncWithBitwarden -> Arrangement.Top
},
) { ) {
ActionCard(
actionCardState = actionCardState, Image(
onDownloadBitwardenClick = onDownloadBitwardenClick, modifier = Modifier.fillMaxWidth(),
onDownloadBitwardenDismissClick = onDismissDownloadBitwardenClick, painter = painterResource(
onSyncWithBitwardenClick = onSyncWithBitwardenClick, id = when (appTheme) {
onSyncWithBitwardenDismissClick = onDismissSyncWithBitwardenClick, AppTheme.DARK -> BitwardenDrawable.ic_empty_vault_dark
onSyncLearnMoreClick = onSyncLearnMoreClick, AppTheme.LIGHT -> BitwardenDrawable.ic_empty_vault_light
modifier = Modifier AppTheme.DEFAULT -> BitwardenDrawable.ic_empty_vault
.standardHorizontalMargin() },
.padding(top = 12.dp, bottom = 16.dp), ),
contentDescription = stringResource(
id = BitwardenString.empty_item_list,
),
contentScale = ContentScale.Fit,
) )
Column( Spacer(modifier = Modifier.height(16.dp))
modifier = modifier Text(
.fillMaxSize() text = stringResource(id = BitwardenString.you_dont_have_items_to_display),
.standardHorizontalMargin(), style = BitwardenTheme.typography.titleMedium,
verticalArrangement = Arrangement.Center, )
horizontalAlignment = Alignment.CenterHorizontally,
) {
Image( Spacer(modifier = Modifier.height(16.dp))
modifier = Modifier.fillMaxWidth(), Text(
painter = painterResource( textAlign = TextAlign.Center,
id = when (appTheme) { text = stringResource(id = BitwardenString.empty_item_list_instruction),
AppTheme.DARK -> BitwardenDrawable.ic_empty_vault_dark )
AppTheme.LIGHT -> BitwardenDrawable.ic_empty_vault_light
AppTheme.DEFAULT -> BitwardenDrawable.ic_empty_vault
},
),
contentDescription = stringResource(
id = BitwardenString.empty_item_list,
),
contentScale = ContentScale.Fit,
)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(16.dp))
Text( BitwardenFilledButton(
text = stringResource(id = BitwardenString.you_dont_have_items_to_display), modifier = Modifier
style = BitwardenTheme.typography.titleMedium, .testTag("AddCodeButton")
) .fillMaxWidth(),
label = stringResource(BitwardenString.add_code),
onClick = onAddCodeClick,
)
Spacer(modifier = Modifier.height(16.dp)) Spacer(modifier = Modifier.height(height = 12.dp))
Text( Spacer(modifier = Modifier.navigationBarsPadding())
textAlign = TextAlign.Center,
text = stringResource(id = BitwardenString.empty_item_list_instruction),
)
Spacer(modifier = Modifier.height(16.dp))
BitwardenFilledButton(
modifier = Modifier
.testTag("AddCodeButton")
.fillMaxWidth(),
label = stringResource(BitwardenString.add_code),
onClick = onAddCodeClick,
)
Spacer(modifier = Modifier.height(height = 12.dp))
Spacer(modifier = Modifier.navigationBarsPadding())
}
} }
} }
} }
@Composable @Composable
private fun DownloadBitwardenActionCard( private fun DownloadBitwardenActionCard(
modifier: Modifier = Modifier,
onDismissClick: () -> Unit, onDismissClick: () -> Unit,
onDownloadBitwardenClick: () -> Unit, onDownloadBitwardenClick: () -> Unit,
modifier: Modifier = Modifier,
) = BitwardenActionCard( ) = BitwardenActionCard(
modifier = modifier, modifier = modifier,
cardSubtitle = stringResource(BitwardenString.download_bitwarden_card_message), cardSubtitle = stringResource(BitwardenString.download_bitwarden_card_message),
@ -761,10 +643,10 @@ private fun DownloadBitwardenActionCard(
@Suppress("LongMethod") @Suppress("LongMethod")
@Composable @Composable
private fun SyncWithBitwardenActionCard( private fun SyncWithBitwardenActionCard(
modifier: Modifier = Modifier,
onDismissClick: () -> Unit, onDismissClick: () -> Unit,
onAppSettingsClick: () -> Unit, onAppSettingsClick: () -> Unit,
onLearnMoreClick: () -> Unit, onLearnMoreClick: () -> Unit,
modifier: Modifier = Modifier,
) { ) {
Card( Card(
modifier = modifier, modifier = modifier,
@ -865,7 +747,6 @@ private fun ActionCard(
} }
} }
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Preview(showBackground = true) @Preview(showBackground = true)
private fun EmptyListingContentPreview() { private fun EmptyListingContentPreview() {
@ -873,8 +754,6 @@ private fun EmptyListingContentPreview() {
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
appTheme = AppTheme.DEFAULT, appTheme = AppTheme.DEFAULT,
onAddCodeClick = { }, onAddCodeClick = { },
onScanQrCodeClick = { },
onEnterSetupKeyClick = { },
actionCardState = ItemListingState.ActionCardState.DownloadBitwardenApp, actionCardState = ItemListingState.ActionCardState.DownloadBitwardenApp,
onDownloadBitwardenClick = { }, onDownloadBitwardenClick = { },
onDismissDownloadBitwardenClick = { }, onDismissDownloadBitwardenClick = { },
@ -884,8 +763,6 @@ private fun EmptyListingContentPreview() {
) )
} }
@Suppress("LongMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@Preview(showBackground = true) @Preview(showBackground = true)
private fun ContentPreview() { private fun ContentPreview() {
@ -934,13 +811,6 @@ private fun ContentPreview() {
), ),
), ),
), ),
snackbarHostState = remember { SnackbarHostState() },
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(
rememberTopAppBarState(),
),
onNavigateToSearch = { },
onScanQrCodeClick = { },
onEnterSetupKeyClick = { },
onItemClick = { }, onItemClick = { },
onDropdownMenuClick = { _, _ -> }, onDropdownMenuClick = { _, _ -> },
onDownloadBitwardenClick = { }, onDownloadBitwardenClick = { },