From d1e7fd0673383bb13fe9a1294307518d2ace6a07 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 08:24:23 +0000 Subject: [PATCH 01/20] First commit for List-Detail with variable number of columns --- app/build.gradle.kts | 2 + .../ListDetailNoPlaceholderScene.kt | 100 ++++++++++++++++++ gradle/libs.versions.toml | 3 + 3 files changed, 105 insertions(+) create mode 100644 app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index f1b2051..e181be5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -71,6 +71,8 @@ dependencies { implementation(libs.androidx.material3.windowsizeclass) implementation(libs.androidx.adaptive.layout) implementation(libs.androidx.material3.navigation3) + implementation(libs.androidx.window) + implementation(libs.androidx.window.core) implementation(libs.kotlinx.serialization.core) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt new file mode 100644 index 0000000..0627b31 --- /dev/null +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt @@ -0,0 +1,100 @@ +package com.example.nav3recipes.scenes.listdeailnoplaceholder + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo +import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.ui.Scene +import androidx.navigation3.ui.SceneStrategy +import androidx.window.core.layout.WindowSizeClass.Companion.BREAKPOINTS_V2 +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND +import androidx.window.layout.WindowMetrics + +@Composable +fun columnsBySize() : Int { + val info = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass + + return when { + info.isWidthAtLeastBreakpoint(WIDTH_DP_EXTRA_LARGE_LOWER_BOUND) -> 5 + info.isWidthAtLeastBreakpoint(WIDTH_DP_LARGE_LOWER_BOUND) -> 4 + info.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> 3 + info.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> 2 + else -> 1 + } +} + +class ListDetailNoPlaceholderScene( + override val entries: List>, + override val previousEntries: List>, + override val key: Any, + private val columns: Int +) : Scene { + + override val content: @Composable (() -> Unit) = { + if (previousEntries.isNotEmpty()) { + Row(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.weight(0.5f)) { + previousEntries.last().Content() + } + Column(modifier = Modifier.weight(0.5f)) { + entries.last().Content() + } + } + } else { + entries.last().Content() + } + } + + companion object { + internal const val LIST = "list" + internal const val DETAIL = "detail" + internal const val COLUMNS_KEY = "columns" + + fun list() = mapOf(LIST to true) + fun detail() = mapOf(DETAIL to true) + fun columns(count: Int) = mapOf(COLUMNS_KEY to count) + } +} + +class ListDetailNoPlaceholderSceneStrategy : SceneStrategy{ + @Composable + override fun calculateScene( + entries: List>, + onBack: (Int) -> Unit + ): Scene? { + val columns = columnsBySize() + + if (entries.size >= 2) { + val lastEntry = entries.last() + val secondLastEntry = entries[entries.size - 2] + if (lastEntry.metadata[ListDetailNoPlaceholderScene.DETAIL] == true && + secondLastEntry.metadata[ListDetailNoPlaceholderScene.LIST] == true) { + return ListDetailNoPlaceholderScene( + entries = listOf(lastEntry), + previousEntries = listOf(secondLastEntry), + key = "list_detail_scene", + columns = columns + ) + } + } + if (entries.isNotEmpty()) { + val lastEntry = entries.last() + if (lastEntry.metadata[ListDetailNoPlaceholderScene.LIST] == true) { + return ListDetailNoPlaceholderScene( + entries = listOf(lastEntry), + previousEntries = emptyList(), + key = "list_only_scene", + columns = columns + ) + } + } + return null + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e4edca..4931f8f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -31,6 +31,7 @@ nav3Material = "1.0.0-SNAPSHOT" ksp = "2.2.0-2.0.2" hilt = "2.57" hiltNavigationCompose = "1.2.0" +window = "1.5.0-rc01" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -59,6 +60,8 @@ kotlinx-serialization-core = { module = "org.jetbrains.kotlinx:kotlinx-serializa kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationCore" } androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" } androidx-material3-navigation3 = { group = "androidx.compose.material3.adaptive", name = "adaptive-navigation3", version.ref = "nav3Material" } +androidx-window = { group = "androidx.window", name = "window", version.ref = "window" } +androidx-window-core = { group = "androidx.window", name = "window-core", version.ref = "window" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } From d019a52756c04a8b1e2c53a81bdb4192b97a0ff2 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 14:48:39 +0200 Subject: [PATCH 02/20] Started strategy for Three Panes List-List-Detail --- app/src/main/AndroidManifest.xml | 4 + .../ListDetailNoPlaceholderActivity.kt | 203 ++++++++++++++++++ .../ListDetailNoPlaceholderScene.kt | 77 +++++-- 3 files changed, 261 insertions(+), 23 deletions(-) create mode 100644 app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4040165..659a8b7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,6 +69,10 @@ android:name=".scenes.materiallistdetail.MaterialListDetailActivity" android:exported="true" android:theme="@style/Theme.Nav3Recipes"/> + = + compositionLocalOf { + throw IllegalStateException( + "Unexpected access to LocalNavSharedTransitionScope. You must provide a " + + "SharedTransitionScope from a call to SharedTransitionLayout() or " + + "SharedTransitionScope()" + ) + } + + /** + * A [NavEntryDecorator] that wraps each entry in a shared element that is controlled by the + * [Scene]. + */ + val sharedEntryInSceneNavEntryDecorator = navEntryDecorator { entry -> + with(localNavSharedTransitionScope.current) { + Box( + Modifier.sharedElement( + rememberSharedContentState(entry.contentKey), + animatedVisibilityScope = LocalNavAnimatedContentScope.current, + ), + ) { + entry.Content() + } + } + } + + val defaultNumberOfColumns = columnsBySize() + var numberOfColumns by remember { mutableIntStateOf(defaultNumberOfColumns) } + + val adaptiveContentDecorator = navEntryDecorator { entry -> + BoxWithConstraints { + numberOfColumns = columnsByComposableWidth(maxWidth) + entry.Content() + } + } + + + val backStack = rememberNavBackStack(Home) + val strategy = remember { ListDetailNoPlaceholderSceneStrategy() } + + SharedTransitionLayout { + CompositionLocalProvider(localNavSharedTransitionScope provides this) { + NavDisplay( + backStack = backStack, + onBack = { keysToRemove -> repeat(keysToRemove) { backStack.removeLastOrNull() } }, + entryDecorators = listOf( + sharedEntryInSceneNavEntryDecorator, + rememberSceneSetupNavEntryDecorator(), + rememberSavedStateNavEntryDecorator(), + adaptiveContentDecorator + ), + sceneStrategy = strategy, + entryProvider = entryProvider { + entry( + metadata = ListDetailNoPlaceholderSceneStrategy.Companion.list() + ) { + ContentRed("Adaptive List") { + val gridCells = GridCells.Fixed(numberOfColumns) + + LazyVerticalGrid(columns = gridCells, modifier = Modifier.fillMaxSize()) { + items(mockProducts.size) { + Text(text = "Product $it", modifier = Modifier.clickable{ + backStack.addProductRoute(1) + }) + } + } + } + } + entry( + metadata = ListDetailNoPlaceholderSceneStrategy.Companion.detail() + ) { product -> + ContentBase( + "Product ${product.id} ", + Modifier.background(colors[product.id % colors.size]) + ) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Button(onClick = { + backStack.addProductRoute(product.id + 1) + }) { + Text("View the next product") + } + Button(onClick = { + backStack.add(Profile) + }) { + Text("View profile") + } + } + } + } + entry { + ContentGreen("Profile (single pane only)") + } + } + ) + } + } + } + } + + private fun SnapshotStateList.addProductRoute(productId: Int) { + val productRoute = + Product(productId) + // Avoid adding the same product route to the back stack twice. + if (!contains(productRoute)) { + add(productRoute) + } + } +} diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt index 0627b31..3e6c4e5 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt @@ -7,9 +7,14 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.runtime.NavEntryDecorator +import androidx.navigation3.runtime.navEntryDecorator import androidx.navigation3.ui.Scene import androidx.navigation3.ui.SceneStrategy +import androidx.navigation3.ui.SinglePaneSceneStrategy import androidx.window.core.layout.WindowSizeClass.Companion.BREAKPOINTS_V2 import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND @@ -18,7 +23,7 @@ import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOW import androidx.window.layout.WindowMetrics @Composable -fun columnsBySize() : Int { +fun columnsBySize(): Int { val info = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass return when { @@ -30,27 +35,54 @@ fun columnsBySize() : Int { } } +fun columnsByComposableWidth(width: Dp): Int { + return when { + width >= WIDTH_DP_EXTRA_LARGE_LOWER_BOUND.dp -> 5 + width >= WIDTH_DP_LARGE_LOWER_BOUND.dp -> 4 + width >= WIDTH_DP_EXPANDED_LOWER_BOUND.dp -> 3 + width >= WIDTH_DP_MEDIUM_LOWER_BOUND.dp -> 2 + else -> 1 + } +} + class ListDetailNoPlaceholderScene( - override val entries: List>, + val list: NavEntry, + val detail: NavEntry, override val previousEntries: List>, override val key: Any, private val columns: Int ) : Scene { + override val entries: List> = listOf(list, detail) + override val content: @Composable (() -> Unit) = { - if (previousEntries.isNotEmpty()) { - Row(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.weight(0.5f)) { - previousEntries.last().Content() - } - Column(modifier = Modifier.weight(0.5f)) { - entries.last().Content() - } + + Row(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.weight(0.5f)) { + list.Content() + } + Column(modifier = Modifier.weight(0.5f)) { + detail.Content() } - } else { - entries.last().Content() } } +} + +class ListNoPlaceholderScene( + val list: NavEntry, + override val previousEntries: List>, + override val key: Any, + private val columns: Int +) : Scene { + + override val entries: List> = listOf(list) + + override val content: @Composable (() -> Unit) = { + list.Content() + } +} + +class ListDetailNoPlaceholderSceneStrategy : SceneStrategy { companion object { internal const val LIST = "list" @@ -59,11 +91,8 @@ class ListDetailNoPlaceholderScene( fun list() = mapOf(LIST to true) fun detail() = mapOf(DETAIL to true) - fun columns(count: Int) = mapOf(COLUMNS_KEY to count) } -} -class ListDetailNoPlaceholderSceneStrategy : SceneStrategy{ @Composable override fun calculateScene( entries: List>, @@ -74,10 +103,12 @@ class ListDetailNoPlaceholderSceneStrategy : SceneStrategy{ if (entries.size >= 2) { val lastEntry = entries.last() val secondLastEntry = entries[entries.size - 2] - if (lastEntry.metadata[ListDetailNoPlaceholderScene.DETAIL] == true && - secondLastEntry.metadata[ListDetailNoPlaceholderScene.LIST] == true) { + if (lastEntry.metadata[DETAIL] == true && + secondLastEntry.metadata[LIST] == true + ) { return ListDetailNoPlaceholderScene( - entries = listOf(lastEntry), + list = secondLastEntry, + detail = lastEntry, previousEntries = listOf(secondLastEntry), key = "list_detail_scene", columns = columns @@ -86,11 +117,11 @@ class ListDetailNoPlaceholderSceneStrategy : SceneStrategy{ } if (entries.isNotEmpty()) { val lastEntry = entries.last() - if (lastEntry.metadata[ListDetailNoPlaceholderScene.LIST] == true) { - return ListDetailNoPlaceholderScene( - entries = listOf(lastEntry), - previousEntries = emptyList(), - key = "list_only_scene", + if (lastEntry.metadata[LIST] == true) { + return ListNoPlaceholderScene( + list = lastEntry, + previousEntries = entries.dropLast(1), + key = "list_scene", columns = columns ) } From a420ca11e5b3d5330d11f01ba75ca8ba7e7c5eb7 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 15:38:05 +0200 Subject: [PATCH 03/20] Initial behaviour - TODO: select multiple items --- .../ListDetailNoPlaceholderActivity.kt | 45 ++++++++++--------- .../ListDetailNoPlaceholderScene.kt | 30 +++++++------ 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 66d9d39..bca1128 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -28,6 +28,7 @@ import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button @@ -42,7 +43,7 @@ import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entry import androidx.navigation3.runtime.entryProvider @@ -85,7 +86,7 @@ private data object Profile : NavKey class ListDetailNoPlaceholderActivity : ComponentActivity() { - private val mockProducts = List(100) { Product(it) } + private val mockProducts = List(10) { Product(it) } @OptIn(ExperimentalSharedTransitionApi::class) override fun onCreate(savedInstanceState: Bundle?) { @@ -93,6 +94,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { + //val defaultNumberOfColumns = columnsBySize() val localNavSharedTransitionScope: ProvidableCompositionLocal = compositionLocalOf { throw IllegalStateException( @@ -102,36 +104,33 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { ) } + + var numberOfColumns by remember { mutableIntStateOf(1) } + /** * A [NavEntryDecorator] that wraps each entry in a shared element that is controlled by the * [Scene]. */ val sharedEntryInSceneNavEntryDecorator = navEntryDecorator { entry -> with(localNavSharedTransitionScope.current) { - Box( + BoxWithConstraints( Modifier.sharedElement( rememberSharedContentState(entry.contentKey), animatedVisibilityScope = LocalNavAnimatedContentScope.current, ), ) { + if (entry.metadata.containsKey(ListDetailNoPlaceholderSceneStrategy.LIST)) { + numberOfColumns = columnsByComposableWidth(maxWidth) + } entry.Content() } } } - val defaultNumberOfColumns = columnsBySize() - var numberOfColumns by remember { mutableIntStateOf(defaultNumberOfColumns) } - - val adaptiveContentDecorator = navEntryDecorator { entry -> - BoxWithConstraints { - numberOfColumns = columnsByComposableWidth(maxWidth) - entry.Content() - } - } - val backStack = rememberNavBackStack(Home) - val strategy = remember { ListDetailNoPlaceholderSceneStrategy() } + val strategy = + remember { ListDetailNoPlaceholderSceneStrategy(listInitialWeight = .5f) } SharedTransitionLayout { CompositionLocalProvider(localNavSharedTransitionScope provides this) { @@ -141,8 +140,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { entryDecorators = listOf( sharedEntryInSceneNavEntryDecorator, rememberSceneSetupNavEntryDecorator(), - rememberSavedStateNavEntryDecorator(), - adaptiveContentDecorator + rememberSavedStateNavEntryDecorator() ), sceneStrategy = strategy, entryProvider = entryProvider { @@ -152,11 +150,18 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { ContentRed("Adaptive List") { val gridCells = GridCells.Fixed(numberOfColumns) - LazyVerticalGrid(columns = gridCells, modifier = Modifier.fillMaxSize()) { + LazyVerticalGrid( + columns = gridCells, + modifier = Modifier.fillMaxSize() + ) { items(mockProducts.size) { - Text(text = "Product $it", modifier = Modifier.clickable{ - backStack.addProductRoute(1) - }) + Text( + text = "Product $it", + modifier = Modifier + .padding(all = 16.dp) + .clickable { + backStack.addProductRoute(it) + }) } } } diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt index 3e6c4e5..37a8c1b 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt @@ -7,6 +7,7 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavEntry @@ -48,9 +49,10 @@ fun columnsByComposableWidth(width: Dp): Int { class ListDetailNoPlaceholderScene( val list: NavEntry, val detail: NavEntry, + val listWeight: Float = 0.5f, + val detailWeight: Float = 0.5f, override val previousEntries: List>, - override val key: Any, - private val columns: Int + override val key: Any ) : Scene { override val entries: List> = listOf(list, detail) @@ -58,10 +60,10 @@ class ListDetailNoPlaceholderScene( override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.weight(0.5f)) { + Column(modifier = Modifier.weight(listWeight)) { list.Content() } - Column(modifier = Modifier.weight(0.5f)) { + Column(modifier = Modifier.weight(detailWeight)) { detail.Content() } } @@ -71,8 +73,7 @@ class ListDetailNoPlaceholderScene( class ListNoPlaceholderScene( val list: NavEntry, override val previousEntries: List>, - override val key: Any, - private val columns: Int + override val key: Any ) : Scene { override val entries: List> = listOf(list) @@ -82,12 +83,12 @@ class ListNoPlaceholderScene( } } -class ListDetailNoPlaceholderSceneStrategy : SceneStrategy { +class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float = 0.5f) : + SceneStrategy { companion object { internal const val LIST = "list" internal const val DETAIL = "detail" - internal const val COLUMNS_KEY = "columns" fun list() = mapOf(LIST to true) fun detail() = mapOf(DETAIL to true) @@ -98,7 +99,10 @@ class ListDetailNoPlaceholderSceneStrategy : SceneStrategy { entries: List>, onBack: (Int) -> Unit ): Scene? { - val columns = columnsBySize() + + if(listInitialWeight > 1f) { + throw IllegalArgumentException("listInitialWeight must be less than or equal to 1f") + } if (entries.size >= 2) { val lastEntry = entries.last() @@ -109,9 +113,10 @@ class ListDetailNoPlaceholderSceneStrategy : SceneStrategy { return ListDetailNoPlaceholderScene( list = secondLastEntry, detail = lastEntry, + listWeight = listInitialWeight, + detailWeight = 1f - listInitialWeight, previousEntries = listOf(secondLastEntry), - key = "list_detail_scene", - columns = columns + key = Pair(secondLastEntry.contentKey, lastEntry.contentKey) ) } } @@ -121,8 +126,7 @@ class ListDetailNoPlaceholderSceneStrategy : SceneStrategy { return ListNoPlaceholderScene( list = lastEntry, previousEntries = entries.dropLast(1), - key = "list_scene", - columns = columns + key = lastEntry.contentKey ) } } From 3d174d9056f4e6538fb5f0cb291e32ea1a3bdaf2 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 15:47:16 +0200 Subject: [PATCH 04/20] Initial behaviour - TODO: select multiple items --- .../listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt index bca1128..435ed90 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -33,6 +33,7 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button import androidx.compose.material3.Text +import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf @@ -94,7 +95,8 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { super.onCreate(savedInstanceState) setContent { - //val defaultNumberOfColumns = columnsBySize() + + //val wsc = currentWindowAdaptiveInfo().windowSizeClass val localNavSharedTransitionScope: ProvidableCompositionLocal = compositionLocalOf { throw IllegalStateException( From 356b7790a528d7e8c70be1f4752a344d8f9c99ad Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 16:36:18 +0200 Subject: [PATCH 05/20] Select multiple items, but only show one in the backstack --- .../ListDetailNoPlaceholderActivity.kt | 15 +++++++++++--- .../ListDetailNoPlaceholderScene.kt | 8 ++++++++ gradle/libs.versions.toml | 20 +++++++++---------- 3 files changed, 30 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 435ed90..9af55b1 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -96,7 +96,6 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { setContent { - //val wsc = currentWindowAdaptiveInfo().windowSizeClass val localNavSharedTransitionScope: ProvidableCompositionLocal = compositionLocalOf { throw IllegalStateException( @@ -202,8 +201,18 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { private fun SnapshotStateList.addProductRoute(productId: Int) { val productRoute = Product(productId) - // Avoid adding the same product route to the back stack twice. - if (!contains(productRoute)) { + + val lastItem = last() + if(lastItem is Product) { + // Avoid adding the same product route to the back stack twice. + if(lastItem == productRoute) { + return + } else { + //Only have a single product as detail + remove(lastItem) + add(productRoute) + } + } else { add(productRoute) } } diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt index 37a8c1b..3d0ca50 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt @@ -104,6 +104,14 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float throw IllegalArgumentException("listInitialWeight must be less than or equal to 1f") } + val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + + // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes. + // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp). + if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) { + return null + } + if (entries.size >= 2) { val lastEntry = entries.last() val secondLastEntry = entries[entries.size - 2] diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4931f8f..8b348f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -13,23 +13,23 @@ # limitations under the License. [versions] -agp = "8.10.1" -kotlin = "2.2.0" -kotlinSerialization = "2.2.0" -coreKtx = "1.16.0" +agp = "8.11.1" +kotlin = "2.2.10" +kotlinSerialization = "2.2.10" +coreKtx = "1.17.0" junit = "4.13.2" junitVersion = "1.3.0" espressoCore = "3.7.0" kotlinxSerializationCore = "1.9.0" -lifecycleRuntimeKtx = "2.9.2" +lifecycleRuntimeKtx = "2.9.3" lifecycleViewmodel = "1.0.0-SNAPSHOT" -activityCompose = "1.12.0-alpha05" -composeBom = "2025.07.01" -navigation3 = "1.0.0-alpha07" -material3 = "1.4.0-beta01" +activityCompose = "1.12.0-alpha07" +composeBom = "2025.08.01" +navigation3 = "1.0.0-alpha08" +material3 = "1.4.0-beta03" nav3Material = "1.0.0-SNAPSHOT" ksp = "2.2.0-2.0.2" -hilt = "2.57" +hilt = "2.57.1" hiltNavigationCompose = "1.2.0" window = "1.5.0-rc01" From a18d4ea325204d8625a58a9dfc8be906efc03b31 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 5 Sep 2025 17:12:52 +0200 Subject: [PATCH 06/20] Add possibility to show third panel with the second. Crashing when going back from profile. --- .idea/vcs.xml | 2 +- .../ListDetailNoPlaceholderActivity.kt | 6 +- .../ListDetailNoPlaceholderScene.kt | 97 +++++++++++++------ 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..35eb1dd 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 9af55b1..fd40575 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -188,8 +188,10 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { } } } - entry { - ContentGreen("Profile (single pane only)") + entry( + metadata = ListDetailNoPlaceholderSceneStrategy.thirdPanel() + ) { + ContentGreen("Profile") } } ) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt index 3d0ca50..e3cc063 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderScene.kt @@ -4,24 +4,17 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo -import androidx.compose.material3.windowsizeclass.WindowWidthSizeClass import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavEntry -import androidx.navigation3.runtime.NavEntryDecorator -import androidx.navigation3.runtime.navEntryDecorator import androidx.navigation3.ui.Scene import androidx.navigation3.ui.SceneStrategy -import androidx.navigation3.ui.SinglePaneSceneStrategy -import androidx.window.core.layout.WindowSizeClass.Companion.BREAKPOINTS_V2 import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND -import androidx.window.layout.WindowMetrics @Composable fun columnsBySize(): Int { @@ -89,9 +82,12 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float companion object { internal const val LIST = "list" internal const val DETAIL = "detail" + internal const val THIRD_PANEL = "thirdPanel" fun list() = mapOf(LIST to true) fun detail() = mapOf(DETAIL to true) + + fun thirdPanel() = mapOf(THIRD_PANEL to true) } @Composable @@ -100,7 +96,7 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float onBack: (Int) -> Unit ): Scene? { - if(listInitialWeight > 1f) { + if (listInitialWeight > 1f) { throw IllegalArgumentException("listInitialWeight must be less than or equal to 1f") } @@ -112,32 +108,73 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float return null } + //if(entries.size >= 3 && windowSizeClass.isWidthAtLeastBreakpoint()) + if (entries.size >= 2) { - val lastEntry = entries.last() - val secondLastEntry = entries[entries.size - 2] - if (lastEntry.metadata[DETAIL] == true && - secondLastEntry.metadata[LIST] == true - ) { - return ListDetailNoPlaceholderScene( - list = secondLastEntry, - detail = lastEntry, - listWeight = listInitialWeight, - detailWeight = 1f - listInitialWeight, - previousEntries = listOf(secondLastEntry), - key = Pair(secondLastEntry.contentKey, lastEntry.contentKey) - ) - } + return buildTwoPaneScene(entries) } + //Only the list is available if (entries.isNotEmpty()) { - val lastEntry = entries.last() - if (lastEntry.metadata[LIST] == true) { - return ListNoPlaceholderScene( - list = lastEntry, - previousEntries = entries.dropLast(1), - key = lastEntry.contentKey - ) - } + return buildAdaptiveListScene(entries) } return null } + + private fun buildTwoPaneScene(entries: List>): Scene? { + val lastEntry = entries.last() + val secondLastEntry = entries[entries.size - 2] + + return if (lastEntry.metadata[DETAIL] == true && + secondLastEntry.metadata[LIST] == true + ) { + buildListDetailScene(secondLastEntry, lastEntry) + } else if (lastEntry.metadata[THIRD_PANEL] == true && + secondLastEntry.metadata[DETAIL] == true && entries.size >= 3 + ) { + val zeroethEntry = entries[entries.size - 3] + buildDetailAndThirdPanelScene(secondLastEntry, lastEntry, zeroethEntry) + } else { + null + } + + } + + private fun buildListDetailScene(firstEntry: NavEntry, secondEntry: NavEntry): Scene { + return ListDetailNoPlaceholderScene( + list = firstEntry, + detail = secondEntry, + listWeight = listInitialWeight, + detailWeight = 1f - listInitialWeight, + previousEntries = listOf(firstEntry), + key = Pair(firstEntry.contentKey, secondEntry.contentKey) + ) + } + + private fun buildDetailAndThirdPanelScene( + firstEntry: NavEntry, + secondEntry: NavEntry, + previousEntry: NavEntry + ): Scene { + return ListDetailNoPlaceholderScene( + list = firstEntry, + detail = secondEntry, + listWeight = listInitialWeight, + detailWeight = 1f - listInitialWeight, + previousEntries = listOf(previousEntry, firstEntry), + key = Pair(firstEntry.contentKey, secondEntry.contentKey) + ) + } + + private fun buildAdaptiveListScene(entries: List>): Scene? { + val lastEntry = entries.last() + if (lastEntry.metadata[LIST] == true) { + return ListNoPlaceholderScene( + list = lastEntry, + previousEntries = entries.dropLast(1), + key = lastEntry.contentKey + ) + } + + return null + } } From d8af65fbe95353509694ce5b09d75ddad8c905c2 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 16:01:38 +0200 Subject: [PATCH 07/20] Make List-Detail without placeholders --- .../ListDetailNoPlaceholderActivity.kt | 8 ++------ gradle/libs.versions.toml | 1 + 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt index fd40575..954db08 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -24,7 +24,6 @@ import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.background import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -33,7 +32,6 @@ import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button import androidx.compose.material3.Text -import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf @@ -41,10 +39,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue -import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.entry import androidx.navigation3.runtime.entryProvider @@ -57,8 +55,6 @@ import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator import com.example.nav3recipes.content.ContentBase import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.content.ContentRed -import com.example.nav3recipes.scenes.twopane.TwoPaneScene -import com.example.nav3recipes.scenes.twopane.TwoPaneSceneStrategy import com.example.nav3recipes.ui.setEdgeToEdgeConfig import com.example.nav3recipes.ui.theme.colors import kotlinx.serialization.Serializable @@ -200,7 +196,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { } } - private fun SnapshotStateList.addProductRoute(productId: Int) { + private fun NavBackStack.addProductRoute(productId: Int) { val productRoute = Product(productId) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 491493b..da13b19 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -32,6 +32,7 @@ ksp = "2.2.0-2.0.2" hilt = "2.57.1" hiltNavigationCompose = "1.3.0" koin = "4.1.1" +window = "1.5.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } From 8e7e3a6d9776ca1b9499d4c2854c825e3dd4f522 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 16:17:08 +0200 Subject: [PATCH 08/20] Add README entry --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3c1a5eb..06eab2e 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ These are the recipes and what they demonstrate. - **[Dialog](app/src/main/java/com/example/nav3recipes/dialog)**: Shows how to create a Dialog destination. - **[Custom Scene](app/src/main/java/com/example/nav3recipes/scenes/twopane)**: Shows how to create a custom layout using a `Scene` and `SceneStrategy` (see video of UI behavior below). - **[Animations](app/src/main/java/com/example/nav3recipes/animations)**: Override the default animations for all destinations and a single destination. +- **[List-Detail without placeholder](app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder)**: Shows how to make a list-detail without a placeholder, adapting the number of columns to the window size ### Material adaptive layouts Examples showing how to use the layouts provided by the [Compose Material3 Adaptive Navigation3 library](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#compose_material3_adaptive_navigation3_version_10_2) From 6ac3c4b772e8d01a312561655c621b376577f623 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 17:17:40 +0200 Subject: [PATCH 09/20] Fix PR comments --- .idea/vcs.xml | 6 ------ README.md | 2 +- app/src/main/AndroidManifest.xml | 2 +- .../ListDetailNoPlaceholderActivity.kt | 15 ++++++++------- .../ListDetailNoPlaceholderScene.kt | 17 +---------------- 5 files changed, 11 insertions(+), 31 deletions(-) delete mode 100644 .idea/vcs.xml rename app/src/main/java/com/example/nav3recipes/scenes/{listdeailnoplaceholder => listdetailnoplaceholder}/ListDetailNoPlaceholderActivity.kt (94%) rename app/src/main/java/com/example/nav3recipes/scenes/{listdeailnoplaceholder => listdetailnoplaceholder}/ListDetailNoPlaceholderScene.kt (90%) diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/README.md b/README.md index 06eab2e..bbeb39e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ These are the recipes and what they demonstrate. - **[Dialog](app/src/main/java/com/example/nav3recipes/dialog)**: Shows how to create a Dialog destination. - **[Custom Scene](app/src/main/java/com/example/nav3recipes/scenes/twopane)**: Shows how to create a custom layout using a `Scene` and `SceneStrategy` (see video of UI behavior below). - **[Animations](app/src/main/java/com/example/nav3recipes/animations)**: Override the default animations for all destinations and a single destination. -- **[List-Detail without placeholder](app/src/main/java/com/example/nav3recipes/scenes/listdeailnoplaceholder)**: Shows how to make a list-detail without a placeholder, adapting the number of columns to the window size +- **[List-Detail without placeholder](app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder)**: Shows how to make a list-detail without a placeholder, adapting the number of columns to the window size ### Material adaptive layouts Examples showing how to use the layouts provided by the [Compose Material3 Adaptive Navigation3 library](https://developer.android.com/jetpack/androidx/releases/compose-material3-adaptive#compose_material3_adaptive_navigation3_version_10_2) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a7d7c9..d738650 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -74,7 +74,7 @@ android:exported="true" android:theme="@style/Theme.Nav3Recipes"/> 5 - info.isWidthAtLeastBreakpoint(WIDTH_DP_LARGE_LOWER_BOUND) -> 4 - info.isWidthAtLeastBreakpoint(WIDTH_DP_EXPANDED_LOWER_BOUND) -> 3 - info.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND) -> 2 - else -> 1 - } -} - fun columnsByComposableWidth(width: Dp): Int { return when { width >= WIDTH_DP_EXTRA_LARGE_LOWER_BOUND.dp -> 5 @@ -108,8 +95,6 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float return null } - //if(entries.size >= 3 && windowSizeClass.isWidthAtLeastBreakpoint()) - if (entries.size >= 2) { return buildTwoPaneScene(entries) } From 15b79ec27940f895a3fb7655f98e2f6bcf7701a0 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 17:55:43 +0200 Subject: [PATCH 10/20] Introduce support for 3 panes --- ...holderScene.kt => AdaptiveTwoPaneScene.kt} | 117 +++++++++++++----- 1 file changed, 87 insertions(+), 30 deletions(-) rename app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/{ListDetailNoPlaceholderScene.kt => AdaptiveTwoPaneScene.kt} (56%) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt similarity index 56% rename from app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderScene.kt rename to app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index 1462147..fafca88 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -26,40 +26,69 @@ fun columnsByComposableWidth(width: Dp): Int { } } -class ListDetailNoPlaceholderScene( - val list: NavEntry, - val detail: NavEntry, - val listWeight: Float = 0.5f, - val detailWeight: Float = 0.5f, +class AdaptiveThreePaneScene( + val firstPane: NavEntry, + val secondPane: NavEntry, + val thirdPane: NavEntry, + val firstPaneWeight: Float = 0.4f, + val secondPaneWeight: Float = 0.3f, + val thirdPaneWeight: Float = 0.3f, override val previousEntries: List>, override val key: Any ) : Scene { - override val entries: List> = listOf(list, detail) + override val entries: List> = listOf(firstPane, secondPane, thirdPane) override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.weight(listWeight)) { - list.Content() + Column(modifier = Modifier.weight(firstPaneWeight)) { + firstPane.Content() } - Column(modifier = Modifier.weight(detailWeight)) { - detail.Content() + Column(modifier = Modifier.weight(secondPaneWeight)) { + secondPane.Content() + } + Column(modifier = Modifier.weight(thirdPaneWeight)) { + thirdPane.Content() + } + } + } +} + +class AdaptiveTwoPaneScene( + val firstPane: NavEntry, + val secondPane: NavEntry, + val firstPaneWeight: Float = 0.5f, + val secondPaneWeight: Float = 0.5f, + override val previousEntries: List>, + override val key: Any +) : Scene { + + override val entries: List> = listOf(firstPane, secondPane) + + override val content: @Composable (() -> Unit) = { + + Row(modifier = Modifier.fillMaxSize()) { + Column(modifier = Modifier.weight(firstPaneWeight)) { + firstPane.Content() + } + Column(modifier = Modifier.weight(secondPaneWeight)) { + secondPane.Content() } } } } -class ListNoPlaceholderScene( - val list: NavEntry, +class AdaptiveSinglePaneScene( + val pane: NavEntry, override val previousEntries: List>, override val key: Any ) : Scene { - override val entries: List> = listOf(list) + override val entries: List> = listOf(pane) override val content: @Composable (() -> Unit) = { - list.Content() + pane.Content() } } @@ -87,7 +116,7 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float throw IllegalArgumentException("listInitialWeight must be less than or equal to 1f") } - val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass + val windowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes. // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp). @@ -95,8 +124,12 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float return null } + if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_LARGE_LOWER_BOUND) && entries.size >= 3) { + return buildAdaptiveThreePanesScene(entries) + } + if (entries.size >= 2) { - return buildTwoPaneScene(entries) + return buildAdaptiveTwoPanesScene(entries) } //Only the list is available if (entries.isNotEmpty()) { @@ -105,7 +138,32 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float return null } - private fun buildTwoPaneScene(entries: List>): Scene? { + private fun buildAdaptiveThreePanesScene(entries: List>): Scene? { + val lastEntry = entries.last() + val secondLastEntry = entries[entries.size - 2] + val thirdLastEntry = entries[entries.size - 3] + + return if (lastEntry.metadata[THIRD_PANEL] == true && + secondLastEntry.metadata[DETAIL] == true && + thirdLastEntry.metadata[LIST] == true + ) { + AdaptiveThreePaneScene( + firstPane = thirdLastEntry, + secondPane = secondLastEntry, + thirdPane = lastEntry, + previousEntries = listOf(thirdLastEntry, secondLastEntry), + key = Triple( + thirdLastEntry.contentKey, + secondLastEntry.contentKey, + lastEntry.contentKey + ) + ) + } else { + null + } + } + + private fun buildAdaptiveTwoPanesScene(entries: List>): Scene? { val lastEntry = entries.last() val secondLastEntry = entries[entries.size - 2] @@ -121,15 +179,14 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float } else { null } - } private fun buildListDetailScene(firstEntry: NavEntry, secondEntry: NavEntry): Scene { - return ListDetailNoPlaceholderScene( - list = firstEntry, - detail = secondEntry, - listWeight = listInitialWeight, - detailWeight = 1f - listInitialWeight, + return AdaptiveTwoPaneScene( + firstPane = firstEntry, + secondPane = secondEntry, + firstPaneWeight = listInitialWeight, + secondPaneWeight = 1f - listInitialWeight, previousEntries = listOf(firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) @@ -140,11 +197,11 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float secondEntry: NavEntry, previousEntry: NavEntry ): Scene { - return ListDetailNoPlaceholderScene( - list = firstEntry, - detail = secondEntry, - listWeight = listInitialWeight, - detailWeight = 1f - listInitialWeight, + return AdaptiveTwoPaneScene( + firstPane = firstEntry, + secondPane = secondEntry, + firstPaneWeight = listInitialWeight, + secondPaneWeight = 1f - listInitialWeight, previousEntries = listOf(previousEntry, firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) @@ -153,8 +210,8 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float private fun buildAdaptiveListScene(entries: List>): Scene? { val lastEntry = entries.last() if (lastEntry.metadata[LIST] == true) { - return ListNoPlaceholderScene( - list = lastEntry, + return AdaptiveSinglePaneScene( + pane = lastEntry, previousEntries = entries.dropLast(1), key = lastEntry.contentKey ) From 6fc115fe0068043c3c9455578f1af899e3adb790 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 18:00:02 +0200 Subject: [PATCH 11/20] Introduce support for variable weights --- .../AdaptiveTwoPaneScene.kt | 72 ++++++++----------- 1 file changed, 28 insertions(+), 44 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index fafca88..570ce69 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -30,9 +30,7 @@ class AdaptiveThreePaneScene( val firstPane: NavEntry, val secondPane: NavEntry, val thirdPane: NavEntry, - val firstPaneWeight: Float = 0.4f, - val secondPaneWeight: Float = 0.3f, - val thirdPaneWeight: Float = 0.3f, + val weights: ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults, override val previousEntries: List>, override val key: Any ) : Scene { @@ -42,13 +40,13 @@ class AdaptiveThreePaneScene( override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.weight(firstPaneWeight)) { + Column(modifier = Modifier.weight(weights.threePanesSceneFirstPaneWeight)) { firstPane.Content() } - Column(modifier = Modifier.weight(secondPaneWeight)) { + Column(modifier = Modifier.weight(weights.threePanesSceneSecondPaneWeight)) { secondPane.Content() } - Column(modifier = Modifier.weight(thirdPaneWeight)) { + Column(modifier = Modifier.weight(weights.threePanesSceneThirdPaneWeight)) { thirdPane.Content() } } @@ -58,8 +56,7 @@ class AdaptiveThreePaneScene( class AdaptiveTwoPaneScene( val firstPane: NavEntry, val secondPane: NavEntry, - val firstPaneWeight: Float = 0.5f, - val secondPaneWeight: Float = 0.5f, + val weights: ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults, override val previousEntries: List>, override val key: Any ) : Scene { @@ -69,10 +66,10 @@ class AdaptiveTwoPaneScene( override val content: @Composable (() -> Unit) = { Row(modifier = Modifier.fillMaxSize()) { - Column(modifier = Modifier.weight(firstPaneWeight)) { + Column(modifier = Modifier.weight(weights.twoPanesScenePaneWeight)) { firstPane.Content() } - Column(modifier = Modifier.weight(secondPaneWeight)) { + Column(modifier = Modifier.weight(1 - weights.twoPanesScenePaneWeight)) { secondPane.Content() } } @@ -80,9 +77,7 @@ class AdaptiveTwoPaneScene( } class AdaptiveSinglePaneScene( - val pane: NavEntry, - override val previousEntries: List>, - override val key: Any + val pane: NavEntry, override val previousEntries: List>, override val key: Any ) : Scene { override val entries: List> = listOf(pane) @@ -92,7 +87,7 @@ class AdaptiveSinglePaneScene( } } -class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float = 0.5f) : +class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeightsDefaults = SceneWeightsDefaults()) : SceneStrategy { companion object { @@ -106,17 +101,20 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float fun thirdPanel() = mapOf(THIRD_PANEL to true) } + data class SceneWeightsDefaults( + val twoPanesScenePaneWeight: Float = .5f, + val threePanesSceneFirstPaneWeight: Float = .4f, + val threePanesSceneSecondPaneWeight: Float = .3f, + val threePanesSceneThirdPaneWeight: Float = .3f, + ) + @Composable override fun calculateScene( - entries: List>, - onBack: (Int) -> Unit + entries: List>, onBack: (Int) -> Unit ): Scene? { - if (listInitialWeight > 1f) { - throw IllegalArgumentException("listInitialWeight must be less than or equal to 1f") - } - - val windowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass + val windowSizeClass = + currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes. // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp). @@ -143,19 +141,15 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float val secondLastEntry = entries[entries.size - 2] val thirdLastEntry = entries[entries.size - 3] - return if (lastEntry.metadata[THIRD_PANEL] == true && - secondLastEntry.metadata[DETAIL] == true && - thirdLastEntry.metadata[LIST] == true - ) { + return if (lastEntry.metadata[THIRD_PANEL] == true && secondLastEntry.metadata[DETAIL] == true && thirdLastEntry.metadata[LIST] == true) { AdaptiveThreePaneScene( firstPane = thirdLastEntry, secondPane = secondLastEntry, thirdPane = lastEntry, + weights = sceneWeights, previousEntries = listOf(thirdLastEntry, secondLastEntry), key = Triple( - thirdLastEntry.contentKey, - secondLastEntry.contentKey, - lastEntry.contentKey + thirdLastEntry.contentKey, secondLastEntry.contentKey, lastEntry.contentKey ) ) } else { @@ -167,13 +161,9 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float val lastEntry = entries.last() val secondLastEntry = entries[entries.size - 2] - return if (lastEntry.metadata[DETAIL] == true && - secondLastEntry.metadata[LIST] == true - ) { + return if (lastEntry.metadata[DETAIL] == true && secondLastEntry.metadata[LIST] == true) { buildListDetailScene(secondLastEntry, lastEntry) - } else if (lastEntry.metadata[THIRD_PANEL] == true && - secondLastEntry.metadata[DETAIL] == true && entries.size >= 3 - ) { + } else if (lastEntry.metadata[THIRD_PANEL] == true && secondLastEntry.metadata[DETAIL] == true && entries.size >= 3) { val zeroethEntry = entries[entries.size - 3] buildDetailAndThirdPanelScene(secondLastEntry, lastEntry, zeroethEntry) } else { @@ -185,23 +175,19 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float return AdaptiveTwoPaneScene( firstPane = firstEntry, secondPane = secondEntry, - firstPaneWeight = listInitialWeight, - secondPaneWeight = 1f - listInitialWeight, + weights = sceneWeights, previousEntries = listOf(firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) } private fun buildDetailAndThirdPanelScene( - firstEntry: NavEntry, - secondEntry: NavEntry, - previousEntry: NavEntry + firstEntry: NavEntry, secondEntry: NavEntry, previousEntry: NavEntry ): Scene { return AdaptiveTwoPaneScene( firstPane = firstEntry, secondPane = secondEntry, - firstPaneWeight = listInitialWeight, - secondPaneWeight = 1f - listInitialWeight, + weights = sceneWeights, previousEntries = listOf(previousEntry, firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) @@ -211,9 +197,7 @@ class ListDetailNoPlaceholderSceneStrategy(val listInitialWeight: Float val lastEntry = entries.last() if (lastEntry.metadata[LIST] == true) { return AdaptiveSinglePaneScene( - pane = lastEntry, - previousEntries = entries.dropLast(1), - key = lastEntry.contentKey + pane = lastEntry, previousEntries = entries.dropLast(1), key = lastEntry.contentKey ) } From 288048d736ce0c747af6d28f938ad60b06d8e8cb Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Thu, 2 Oct 2025 18:01:59 +0200 Subject: [PATCH 12/20] Introduce support for variable weights --- .../ListDetailNoPlaceholderActivity.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 4dcba50..63fd2b9 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -127,8 +127,15 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { val backStack = rememberNavBackStack(Home) + + /** + * A [SceneWeightsDefaults] that wraps variable initial weights to customise the appearance + * of each panel + */ + val weights = ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults() + .copy(twoPanesScenePaneWeight = .4f) val strategy = - remember { ListDetailNoPlaceholderSceneStrategy(listInitialWeight = .5f) } + remember { ListDetailNoPlaceholderSceneStrategy(weights) } SharedTransitionLayout { CompositionLocalProvider(localNavSharedTransitionScope provides this) { @@ -202,9 +209,9 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { Product(productId) val lastItem = last() - if(lastItem is Product) { + if (lastItem is Product) { // Avoid adding the same product route to the back stack twice. - if(lastItem == productRoute) { + if (lastItem == productRoute) { return } else { //Only have a single product as detail From 5fa0c860a95908f945c920c1c895bd740556c6e2 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 3 Oct 2025 09:10:42 +0200 Subject: [PATCH 13/20] Code cleanup --- .../scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt | 4 +++- .../ListDetailNoPlaceholderActivity.kt | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index 570ce69..126dd44 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -95,9 +95,11 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh internal const val DETAIL = "detail" internal const val THIRD_PANEL = "thirdPanel" + @JvmStatic fun list() = mapOf(LIST to true) + @JvmStatic fun detail() = mapOf(DETAIL to true) - + @JvmStatic fun thirdPanel() = mapOf(THIRD_PANEL to true) } diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 63fd2b9..9fb0b30 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -150,7 +150,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { sceneStrategy = strategy, entryProvider = entryProvider { entry( - metadata = ListDetailNoPlaceholderSceneStrategy.Companion.list() + metadata = ListDetailNoPlaceholderSceneStrategy.list() ) { ContentRed("Adaptive List") { val gridCells = GridCells.Fixed(numberOfColumns) @@ -172,7 +172,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { } } entry( - metadata = ListDetailNoPlaceholderSceneStrategy.Companion.detail() + metadata = ListDetailNoPlaceholderSceneStrategy.detail() ) { product -> ContentBase( "Product ${product.id} ", From 0412b5dd1e8674f614a0c5faae449b3e6519a04a Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 3 Oct 2025 14:28:53 +0200 Subject: [PATCH 14/20] Remove single pane scene to leverage default single pane --- .../AdaptiveTwoPaneScene.kt | 26 ------------------- 1 file changed, 26 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index 126dd44..b4e11fc 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -76,17 +76,6 @@ class AdaptiveTwoPaneScene( } } -class AdaptiveSinglePaneScene( - val pane: NavEntry, override val previousEntries: List>, override val key: Any -) : Scene { - - override val entries: List> = listOf(pane) - - override val content: @Composable (() -> Unit) = { - pane.Content() - } -} - class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeightsDefaults = SceneWeightsDefaults()) : SceneStrategy { @@ -131,10 +120,6 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh if (entries.size >= 2) { return buildAdaptiveTwoPanesScene(entries) } - //Only the list is available - if (entries.isNotEmpty()) { - return buildAdaptiveListScene(entries) - } return null } @@ -194,15 +179,4 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) } - - private fun buildAdaptiveListScene(entries: List>): Scene? { - val lastEntry = entries.last() - if (lastEntry.metadata[LIST] == true) { - return AdaptiveSinglePaneScene( - pane = lastEntry, previousEntries = entries.dropLast(1), key = lastEntry.contentKey - ) - } - - return null - } } From c07d62ebdd1acc3eea0a388a67156c10d7173a63 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 3 Oct 2025 14:43:28 +0200 Subject: [PATCH 15/20] Move helper function closer to its usage --- .../AdaptiveTwoPaneScene.kt | 18 ++---------------- .../ListDetailNoPlaceholderActivity.kt | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index b4e11fc..47b1f97 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -6,27 +6,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.Dp -import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavEntry import androidx.navigation3.ui.Scene import androidx.navigation3.ui.SceneStrategy -import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND -import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND -fun columnsByComposableWidth(width: Dp): Int { - return when { - width >= WIDTH_DP_EXTRA_LARGE_LOWER_BOUND.dp -> 5 - width >= WIDTH_DP_LARGE_LOWER_BOUND.dp -> 4 - width >= WIDTH_DP_EXPANDED_LOWER_BOUND.dp -> 3 - width >= WIDTH_DP_MEDIUM_LOWER_BOUND.dp -> 2 - else -> 1 - } -} - -class AdaptiveThreePaneScene( +internal class AdaptiveThreePaneScene( val firstPane: NavEntry, val secondPane: NavEntry, val thirdPane: NavEntry, @@ -53,7 +39,7 @@ class AdaptiveThreePaneScene( } } -class AdaptiveTwoPaneScene( +internal class AdaptiveTwoPaneScene( val firstPane: NavEntry, val secondPane: NavEntry, val weights: ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults, diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 9fb0b30..c6f8de7 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -41,6 +41,7 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey @@ -52,6 +53,10 @@ import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.NavDisplay import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND +import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND import com.example.nav3recipes.content.ContentBase import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.content.ContentRed @@ -222,4 +227,14 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { add(productRoute) } } + + fun columnsByComposableWidth(width: Dp): Int { + return when { + width >= WIDTH_DP_EXTRA_LARGE_LOWER_BOUND.dp -> 5 + width >= WIDTH_DP_LARGE_LOWER_BOUND.dp -> 4 + width >= WIDTH_DP_EXPANDED_LOWER_BOUND.dp -> 3 + width >= WIDTH_DP_MEDIUM_LOWER_BOUND.dp -> 2 + else -> 1 + } + } } From ebc76bc035abb935badbae36d2441434cea94b13 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Fri, 3 Oct 2025 14:46:07 +0200 Subject: [PATCH 16/20] Add some more comments --- .../ListDetailNoPlaceholderActivity.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index c6f8de7..71c40a4 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -228,6 +228,10 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { } } + /*** + * This function exemplify how to calculate the number of columns for the adaptive list based + * on how much space is available inside the container + */ fun columnsByComposableWidth(width: Dp): Int { return when { width >= WIDTH_DP_EXTRA_LARGE_LOWER_BOUND.dp -> 5 From c7785d26d79ff02a8e6ccb7ecb8c0fda14fd7e06 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Mon, 6 Oct 2025 09:53:14 +0200 Subject: [PATCH 17/20] Add possibility to also show SupportingPane --- .../AdaptiveTwoPaneScene.kt | 73 +++++++++++++++++-- .../ListDetailNoPlaceholderActivity.kt | 30 ++++++-- 2 files changed, 91 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index 47b1f97..a56ee87 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -3,6 +3,9 @@ package com.example.nav3recipes.scenes.listdetailnoplaceholder import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ModalBottomSheet +import androidx.compose.material3.ModalBottomSheetProperties import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -62,20 +65,48 @@ internal class AdaptiveTwoPaneScene( } } +internal class BottomPaneScene( + val pane: NavEntry, + override val previousEntries: List>, + override val key: Any, + val onBack: (Int) -> Unit +) : Scene { + + override val entries: List> = listOf(pane) + + @OptIn(ExperimentalMaterial3Api::class) + override val content: @Composable (() -> Unit) = { + + ModalBottomSheet( + onDismissRequest = { onBack(1) }, + properties = ModalBottomSheetProperties() + ) { + pane.Content() + } + + } +} + class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeightsDefaults = SceneWeightsDefaults()) : SceneStrategy { companion object { - internal const val LIST = "list" + internal const val MAIN = "main" internal const val DETAIL = "detail" + internal const val SUPPORT = "support" internal const val THIRD_PANEL = "thirdPanel" @JvmStatic - fun list() = mapOf(LIST to true) + fun main() = mapOf(MAIN to true) + @JvmStatic fun detail() = mapOf(DETAIL to true) + @JvmStatic fun thirdPanel() = mapOf(THIRD_PANEL to true) + + @JvmStatic + fun support() = mapOf(SUPPORT to true) } data class SceneWeightsDefaults( @@ -92,11 +123,22 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh val windowSizeClass = currentWindowAdaptiveInfo(supportLargeAndXLargeWidth = true).windowSizeClass + val isLastEntrySupportingPane = entries.lastOrNull()?.metadata[SUPPORT] == true - // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes. + // Condition 1: Only return a Scene if the window is sufficiently wide to render two panes, + // or if a supporting pane is detected. + // // We use isWidthAtLeastBreakpoint with WIDTH_DP_MEDIUM_LOWER_BOUND (600dp). if (!windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_MEDIUM_LOWER_BOUND)) { - return null + return if (isLastEntrySupportingPane) { + buildSupportingPaneScene( + pane = entries.last(), + previousEntry = entries[entries.size - 2], + onBack = onBack + ) + } else { + null + } } if (windowSizeClass.isWidthAtLeastBreakpoint(WIDTH_DP_LARGE_LOWER_BOUND) && entries.size >= 3) { @@ -114,7 +156,7 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh val secondLastEntry = entries[entries.size - 2] val thirdLastEntry = entries[entries.size - 3] - return if (lastEntry.metadata[THIRD_PANEL] == true && secondLastEntry.metadata[DETAIL] == true && thirdLastEntry.metadata[LIST] == true) { + return if (lastEntry.metadata[THIRD_PANEL] == true && secondLastEntry.metadata[DETAIL] == true && thirdLastEntry.metadata[MAIN] == true) { AdaptiveThreePaneScene( firstPane = thirdLastEntry, secondPane = secondLastEntry, @@ -130,13 +172,17 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh } } + private fun NavEntry.isMainPane() : Boolean = metadata[MAIN] == true + private fun NavEntry.isSecondPane() : Boolean = metadata[DETAIL] == true || metadata[SUPPORT] == true + private fun NavEntry.isLastPane() : Boolean = metadata[THIRD_PANEL] == true + private fun buildAdaptiveTwoPanesScene(entries: List>): Scene? { val lastEntry = entries.last() val secondLastEntry = entries[entries.size - 2] - return if (lastEntry.metadata[DETAIL] == true && secondLastEntry.metadata[LIST] == true) { + return if (lastEntry.isSecondPane() && secondLastEntry.isMainPane()) { buildListDetailScene(secondLastEntry, lastEntry) - } else if (lastEntry.metadata[THIRD_PANEL] == true && secondLastEntry.metadata[DETAIL] == true && entries.size >= 3) { + } else if (lastEntry.isLastPane() && secondLastEntry.isSecondPane() && entries.size >= 3) { val zeroethEntry = entries[entries.size - 3] buildDetailAndThirdPanelScene(secondLastEntry, lastEntry, zeroethEntry) } else { @@ -165,4 +211,17 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) } + + private fun buildSupportingPaneScene( + pane: NavEntry, + previousEntry: NavEntry, + onBack: (Int) -> Unit + ): Scene { + return BottomPaneScene( + pane = pane, + previousEntries = listOf(previousEntry), + key = pane.contentKey, + onBack = onBack + ) + } } diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 71c40a4..34b03ce 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -26,8 +26,9 @@ import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button @@ -58,6 +59,7 @@ import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARG import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND import com.example.nav3recipes.content.ContentBase +import com.example.nav3recipes.content.ContentBlue import com.example.nav3recipes.content.ContentGreen import com.example.nav3recipes.content.ContentRed import com.example.nav3recipes.ui.setEdgeToEdgeConfig @@ -86,6 +88,9 @@ private data class Product(val id: Int) : NavKey @Serializable private data object Profile : NavKey +@Serializable +private data object Toolbar : NavKey + class ListDetailNoPlaceholderActivity : ComponentActivity() { @@ -122,7 +127,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { animatedVisibilityScope = LocalNavAnimatedContentScope.current, ), ) { - if (entry.metadata.containsKey(ListDetailNoPlaceholderSceneStrategy.LIST)) { + if (entry.metadata.containsKey(ListDetailNoPlaceholderSceneStrategy.MAIN)) { numberOfColumns = columnsByComposableWidth(maxWidth) } entry.Content() @@ -155,14 +160,16 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { sceneStrategy = strategy, entryProvider = entryProvider { entry( - metadata = ListDetailNoPlaceholderSceneStrategy.list() + metadata = ListDetailNoPlaceholderSceneStrategy.main() ) { ContentRed("Adaptive List") { val gridCells = GridCells.Fixed(numberOfColumns) LazyVerticalGrid( columns = gridCells, - modifier = Modifier.fillMaxSize() + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() ) { items(mockProducts.size) { Text( @@ -174,6 +181,13 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { }) } } + + Button( + onClick = { backStack.add(Toolbar) }, + modifier = Modifier.padding(top = 32.dp) + ) { + Text("Open toolbar") + } } } entry( @@ -202,6 +216,12 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { ) { ContentGreen("Profile") } + + entry( + metadata = ListDetailNoPlaceholderSceneStrategy.support() + ) { + ContentBlue("Toolbar") + } } ) } @@ -214,7 +234,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { Product(productId) val lastItem = last() - if (lastItem is Product) { + if (lastItem is Product || lastItem is Toolbar) { // Avoid adding the same product route to the back stack twice. if (lastItem == productRoute) { return From 9893103e988bfa190b952df74fd3372cb5946c7b Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Mon, 6 Oct 2025 09:59:45 +0200 Subject: [PATCH 18/20] Fix build issues --- .../ListDetailNoPlaceholderActivity.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 34b03ce..2afd020 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -53,6 +53,7 @@ import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.NavDisplay +import androidx.navigation3.ui.SceneStrategy import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND @@ -144,8 +145,8 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { */ val weights = ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults() .copy(twoPanesScenePaneWeight = .4f) - val strategy = - remember { ListDetailNoPlaceholderSceneStrategy(weights) } + val strategy : SceneStrategy = + remember { ListDetailNoPlaceholderSceneStrategy(weights) } SharedTransitionLayout { CompositionLocalProvider(localNavSharedTransitionScope provides this) { From 9906549125ce24938b3c397e2873fed301c1e7ad Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Mon, 6 Oct 2025 10:15:03 +0200 Subject: [PATCH 19/20] Fix build issues --- .../scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt | 4 ++-- .../ListDetailNoPlaceholderActivity.kt | 7 +++---- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index a56ee87..f90276e 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -10,8 +10,8 @@ import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.navigation3.runtime.NavEntry -import androidx.navigation3.ui.Scene -import androidx.navigation3.ui.SceneStrategy +import androidx.navigation3.scene.Scene +import androidx.navigation3.scene.SceneStrategy import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_MEDIUM_LOWER_BOUND diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index 2afd020..ca3f7a6 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -46,15 +46,14 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavKey -import androidx.navigation3.runtime.entry import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.navEntryDecorator import androidx.navigation3.runtime.rememberNavBackStack import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator +import androidx.navigation3.scene.SceneStrategy +import androidx.navigation3.scene.rememberSceneSetupNavEntryDecorator import androidx.navigation3.ui.LocalNavAnimatedContentScope import androidx.navigation3.ui.NavDisplay -import androidx.navigation3.ui.SceneStrategy -import androidx.navigation3.ui.rememberSceneSetupNavEntryDecorator import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXPANDED_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_EXTRA_LARGE_LOWER_BOUND import androidx.window.core.layout.WindowSizeClass.Companion.WIDTH_DP_LARGE_LOWER_BOUND @@ -145,7 +144,7 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { */ val weights = ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults() .copy(twoPanesScenePaneWeight = .4f) - val strategy : SceneStrategy = + val strategy : SceneStrategy = remember { ListDetailNoPlaceholderSceneStrategy(weights) } SharedTransitionLayout { From 3c0967bc33557c1b9b68e82ac572d4d2ba3a6e00 Mon Sep 17 00:00:00 2001 From: Rob Orgiu Date: Mon, 6 Oct 2025 15:01:55 +0200 Subject: [PATCH 20/20] Add more customization for BottomSheet --- .../AdaptiveTwoPaneScene.kt | 37 +++++++++++++++---- .../ListDetailNoPlaceholderActivity.kt | 6 ++- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt index f90276e..9f34c0f 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/AdaptiveTwoPaneScene.kt @@ -1,3 +1,20 @@ +/* + * Copyright 2025 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@file:OptIn(ExperimentalMaterial3Api::class) package com.example.nav3recipes.scenes.listdetailnoplaceholder import androidx.compose.foundation.layout.Column @@ -19,7 +36,7 @@ internal class AdaptiveThreePaneScene( val firstPane: NavEntry, val secondPane: NavEntry, val thirdPane: NavEntry, - val weights: ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults, + val weights: ListDetailNoPlaceholderSceneStrategy.SceneDefaults, override val previousEntries: List>, override val key: Any ) : Scene { @@ -45,7 +62,7 @@ internal class AdaptiveThreePaneScene( internal class AdaptiveTwoPaneScene( val firstPane: NavEntry, val secondPane: NavEntry, - val weights: ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults, + val weights: ListDetailNoPlaceholderSceneStrategy.SceneDefaults, override val previousEntries: List>, override val key: Any ) : Scene { @@ -65,8 +82,10 @@ internal class AdaptiveTwoPaneScene( } } + internal class BottomPaneScene( val pane: NavEntry, + val properties: ModalBottomSheetProperties = ModalBottomSheetProperties(), override val previousEntries: List>, override val key: Any, val onBack: (Int) -> Unit @@ -79,7 +98,7 @@ internal class BottomPaneScene( ModalBottomSheet( onDismissRequest = { onBack(1) }, - properties = ModalBottomSheetProperties() + properties = properties ) { pane.Content() } @@ -87,7 +106,7 @@ internal class BottomPaneScene( } } -class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeightsDefaults = SceneWeightsDefaults()) : +class ListDetailNoPlaceholderSceneStrategy(val sceneDefaults: SceneDefaults = SceneDefaults()) : SceneStrategy { companion object { @@ -109,11 +128,12 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh fun support() = mapOf(SUPPORT to true) } - data class SceneWeightsDefaults( + data class SceneDefaults( val twoPanesScenePaneWeight: Float = .5f, val threePanesSceneFirstPaneWeight: Float = .4f, val threePanesSceneSecondPaneWeight: Float = .3f, val threePanesSceneThirdPaneWeight: Float = .3f, + val bottomSheetProperties: ModalBottomSheetProperties = ModalBottomSheetProperties() ) @Composable @@ -161,7 +181,7 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh firstPane = thirdLastEntry, secondPane = secondLastEntry, thirdPane = lastEntry, - weights = sceneWeights, + weights = sceneDefaults, previousEntries = listOf(thirdLastEntry, secondLastEntry), key = Triple( thirdLastEntry.contentKey, secondLastEntry.contentKey, lastEntry.contentKey @@ -194,7 +214,7 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh return AdaptiveTwoPaneScene( firstPane = firstEntry, secondPane = secondEntry, - weights = sceneWeights, + weights = sceneDefaults, previousEntries = listOf(firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) @@ -206,7 +226,7 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh return AdaptiveTwoPaneScene( firstPane = firstEntry, secondPane = secondEntry, - weights = sceneWeights, + weights = sceneDefaults, previousEntries = listOf(previousEntry, firstEntry), key = Pair(firstEntry.contentKey, secondEntry.contentKey) ) @@ -219,6 +239,7 @@ class ListDetailNoPlaceholderSceneStrategy(val sceneWeights: SceneWeigh ): Scene { return BottomPaneScene( pane = pane, + properties = sceneDefaults.bottomSheetProperties, previousEntries = listOf(previousEntry), key = pane.contentKey, onBack = onBack diff --git a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt index ca3f7a6..fbe06e0 100644 --- a/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt +++ b/app/src/main/java/com/example/nav3recipes/scenes/listdetailnoplaceholder/ListDetailNoPlaceholderActivity.kt @@ -32,6 +32,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.lazy.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.ProvidableCompositionLocal @@ -92,6 +93,7 @@ private data object Profile : NavKey private data object Toolbar : NavKey +@OptIn(ExperimentalMaterial3Api::class) class ListDetailNoPlaceholderActivity : ComponentActivity() { private val mockProducts = List(10) { Product(it) } @@ -142,10 +144,10 @@ class ListDetailNoPlaceholderActivity : ComponentActivity() { * A [SceneWeightsDefaults] that wraps variable initial weights to customise the appearance * of each panel */ - val weights = ListDetailNoPlaceholderSceneStrategy.SceneWeightsDefaults() + val defaults = ListDetailNoPlaceholderSceneStrategy.SceneDefaults() .copy(twoPanesScenePaneWeight = .4f) val strategy : SceneStrategy = - remember { ListDetailNoPlaceholderSceneStrategy(weights) } + remember { ListDetailNoPlaceholderSceneStrategy(defaults) } SharedTransitionLayout { CompositionLocalProvider(localNavSharedTransitionScope provides this) {