Instantly share code, notes, and snippets.
Last active
December 21, 2023 20:19
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save Andrew0000/a99124c0f06f73ebd02c2515752c4f72 to your computer and use it in GitHub Desktop.
NavigationMediator + NavigationUpdater
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import androidx.annotation.MainThread | |
| import kotlinx.coroutines.CoroutineScope | |
| import kotlinx.coroutines.Dispatchers | |
| import kotlinx.coroutines.SupervisorJob | |
| import kotlinx.coroutines.flow.Flow | |
| import kotlinx.coroutines.flow.MutableSharedFlow | |
| import kotlinx.coroutines.launch | |
| import timber.log.Timber | |
| class NavigationMediator { | |
| val startScreen = AppScreen.MAP | |
| private var navRequestCount = 0 | |
| private val _requestedScreen = MutableSharedFlow<NavRequest>() | |
| val requestedScreen: Flow<NavRequest?> = _requestedScreen | |
| // Compose Navigation doesn't allow to pass arguments beyond query parameters | |
| // which is not very convenient. | |
| // Here is a possible workaround - our own arguments storage. | |
| // TODO think about clearing or use another navigation library | |
| private val requestedScreenArgs = mutableMapOf<AppScreen, Map<String, Any>>() | |
| private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main) | |
| fun requestScreen(screen: AppScreen, args: Map<String, Any>? = null) { | |
| scope.launch { | |
| args?.let { | |
| requestedScreenArgs[screen] = it | |
| } | |
| Timber.d("[Navigation] request screen: $screen") | |
| _requestedScreen.emit(screen.toNavRequest()) | |
| } | |
| } | |
| @MainThread | |
| fun getArgs(screen: AppScreen): Map<String, Any>? = | |
| requestedScreenArgs[screen] | |
| private fun AppScreen.toNavRequest() = | |
| NavRequest(this, uid = navRequestCount++) | |
| } | |
| data class NavRequest( | |
| val target: AppScreen, | |
| val uid: Int, | |
| ) | |
| object NavArg { | |
| const val MAP_POINT = "map_point" | |
| const val NEW_LAT_LNG = "new_lat_lng" | |
| } | |
| enum class AppScreen(val id: String) { | |
| MAP("map"), | |
| LIST("list"), | |
| DETAILS("details"), | |
| } | |
| @Composable | |
| private fun RowScope.NavItem( | |
| target: AppScreen, | |
| @DrawableRes imageId: Int, | |
| navController : NavHostController, | |
| ) { | |
| val currentBackStack = navController.currentBackStack.collectAsState() | |
| val currentBackStackLast = currentBackStack.value.lastOrNull() | |
| NavigationBarItem( | |
| selected = currentBackStackLast?.destination?.route == target.id, | |
| icon = { | |
| Icon( | |
| painterResource(imageId), | |
| contentDescription = null, | |
| ) | |
| }, | |
| onClick = { | |
| navigationMediator.requestScreen(target) | |
| } | |
| ) | |
| } | |
| @Composable | |
| private fun AppNavigationHost(navController: NavHostController) { | |
| NavHost( | |
| modifier = Modifier | |
| .fillMaxSize() | |
| .padding(bottom = Dimensions.navBarHeight), | |
| navController = navController, | |
| startDestination = navigationMediator.startScreen.id, | |
| ) { | |
| composable( | |
| AppScreen.MAP.id, | |
| enterTransition = { slideInHorizontally(initialOffsetX = { -it }) }, | |
| exitTransition = { slideOutHorizontally(targetOffsetX = { -it }) }, | |
| popEnterTransition = { slideInHorizontally(initialOffsetX = { -it }) }, | |
| popExitTransition = { slideOutHorizontally(targetOffsetX = { -it }) }, | |
| ) { MapLayout() } | |
| composable( | |
| AppScreen.LIST.id, | |
| enterTransition = { slideInHorizontally(initialOffsetX = { it }) }, | |
| exitTransition = { slideOutHorizontally(targetOffsetX = { it }) }, | |
| popEnterTransition = { slideInHorizontally(initialOffsetX = { it }) }, | |
| popExitTransition = { slideOutHorizontally(targetOffsetX = { it }) }, | |
| ) { ListLayout() } | |
| composable( | |
| AppScreen.DETAILS.id, | |
| enterTransition = { slideInVertically(initialOffsetY = { it }) }, | |
| exitTransition = { slideOutVertically(targetOffsetY = { it }) }, | |
| popEnterTransition = { slideInVertically(initialOffsetY = { it }) }, | |
| popExitTransition = { slideOutVertically(targetOffsetY = { it }) }, | |
| ) { | |
| val args = navigationMediator.getArgs(AppScreen.DETAILS) | |
| DetailsLayout( | |
| newLatLng = args?.get(NavArg.NEW_LAT_LNG) as? LatLng, | |
| existingPoint = args?.get(NavArg.MAP_POINT) as? MapPoint, | |
| ) | |
| } | |
| } | |
| } | |
| @Composable | |
| private fun NavigationUpdater( | |
| navController: NavHostController, | |
| ) { | |
| val requestedScreen = navigationMediator.requestedScreen.collectAsState(null) | |
| val requestedScreenValue = requestedScreen.value ?: return | |
| LaunchedEffect(requestedScreenValue) { | |
| Timber.d("[Navigation] requestedScreen: $requestedScreenValue") | |
| val currentRoute = navController.currentDestination?.route | |
| val target = requestedScreenValue.target | |
| if (currentRoute != target.id) { | |
| if (target.needPopBack()) { | |
| navController.popBackStack() | |
| } | |
| navController.navigate(target.id) | |
| } | |
| } | |
| } | |
| private fun AppScreen.needPopBack() = | |
| this != AppScreen.DETAILS | |
| private fun String?.isRootRoute() = | |
| this == AppScreen.MAP.id || this == AppScreen.LIST.id |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment