diff options
6 files changed, 545 insertions, 5 deletions
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp index 40613ceaaf5f..139f3e13d5bc 100644 --- a/packages/SettingsLib/Spa/spa/Android.bp +++ b/packages/SettingsLib/Spa/spa/Android.bp @@ -27,6 +27,7 @@ android_library { "androidx.slice_slice-builders", "androidx.slice_slice-core", "androidx.slice_slice-view", + "androidx.compose.animation_animation", "androidx.compose.material3_material3", "androidx.compose.material_material-icons-extended", "androidx.compose.runtime_runtime", diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt index aa10cc82a14e..a81e2e330b0f 100644 --- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt @@ -14,6 +14,8 @@ * limitations under the License. */ +@file:OptIn(ExperimentalAnimationApi::class) + package com.android.settingslib.spa.framework import android.content.Intent @@ -21,25 +23,31 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.annotation.VisibleForTesting +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.unit.IntOffset import androidx.core.view.WindowCompat import androidx.navigation.NavGraph.Companion.findStartDestination -import androidx.navigation.compose.NavHost -import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import com.android.settingslib.spa.R import com.android.settingslib.spa.framework.common.LogCategory import com.android.settingslib.spa.framework.common.SettingsPage import com.android.settingslib.spa.framework.common.SettingsPageProvider import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory +import com.android.settingslib.spa.framework.compose.AnimatedNavHost import com.android.settingslib.spa.framework.compose.LocalNavController import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl +import com.android.settingslib.spa.framework.compose.composable import com.android.settingslib.spa.framework.compose.localNavController +import com.android.settingslib.spa.framework.compose.rememberAnimatedNavController import com.android.settingslib.spa.framework.theme.SettingsTheme import com.android.settingslib.spa.framework.util.PageEvent import com.android.settingslib.spa.framework.util.getDestination @@ -86,7 +94,7 @@ open class BrowseActivity : ComponentActivity() { @VisibleForTesting @Composable fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: Intent? = null) { - val navController = rememberNavController() + val navController = rememberAnimatedNavController() CompositionLocalProvider(navController.localNavController()) { val controller = LocalNavController.current as NavControllerWrapperImpl controller.NavContent(sppRepository.getAllProviders()) @@ -97,15 +105,41 @@ fun BrowseContent(sppRepository: SettingsPageProviderRepository, initialIntent: @Composable private fun NavControllerWrapperImpl.NavContent(allProvider: Collection<SettingsPageProvider>) { val nullPage = SettingsPage.createNull() - NavHost( + AnimatedNavHost( navController = navController, startDestination = nullPage.sppName, ) { + val slideEffect = tween<IntOffset>(durationMillis = 300) + val fadeEffect = tween<Float>(durationMillis = 300) composable(nullPage.sppName) {} for (spp in allProvider) { composable( route = spp.name + spp.parameter.navRoute(), arguments = spp.parameter, + enterTransition = { + slideIntoContainer( + AnimatedContentScope.SlideDirection.Left, + animationSpec = slideEffect + ) + fadeIn(animationSpec = fadeEffect) + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentScope.SlideDirection.Left, + animationSpec = slideEffect + ) + fadeOut(animationSpec = fadeEffect) + }, + popEnterTransition = { + slideIntoContainer( + AnimatedContentScope.SlideDirection.Right, + animationSpec = slideEffect + ) + fadeIn(animationSpec = fadeEffect) + }, + popExitTransition = { + slideOutOfContainer( + AnimatedContentScope.SlideDirection.Right, + animationSpec = slideEffect + ) + fadeOut(animationSpec = fadeEffect) + }, ) { navBackStackEntry -> spp.PageEvent(navBackStackEntry.arguments) spp.Page(navBackStackEntry.arguments) diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt new file mode 100644 index 000000000000..930a83f76e3f --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedComposeNavigator.kt @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.NavOptions +import androidx.navigation.Navigator + +/** + * Navigator that navigates through [Composable]s. Every destination using this Navigator must + * set a valid [Composable] by setting it directly on an instantiated [Destination] or calling + * [composable]. + */ +@ExperimentalAnimationApi +@Navigator.Name("animatedComposable") +public class AnimatedComposeNavigator : Navigator<AnimatedComposeNavigator.Destination>() { + internal val transitionsInProgress get() = state.transitionsInProgress + + internal val backStack get() = state.backStack + + internal val isPop = mutableStateOf(false) + + override fun navigate( + entries: List<NavBackStackEntry>, + navOptions: NavOptions?, + navigatorExtras: Extras? + ) { + entries.forEach { entry -> + state.pushWithTransition(entry) + } + isPop.value = false + } + + override fun createDestination(): Destination { + return Destination(this, content = { }) + } + + override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) { + state.popWithTransition(popUpTo, savedState) + isPop.value = true + } + + internal fun markTransitionComplete(entry: NavBackStackEntry) { + state.markTransitionComplete(entry) + } + + /** + * NavDestination specific to [AnimatedComposeNavigator] + */ + @ExperimentalAnimationApi + @NavDestination.ClassType(Composable::class) + public class Destination( + navigator: AnimatedComposeNavigator, + internal val content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit + ) : NavDestination(navigator) + + internal companion object { + internal const val NAME = "animatedComposable" + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt new file mode 100644 index 000000000000..013757282427 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/AnimatedNavHost.kt @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.ContentTransform +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.animation.core.tween +import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.with +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.NavDestination.Companion.hierarchy +import androidx.navigation.NavGraph +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.Navigator +import androidx.navigation.compose.DialogHost +import androidx.navigation.compose.DialogNavigator +import androidx.navigation.compose.LocalOwnersProvider +import androidx.navigation.createGraph +import androidx.navigation.get +import kotlinx.coroutines.flow.map + +/** + * Provides in place in the Compose hierarchy for self contained navigation to occur. + * + * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from + * the provided [navController]. + * + * The builder passed into this method is [remember]ed. This means that for this NavHost, the + * contents of the builder cannot be changed. + * + * @param navController the navController for this host + * @param startDestination the route for the start destination + * @param modifier The modifier to be applied to the layout. + * @param route the route for the graph + * @param enterTransition callback to define enter transitions for destination in this host + * @param exitTransition callback to define exit transitions for destination in this host + * @param popEnterTransition callback to define popEnter transitions for destination in this host + * @param popExitTransition callback to define popExit transitions for destination in this host + * @param builder the builder used to construct the graph + */ +@Composable +@ExperimentalAnimationApi +public fun AnimatedNavHost( + navController: NavHostController, + startDestination: String, + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.Center, + route: String? = null, + enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) = + { fadeIn(animationSpec = tween(700)) }, + exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) = + { fadeOut(animationSpec = tween(700)) }, + popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) = + enterTransition, + popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) = + exitTransition, + builder: NavGraphBuilder.() -> Unit +) { + AnimatedNavHost( + navController, + remember(route, startDestination, builder) { + navController.createGraph(startDestination, route, builder) + }, + modifier, + contentAlignment, + enterTransition, + exitTransition, + popEnterTransition, + popExitTransition + ) +} + +/** + * Provides in place in the Compose hierarchy for self contained navigation to occur. + * + * Once this is called, any Composable within the given [NavGraphBuilder] can be navigated to from + * the provided [navController]. + * + * @param navController the navController for this host + * @param graph the graph for this host + * @param modifier The modifier to be applied to the layout. + * @param enterTransition callback to define enter transitions for destination in this host + * @param exitTransition callback to define exit transitions for destination in this host + * @param popEnterTransition callback to define popEnter transitions for destination in this host + * @param popExitTransition callback to define popExit transitions for destination in this host + */ +@ExperimentalAnimationApi +@Composable +public fun AnimatedNavHost( + navController: NavHostController, + graph: NavGraph, + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.Center, + enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) = + { fadeIn(animationSpec = tween(700)) }, + exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) = + { fadeOut(animationSpec = tween(700)) }, + popEnterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition) = + enterTransition, + popExitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition) = + exitTransition, +) { + + val lifecycleOwner = LocalLifecycleOwner.current + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "NavHost requires a ViewModelStoreOwner to be provided via LocalViewModelStoreOwner" + } + val onBackPressedDispatcherOwner = LocalOnBackPressedDispatcherOwner.current + val onBackPressedDispatcher = onBackPressedDispatcherOwner?.onBackPressedDispatcher + + // on successful recompose we setup the navController with proper inputs + // after the first time, this will only happen again if one of the inputs changes + navController.setLifecycleOwner(lifecycleOwner) + navController.setViewModelStore(viewModelStoreOwner.viewModelStore) + if (onBackPressedDispatcher != null) { + navController.setOnBackPressedDispatcher(onBackPressedDispatcher) + } + + navController.graph = graph + + val saveableStateHolder = rememberSaveableStateHolder() + + // Find the ComposeNavigator, returning early if it isn't found + // (such as is the case when using TestNavHostController) + val composeNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>( + AnimatedComposeNavigator.NAME + ) as? AnimatedComposeNavigator ?: return + val visibleEntries by remember(navController.visibleEntries) { + navController.visibleEntries.map { + it.filter { entry -> + entry.destination.navigatorName == AnimatedComposeNavigator.NAME + } + } + }.collectAsState(emptyList()) + + val backStackEntry = visibleEntries.lastOrNull() + + if (backStackEntry != null) { + val finalEnter: AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition = { + val targetDestination = targetState.destination as AnimatedComposeNavigator.Destination + + if (composeNavigator.isPop.value) { + targetDestination.hierarchy.firstNotNullOfOrNull { destination -> + popEnterTransitions[destination.route]?.invoke(this) + } ?: popEnterTransition.invoke(this) + } else { + targetDestination.hierarchy.firstNotNullOfOrNull { destination -> + enterTransitions[destination.route]?.invoke(this) + } ?: enterTransition.invoke(this) + } + } + + val finalExit: AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition = { + val initialDestination = + initialState.destination as AnimatedComposeNavigator.Destination + + if (composeNavigator.isPop.value) { + initialDestination.hierarchy.firstNotNullOfOrNull { destination -> + popExitTransitions[destination.route]?.invoke(this) + } ?: popExitTransition.invoke(this) + } else { + initialDestination.hierarchy.firstNotNullOfOrNull { destination -> + exitTransitions[destination.route]?.invoke(this) + } ?: exitTransition.invoke(this) + } + } + + val transition = updateTransition(backStackEntry, label = "entry") + transition.AnimatedContent( + modifier, + transitionSpec = { + val zIndex = if (composeNavigator.isPop.value) { + visibleEntries.indexOf(initialState).toFloat() + } else { + visibleEntries.indexOf(targetState).toFloat() + } + // If the initialState of the AnimatedContent is not in visibleEntries, we are in + // a case where visible has cleared the old state for some reason, so instead of + // attempting to animate away from the initialState, we skip the animation. + if (initialState in visibleEntries) { + ContentTransform(finalEnter(this), finalExit(this), zIndex) + } else { + EnterTransition.None with ExitTransition.None + } + }, + contentAlignment, + contentKey = { it.id } + ) { + // In some specific cases, such as clearing your back stack by changing your + // start destination, AnimatedContent can contain an entry that is no longer + // part of visible entries since it was cleared from the back stack and is not + // animating. In these cases the currentEntry will be null, and in those cases, + // AnimatedContent will just skip attempting to transition the old entry. + // See https://issuetracker.google.com/238686802 + val currentEntry = visibleEntries.lastOrNull { entry -> + it == entry + } + // while in the scope of the composable, we provide the navBackStackEntry as the + // ViewModelStoreOwner and LifecycleOwner + currentEntry?.LocalOwnersProvider(saveableStateHolder) { + (currentEntry.destination as AnimatedComposeNavigator.Destination) + .content(this, currentEntry) + } + } + if (transition.currentState == transition.targetState) { + visibleEntries.forEach { entry -> + composeNavigator.markTransitionComplete(entry) + } + } + } + + val dialogNavigator = navController.navigatorProvider.get<Navigator<out NavDestination>>( + "dialog" + ) as? DialogNavigator ?: return + + // Show any dialog destinations + DialogHost(dialogNavigator) +} + +@ExperimentalAnimationApi +internal val enterTransitions = + mutableMapOf<String?, + (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>() + +@ExperimentalAnimationApi +internal val exitTransitions = + mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>() + +@ExperimentalAnimationApi +internal val popEnterTransitions = + mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)?>() + +@ExperimentalAnimationApi +internal val popExitTransitions = + mutableMapOf<String?, (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)?>() diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt new file mode 100644 index 000000000000..9e58603bbaff --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavGraphBuilder.kt @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.compose.animation.AnimatedContentScope +import androidx.compose.animation.AnimatedVisibilityScope +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.Composable +import androidx.navigation.NamedNavArgument +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDeepLink +import androidx.navigation.NavGraph +import androidx.navigation.NavGraphBuilder +import androidx.navigation.compose.navigation +import androidx.navigation.get + +/** + * Add the [Composable] to the [NavGraphBuilder] + * + * @param route route for the destination + * @param arguments list of arguments to associate with destination + * @param deepLinks list of deep links to associate with the destinations + * @param enterTransition callback to determine the destination's enter transition + * @param exitTransition callback to determine the destination's exit transition + * @param popEnterTransition callback to determine the destination's popEnter transition + * @param popExitTransition callback to determine the destination's popExit transition + * @param content composable for the destination + */ +@ExperimentalAnimationApi +public fun NavGraphBuilder.composable( + route: String, + arguments: List<NamedNavArgument> = emptyList(), + deepLinks: List<NavDeepLink> = emptyList(), + enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null, + exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null, + popEnterTransition: ( + AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition? + )? = enterTransition, + popExitTransition: ( + AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition? + )? = exitTransition, + content: @Composable AnimatedVisibilityScope.(NavBackStackEntry) -> Unit +) { + addDestination( + AnimatedComposeNavigator.Destination( + provider[AnimatedComposeNavigator::class], + content + ).apply { + this.route = route + arguments.forEach { (argumentName, argument) -> + addArgument(argumentName, argument) + } + deepLinks.forEach { deepLink -> + addDeepLink(deepLink) + } + enterTransition?.let { enterTransitions[route] = enterTransition } + exitTransition?.let { exitTransitions[route] = exitTransition } + popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition } + popExitTransition?.let { popExitTransitions[route] = popExitTransition } + } + ) +} + +/** + * Construct a nested [NavGraph] + * + * @param startDestination the starting destination's route for this NavGraph + * @param route the destination's unique route + * @param arguments list of arguments to associate with destination + * @param deepLinks list of deep links to associate with the destinations + * @param enterTransition callback to define enter transitions for destination in this NavGraph + * @param exitTransition callback to define exit transitions for destination in this NavGraph + * @param popEnterTransition callback to define pop enter transitions for destination in this + * NavGraph + * @param popExitTransition callback to define pop exit transitions for destination in this NavGraph + * @param builder the builder used to construct the graph + * + * @return the newly constructed nested NavGraph + */ +@ExperimentalAnimationApi +public fun NavGraphBuilder.navigation( + startDestination: String, + route: String, + arguments: List<NamedNavArgument> = emptyList(), + deepLinks: List<NavDeepLink> = emptyList(), + enterTransition: (AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition?)? = null, + exitTransition: (AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition?)? = null, + popEnterTransition: ( + AnimatedContentScope<NavBackStackEntry>.() -> EnterTransition? + )? = enterTransition, + popExitTransition: ( + AnimatedContentScope<NavBackStackEntry>.() -> ExitTransition? + )? = exitTransition, + builder: NavGraphBuilder.() -> Unit +) { + navigation(startDestination, route, arguments, deepLinks, builder).apply { + enterTransition?.let { enterTransitions[route] = enterTransition } + exitTransition?.let { exitTransitions[route] = exitTransition } + popEnterTransition?.let { popEnterTransitions[route] = popEnterTransition } + popExitTransition?.let { popExitTransitions[route] = popExitTransition } + } +} diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt new file mode 100644 index 000000000000..a8ac86c2fb15 --- /dev/null +++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/NavHostController.kt @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023 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. + */ + +package com.android.settingslib.spa.framework.compose + +import androidx.compose.animation.ExperimentalAnimationApi +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.navigation.NavDestination +import androidx.navigation.NavHostController +import androidx.navigation.Navigator +import androidx.navigation.compose.rememberNavController + +/** + * Creates a NavHostController that handles the adding of the [ComposeNavigator], [DialogNavigator] + * and [AnimatedComposeNavigator]. Additional [androidx.navigation.Navigator] instances should be + * added in a [androidx.compose.runtime.SideEffect] block. + * + * @see AnimatedNavHost + */ +@ExperimentalAnimationApi +@Composable +fun rememberAnimatedNavController( + vararg navigators: Navigator<out NavDestination> +): NavHostController { + val animatedNavigator = remember { AnimatedComposeNavigator() } + return rememberNavController(animatedNavigator, *navigators) +} |