diff --git a/app/dependencies/prodReleaseRuntimeClasspath.txt b/app/dependencies/prodReleaseRuntimeClasspath.txt index 71e0ab289..1703b0c36 100644 --- a/app/dependencies/prodReleaseRuntimeClasspath.txt +++ b/app/dependencies/prodReleaseRuntimeClasspath.txt @@ -17,10 +17,10 @@ androidx.compose.animation:animation-android:1.7.0-alpha06 androidx.compose.animation:animation-core-android:1.7.0-alpha06 androidx.compose.animation:animation-core:1.7.0-alpha06 androidx.compose.animation:animation:1.7.0-alpha06 -androidx.compose.foundation:foundation-android:1.6.3 -androidx.compose.foundation:foundation-layout-android:1.6.3 -androidx.compose.foundation:foundation-layout:1.6.3 -androidx.compose.foundation:foundation:1.6.3 +androidx.compose.foundation:foundation-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout-android:1.7.0-alpha06 +androidx.compose.foundation:foundation-layout:1.7.0-alpha06 +androidx.compose.foundation:foundation:1.7.0-alpha06 androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha10 androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha10 androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha10 @@ -103,11 +103,11 @@ androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha04 androidx.loader:loader:1.0.0 androidx.localbroadcastmanager:localbroadcastmanager:1.0.0 androidx.metrics:metrics-performance:1.0.0-alpha04 -androidx.navigation:navigation-common-ktx:2.7.7 -androidx.navigation:navigation-common:2.7.7 -androidx.navigation:navigation-compose:2.7.7 -androidx.navigation:navigation-runtime-ktx:2.7.7 -androidx.navigation:navigation-runtime:2.7.7 +androidx.navigation:navigation-common-ktx:2.8.0-alpha06 +androidx.navigation:navigation-common:2.8.0-alpha06 +androidx.navigation:navigation-compose:2.8.0-alpha06 +androidx.navigation:navigation-runtime-ktx:2.8.0-alpha06 +androidx.navigation:navigation-runtime:2.8.0-alpha06 androidx.print:print:1.0.0 androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta05 androidx.privacysandbox.ads:ads-adservices:1.0.0-beta05 diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt index d618c2d47..40ce9c116 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/Interests2PaneViewModel.kt @@ -27,7 +27,8 @@ import javax.inject.Inject class Interests2PaneViewModel @Inject constructor( private val savedStateHandle: SavedStateHandle, ) : ViewModel() { - val selectedTopicId: StateFlow = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null) + val selectedTopicId: StateFlow = + savedStateHandle.getStateFlow(TOPIC_ID_ARG, savedStateHandle[TOPIC_ID_ARG]) fun onTopicClick(topicId: String?) { savedStateHandle[TOPIC_ID_ARG] = topicId diff --git a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt index 4cc4345ef..ada4e49d1 100644 --- a/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt +++ b/app/src/main/kotlin/com/google/samples/apps/nowinandroid/ui/interests2pane/InterestsListDetailScreen.kt @@ -18,14 +18,21 @@ package com.google.samples.apps.nowinandroid.ui.interests2pane import androidx.activity.compose.BackHandler import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi +import androidx.compose.material3.adaptive.layout.AnimatedPane import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole import androidx.compose.material3.adaptive.layout.PaneAdaptedValue +import androidx.compose.material3.adaptive.layout.ThreePaneScaffoldDestinationItem import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.key +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavGraphBuilder @@ -39,8 +46,10 @@ import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERES import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE +import com.google.samples.apps.nowinandroid.feature.topic.navigation.createTopicRoute import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen +import java.util.UUID private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route" @@ -76,17 +85,42 @@ internal fun InterestsListDetailScreen( selectedTopicId: String?, onTopicClick: (String) -> Unit, ) { - val listDetailNavigator = rememberListDetailPaneScaffoldNavigator() + val listDetailNavigator = rememberListDetailPaneScaffoldNavigator( + initialDestinationHistory = listOfNotNull( + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.List), + ThreePaneScaffoldDestinationItem(ListDetailPaneScaffoldRole.Detail).takeIf { + selectedTopicId != null + }, + ), + ) BackHandler(listDetailNavigator.canNavigateBack()) { listDetailNavigator.navigateBack() } - val nestedNavController = rememberNavController() + var nestedNavHostStartDestination by remember { + mutableStateOf(selectedTopicId?.let(::createTopicRoute) ?: TOPIC_ROUTE) + } + var nestedNavKey by rememberSaveable( + stateSaver = Saver({ it.toString() }, UUID::fromString), + ) { + mutableStateOf(UUID.randomUUID()) + } + val nestedNavController = key(nestedNavKey) { + rememberNavController() + } fun onTopicClickShowDetailPane(topicId: String) { onTopicClick(topicId) - nestedNavController.navigateToTopic(topicId) { - popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + if (listDetailNavigator.isDetailPaneVisible()) { + // If the detail pane was visible, then use the nestedNavController navigate call + // directly + nestedNavController.navigateToTopic(topicId) { + popUpTo(DETAIL_PANE_NAVHOST_ROUTE) + } + } else { + // Otherwise, recreate the NavHost entirely, and start at the new destination + nestedNavHostStartDestination = createTopicRoute(topicId) + nestedNavKey = UUID.randomUUID() } listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail) } @@ -95,34 +129,34 @@ internal fun InterestsListDetailScreen( value = listDetailNavigator.scaffoldValue, directive = listDetailNavigator.scaffoldDirective, listPane = { - InterestsRoute( - onTopicClick = ::onTopicClickShowDetailPane, - highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), - ) - }, - detailPane = { - NavHost( - navController = nestedNavController, - startDestination = TOPIC_ROUTE, - route = DETAIL_PANE_NAVHOST_ROUTE, - ) { - topicScreen( - showBackButton = !listDetailNavigator.isListPaneVisible(), - onBackClick = listDetailNavigator::navigateBack, + AnimatedPane { + InterestsRoute( onTopicClick = ::onTopicClickShowDetailPane, + highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(), ) - composable(route = TOPIC_ROUTE) { - TopicDetailPlaceholder() + } + }, + detailPane = { + AnimatedPane { + key(nestedNavKey) { + NavHost( + navController = nestedNavController, + startDestination = nestedNavHostStartDestination, + route = DETAIL_PANE_NAVHOST_ROUTE, + ) { + topicScreen( + showBackButton = !listDetailNavigator.isListPaneVisible(), + onBackClick = listDetailNavigator::navigateBack, + onTopicClick = ::onTopicClickShowDetailPane, + ) + composable(route = TOPIC_ROUTE) { + TopicDetailPlaceholder() + } + } } } }, ) - LaunchedEffect(Unit) { - if (selectedTopicId != null) { - // Initial topic ID was provided when navigating to Interests, so show its details. - onTopicClickShowDetailPane(selectedTopicId) - } - } } @OptIn(ExperimentalMaterial3AdaptiveApi::class) diff --git a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt index 41804b634..394c53303 100644 --- a/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt +++ b/feature/topic/src/main/kotlin/com/google/samples/apps/nowinandroid/feature/topic/navigation/TopicNavigation.kt @@ -41,13 +41,16 @@ internal class TopicArgs(val topicId: String) { } fun NavController.navigateToTopic(topicId: String, navOptions: NavOptionsBuilder.() -> Unit = {}) { - val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) - val newRoute = "$TOPIC_ROUTE/$encodedId" - navigate(newRoute) { + navigate(createTopicRoute(topicId)) { navOptions() } } +fun createTopicRoute(topicId: String): String { + val encodedId = URLEncoder.encode(topicId, URL_CHARACTER_ENCODING) + return "$TOPIC_ROUTE/$encodedId" +} + fun NavGraphBuilder.topicScreen( showBackButton: Boolean, onBackClick: () -> Unit, diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ef84555fa..6bc85e10e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -20,7 +20,7 @@ androidxHiltNavigationCompose = "1.2.0" androidxLifecycle = "2.7.0" androidxMacroBenchmark = "1.2.2" androidxMetrics = "1.0.0-alpha04" -androidxNavigation = "2.7.7" +androidxNavigation = "2.8.0-alpha06" androidxProfileinstaller = "1.3.1" androidxTestCore = "1.5.0" androidxTestExt = "1.1.5"