From 9caf04f97c43aedf88d1cf2e94253bd5160519a7 Mon Sep 17 00:00:00 2001 From: jbw0033 Date: Tue, 19 Aug 2025 03:32:11 +0000 Subject: [PATCH 1/3] Add bottom sheet recipe This commit adds a recipe that demonstrates how to display a navigation destination within a Material `ModalBottomSheet`. It introduces `BottomSheetSceneStrategy`, a `SceneStrategy` that can be added to a `NavDisplay`. This strategy checks for specific metadata on a `NavEntry` to determine if it should be rendered as a bottom sheet. A new `BottomSheetActivity` is added to showcase how to use this strategy. --- app/src/main/AndroidManifest.xml | 4 + .../bottomsheet/BottomSheetActivity.kt | 94 +++++++++++++++++++ .../bottomsheet/BottomSheetSceneStrategy.kt | 79 ++++++++++++++++ 3 files changed, 177 insertions(+) create mode 100644 app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetActivity.kt create mode 100644 app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4040165..d6afe26 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -65,6 +65,10 @@ android:name=".dialog.DialogActivity" android:exported="true" android:theme="@style/Theme.Nav3Recipes"/> + () } + + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + sceneStrategy = bottomSheetStrategy, + entryProvider = entryProvider { + entry { + ContentGreen("Welcome to Nav3") { + Button(onClick = { + backStack.add(RouteB("123")) + }) { + Text("Click to open bottom sheet") + } + } + } + entry( + metadata = BottomSheetSceneStrategy.bottomSheet() + ) { key -> + ContentBlue( + title = "Route id: ${key.id}", + modifier = Modifier.clip( + shape = RoundedCornerShape(16.dp) + ) + ) + } + } + ) + } + } +} diff --git a/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt new file mode 100644 index 0000000..0939ddb --- /dev/null +++ b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt @@ -0,0 +1,79 @@ +package com.example.nav3recipes.bottomsheet + +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.ModalBottomSheetProperties +import androidx.compose.runtime.Composable +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.ui.OverlayScene +import androidx.navigation3.ui.Scene +import androidx.navigation3.ui.SceneStrategy + +/** An [OverlayScene] that renders an [entry] within a [ModalBottomSheet]. */ +@OptIn(ExperimentalMaterial3Api::class) +internal class BottomSheetScene( + override val key: T, + override val previousEntries: List>, + override val overlaidEntries: List>, + private val entry: NavEntry, + private val modalBottomSheetProperties: ModalBottomSheetProperties, + private val onBack: (count: Int) -> Unit, +) : OverlayScene { + + override val entries: List> = listOf(entry) + + override val content: @Composable (() -> Unit) = { + ModalBottomSheet( + onDismissRequest = { onBack(1) }, + properties = modalBottomSheetProperties, + ) { + entry.Content() + } + } +} + +/** + * A [SceneStrategy] that displays entries that have added [bottomSheet] to their [NavEntry.metadata] + * within a [ModalBottomSheet] instance. + * + * This strategy should always be added before any non-overlay scene strategies. + */ +@OptIn(ExperimentalMaterial3Api::class) +class BottomSheetSceneStrategy() : SceneStrategy { + + @Composable + override fun calculateScene( + entries: List>, + onBack: (Int) -> Unit + ): Scene? { + val lastEntry = entries.lastOrNull() + val bottomSheetProperties = lastEntry?.metadata?.get(BOTTOM_SHEET_KEY) as? ModalBottomSheetProperties + return bottomSheetProperties?.let { properties -> + @Suppress("UNCHECKED_CAST") + BottomSheetScene( + key = lastEntry.contentKey as T, + previousEntries = entries.dropLast(1), + overlaidEntries = entries.dropLast(1), + entry = lastEntry, + modalBottomSheetProperties = properties, + onBack = onBack + ) + } + } + + companion object { + /** + * Function to be called on the [NavEntry.metadata] to mark this entry as something that + * should be displayed within a [ModalBottomSheet]. + * + * @param modalBottomSheetProperties properties that should be passed to the containing + * [ModalBottomSheet]. + */ + @OptIn(ExperimentalMaterial3Api::class) + fun bottomSheet( + modalBottomSheetProperties: ModalBottomSheetProperties = ModalBottomSheetProperties() + ): Map = mapOf(BOTTOM_SHEET_KEY to modalBottomSheetProperties) + + internal const val BOTTOM_SHEET_KEY = "bottomsheet" + } +} \ No newline at end of file From bd08782d608b784c5f7b499da0f30b3af968bb72 Mon Sep 17 00:00:00 2001 From: jbw0033 Date: Tue, 19 Aug 2025 18:07:39 +0000 Subject: [PATCH 2/3] Remove parenthesis from BottomSheetSceneStrategy Doing this for Kotlin code syntax --- .../example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt index 0939ddb..1a79615 100644 --- a/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt +++ b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetSceneStrategy.kt @@ -39,7 +39,7 @@ internal class BottomSheetScene( * This strategy should always be added before any non-overlay scene strategies. */ @OptIn(ExperimentalMaterial3Api::class) -class BottomSheetSceneStrategy() : SceneStrategy { +class BottomSheetSceneStrategy : SceneStrategy { @Composable override fun calculateScene( From bde7f7476ccfce67c9d52c7a03cd52bae5958a37 Mon Sep 17 00:00:00 2001 From: Don Turner Date: Thu, 21 Aug 2025 17:26:34 +0100 Subject: [PATCH 3/3] Minor edits --- .../main/java/com/example/nav3recipes/RecipePickerActivity.kt | 4 +++- .../example/nav3recipes/bottomsheet/BottomSheetActivity.kt | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt b/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt index 127ad53..6437799 100644 --- a/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/RecipePickerActivity.kt @@ -29,6 +29,7 @@ import com.example.nav3recipes.animations.AnimatedActivity import com.example.nav3recipes.basic.BasicActivity import com.example.nav3recipes.basicdsl.BasicDslActivity import com.example.nav3recipes.basicsaveable.BasicSaveableActivity +import com.example.nav3recipes.bottomsheet.BottomSheetActivity import com.example.nav3recipes.commonui.CommonUiActivity import com.example.nav3recipes.conditional.ConditionalActivity import com.example.nav3recipes.dialog.DialogActivity @@ -56,8 +57,9 @@ private val recipes = listOf( Recipe("Basic Saveable", BasicSaveableActivity::class.java), Heading("Layouts and animations"), - Recipe("Material list-detail layout", MaterialListDetailActivity::class.java), + Recipe("Bottom Sheet", BottomSheetActivity::class.java), Recipe("Dialog", DialogActivity::class.java), + Recipe("Material list-detail layout", MaterialListDetailActivity::class.java), Recipe("Two pane layout (custom scene)", TwoPaneActivity::class.java), Recipe("Animations", AnimatedActivity::class.java), diff --git a/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetActivity.kt b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetActivity.kt index 29b3935..57b43b7 100644 --- a/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/bottomsheet/BottomSheetActivity.kt @@ -41,7 +41,7 @@ import kotlinx.serialization.Serializable * This recipe demonstrates how to create a bottom sheet. It does this by: * * - Adding the `BottomSheetSceneStrategy` to the list of strategies used by `NavDisplay`. - * - Adding `BottomSheetSceneStrategy.bottomsheet` to a `NavEntry`'s metadata to indicate that it + * - Adding `BottomSheetSceneStrategy.bottomSheet()` to a `NavEntry`'s metadata to indicate that it * is a bottom sheet. In this case it is applied to the `NavEntry` for `RouteB`. * * See also https://developer.android.com/guide/navigation/navigation-3/custom-layouts