PM-25003: Migrate bottom sheet to the UI module (#5751)

This commit is contained in:
David Perez
2025-08-19 15:58:03 -05:00
committed by GitHub
parent 070ef45087
commit 4a18e57cca
8 changed files with 15 additions and 15 deletions

View File

@@ -1,111 +0,0 @@
package com.x8bit.bitwarden.ui.platform.components.bottomsheet
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.SemanticsPropertyKey
import androidx.compose.ui.semantics.SemanticsPropertyReceiver
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.unit.dp
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.bitwarden.ui.platform.components.scaffold.BitwardenScaffold
import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import kotlinx.coroutines.launch
import org.jetbrains.annotations.VisibleForTesting
/**
* A reusable modal bottom sheet that applies provides a bottom sheet layout with the
* standard [BitwardenScaffold] and [BitwardenTopAppBar] and expected scrolling behavior with
* passed in [sheetContent]
*
* @param sheetTitle The title to display in the [BitwardenTopAppBar]
* @param onDismiss The action to perform when the bottom sheet is dismissed will also be performed
* when the "close" icon is clicked, caller must handle any desired animation or hiding of the
* bottom sheet. This will be invoked _after_ the sheet has been animated away.
* @param topBarActions Row of actions to add the top bar of the bottom sheet.
* @param showBottomSheet Whether or not to show the bottom sheet, by default this is true assuming
* the showing/hiding will be handled by the caller.
* @param sheetContent Content to display in the bottom sheet. The content is passed the padding
* from the containing [BitwardenScaffold] and a `onDismiss` lambda to be used for manual dismissal
* that will include the dismissal animation.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun BitwardenModalBottomSheet(
sheetTitle: String,
onDismiss: () -> Unit,
modifier: Modifier = Modifier,
topBarActions: @Composable RowScope.(animatedOnDismiss: () -> Unit) -> Unit = {},
showBottomSheet: Boolean = true,
sheetState: SheetState = rememberModalBottomSheetState(),
sheetContent: @Composable (animatedOnDismiss: () -> Unit) -> Unit,
) {
if (!showBottomSheet) return
ModalBottomSheet(
onDismissRequest = onDismiss,
modifier = modifier.semantics { this.IsBottomSheet = true },
dragHandle = null,
sheetState = sheetState,
contentWindowInsets = {
WindowInsets(left = 0, top = 0, right = 0, bottom = 0)
},
shape = BitwardenTheme.shapes.bottomSheet,
) {
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior()
val animatedOnDismiss = sheetState.createAnimatedDismissAction(onDismiss = onDismiss)
BitwardenScaffold(
topBar = {
BitwardenTopAppBar(
title = sheetTitle,
navigationIcon = NavigationIcon(
navigationIcon = rememberVectorPainter(BitwardenDrawable.ic_close),
onNavigationIconClick = animatedOnDismiss,
navigationIconContentDescription = stringResource(BitwardenString.close),
),
actions = {
topBarActions(animatedOnDismiss)
},
scrollBehavior = scrollBehavior,
minimumHeight = 64.dp,
)
},
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection)
.fillMaxSize(),
) {
sheetContent(animatedOnDismiss)
}
}
}
/**
* SemanticPropertyKey used for Unit tests where checking if the content is part of a bottom sheet.
*/
@VisibleForTesting
val IsBottomSheetKey = SemanticsPropertyKey<Boolean>("IsBottomSheet")
private var SemanticsPropertyReceiver.IsBottomSheet by IsBottomSheetKey
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun SheetState.createAnimatedDismissAction(onDismiss: () -> Unit): () -> Unit {
val scope = rememberCoroutineScope()
return {
scope
.launch { this@createAnimatedDismissAction.hide() }
.invokeOnCompletion { onDismiss() }
}
}

View File

@@ -45,6 +45,7 @@ import com.bitwarden.ui.platform.base.util.cardStyle
import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.base.util.toListItemCardStyle
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
import com.bitwarden.ui.platform.components.content.BitwardenErrorContent
@@ -59,7 +60,6 @@ import com.bitwarden.ui.platform.components.util.rememberVectorPainter
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.x8bit.bitwarden.ui.platform.composition.LocalPermissionsManager
import com.x8bit.bitwarden.ui.platform.manager.permissions.PermissionsManager

View File

@@ -46,6 +46,7 @@ import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.bitwarden.ui.platform.components.appbar.action.BitwardenOverflowActionItem
import com.bitwarden.ui.platform.components.appbar.model.OverflowMenuItemData
import com.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.bitwarden.ui.platform.components.button.BitwardenTextButton
import com.bitwarden.ui.platform.components.content.BitwardenErrorContent
import com.bitwarden.ui.platform.components.content.BitwardenLoadingContent
@@ -67,7 +68,6 @@ import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.bitwarden.ui.util.Text
import com.x8bit.bitwarden.ui.credentials.manager.CredentialProviderCompletionManager
import com.x8bit.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.x8bit.bitwarden.ui.platform.components.coachmark.CoachMarkContainer
import com.x8bit.bitwarden.ui.platform.components.coachmark.rememberLazyListCoachMarkState
import com.x8bit.bitwarden.ui.platform.components.dialog.BitwardenMasterPasswordDialog

View File

@@ -45,6 +45,7 @@ import com.bitwarden.ui.platform.base.util.standardHorizontalMargin
import com.bitwarden.ui.platform.base.util.toAnnotatedString
import com.bitwarden.ui.platform.components.appbar.BitwardenTopAppBar
import com.bitwarden.ui.platform.components.appbar.NavigationIcon
import com.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.bitwarden.ui.platform.components.button.BitwardenFilledButton
import com.bitwarden.ui.platform.components.button.BitwardenOutlinedButton
import com.bitwarden.ui.platform.components.card.BitwardenContentCard
@@ -58,7 +59,6 @@ import com.bitwarden.ui.platform.manager.IntentManager
import com.bitwarden.ui.platform.resource.BitwardenDrawable
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.platform.theme.BitwardenTheme
import com.x8bit.bitwarden.ui.platform.components.bottomsheet.BitwardenModalBottomSheet
import com.x8bit.bitwarden.ui.vault.feature.importlogins.components.ImportLoginsInstructionStep
import com.x8bit.bitwarden.ui.vault.feature.importlogins.handlers.ImportLoginHandler
import com.x8bit.bitwarden.ui.vault.feature.importlogins.handlers.rememberImportLoginHandler

View File

@@ -2,17 +2,8 @@ package com.x8bit.bitwarden.ui.util
import androidx.compose.ui.semantics.getOrNull
import androidx.compose.ui.test.SemanticsMatcher
import com.x8bit.bitwarden.ui.platform.components.bottomsheet.IsBottomSheetKey
import com.x8bit.bitwarden.ui.platform.components.coachmark.IsCoachMarkToolTipKey
/**
* A [SemanticsMatcher] user to find nodes used specifically inside a bottom sheet.
*/
val isBottomSheet: SemanticsMatcher
get() = SemanticsMatcher("Node is used to to indicate it is a bottom sheet.") {
it.config.getOrNull(IsBottomSheetKey) == true
}
/**
* A [SemanticsMatcher] user to find Popup nodes used specifically for CoachMarkToolTips
*/

View File

@@ -45,6 +45,7 @@ import com.bitwarden.ui.platform.manager.IntentManager
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.assertNoDialogExists
import com.bitwarden.ui.util.assertScrollableNodeDoesNotExist
import com.bitwarden.ui.util.isBottomSheet
import com.bitwarden.ui.util.isProgressBar
import com.bitwarden.ui.util.onAllNodesWithContentDescriptionAfterScroll
import com.bitwarden.ui.util.onAllNodesWithTextAfterScroll
@@ -60,7 +61,6 @@ import com.x8bit.bitwarden.ui.platform.manager.biometrics.BiometricsManager
import com.x8bit.bitwarden.ui.platform.manager.exit.ExitManager
import com.x8bit.bitwarden.ui.platform.manager.permissions.FakePermissionManager
import com.x8bit.bitwarden.ui.tools.feature.generator.model.GeneratorMode
import com.x8bit.bitwarden.ui.util.isBottomSheet
import com.x8bit.bitwarden.ui.util.isCoachMarkToolTip
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldAction
import com.x8bit.bitwarden.ui.vault.feature.addedit.model.CustomFieldType

View File

@@ -19,9 +19,9 @@ import com.bitwarden.ui.platform.manager.IntentManager
import com.bitwarden.ui.platform.resource.BitwardenString
import com.bitwarden.ui.util.asText
import com.bitwarden.ui.util.assertNoDialogExists
import com.bitwarden.ui.util.isBottomSheet
import com.x8bit.bitwarden.data.util.advanceTimeByAndRunCurrent
import com.x8bit.bitwarden.ui.platform.base.BitwardenComposeTest
import com.x8bit.bitwarden.ui.util.isBottomSheet
import io.mockk.every
import io.mockk.just
import io.mockk.mockk