diff --git a/app/src/main/java/com/stslex93/notes/ui/AppInit.kt b/app/src/main/java/com/stslex93/notes/ui/AppInit.kt index 0de90b6e..96529365 100644 --- a/app/src/main/java/com/stslex93/notes/ui/AppInit.kt +++ b/app/src/main/java/com/stslex93/notes/ui/AppInit.kt @@ -9,12 +9,12 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.navigation.NavHostController import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController @Composable fun AppInit( - navController: NavHostController, + navController: NavExtrasHostController, modifier: Modifier = Modifier ) { val systemUiController = rememberSystemUiController() @@ -34,7 +34,7 @@ fun AppInit( .background(MaterialTheme.colorScheme.background) ) { NavigationHost( - navHostController = navController, + navController = navController, modifier = modifier ) } diff --git a/app/src/main/java/com/stslex93/notes/ui/MainActivity.kt b/app/src/main/java/com/stslex93/notes/ui/MainActivity.kt index 7e8d6c65..56c5933c 100644 --- a/app/src/main/java/com/stslex93/notes/ui/MainActivity.kt +++ b/app/src/main/java/com/stslex93/notes/ui/MainActivity.kt @@ -4,8 +4,8 @@ import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat -import androidx.navigation.compose.rememberNavController import com.stslex93.notes.core.navigation.di.NavigationComponentBuilder +import com.stslex93.notes.core.navigation.v2.controller.rememberNavExtrasController import com.stslex93.notes.core.ui.di.MainUiApi import com.stslex93.notes.core.ui.di.NavigationApi import com.stslex93.notes.core.ui.theme.AppTheme @@ -21,7 +21,7 @@ class MainActivity : AppCompatActivity(), MainUiApi { super.onCreate(savedInstanceState) WindowCompat.setDecorFitsSystemWindows(window, false) setContent { - val navController = rememberNavController() + val navController = rememberNavExtrasController() _navigationApi = NavigationComponentBuilder .build(navController) .also { api -> diff --git a/app/src/main/java/com/stslex93/notes/ui/NavigationHost.kt b/app/src/main/java/com/stslex93/notes/ui/NavigationHost.kt index 6023240a..827a6781 100644 --- a/app/src/main/java/com/stslex93/notes/ui/NavigationHost.kt +++ b/app/src/main/java/com/stslex93/notes/ui/NavigationHost.kt @@ -2,25 +2,22 @@ package com.stslex93.notes.ui import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation.NavHostController -import androidx.navigation.compose.NavHost -import com.stslex93.notes.core.navigation.model.AppDestination +import com.stslex93.notes.core.navigation.v2.compose.NavExtrasHost +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController import com.stslex93.notes.feature.edit.ui.init.editGraph import com.stslex93.notes.feature.edit_label.navigation.graph.editLabelGraph import com.stslex93.notes.feature.home.navigation.homeGraph @Composable fun NavigationHost( - navHostController: NavHostController, + navController: NavExtrasHostController, modifier: Modifier = Modifier, - startDestination: AppDestination = AppDestination.HOME ) { - NavHost( - navController = navHostController, - startDestination = startDestination.route + NavExtrasHost( + navController = navController, ) { homeGraph(modifier) editGraph(modifier) editLabelGraph(modifier) } -} \ No newline at end of file +} diff --git a/core/navigation/build.gradle.kts b/core/navigation/build.gradle.kts index 58974338..f8eff64c 100644 --- a/core/navigation/build.gradle.kts +++ b/core/navigation/build.gradle.kts @@ -1,6 +1,7 @@ plugins { id("notes.android.library") id("notes.android.library.compose") + id("kotlin-parcelize") } dependencies { diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponent.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponent.kt index 2aef3eb1..195fe311 100644 --- a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponent.kt +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponent.kt @@ -1,6 +1,6 @@ package com.stslex93.notes.core.navigation.di -import androidx.navigation.NavHostController +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController import com.stslex93.notes.core.ui.di.NavigationApi import dagger.BindsInstance import dagger.Component @@ -14,7 +14,7 @@ interface NavigationComponent : NavigationApi { interface Builder { @BindsInstance - fun controller(navHostController: NavHostController): Builder + fun controller(navController: NavExtrasHostController): Builder fun build(): NavigationApi } diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponentBuilder.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponentBuilder.kt index 6344a144..61f62133 100644 --- a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponentBuilder.kt +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationComponentBuilder.kt @@ -1,14 +1,14 @@ package com.stslex93.notes.core.navigation.di -import androidx.navigation.NavHostController +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController import com.stslex93.notes.core.ui.di.NavigationApi object NavigationComponentBuilder { fun build( - navHostController: NavHostController + navController: NavExtrasHostController ): NavigationApi = DaggerNavigationComponent .builder() - .controller(navHostController) + .controller(navController) .build() } \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationModule.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationModule.kt index 46448e37..dcbe5c3a 100644 --- a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationModule.kt +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/di/NavigationModule.kt @@ -1,6 +1,6 @@ package com.stslex93.notes.core.navigation.di -import com.stslex93.notes.core.navigation.navigator.NavigatorImpl +import com.stslex93.notes.core.navigation.v2.navigator.NavigatorImpl import com.stslex93.notes.core.ui.di.Navigator import dagger.Binds import dagger.Module diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/model/NavigationScreen.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/model/NavigationScreen.kt index b08ae944..8ecc9f9b 100644 --- a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/model/NavigationScreen.kt +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/model/NavigationScreen.kt @@ -2,42 +2,42 @@ package com.stslex93.notes.core.navigation.model import com.stslex93.notes.core.ui.di.Screen -sealed class NavigationScreen : Screen { - - abstract val screen: AppDestination - - val screenRoute: String - get() = "${screen.route}${appArgs.argumentsForRoute}" - - open val isSingleTop: Boolean - get() = false - - open val appArgs: AppArguments - get() = AppArguments.Empty - - data object HomeScreen : NavigationScreen() { - override val screen: AppDestination = AppDestination.HOME - override val isSingleTop: Boolean = true - } - - data class EditNoteScreen( - private val noteId: Int, - private val isEdit: Boolean - ) : NavigationScreen() { - override val screen: AppDestination = AppDestination.NOTE_EDIT - override val appArgs: AppArguments = AppArguments.NoteEdit(noteId, isEdit) - } - - data class EditLabelScreen( - private val noteIds: Set - ) : NavigationScreen() { - override val screen = AppDestination.LABEL_EDIT - override val appArgs = AppArguments.LabelEdit(noteIds) - } - - data object PopBackStack : NavigationScreen() { - - override val screen: AppDestination = AppDestination.UNDEFINED - override val appArgs: AppArguments = AppArguments.Empty - } -} \ No newline at end of file +//sealed class NavigationScreen : Screen { +// +// abstract val screen: AppDestination +// +// val screenRoute: String +// get() = "${screen.route}${appArgs.argumentsForRoute}" +// +// open val isSingleTop: Boolean +// get() = false +// +// open val appArgs: AppArguments +// get() = AppArguments.Empty +// +// data object HomeScreen : NavigationScreen() { +// override val screen: AppDestination = AppDestination.HOME +// override val isSingleTop: Boolean = true +// } +// +// data class EditNoteScreen( +// private val noteId: Int, +// private val isEdit: Boolean +// ) : NavigationScreen() { +// override val screen: AppDestination = AppDestination.NOTE_EDIT +// override val appArgs: AppArguments = AppArguments.NoteEdit(noteId, isEdit) +// } +// +// data class EditLabelScreen( +// private val noteIds: Set +// ) : NavigationScreen() { +// override val screen = AppDestination.LABEL_EDIT +// override val appArgs = AppArguments.LabelEdit(noteIds) +// } +// +// data object PopBackStack : NavigationScreen() { +// +// override val screen: AppDestination = AppDestination.UNDEFINED +// override val appArgs: AppArguments = AppArguments.Empty +// } +//} \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/navigator/NavigatorImpl.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/navigator/NavigatorImpl.kt index 05e32a3a..237fa8df 100644 --- a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/navigator/NavigatorImpl.kt +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/navigator/NavigatorImpl.kt @@ -1,38 +1,31 @@ package com.stslex93.notes.core.navigation.navigator -import androidx.navigation.NavHostController -import com.stslex.aproselection.core.core.Logger -import com.stslex93.notes.core.navigation.model.NavigationScreen -import com.stslex93.notes.core.ui.di.Navigator -import com.stslex93.notes.core.ui.di.Screen -import javax.inject.Inject - -class NavigatorImpl @Inject constructor( - private val navController: NavHostController -) : Navigator { - - override fun invoke(screen: Screen) { - when (screen) { - is NavigationScreen.PopBackStack -> navController.popBackStack() - is NavigationScreen -> navigateScreen(screen) - else -> { - Logger.debug("unresolve navigation route", this::class.simpleName) - } - } - } - - private fun navigateScreen(screen: NavigationScreen) { - val currentRoute = navController.currentDestination?.route ?: return - if (currentRoute == screen.screen.navigationRoute) return - - navController.navigate(screen.screenRoute) { - if (screen.isSingleTop.not()) return@navigate - - popUpTo(currentRoute) { - inclusive = true - saveState = true - } - launchSingleTop = true - } - } -} \ No newline at end of file +//class NavigatorImpl @Inject constructor( +// private val navController: NavHostController +//) : Navigator { +// +// override fun invoke(screen: Screen) { +// when (screen) { +// is NavigationScreen.PopBackStack -> navController.popBackStack() +// is NavigationScreen -> navigateScreen(screen) +// else -> { +// Logger.debug("unresolve navigation route", this::class.simpleName) +// } +// } +// } +// +// private fun navigateScreen(screen: NavigationScreen) { +// val currentRoute = navController.currentDestination?.route ?: return +// if (currentRoute == screen.screen.navigationRoute) return +// +// navController.navigate(screen.screenRoute) { +// if (screen.isSingleTop.not()) return@navigate +// +// popUpTo(currentRoute) { +// inclusive = true +// saveState = true +// } +// launchSingleTop = true +// } +// } +//} \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/NoteEditArgs.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/NoteEditArgs.kt new file mode 100644 index 00000000..6bc62b89 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/NoteEditArgs.kt @@ -0,0 +1,10 @@ +package com.stslex93.notes.core.navigation.v2 + +import android.os.Parcelable +import kotlinx.parcelize.Parcelize + +@Parcelize +data class NoteEditArgs( + val noteId: Int, + val isEdit: Boolean +) : Parcelable \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/ComposeGraph.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/ComposeGraph.kt new file mode 100644 index 00000000..7b7daeef --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/ComposeGraph.kt @@ -0,0 +1,64 @@ +package com.stslex93.notes.core.navigation.v2.compose + +import android.os.Build +import android.os.Bundle +import android.os.Parcelable +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 +import com.stslex93.notes.core.navigation.v2.controller.ComposeNavigator +import com.stslex93.notes.core.navigation.v2.controller.Destination +import com.stslex93.notes.core.navigation.v2.screen.HostScreen + +fun NavGraphBuilder.composable( + route: String, + arguments: List = emptyList(), + deepLinks: List = emptyList(), + content: @Composable (backStackEntry: NavBackStackEntry, arguments: Bundle?) -> Unit +) { + addDestination( + Destination(provider[ComposeNavigator::class], content).apply { + this.route = route + arguments.forEach { (argumentName, argument) -> + addArgument(argumentName, argument) + } + deepLinks.forEach { deepLink -> + addDeepLink(deepLink) + } + } + ) +} + +inline fun NavGraphBuilder.composable( + screen: HostScreen, + arguments: List = emptyList(), + deepLinks: List = emptyList(), + crossinline content: @Composable (backStackEntry: NavBackStackEntry, arguments: T?) -> Unit +) { + composable( + route = screen.route, + arguments = arguments, + deepLinks = deepLinks, + ) { backStackEntry, args -> + val data = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + args?.getParcelable(screen.key, T::class.java) + } else { + args?.getParcelable(screen.key) + } + content(backStackEntry, data) + } +} + +inline fun NavGraphBuilder.composable( + screen: HostScreen, + crossinline content: @Composable (data: T) -> Unit +) { + composable(screen) { _, arguments -> + arguments?.let { data -> + content(data) + } + } +} diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/NavHost.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/NavHost.kt new file mode 100644 index 00000000..448a6cf0 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/compose/NavHost.kt @@ -0,0 +1,150 @@ +package com.stslex93.notes.core.navigation.v2.compose + +import android.os.Bundle +import androidx.activity.compose.BackHandler +import androidx.activity.compose.LocalOnBackPressedDispatcherOwner +import androidx.compose.animation.Crossfade +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveableStateHolder +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner +import androidx.navigation.NavDestination +import androidx.navigation.NavGraph +import androidx.navigation.NavGraphBuilder +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 com.stslex93.notes.core.navigation.v2.controller.ComposeNavigator +import com.stslex93.notes.core.navigation.v2.controller.Destination +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController +import kotlinx.coroutines.flow.map + +@Composable +fun NavExtrasHost( + navController: NavExtrasHostController, + modifier: Modifier = Modifier, + route: String? = null, + builder: NavGraphBuilder.() -> Unit +) { + NavHost( + navController = navController, + remember(route, navController.startDestination.route, builder) { + navController.createGraph(navController.startDestination.route, route, builder) + }, + modifier + ) +} + +@Composable +fun NavHost( + navController: NavExtrasHostController, + graph: NavGraph, + modifier: Modifier = Modifier +) { + 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 + + // Setup the navController with proper owners + navController.setLifecycleOwner(lifecycleOwner) + navController.setViewModelStore(viewModelStoreOwner.viewModelStore) + + if (onBackPressedDispatcher != null) { + navController.setOnBackPressedDispatcher(onBackPressedDispatcher) + } + + // Ensure that the NavController only receives back events while + // the NavHost is in composition + + DisposableEffect(navController) { + navController.enableOnBackPressed(true) + onDispose { + navController.enableOnBackPressed(false) + } + } + + // Then set the graph + 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>( + ComposeNavigator.NAME + ) as? ComposeNavigator ?: return + val visibleEntries by remember(navController.visibleEntries) { + navController.visibleEntries.map { + it.filter { entry -> + entry.destination.navigatorName == ComposeNavigator.NAME + } + } + }.collectAsState(emptyList()) + + val screensBackStack by navController.currentScreensBackStack.collectAsState() + + val backStackEntry = visibleEntries.lastOrNull() + + var initialCrossfade by remember { mutableStateOf(true) } + if (backStackEntry != null) { + // while in the scope of the composable, we provide the navBackStackEntry as the + // ViewModelStoreOwner and LifecycleOwner + Crossfade( + targetState = backStackEntry.id, + modifier = modifier, + label = "crossfadde" + ) { + val lastEntry = visibleEntries.lastOrNull { entry -> + it == entry.id + } ?: return@Crossfade + // We are disposing on a Unit as we only want to dispose when the CrossFade completes + DisposableEffect(Unit) { + if (initialCrossfade) { + // There's no animation for the initial crossfade, + // so we can instantly mark the transition as complete + visibleEntries.forEach { entry -> + composeNavigator.onTransitionComplete(entry) + } + initialCrossfade = false + } + onDispose { + visibleEntries.forEach { entry -> + composeNavigator.onTransitionComplete(entry) + } + } + } + + lastEntry.LocalOwnersProvider(saveableStateHolder) { + val previousScreenExtras = screensBackStack[lastEntry.destination.route]?.arguments + + val newLastEntryArguments = (lastEntry.arguments ?: Bundle()).apply { + if (previousScreenExtras != null) { + putAll(previousScreenExtras) + } + } + + (lastEntry.destination as Destination).content(lastEntry, newLastEntryArguments) + } + } + } + + val dialogNavigator = navController.navigatorProvider.get>( + ComposeNavigator.NAME + ) as? DialogNavigator ?: return + + // Show any dialog destinations + DialogHost(dialogNavigator) +} \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/ComposeNavigator.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/ComposeNavigator.kt new file mode 100644 index 00000000..244a7742 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/ComposeNavigator.kt @@ -0,0 +1,27 @@ +package com.stslex93.notes.core.navigation.v2.controller + +import android.os.Bundle +import androidx.compose.runtime.Composable +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavDestination +import androidx.navigation.Navigator + +@Navigator.Name("composable") +class ComposeNavigator : Navigator() { + + override fun createDestination(): Destination = Destination(this) { _, _ -> } + + fun onTransitionComplete(entry: NavBackStackEntry) { + state.markTransitionComplete(entry) + } + + internal companion object { + const val NAME = "composable" + } +} + +@NavDestination.ClassType(Composable::class) +class Destination( + navigator: ComposeNavigator, + internal val content: @Composable (backStackEntry: NavBackStackEntry, arguments: Bundle?) -> Unit +) : NavDestination(navigator) \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/NavExtrasHostController.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/NavExtrasHostController.kt new file mode 100644 index 00000000..4f3d7bc2 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/controller/NavExtrasHostController.kt @@ -0,0 +1,84 @@ +package com.stslex93.notes.core.navigation.v2.controller + +import android.content.Context +import androidx.compose.runtime.Composable +import androidx.compose.runtime.saveable.Saver +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.platform.LocalContext +import androidx.navigation.NavDestination +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import androidx.navigation.NavOptionsBuilder +import androidx.navigation.Navigator +import androidx.navigation.compose.DialogNavigator +import com.stslex93.notes.core.navigation.v2.screen.Home +import com.stslex93.notes.core.ui.base.NavigationScreen +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update + +class NavExtrasHostController( + context: Context, + val startDestination: NavigationScreen +) : NavHostController(context) { + + private val _currentScreensBackStack: MutableStateFlow> = + MutableStateFlow(mutableMapOf(startDestination.route to startDestination)) + + val currentScreensBackStack: StateFlow> = + _currentScreensBackStack.asStateFlow() + + override fun popBackStack(): Boolean { + _currentScreensBackStack.update { screensMap -> + screensMap.apply { remove(currentDestination?.route) } + } + return super.popBackStack() + } + + fun navigate(screen: NavigationScreen, navOptions: NavOptions? = null) { + _currentScreensBackStack.update { it.apply { put(screen.route, screen) } } + navigate(route = screen.route, navOptions = navOptions) + } + + fun navigate(screen: NavigationScreen, builder: NavOptionsBuilder.() -> Unit) { + navigate(route = screen.route, builder = builder) + } +} + +private fun NavExtrasControllerSaver( + context: Context, + startDestination: NavigationScreen +): Saver = Saver( + save = { it.saveState() }, + restore = { createNavExtrasController(context, startDestination).apply { restoreState(it) } } +) + +private fun createNavExtrasController( + context: Context, + startDestination: NavigationScreen +) = NavExtrasHostController( + context = context, + startDestination = startDestination +).apply { + navigatorProvider.addNavigator(ComposeNavigator()) + navigatorProvider.addNavigator(DialogNavigator()) +} + +@Composable +fun rememberNavExtrasController( + startDestination: NavigationScreen = Home, + vararg navigators: Navigator +): NavExtrasHostController { + val context = LocalContext.current + return rememberSaveable( + inputs = navigators, + saver = NavExtrasControllerSaver(context, startDestination) + ) { + createNavExtrasController(context, startDestination) + }.apply { + for (navigator in navigators) { + navigatorProvider.addNavigator(navigator) + } + } +} \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/navigator/NavigatorImpl.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/navigator/NavigatorImpl.kt new file mode 100644 index 00000000..107ecd9c --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/navigator/NavigatorImpl.kt @@ -0,0 +1,40 @@ +package com.stslex93.notes.core.navigation.v2.navigator + +import androidx.navigation.NavOptions +import com.stslex93.notes.core.navigation.v2.controller.NavExtrasHostController +import com.stslex93.notes.core.ui.base.NavigationScreen +import com.stslex93.notes.core.ui.di.Navigator +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class NavigatorImpl @Inject constructor( + private val navController: NavExtrasHostController +) : Navigator { + + override fun navigate(screen: NavigationScreen) { + + val options = navController.currentDestination + ?.route + ?.takeIf { screen.isSingleTop } + ?.let { currentRoute -> + NavOptions.Builder().apply { + setPopUpTo( + route = currentRoute, + inclusive = true, + saveState = true + ) + setLaunchSingleTop(true) + } + }?.build() + + navController.navigate( + screen = screen, + navOptions = options + ) + } + + override fun popBackStack() { + navController.popBackStack() + } +} diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/AppNavScreen.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/AppNavScreen.kt new file mode 100644 index 00000000..52fee411 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/AppNavScreen.kt @@ -0,0 +1,25 @@ +package com.stslex93.notes.core.navigation.v2.screen + +import com.stslex93.notes.core.navigation.v2.NoteEditArgs +import com.stslex93.notes.core.ui.base.NavigationScreen +import com.stslex93.notes.core.ui.base.saveBundle + +data object Home : NavigationScreen(HostScreen.HOME.route) { + + override val isSingleTop: Boolean = true +} + +data class NoteEdit( + val noteEdit: NoteEditArgs +) : NavigationScreen( + route = HostScreen.NOTE_EDIT.route, + extra = Extra(HostScreen.NOTE_EDIT.key, noteEdit) +) + +//sealed class AppNavScreen( +// route: String, +// extra: Extra? = null +//) : NavigationScreen(route, extra.saveBundle) { +// +// +//} \ No newline at end of file diff --git a/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/HostScreen.kt b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/HostScreen.kt new file mode 100644 index 00000000..689ca9b3 --- /dev/null +++ b/core/navigation/src/main/java/com/stslex93/notes/core/navigation/v2/screen/HostScreen.kt @@ -0,0 +1,9 @@ +package com.stslex93.notes.core.navigation.v2.screen + +enum class HostScreen { + HOME, + NOTE_EDIT; + + val route: String = name.lowercase() + val key: String = "${name.lowercase()}_key" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/stslex93/notes/core/ui/base/NavigationScreen.kt b/core/ui/src/main/java/com/stslex93/notes/core/ui/base/NavigationScreen.kt new file mode 100644 index 00000000..b4338857 --- /dev/null +++ b/core/ui/src/main/java/com/stslex93/notes/core/ui/base/NavigationScreen.kt @@ -0,0 +1,23 @@ +package com.stslex93.notes.core.ui.base + +import android.os.Bundle +import android.os.Parcelable +import androidx.core.os.bundleOf + +abstract class NavigationScreen( + open val route: String, + open val arguments: Bundle? = null +) { + constructor(route: String, extra: Extra) : this(route, extra.saveBundle) + + data class Extra(val key: String, val parcelable: Parcelable) + + open val isSingleTop: Boolean = false +} + +val NavigationScreen.Extra?.saveBundle: Bundle + get() = bundleOf().apply { + this@saveBundle?.let { extra -> + putParcelable(extra.key, extra.parcelable) + } + } \ No newline at end of file diff --git a/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Navigator.kt b/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Navigator.kt index 9fdd9b6d..fb15cae9 100644 --- a/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Navigator.kt +++ b/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Navigator.kt @@ -1,5 +1,10 @@ package com.stslex93.notes.core.ui.di -fun interface Navigator { - operator fun invoke(screen: Screen) +import com.stslex93.notes.core.ui.base.NavigationScreen + +interface Navigator { + + fun navigate(screen: NavigationScreen) + + fun popBackStack() } diff --git a/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Screen.kt b/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Screen.kt deleted file mode 100644 index dd44090b..00000000 --- a/core/ui/src/main/java/com/stslex93/notes/core/ui/di/Screen.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.stslex93.notes.core.ui.di - -interface Screen \ No newline at end of file diff --git a/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/EditNoteViewModel.kt b/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/EditNoteViewModel.kt index 6ccc0281..6158e484 100644 --- a/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/EditNoteViewModel.kt +++ b/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/EditNoteViewModel.kt @@ -1,7 +1,6 @@ package com.stslex93.notes.feature.edit.ui -import com.stslex93.notes.core.navigation.model.AppArguments -import com.stslex93.notes.core.navigation.model.NavigationScreen +import com.stslex93.notes.core.navigation.v2.NoteEditArgs import com.stslex93.notes.core.ui.base.BaseViewModel import com.stslex93.notes.core.ui.di.Navigator import com.stslex93.notes.feature.edit.ui.store.EditStore @@ -12,7 +11,7 @@ class EditNoteViewModel @Inject constructor( private val navigator: Navigator, ) : BaseViewModel(store) { - fun init(arguments: AppArguments.NoteEdit) { + fun init(arguments: NoteEditArgs) { sendAction( EditStore.Action.Init( id = arguments.noteId, @@ -22,6 +21,6 @@ class EditNoteViewModel @Inject constructor( } fun popBackStack() { - navigator(NavigationScreen.PopBackStack) + navigator.popBackStack() } } \ No newline at end of file diff --git a/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/init/EditGraph.kt b/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/init/EditGraph.kt index c55747f7..140b9edb 100644 --- a/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/init/EditGraph.kt +++ b/feature/edit/src/main/java/com/stslex93/notes/feature/edit/ui/init/EditGraph.kt @@ -3,29 +3,15 @@ package com.stslex93.notes.feature.edit.ui.init import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Modifier import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.stslex93.notes.core.navigation.model.AppArguments -import com.stslex93.notes.core.navigation.model.AppDestination -import com.stslex93.notes.core.navigation.utils.NavExt.composableArguments -import com.stslex93.notes.core.navigation.utils.NavExt.parseArguments +import com.stslex93.notes.core.navigation.v2.NoteEditArgs +import com.stslex93.notes.core.navigation.v2.compose.composable +import com.stslex93.notes.core.navigation.v2.screen.HostScreen import com.stslex93.notes.feature.edit.di.setupComponent fun NavGraphBuilder.editGraph( modifier: Modifier = Modifier, ) { - composable( - route = AppDestination.NOTE_EDIT.navigationRoute, - arguments = AppDestination.NOTE_EDIT.composableArguments - ) { navBackStackEntry -> - - val arguments = AppDestination.NOTE_EDIT - .parseArguments(navBackStackEntry) - .let { args -> - AppArguments.NoteEdit( - noteId = args[0].toIntOrNull() ?: -1, - isEdit = args[1].toBooleanStrictOrNull() ?: false - ) - } + composable(HostScreen.NOTE_EDIT) { arguments -> val viewModel = setupComponent(arguments.hashCode().toString()) diff --git a/feature/home/src/main/java/com/stslex93/notes/feature/home/navigation/HomeGraph.kt b/feature/home/src/main/java/com/stslex93/notes/feature/home/navigation/HomeGraph.kt index 8dfcf228..7ab98a24 100644 --- a/feature/home/src/main/java/com/stslex93/notes/feature/home/navigation/HomeGraph.kt +++ b/feature/home/src/main/java/com/stslex93/notes/feature/home/navigation/HomeGraph.kt @@ -2,17 +2,15 @@ package com.stslex93.notes.feature.home.navigation import androidx.compose.ui.Modifier import androidx.navigation.NavGraphBuilder -import androidx.navigation.compose.composable -import com.stslex93.notes.core.navigation.model.AppDestination +import com.stslex93.notes.core.navigation.v2.compose.composable +import com.stslex93.notes.core.navigation.v2.screen.HostScreen import com.stslex93.notes.feature.home.di.setupComponent import com.stslex93.notes.feature.home.ui.init.HomeScreeInit fun NavGraphBuilder.homeGraph( modifier: Modifier = Modifier, ) { - composable( - route = AppDestination.HOME.navigationRoute - ) { + composable(HostScreen.HOME.route) { _, _ -> HomeScreeInit( modifier = modifier, viewModel = setupComponent()