Migrate from AnimatedNavHost am: 93cb527c80
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/23807563 Change-Id: Iff0d94ff8cc6945ec8f5dc9550344e2684a416aa Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -62,7 +62,7 @@ dependencies {
|
|||||||
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
|
api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
|
||||||
api("androidx.lifecycle:lifecycle-livedata-ktx")
|
api("androidx.lifecycle:lifecycle-livedata-ktx")
|
||||||
api("androidx.lifecycle:lifecycle-runtime-compose")
|
api("androidx.lifecycle:lifecycle-runtime-compose")
|
||||||
api("androidx.navigation:navigation-compose:2.6.0-alpha08")
|
api("androidx.navigation:navigation-compose:2.7.0-beta01")
|
||||||
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
|
api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
|
||||||
api("com.google.android.material:material:1.7.0-alpha03")
|
api("com.google.android.material:material:1.7.0-alpha03")
|
||||||
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
|
debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@file:OptIn(ExperimentalAnimationApi::class)
|
|
||||||
|
|
||||||
package com.android.settingslib.spa.framework
|
package com.android.settingslib.spa.framework
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@@ -24,20 +22,17 @@ import android.util.Log
|
|||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
import androidx.compose.animation.AnimatedContentTransitionScope
|
|
||||||
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.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.saveable.rememberSaveable
|
import androidx.compose.runtime.saveable.rememberSaveable
|
||||||
import androidx.compose.ui.unit.IntOffset
|
|
||||||
import androidx.core.view.WindowCompat
|
import androidx.core.view.WindowCompat
|
||||||
import androidx.navigation.NavGraph.Companion.findStartDestination
|
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.R
|
||||||
import com.android.settingslib.spa.framework.common.LogCategory
|
import com.android.settingslib.spa.framework.common.LogCategory
|
||||||
import com.android.settingslib.spa.framework.common.NullPageProvider
|
import com.android.settingslib.spa.framework.common.NullPageProvider
|
||||||
@@ -46,12 +41,10 @@ import com.android.settingslib.spa.framework.common.SettingsPageProvider
|
|||||||
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
|
import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
|
||||||
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
|
import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
|
||||||
import com.android.settingslib.spa.framework.common.createSettingsPage
|
import com.android.settingslib.spa.framework.common.createSettingsPage
|
||||||
import com.android.settingslib.spa.framework.compose.AnimatedNavHost
|
|
||||||
import com.android.settingslib.spa.framework.compose.LocalNavController
|
import com.android.settingslib.spa.framework.compose.LocalNavController
|
||||||
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
|
import com.android.settingslib.spa.framework.compose.NavControllerWrapperImpl
|
||||||
import com.android.settingslib.spa.framework.compose.composable
|
import com.android.settingslib.spa.framework.compose.animatedComposable
|
||||||
import com.android.settingslib.spa.framework.compose.localNavController
|
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.theme.SettingsTheme
|
||||||
import com.android.settingslib.spa.framework.util.PageLogger
|
import com.android.settingslib.spa.framework.util.PageLogger
|
||||||
import com.android.settingslib.spa.framework.util.getDestination
|
import com.android.settingslib.spa.framework.util.getDestination
|
||||||
@@ -108,7 +101,7 @@ internal fun BrowseContent(
|
|||||||
isPageEnabled: (SettingsPage) -> Boolean,
|
isPageEnabled: (SettingsPage) -> Boolean,
|
||||||
initialIntent: Intent?,
|
initialIntent: Intent?,
|
||||||
) {
|
) {
|
||||||
val navController = rememberAnimatedNavController()
|
val navController = rememberNavController()
|
||||||
CompositionLocalProvider(navController.localNavController()) {
|
CompositionLocalProvider(navController.localNavController()) {
|
||||||
val controller = LocalNavController.current as NavControllerWrapperImpl
|
val controller = LocalNavController.current as NavControllerWrapperImpl
|
||||||
controller.NavContent(sppRepository.getAllProviders()) { page ->
|
controller.NavContent(sppRepository.getAllProviders()) { page ->
|
||||||
@@ -133,41 +126,15 @@ private fun NavControllerWrapperImpl.NavContent(
|
|||||||
allProvider: Collection<SettingsPageProvider>,
|
allProvider: Collection<SettingsPageProvider>,
|
||||||
content: @Composable (SettingsPage) -> Unit,
|
content: @Composable (SettingsPage) -> Unit,
|
||||||
) {
|
) {
|
||||||
AnimatedNavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
startDestination = NullPageProvider.name,
|
startDestination = NullPageProvider.name,
|
||||||
) {
|
) {
|
||||||
val slideEffect = tween<IntOffset>(durationMillis = 300)
|
|
||||||
val fadeEffect = tween<Float>(durationMillis = 300)
|
|
||||||
composable(NullPageProvider.name) {}
|
composable(NullPageProvider.name) {}
|
||||||
for (spp in allProvider) {
|
for (spp in allProvider) {
|
||||||
composable(
|
animatedComposable(
|
||||||
route = spp.name + spp.parameter.navRoute(),
|
route = spp.name + spp.parameter.navRoute(),
|
||||||
arguments = spp.parameter,
|
arguments = spp.parameter,
|
||||||
enterTransition = {
|
|
||||||
slideIntoContainer(
|
|
||||||
AnimatedContentTransitionScope.SlideDirection.Start,
|
|
||||||
animationSpec = slideEffect
|
|
||||||
) + fadeIn(animationSpec = fadeEffect)
|
|
||||||
},
|
|
||||||
exitTransition = {
|
|
||||||
slideOutOfContainer(
|
|
||||||
AnimatedContentTransitionScope.SlideDirection.Start,
|
|
||||||
animationSpec = slideEffect
|
|
||||||
) + fadeOut(animationSpec = fadeEffect)
|
|
||||||
},
|
|
||||||
popEnterTransition = {
|
|
||||||
slideIntoContainer(
|
|
||||||
AnimatedContentTransitionScope.SlideDirection.End,
|
|
||||||
animationSpec = slideEffect
|
|
||||||
) + fadeIn(animationSpec = fadeEffect)
|
|
||||||
},
|
|
||||||
popExitTransition = {
|
|
||||||
slideOutOfContainer(
|
|
||||||
AnimatedContentTransitionScope.SlideDirection.End,
|
|
||||||
animationSpec = slideEffect
|
|
||||||
) + fadeOut(animationSpec = fadeEffect)
|
|
||||||
},
|
|
||||||
) { navBackStackEntry ->
|
) { navBackStackEntry ->
|
||||||
val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
|
val page = remember { spp.createSettingsPage(navBackStackEntry.arguments) }
|
||||||
content(page)
|
content(page)
|
||||||
|
|||||||
@@ -1,79 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
* 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.AnimatedContentTransitionScope
|
||||||
|
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||||
|
import androidx.compose.animation.core.LinearOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.unit.IntOffset
|
||||||
|
import androidx.navigation.NamedNavArgument
|
||||||
|
import androidx.navigation.NavBackStackEntry
|
||||||
|
import androidx.navigation.NavDeepLink
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the [Composable] to the [NavGraphBuilder] with animation
|
||||||
|
*
|
||||||
|
* @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 content composable for the destination
|
||||||
|
*/
|
||||||
|
internal fun NavGraphBuilder.animatedComposable(
|
||||||
|
route: String,
|
||||||
|
arguments: List<NamedNavArgument> = emptyList(),
|
||||||
|
deepLinks: List<NavDeepLink> = emptyList(),
|
||||||
|
content: @Composable AnimatedContentScope.(NavBackStackEntry) -> Unit,
|
||||||
|
) = composable(
|
||||||
|
route = route,
|
||||||
|
arguments = arguments,
|
||||||
|
deepLinks = deepLinks,
|
||||||
|
enterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||||
|
animationSpec = slideInEffect,
|
||||||
|
initialOffset = offsetFunc,
|
||||||
|
) + fadeIn(animationSpec = fadeInEffect)
|
||||||
|
},
|
||||||
|
exitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.Start,
|
||||||
|
animationSpec = slideOutEffect,
|
||||||
|
targetOffset = offsetFunc,
|
||||||
|
) + fadeOut(animationSpec = fadeOutEffect)
|
||||||
|
},
|
||||||
|
popEnterTransition = {
|
||||||
|
slideIntoContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.End,
|
||||||
|
animationSpec = slideInEffect,
|
||||||
|
initialOffset = offsetFunc,
|
||||||
|
) + fadeIn(animationSpec = fadeInEffect)
|
||||||
|
},
|
||||||
|
popExitTransition = {
|
||||||
|
slideOutOfContainer(
|
||||||
|
towards = AnimatedContentTransitionScope.SlideDirection.End,
|
||||||
|
animationSpec = slideOutEffect,
|
||||||
|
targetOffset = offsetFunc,
|
||||||
|
) + fadeOut(animationSpec = fadeOutEffect)
|
||||||
|
},
|
||||||
|
content = content,
|
||||||
|
)
|
||||||
|
|
||||||
|
private const val FADE_OUT_MILLIS = 75
|
||||||
|
private const val FADE_IN_MILLIS = 300
|
||||||
|
|
||||||
|
private val slideInEffect = tween<IntOffset>(
|
||||||
|
durationMillis = FADE_IN_MILLIS,
|
||||||
|
delayMillis = FADE_OUT_MILLIS,
|
||||||
|
easing = LinearOutSlowInEasing,
|
||||||
|
)
|
||||||
|
private val slideOutEffect = tween<IntOffset>(durationMillis = FADE_IN_MILLIS)
|
||||||
|
private val fadeOutEffect = tween<Float>(
|
||||||
|
durationMillis = FADE_OUT_MILLIS,
|
||||||
|
easing = FastOutLinearInEasing,
|
||||||
|
)
|
||||||
|
private val fadeInEffect = tween<Float>(
|
||||||
|
durationMillis = FADE_IN_MILLIS,
|
||||||
|
delayMillis = FADE_OUT_MILLIS,
|
||||||
|
easing = LinearOutSlowInEasing,
|
||||||
|
)
|
||||||
|
private val offsetFunc: (offsetForFullSlide: Int) -> Int = { it.div(5) }
|
||||||
@@ -1,252 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.AnimatedContentTransitionScope
|
|
||||||
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: (AnimatedScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) },
|
|
||||||
exitTransition: (AnimatedScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) },
|
|
||||||
popEnterTransition: (AnimatedScope.() -> EnterTransition) = enterTransition,
|
|
||||||
popExitTransition: (AnimatedScope.() -> 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: (AnimatedScope.() -> EnterTransition) = { fadeIn(animationSpec = tween(700)) },
|
|
||||||
exitTransition: (AnimatedScope.() -> ExitTransition) = { fadeOut(animationSpec = tween(700)) },
|
|
||||||
popEnterTransition: (AnimatedScope.() -> EnterTransition) = enterTransition,
|
|
||||||
popExitTransition: (AnimatedScope.() -> 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: AnimatedScope.() -> 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: AnimatedScope.() -> 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 = composeNavigator.backStack.value.size.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)
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(ExperimentalAnimationApi::class)
|
|
||||||
internal typealias AnimatedScope = AnimatedContentTransitionScope<NavBackStackEntry>
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
internal val enterTransitions = mutableMapOf<String?, (AnimatedScope.() -> EnterTransition?)?>()
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
internal val exitTransitions = mutableMapOf<String?, (AnimatedScope.() -> ExitTransition?)?>()
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
internal val popEnterTransitions = mutableMapOf<String?, (AnimatedScope.() -> EnterTransition?)?>()
|
|
||||||
|
|
||||||
@ExperimentalAnimationApi
|
|
||||||
internal val popExitTransitions = mutableMapOf<String?, (AnimatedScope.() -> ExitTransition?)?>()
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.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.NavGraphBuilder
|
|
||||||
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: (AnimatedScope.() -> EnterTransition?)? = null,
|
|
||||||
exitTransition: (AnimatedScope.() -> ExitTransition?)? = null,
|
|
||||||
popEnterTransition: (AnimatedScope.() -> EnterTransition?)? = enterTransition,
|
|
||||||
popExitTransition: (AnimatedScope.() -> 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 }
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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)
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user