diff --git a/CHANGELOG.md b/CHANGELOG.md index 5404fed1d..7c6da0aba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Change Log - Khonshu's own navigation implementation has been moved from `navigation-experimental` into the main `navigation` artifact. +- Removed `navigation-compose` artifact that was based on androidx.navigation. ### Codegen diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 968b469d6..ac6293187 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -21,8 +21,6 @@ androidx-activity = "1.9.0" androidx-annotations = "1.5.0" androidx-core = "1.13.0" androidx-lifecycle = "2.7.0" -androidx-navigation = "2.7.7" -androidx-savedstate = "1.2.1" uri = "0.0.18" @@ -58,10 +56,8 @@ collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-im androidx-compose-compiler = { module = "androidx.compose.compiler:compiler", version.ref = "androidx-compose-compiler" } androidx-compose-runtime = { module = "androidx.compose.runtime:runtime", version.ref = "androidx-compose-runtime" } -androidx-compose-runtime-saveable = { module = "androidx.compose.runtime:runtime-saveable", version.ref = "androidx-compose-runtime" } androidx-compose-ui = { module = "androidx.compose.ui:ui", version.ref = "androidx-compose-ui" } androidx-compose-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-compose-ui" } -androidx-compose-animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-compose-animation" } jetbrains-compose-runtime = { module = "org.jetbrains.compose.runtime:runtime", version.ref = "jetbrains-compose" } jetbrains-compose-ui = { module = "org.jetbrains.compose.ui:ui", version.ref = "jetbrains-compose" } jetbrains-compose-compiler = { module = "org.jetbrains.compose.compiler:compiler", version.ref = "jetbrains-compose-compiler" } @@ -76,10 +72,6 @@ androidx-lifecycle-testing = { module = "androidx.lifecycle:lifecycle-runtime-te androidx-viewmodel = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } androidx-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } androidx-viewmodel-savedstate = { module = "androidx.lifecycle:lifecycle-viewmodel-savedstate", version.ref = "androidx-lifecycle" } -androidx-navigation-common = { module = "androidx.navigation:navigation-common", version.ref = "androidx-navigation" } -androidx-navigation-runtime = { module = "androidx.navigation:navigation-runtime", version.ref = "androidx-navigation" } -androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "androidx-navigation" } -androidx-savedstate = { module = "androidx.savedstate:savedstate", version.ref = "androidx-savedstate" } uri = { module = "com.eygraber:uri-kmp", version.ref = "uri" } toml = { module = "net.peanuuutz.tomlkt:tomlkt", version = "0.3.7" } diff --git a/navigation-compose/api/navigation-compose.api b/navigation-compose/api/navigation-compose.api deleted file mode 100644 index 69a356662..000000000 --- a/navigation-compose/api/navigation-compose.api +++ /dev/null @@ -1,33 +0,0 @@ -public final class com/freeletics/khonshu/navigation/androidx/NavHostDefaults { - public static final field $stable I - public static final field INSTANCE Lcom/freeletics/khonshu/navigation/androidx/NavHostDefaults; - public final fun transitionAnimations (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lcom/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations; - public static synthetic fun transitionAnimations$default (Lcom/freeletics/khonshu/navigation/androidx/NavHostDefaults;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;ILjava/lang/Object;)Lcom/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations; -} - -public final class com/freeletics/khonshu/navigation/androidx/NavHostKt { - public static final fun NavHost (Lcom/freeletics/khonshu/navigation/NavRoot;Lkotlinx/collections/immutable/ImmutableSet;Landroidx/compose/ui/Modifier;Lkotlinx/collections/immutable/ImmutableSet;Lkotlinx/collections/immutable/ImmutableSet;Lcom/freeletics/khonshu/navigation/NavEventNavigator;Lkotlin/jvm/functions/Function1;Lcom/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations;Landroidx/compose/runtime/Composer;II)V -} - -public final class com/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations { - public static final field $stable I - public static final field Companion Lcom/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations$Companion; - public fun equals (Ljava/lang/Object;)Z - public final fun getEnterTransition ()Lkotlin/jvm/functions/Function1; - public final fun getExitTransition ()Lkotlin/jvm/functions/Function1; - public final fun getPopEnterTransition ()Lkotlin/jvm/functions/Function1; - public final fun getPopExitTransition ()Lkotlin/jvm/functions/Function1; - public fun hashCode ()I -} - -public final class com/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations$Companion { - public final fun noAnimations ()Lcom/freeletics/khonshu/navigation/androidx/NavHostTransitionAnimations; -} - -public final class com/freeletics/khonshu/navigation/androidx/internal/ComposableSingletons$OverlayNavigatorKt { - public static final field INSTANCE Lcom/freeletics/khonshu/navigation/androidx/internal/ComposableSingletons$OverlayNavigatorKt; - public static field lambda-1 Lkotlin/jvm/functions/Function3; - public fun ()V - public final fun getLambda-1$navigation_compose_release ()Lkotlin/jvm/functions/Function3; -} - diff --git a/navigation-compose/gradle.properties b/navigation-compose/gradle.properties deleted file mode 100644 index cbbc02818..000000000 --- a/navigation-compose/gradle.properties +++ /dev/null @@ -1,3 +0,0 @@ -POM_ARTIFACT_ID=navigation-compose -POM_NAME=Khonshu Navigation Compose -POM_DESCRIPTION=Khonshu Navigation Compose diff --git a/navigation-compose/navigation-compose.gradle.kts b/navigation-compose/navigation-compose.gradle.kts index dcaf3604e..6ac4c9600 100644 --- a/navigation-compose/navigation-compose.gradle.kts +++ b/navigation-compose/navigation-compose.gradle.kts @@ -1,35 +1,6 @@ -import com.android.build.api.dsl.CommonExtension - plugins { alias(libs.plugins.fgp.android) - alias(libs.plugins.fgp.publish) -} - -freeletics { - optIn("com.freeletics.khonshu.navigation.internal.InternalNavigationApi") - - useCompose() -} - -extensions.configure(CommonExtension::class.java) { - lint { - disable.add("UnsafeOptInUsageError") - } } dependencies { - api(projects.navigation) - api(libs.androidx.compose.runtime) - api(libs.androidx.compose.ui) - api(libs.androidx.navigation.common) - api(libs.androidx.navigation.runtime) - api(libs.androidx.viewmodel.savedstate) - api(libs.collections.immutable) - - implementation(libs.coroutines.core) - implementation(libs.androidx.compose.runtime.saveable) - implementation(libs.androidx.compose.animation) - implementation(libs.androidx.lifecycle.common) - implementation(libs.androidx.viewmodel) - implementation(libs.androidx.navigation.compose) } diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/NavHost.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/NavHost.kt deleted file mode 100644 index 95a9ffdb9..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/NavHost.kt +++ /dev/null @@ -1,326 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx - -import androidx.compose.animation.AnimatedContentTransitionScope -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -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.DisposableEffect -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.NonRestartableComposable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.State -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveable -import androidx.compose.runtime.snapshotFlow -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalView -import androidx.navigation.NavArgument -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController -import androidx.navigation.NavController.OnDestinationChangedListener -import androidx.navigation.NavDestination as AndroidXNavDestination -import androidx.navigation.NavGraph -import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavHostController -import androidx.navigation.Navigation -import androidx.navigation.compose.ComposeNavigator -import androidx.navigation.compose.NavHost as AndroidXNavHost -import androidx.navigation.compose.rememberNavController -import androidx.navigation.createGraph -import androidx.navigation.get -import com.freeletics.khonshu.navigation.ActivityDestination -import com.freeletics.khonshu.navigation.BaseRoute -import com.freeletics.khonshu.navigation.LocalNavigationExecutor -import com.freeletics.khonshu.navigation.NavDestination -import com.freeletics.khonshu.navigation.NavEventNavigator -import com.freeletics.khonshu.navigation.NavRoot -import com.freeletics.khonshu.navigation.NavRoute -import com.freeletics.khonshu.navigation.NavigationSetup -import com.freeletics.khonshu.navigation.OverlayDestination -import com.freeletics.khonshu.navigation.ScreenDestination -import com.freeletics.khonshu.navigation.androidx.internal.AndroidXNavigationExecutor -import com.freeletics.khonshu.navigation.androidx.internal.CustomActivityNavigator -import com.freeletics.khonshu.navigation.androidx.internal.OverlayHost -import com.freeletics.khonshu.navigation.androidx.internal.OverlayNavigator -import com.freeletics.khonshu.navigation.androidx.internal.destinationId -import com.freeletics.khonshu.navigation.androidx.internal.getArguments -import com.freeletics.khonshu.navigation.androidx.internal.handleDeepLink -import com.freeletics.khonshu.navigation.androidx.internal.requireRoute -import com.freeletics.khonshu.navigation.deeplinks.DeepLinkHandler -import com.freeletics.khonshu.navigation.findActivity -import java.io.Serializable -import kotlinx.collections.immutable.ImmutableSet -import kotlinx.collections.immutable.persistentSetOf - -/** - * Create a new [androidx.navigation.compose.NavHost] with a [androidx.navigation.NavGraph] - * containing all given [destinations]. [startRoute] will be used as the start destination - * of the graph. - * - * To support deep links a set of [deepLinkHandlers] can be passed in optionally. - * These will be used to build the correct back stack when the current `Activity` was launched with - * an `ACTION_VIEW` `Intent` that contains an url in it's data. [deepLinkPrefixes] can be used to - * provide a default set of url patterns that should be matched by any [DeepLinkHandler] that - * doesn't provide its own [DeepLinkHandler.prefixes]. - * - * If a [NavEventNavigator] is passed it will be automatically set up and can be used to - * navigate within the `NavHost`. - * - * The [destinationChangedCallback] can be used to be notified when the current destination - * changes. Note that this will not be invoked when navigating to a [ActivityDestination]. - * - * Optional [transitionAnimations] override default set of transition animations. - */ -@Composable -public fun NavHost( - startRoute: NavRoot, - destinations: ImmutableSet, - modifier: Modifier = Modifier, - deepLinkHandlers: ImmutableSet = persistentSetOf(), - deepLinkPrefixes: ImmutableSet = persistentSetOf(), - navEventNavigator: NavEventNavigator? = null, - destinationChangedCallback: ((BaseRoute) -> Unit)? = null, - transitionAnimations: NavHostTransitionAnimations = NavHostTransitionAnimations.noAnimations(), -) { - val context = LocalContext.current - - // should be called before rememberNavController to fill intent data with deeplinks - context.findActivity().handleDeepLink(deepLinkHandlers, deepLinkPrefixes) - - val overlayNavigator = remember { OverlayNavigator() } - val customActivityNavigator = remember(context) { CustomActivityNavigator(context) } - val navController: NavHostController = rememberNavController(overlayNavigator, customActivityNavigator) - - val composeView = LocalView.current - DisposableEffect(navController, composeView) { - Navigation.setViewNavController(composeView, navController) - - onDispose { - Navigation.setViewNavController(composeView, null) - } - } - - // This state is used to save the start route, so that we can update the start destination of the graph. - // It is updated when NavEventNavigation#replaceAll is called or when the `startRoute` parameter changes. - val savedStartRouteState = rememberSaveable { mutableStateOf(startRoute) } - - val executor = remember(navController, savedStartRouteState) { - AndroidXNavigationExecutor( - controller = navController, - onSaveStartRoute = { savedStartRouteState.value = it }, - ) - } - - if (destinationChangedCallback != null) { - DisposableEffect(navController, destinationChangedCallback) { - val listener = OnDestinationChangedListener { _, _, arguments -> - val route = arguments.requireRoute() - destinationChangedCallback.invoke(route) - } - navController.addOnDestinationChangedListener(listener) - - onDispose { - navController.removeOnDestinationChangedListener(listener) - } - } - } - - val graph = remember(navController, startRoute, destinations) { - @Suppress("deprecation") - navController.createGraph(startDestination = startRoute.destinationId()) { - destinations.forEach { destination -> - addDestination(navController, destination, startRoute) - } - }.also { - // Update the saved start route when a new graph is created. - savedStartRouteState.value = startRoute - } - } - - SetStartDestinationSideEffect(savedStartRouteState, graph) - - CompositionLocalProvider(LocalNavigationExecutor provides executor) { - if (navEventNavigator != null) { - NavigationSetup(navigator = navEventNavigator) - } - - AndroidXNavHost( - navController = navController, - graph = graph, - modifier = modifier, - enterTransition = transitionAnimations.enterTransition, - exitTransition = transitionAnimations.exitTransition, - popEnterTransition = transitionAnimations.popEnterTransition, - popExitTransition = transitionAnimations.popExitTransition, - ) - - OverlayHost( - overlayNavigator = navController.navigatorProvider[OverlayNavigator::class], - ) - } -} - -/* - * When the start route changes because NavEventNavigation#replaceAll was called, - * we need to update the start destination of the graph. - * - * This is really necessary, because after a configuration changes or a process death, - * the NavController restores the its state, but the created graph has the wrong start destination, - * since it was created with the `startRoute` parameter that was passed to this NavHost composable. -*/ -@NonRestartableComposable -@Composable -private fun SetStartDestinationSideEffect( - savedStartRouteState: State, - graph: NavGraph, -) = - LaunchedEffect(savedStartRouteState, graph) { - snapshotFlow { savedStartRouteState.value }.collect { - // Call graph.setStartDestination(rootId) to make sure that - // other methods of AndroidXNavigationExecutor can access the correct start destination - // (via controller.graph.startDestinationId). - graph.setStartDestination(it.destinationId()) - } - } - -/** - * Defaults used in [NavHost] - */ -public object NavHostDefaults { - - @Stable - public fun transitionAnimations( - enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = - { fadeIn(animationSpec = tween(700)) }, - exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = - { fadeOut(animationSpec = tween(700)) }, - popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition) = - enterTransition, - popExitTransition: (AnimatedContentTransitionScope.() -> ExitTransition) = - exitTransition, - ): NavHostTransitionAnimations = NavHostTransitionAnimations( - enterTransition = enterTransition, - exitTransition = exitTransition, - popEnterTransition = popEnterTransition, - popExitTransition = popExitTransition, - ) -} - -/** - * Represents set of callbacks to define transition animations for destinations in [NavHost] - */ -@Stable -public class NavHostTransitionAnimations internal constructor( - public val enterTransition: (AnimatedContentTransitionScope.() -> EnterTransition), - public val exitTransition: (AnimatedContentTransitionScope.() -> ExitTransition), - public val popEnterTransition: (AnimatedContentTransitionScope.() -> EnterTransition), - public val popExitTransition: (AnimatedContentTransitionScope.() -> ExitTransition), -) { - public companion object { - /** - * Disables all transition animations - */ - @Stable - public fun noAnimations(): NavHostTransitionAnimations = NavHostTransitionAnimations( - enterTransition = { EnterTransition.None }, - exitTransition = { ExitTransition.None }, - popEnterTransition = { EnterTransition.None }, - popExitTransition = { ExitTransition.None }, - ) - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || other !is NavHostTransitionAnimations) return false - - if (enterTransition != other.enterTransition) return false - if (exitTransition != other.exitTransition) return false - if (popEnterTransition != other.popEnterTransition) return false - if (popExitTransition != other.popExitTransition) return false - - return true - } - - override fun hashCode(): Int { - var result = enterTransition.hashCode() - result = 31 * result + exitTransition.hashCode() - result = 31 * result + popEnterTransition.hashCode() - result = 31 * result + popExitTransition.hashCode() - return result - } -} - -// the BottomSheet class and creator methods are marked with ExperimentalMaterialNavigationApi -// if those stay unused the experimental code is never called, so we swallow the warning here -private fun NavGraphBuilder.addDestination( - controller: NavController, - destination: NavDestination, - startRoute: BaseRoute, -) { - val newDestination = when (destination) { - is ScreenDestination<*> -> destination.toDestination(controller, startRoute) - is OverlayDestination<*> -> destination.toDestination(controller) - is ActivityDestination -> destination.toDestination(controller) - } - addDestination(newDestination) -} - -private fun ScreenDestination.toDestination( - controller: NavController, - startRoute: BaseRoute, -): ComposeNavigator.Destination { - val navigator = controller.navigatorProvider[ComposeNavigator::class] - return ComposeNavigator.Destination(navigator) { content(it.arguments.requireRoute()) }.also { - it.id = id.destinationId() - it.addExtra(extra) - if (startRoute::class == id.route) { - val arguments = startRoute.getArguments() - @Suppress("DEPRECATION") - arguments.keySet().forEach { key -> - val argument = NavArgument.Builder() - .setDefaultValue(arguments.get(key)) - .setIsNullable(false) - .build() - it.addArgument(key, argument) - } - } - } -} - -private fun OverlayDestination.toDestination( - controller: NavController, -): OverlayNavigator.Destination { - val navigator = controller.navigatorProvider[OverlayNavigator::class] - return OverlayNavigator.Destination(navigator) { content(it.arguments.requireRoute()) }.also { - it.id = id.destinationId() - it.addExtra(extra) - } -} - -private fun AndroidXNavDestination.addExtra(extra: Serializable?) { - if (extra == null) { - return - } - - val argument = NavArgument.Builder() - .setDefaultValue(extra) - .setIsNullable(false) - .build() - addArgument("NAV_SECRET_EXTRA", argument) -} - -private fun ActivityDestination.toDestination( - controller: NavController, -): CustomActivityNavigator.Destination { - val navigator = controller.navigatorProvider[CustomActivityNavigator::class] - return CustomActivityNavigator.Destination(navigator).also { - it.id = id.destinationId() - it.intent = intent - } -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXDeepLinkHandler.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXDeepLinkHandler.kt deleted file mode 100644 index 40976e9c4..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXDeepLinkHandler.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx.internal - -import android.app.Activity -import android.content.Intent -import android.os.Bundle -import android.os.Parcelable -import androidx.navigation.NavController.Companion.KEY_DEEP_LINK_ARGS -import androidx.navigation.NavController.Companion.KEY_DEEP_LINK_IDS -import com.eygraber.uri.Uri -import com.freeletics.khonshu.navigation.ActivityRoute -import com.freeletics.khonshu.navigation.BaseRoute -import com.freeletics.khonshu.navigation.deeplinks.DeepLink -import com.freeletics.khonshu.navigation.deeplinks.DeepLinkHandler -import com.freeletics.khonshu.navigation.deeplinks.EXTRA_DEEPLINK_ROUTES -import com.freeletics.khonshu.navigation.deeplinks.buildIntent -import com.freeletics.khonshu.navigation.deeplinks.createDeepLinkIfMatching -import com.freeletics.khonshu.navigation.internal.InternalNavigationApi - -/** - * Handles 2 different kind of deep links: - * - url based deep links when the `Activity` was launched with a standard `ACTION_VIEW` `Intent` - * that contains the url in [Intent.getData] - * - programmatically launched deep links when the `Activity` was launched with an `Intent` created - * through one of the `build...` methods on [DeepLink] - * - * In the case of url based deep links the given [deepLinkHandlers] will be searched for a match - * through which a [DeepLink] will be created. An `Intent` created with [DeepLink.buildIntent] will - * then replace the original [Activity.getIntent]. - * - * For programmatically launched deep links the current `Intent` will be augmented with additional - * information for AndroidX. - */ -@InternalNavigationApi -public fun Activity.handleDeepLink( - deepLinkHandlers: Set = emptySet(), - deepLinkPrefixes: Set = emptySet(), -) { - if (intent.hasExtra(KEY_DEEP_LINK_IDS)) { - return - } - val uri = intent.dataString - if (uri != null) { - val deepLink = deepLinkHandlers.createDeepLinkIfMatching(Uri.parse(uri), deepLinkPrefixes) - val deepLinkIntent = deepLink?.buildIntent(this) - if (deepLinkIntent != null) { - deepLinkIntent.setNavDeepLinkExtras() - intent = deepLinkIntent - } - } else { - intent.setNavDeepLinkExtras() - } -} - -private fun Intent.setNavDeepLinkExtras() { - @Suppress("DEPRECATION") - val routes = getParcelableArrayListExtra(EXTRA_DEEPLINK_ROUTES) - if (routes != null) { - val extra = 1 - val deepLinkIds = IntArray(routes.size + extra) - val deepLinkArgs = ArrayList(routes.size + extra) - // represents the nav graph, we always create it with 0 as id - deepLinkIds[0] = 0 - deepLinkArgs.add(Bundle.EMPTY) - - routes.forEachIndexed { index, route -> - if (route is BaseRoute) { - deepLinkIds[index + 1] = route.destinationId() - deepLinkArgs.add(route.getArguments()) - } else if (route is ActivityRoute) { - deepLinkIds[index + 1] = route.destinationId() - deepLinkArgs.add(route.getArguments()) - } else { - throw IllegalArgumentException("Unknown type of route $route") - } - } - - putExtra(KEY_DEEP_LINK_IDS, deepLinkIds) - putParcelableArrayListExtra(KEY_DEEP_LINK_ARGS, deepLinkArgs) - } -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXNavigationExecutor.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXNavigationExecutor.kt deleted file mode 100644 index bef2f44ad..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/AndroidXNavigationExecutor.kt +++ /dev/null @@ -1,120 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx.internal - -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModelProvider -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController -import androidx.navigation.NavOptions -import androidx.navigation.navOptions -import com.freeletics.khonshu.navigation.ActivityRoute -import com.freeletics.khonshu.navigation.BaseRoute -import com.freeletics.khonshu.navigation.NavRoot -import com.freeletics.khonshu.navigation.NavRoute -import com.freeletics.khonshu.navigation.internal.DestinationId -import com.freeletics.khonshu.navigation.internal.InternalNavigationApi -import com.freeletics.khonshu.navigation.internal.NavigationExecutor -import java.io.Serializable - -@InternalNavigationApi -public class AndroidXNavigationExecutor( - private val controller: NavController, - private val onSaveStartRoute: (NavRoot) -> Unit, -) : NavigationExecutor { - - override fun navigateTo(route: NavRoute) { - controller.navigate(route.destinationId(), route.getArguments()) - } - - override fun navigateTo(route: ActivityRoute) { - controller.navigate(route.destinationId(), route.getArguments()) - } - - override fun navigateToRoot(root: NavRoot, restoreRootState: Boolean) { - val options = NavOptions.Builder() - // save the state of the current root before leaving it - .setPopUpTo( - controller.graph.startDestinationId, - inclusive = false, - saveState = true, - ) - // restoring the state of the target root - .setRestoreState(restoreRootState) - // makes sure that if the destination is already on the backstack, it and - // everything above it gets removed - .setLaunchSingleTop(true) - .build() - controller.navigate(root.destinationId(), root.getArguments(), options) - } - - override fun navigateBack() { - controller.popBackStack() - } - - override fun navigateUp() { - controller.navigateUp() - } - - override fun navigateBackToInternal(popUpTo: DestinationId, inclusive: Boolean) { - controller.popBackStack(popUpTo.destinationId(), inclusive) - } - - override fun resetToRoot(root: NavRoot) { - val options = NavOptions.Builder() - // save the state of the current root before leaving it - .setPopUpTo( - controller.graph.startDestinationId, - inclusive = false, - saveState = false, - ) - // restoring the state of the target root - .setRestoreState(false) - // makes sure that if the destination is already on the backstack, it and - // everything above it gets removed - .setLaunchSingleTop(true) - .build() - controller.navigate(root.destinationId(), root.getArguments(), options) - } - - override fun replaceAll(root: NavRoot) { - val options = navOptions { - // pop all entries from the backstack - popUpTo(id = controller.graph.id) { - inclusive = true - saveState = false - } - - // Avoid multiple copies of the same destination. - launchSingleTop = true - - // Don't restore the state of the target destination. - restoreState = false - } - - controller.navigate(root.destinationId(), root.getArguments(), options) - onSaveStartRoute(root) - } - - override fun savedStateHandleFor(destinationId: DestinationId): SavedStateHandle { - return entryFor(destinationId).savedStateHandle - } - - override fun routeFor(destinationId: DestinationId): T { - return entryFor(destinationId).arguments.requireRoute() - } - - override fun storeFor(destinationId: DestinationId): NavigationExecutor.Store { - val viewModelStore = entryFor(destinationId).viewModelStore - val factory = ViewModelProvider.NewInstanceFactory() - return ViewModelProvider(viewModelStore, factory)[StoreViewModel::class.java] - } - - override fun extra(destinationId: DestinationId): Serializable { - val destination = entryFor(destinationId).destination - val extraArgument = destination.arguments["NAV_SECRET_EXTRA"]!! - return extraArgument.defaultValue as Serializable - } - - private fun entryFor(destinationId: DestinationId<*>): NavBackStackEntry { - return controller.getBackStackEntry(destinationId.destinationId()) - } -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/Bundle.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/Bundle.kt deleted file mode 100644 index 32ae6437f..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/Bundle.kt +++ /dev/null @@ -1,30 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx.internal - -import android.os.Bundle -import com.freeletics.khonshu.navigation.ActivityRoute -import com.freeletics.khonshu.navigation.BaseRoute -import com.freeletics.khonshu.navigation.EXTRA_ROUTE -import com.freeletics.khonshu.navigation.internal.InternalNavigationApi - -@InternalNavigationApi -public fun Bundle?.requireRoute(): T { - requireNotNull(this) { - "Bundle is null. Can't extract Route data." - } - @Suppress("DEPRECATION") - return requireNotNull(getParcelable(EXTRA_ROUTE)) { - "Bundle doesn't contain Route data in \"$EXTRA_ROUTE\"" - } -} - -@InternalNavigationApi -public fun BaseRoute.getArguments(): Bundle = Bundle().also { - it.putParcelable(EXTRA_ROUTE, this) -} - -@InternalNavigationApi -public fun ActivityRoute.getArguments(): Bundle = Bundle().also { - it.putParcelable(EXTRA_FILL_IN_INTENT, fillInIntent()) -} - -internal const val EXTRA_FILL_IN_INTENT: String = "com.freeletics.khonshu.navigation.FILL_IN_INTENT" diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/CustomActivityNavigator.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/CustomActivityNavigator.kt deleted file mode 100644 index 41ecd17fd..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/CustomActivityNavigator.kt +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2017 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.freeletics.khonshu.navigation.androidx.internal - -import android.app.Activity -import android.content.Context -import android.content.ContextWrapper -import android.content.Intent -import android.os.Bundle -import androidx.navigation.NavController -import androidx.navigation.NavDestination -import androidx.navigation.NavOptions -import androidx.navigation.Navigator -import androidx.navigation.NavigatorProvider -import com.freeletics.khonshu.navigation.internal.InternalNavigationApi - -/** - * ActivityNavigator implements cross-activity navigation. - */ -@Navigator.Name("activity") -internal class CustomActivityNavigator( - private val context: Context, -) : Navigator() { - private val hostActivity: Activity? = generateSequence(context) { - if (it is ContextWrapper) { - it.baseContext - } else { - null - } - }.firstOrNull { - it is Activity - } as Activity? - - override fun createDestination(): Destination { - return Destination(this) - } - - override fun popBackStack(): Boolean { - if (hostActivity != null) { - hostActivity.finish() - return true - } - return false - } - - /** - * Navigate to a destination. - * - *

Requests navigation to a given destination associated with this navigator in - * the navigation graph. This method generally should not be called directly; - * NavController will delegate to it when appropriate.

- * - * @param destination destination node to navigate to - * @param args arguments to use for navigation - * @param navOptions additional options for navigation - * @param navigatorExtras extras unique to your Navigator. - * @return The NavDestination that should be added to the back stack or null if - * no change was made to the back stack (i.e., in cases of single top operations - * where the destination is already on top of the back stack). - * - * @throws IllegalArgumentException if the given destination has no Intent - */ - override fun navigate( - destination: Destination, - args: Bundle?, - navOptions: NavOptions?, - navigatorExtras: Extras?, - ): NavDestination? { - checkNotNull(destination.intent) { - ("Destination ${destination.id} does not have an Intent set.") - } - val intent = Intent(destination.intent) - if (args != null) { - @Suppress("DEPRECATION") - val fillInIntent = args.getParcelable(EXTRA_FILL_IN_INTENT) - if (fillInIntent != null) { - intent.fillIn(fillInIntent, 0) - } - } - if (hostActivity == null) { - // If we're not launching from an Activity context we have to launch in a new task. - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - } - if (navOptions != null && navOptions.shouldLaunchSingleTop()) { - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) - } - if (hostActivity != null) { - val hostIntent = hostActivity.intent - if (hostIntent != null) { - val hostCurrentId = hostIntent.getIntExtra(EXTRA_NAV_CURRENT, 0) - if (hostCurrentId != 0) { - intent.putExtra(EXTRA_NAV_SOURCE, hostCurrentId) - } - } - } - val destId = destination.id - intent.putExtra(EXTRA_NAV_CURRENT, destId) - - context.startActivity(intent) - - // You can't pop the back stack from the caller of a new Activity, - // so we don't add this navigator to the controller's back stack - return null - } - - /** - * NavDestination for activity navigation - * - * Construct a new activity destination. This destination is not valid until you set the - * Intent via [setIntent] or one or more of the other set method. - * - * @param activityNavigator The [ActivityNavigator] which this destination - * will be associated with. Generally retrieved via a - * [NavController]'s - * [NavigatorProvider.getNavigator] method. - */ - @NavDestination.ClassType(Activity::class) - @InternalNavigationApi - class Destination( - activityNavigator: Navigator, - ) : NavDestination(activityNavigator) { - /** - * The Intent associated with this destination. - */ - var intent: Intent? = null - - /** - * Construct a new activity destination. This destination is not valid until you set the - * Intent via [setIntent] or one or more of the other set method. - * - * @param navigatorProvider The [NavController] which this destination - * will be associated with. - */ - constructor( - navigatorProvider: NavigatorProvider, - ) : this(navigatorProvider.getNavigator(CustomActivityNavigator::class.java)) - - override fun equals(other: Any?): Boolean { - if (other == null || other !is Destination) return false - return super.equals(other) && - intent?.filterEquals(other.intent) ?: (other.intent == null) - } - - override fun hashCode(): Int { - var result = super.hashCode() - result = 31 * result + (intent?.filterHashCode() ?: 0) - return result - } - } - - private companion object { - private const val EXTRA_NAV_SOURCE = "android-support-navigation:ActivityNavigator:source" - private const val EXTRA_NAV_CURRENT = "android-support-navigation:ActivityNavigator:current" - } -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/DestinationIdInt.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/DestinationIdInt.kt deleted file mode 100644 index 9335c7139..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/DestinationIdInt.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx.internal - -import com.freeletics.khonshu.navigation.ActivityRoute -import com.freeletics.khonshu.navigation.BaseRoute -import com.freeletics.khonshu.navigation.internal.ActivityDestinationId -import com.freeletics.khonshu.navigation.internal.DestinationId -import com.freeletics.khonshu.navigation.internal.InternalNavigationApi -import kotlin.reflect.KClass - -@InternalNavigationApi -public fun BaseRoute.destinationId(): Int = this::class.destinationId() - -@InternalNavigationApi -public fun KClass.destinationId(): Int = internalDestinationId() - -@InternalNavigationApi -public fun DestinationId<*>.destinationId(): Int = route.internalDestinationId() - -@InternalNavigationApi -public fun ActivityRoute.destinationId(): Int = this::class.activityDestinationId() - -@InternalNavigationApi -public fun ActivityDestinationId<*>.destinationId(): Int = route.internalDestinationId() - -@InternalNavigationApi -public fun KClass.activityDestinationId(): Int = internalDestinationId() - -private fun KClass<*>.internalDestinationId() = qualifiedName!!.hashCode() diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayHost.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayHost.kt deleted file mode 100644 index 42cb94e34..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayHost.kt +++ /dev/null @@ -1,108 +0,0 @@ -/* - * Copyright 2021 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.freeletics.khonshu.navigation.androidx.internal - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateListOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.rememberSaveableStateHolder -import androidx.compose.runtime.snapshots.SnapshotStateList -import androidx.compose.ui.platform.LocalInspectionMode -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.navigation.NavBackStackEntry -import androidx.navigation.compose.LocalOwnersProvider - -@Composable -internal fun OverlayHost(overlayNavigator: OverlayNavigator) { - val saveableStateHolder = rememberSaveableStateHolder() - val dialogBackStack by overlayNavigator.backStack.collectAsState() - val visibleBackStack = rememberVisibleList(dialogBackStack) - visibleBackStack.PopulateVisibleList(dialogBackStack) - - visibleBackStack.forEach { backStackEntry -> - val destination = backStackEntry.destination as OverlayNavigator.Destination - DisposableEffect(backStackEntry) { - onDispose { - overlayNavigator.onTransitionComplete(backStackEntry) - } - } - - // while in the scope of the composable, we provide the navBackStackEntry as the - // ViewModelStoreOwner and LifecycleOwner - backStackEntry.LocalOwnersProvider(saveableStateHolder) { - destination.content(backStackEntry) - } - } -} - -@Composable -internal fun MutableList.PopulateVisibleList( - transitionsInProgress: Collection, -) { - val isInspecting = LocalInspectionMode.current - transitionsInProgress.forEach { entry -> - DisposableEffect(entry.lifecycle) { - val observer = LifecycleEventObserver { _, event -> - // show dialog in preview - if (isInspecting && !contains(entry)) { - add(entry) - } - // ON_START -> add to visibleBackStack, ON_STOP -> remove from visibleBackStack - if (event == Lifecycle.Event.ON_START) { - // We want to treat the visible lists as Sets but we want to keep - // the functionality of mutableStateListOf() so that we recompose in response - // to adds and removes. - if (!contains(entry)) { - add(entry) - } - } - if (event == Lifecycle.Event.ON_STOP) { - remove(entry) - } - } - entry.lifecycle.addObserver(observer) - onDispose { - entry.lifecycle.removeObserver(observer) - } - } - } -} - -@Composable -internal fun rememberVisibleList( - transitionsInProgress: Collection, -): SnapshotStateList { - // show dialog in preview - val isInspecting = LocalInspectionMode.current - return remember(transitionsInProgress) { - mutableStateListOf().also { - it.addAll( - transitionsInProgress.filter { entry -> - if (isInspecting) { - true - } else { - entry.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED) - } - }, - ) - } - } -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayNavigator.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayNavigator.kt deleted file mode 100644 index 17a39c4ab..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/OverlayNavigator.kt +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2020 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.freeletics.khonshu.navigation.androidx.internal - -import androidx.compose.runtime.Composable -import androidx.navigation.FloatingWindow -import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavDestination -import androidx.navigation.NavOptions -import androidx.navigation.Navigator -import com.freeletics.khonshu.navigation.androidx.internal.OverlayNavigator.Destination - -@Navigator.Name("overlay") -internal class OverlayNavigator : Navigator() { - - /** - * Get the back stack from the [state]. - */ - internal val backStack get() = state.backStack - - override fun navigate( - entries: List, - navOptions: NavOptions?, - navigatorExtras: Extras?, - ) { - entries.forEach { entry -> - state.push(entry) - } - } - - override fun createDestination(): Destination { - return Destination(this) { } - } - - override fun popBackStack(popUpTo: NavBackStackEntry, savedState: Boolean) { - state.popWithTransition(popUpTo, savedState) - } - - internal fun onTransitionComplete(entry: NavBackStackEntry) { - state.markTransitionComplete(entry) - } - - /** - * NavDestination specific to [OverlayNavigator] - */ - @NavDestination.ClassType(Composable::class) - class Destination( - navigator: OverlayNavigator, - internal val content: @Composable (NavBackStackEntry) -> Unit, - ) : NavDestination(navigator), FloatingWindow -} diff --git a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/StoreViewModel.kt b/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/StoreViewModel.kt deleted file mode 100644 index 2415273a1..000000000 --- a/navigation-compose/src/main/kotlin/com/freeletics/khonshu/navigation/androidx/internal/StoreViewModel.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.freeletics.khonshu.navigation.androidx.internal - -import androidx.lifecycle.ViewModel -import com.freeletics.khonshu.navigation.internal.NavigationExecutor -import com.freeletics.khonshu.navigation.internal.NavigationExecutorStore - -internal class StoreViewModel private constructor( - store: NavigationExecutorStore, -) : ViewModel(store), NavigationExecutor.Store by store { - constructor() : this(NavigationExecutorStore()) -}