Skip to content

Instantly share code, notes, and snippets.

@eygraber
Last active July 2, 2025 22:19
Show Gist options
  • Save eygraber/218406422590a739f0e284743265b047 to your computer and use it in GitHub Desktop.
Save eygraber/218406422590a739f0e284743265b047 to your computer and use it in GitHub Desktop.
A Material3 BottomSheetScene and BottomSheetSceneStrategy for AndroidX Nav3
@OptIn(ExperimentalMaterial3Api::class)
@Immutable
data class BottomSheetSceneProperties(
val modalBottomSheetModifier: Modifier = Modifier,
val properties: ModalBottomSheetProperties = ModalBottomSheetDefaults.properties,
val skipPartiallyExpanded: Boolean = false,
val confirmValueChange: (SheetValue) -> Boolean = { true },
val contentWindowInsets: @Composable (SheetState) -> WindowInsets = defaultWindowInsets(),
val dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
val sheetMaxWidth: Dp = BottomSheetDefaults.SheetMaxWidth,
val tonalElevation: Dp = 0.dp,
val shape: @Composable () -> Shape = { BottomSheetDefaults.ExpandedShape },
val containerColor: @Composable () -> Color = { BottomSheetDefaults.ContainerColor },
val contentColor: @Composable () -> Color = { contentColorFor(containerColor()) },
val scrimColor: @Composable () -> Color = { BottomSheetDefaults.ScrimColor },
) {
companion object {
@OptIn(ExperimentalMaterial3Api::class)
fun defaultWindowInsets(): @Composable (
SheetState,
) -> WindowInsets = {
BottomSheetDefaults.windowInsets
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
internal class BottomSheetScene<T : Any>(
override val key: Any,
override val previousEntries: List<NavEntry<T>>,
override val overlaidEntries: List<NavEntry<T>>,
private val entry: NavEntry<T>,
private val properties: BottomSheetSceneProperties,
private val onBack: (count: Int) -> Unit,
) : OverlayScene<T> {
override val entries: List<NavEntry<T>> = listOf(entry)
override val content: @Composable (() -> Unit) = {
val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = properties.skipPartiallyExpanded,
confirmValueChange = properties.confirmValueChange,
)
ModalBottomSheet(
onDismissRequest = { onBack(1) },
sheetState = sheetState,
modifier = properties.modalBottomSheetModifier,
sheetMaxWidth = BottomSheetDefaults.SheetMaxWidth,
shape = properties.shape(),
properties = properties.properties,
containerColor = properties.containerColor(),
contentColor = properties.contentColor(),
tonalElevation = properties.tonalElevation,
scrimColor = properties.scrimColor(),
dragHandle = properties.dragHandle,
contentWindowInsets = {
properties.contentWindowInsets(sheetState)
},
) { entry.Content() }
}
}
class BottomSheetSceneStrategy<T : Any>() : SceneStrategy<T> {
@Composable
override fun calculateScene(
entries: List<NavEntry<T>>,
onBack: (count: Int) -> Unit,
): Scene<T>? {
val lastEntry = entries.lastOrNull()
val bottomSheetProperties = lastEntry?.metadata?.get(BOTTOM_SHEET_KEY) as? BottomSheetSceneProperties
return bottomSheetProperties?.let { properties ->
BottomSheetScene(
key = lastEntry.contentKey,
previousEntries = entries.dropLast(1),
overlaidEntries = entries.dropLast(1),
entry = lastEntry,
properties = properties,
onBack = onBack,
)
}
}
companion object {
@OptIn(ExperimentalMaterial3Api::class)
fun bottomSheet(
bottomSheetProperties: BottomSheetSceneProperties = BottomSheetSceneProperties()
): Map<String, Any> = mapOf(BOTTOM_SHEET_KEY to bottomSheetProperties)
internal const val BOTTOM_SHEET_KEY = "bottom_sheet"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment