|
|
@@ -6,9 +6,20 @@
|
|
|
* w: https://mjdev.org
|
|
|
*/
|
|
|
|
|
|
+@file:Suppress("MemberVisibilityCanBePrivate")
|
|
|
+
|
|
|
package org.mjdev.tvapp.base.navigation
|
|
|
|
|
|
+import android.os.Bundle
|
|
|
import androidx.annotation.CallSuper
|
|
|
+import androidx.annotation.MainThread
|
|
|
+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.slideInVertically
|
|
|
+import androidx.compose.animation.slideOutVertically
|
|
|
+import androidx.compose.foundation.background
|
|
|
import androidx.compose.foundation.layout.Arrangement
|
|
|
import androidx.compose.foundation.layout.Column
|
|
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
|
@@ -16,10 +27,14 @@ import androidx.compose.runtime.Composable
|
|
|
import androidx.compose.ui.Alignment
|
|
|
import androidx.compose.ui.Modifier
|
|
|
import androidx.compose.ui.graphics.Color
|
|
|
+import androidx.compose.ui.graphics.RectangleShape
|
|
|
import androidx.compose.ui.graphics.vector.ImageVector
|
|
|
import androidx.compose.ui.unit.sp
|
|
|
import androidx.navigation.NamedNavArgument
|
|
|
import androidx.navigation.NavBackStackEntry
|
|
|
+import androidx.navigation.NavController
|
|
|
+import androidx.navigation.NavDeepLink
|
|
|
+import androidx.navigation.NavDestination
|
|
|
import androidx.navigation.NavHostController
|
|
|
import androidx.navigation.compose.composable
|
|
|
import androidx.tv.material3.Text
|
|
|
@@ -37,7 +52,7 @@ import kotlin.reflect.full.createInstance
|
|
|
* @constructor Create empty constructor for screen
|
|
|
*/
|
|
|
@Suppress("unused")
|
|
|
-open class Screen {
|
|
|
+open class Screen : NavController.OnDestinationChangedListener {
|
|
|
|
|
|
/**
|
|
|
* Autogenerated path / route
|
|
|
@@ -78,6 +93,11 @@ open class Screen {
|
|
|
*/
|
|
|
open val menuIcon: ImageVector? = null
|
|
|
|
|
|
+ /**
|
|
|
+ * Show once and hide on back pressed, remove from backstack
|
|
|
+ */
|
|
|
+ open val showOnce = false
|
|
|
+
|
|
|
/**
|
|
|
* Autogenerated menu item, if any
|
|
|
* Generated only if [menuTitleResId] > 0
|
|
|
@@ -102,7 +122,7 @@ open class Screen {
|
|
|
|
|
|
/**
|
|
|
* Compose function for compose screen.
|
|
|
- * This should be only a method overriden in super classes.
|
|
|
+ * This should be only a method overridden in super classes.
|
|
|
*
|
|
|
* @param navController Nav controller
|
|
|
* @param backStackEntry Back stack entry
|
|
|
@@ -116,22 +136,43 @@ open class Screen {
|
|
|
args: Map<String, Any?>
|
|
|
) {
|
|
|
|
|
|
+ if (showOnce) {
|
|
|
+ navController?.addOnDestinationChangedListener(this)
|
|
|
+ }
|
|
|
+
|
|
|
Column(
|
|
|
- modifier = Modifier.fillMaxSize(),
|
|
|
+ modifier = Modifier
|
|
|
+ .fillMaxSize()
|
|
|
+ .background(Color.DarkGray, RectangleShape),
|
|
|
verticalArrangement = Arrangement.Center,
|
|
|
horizontalAlignment = Alignment.CenterHorizontally
|
|
|
) {
|
|
|
+
|
|
|
Text(
|
|
|
text = "Empty Screen",
|
|
|
fontSize = 32.sp,
|
|
|
color = Color.White
|
|
|
)
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
+ override fun onDestinationChanged(
|
|
|
+ controller: NavController,
|
|
|
+ destination: NavDestination,
|
|
|
+ arguments: Bundle?
|
|
|
+ ) {
|
|
|
+ if (showOnce && destination.route == completeRoute) {
|
|
|
+ controller.popBackStack()
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
companion object {
|
|
|
|
|
|
+ val SlideInVertically = slideInVertically(animationSpec = tween(1000))
|
|
|
+ val SlideOutVertically = slideOutVertically(animationSpec = tween(1000))
|
|
|
+
|
|
|
/**
|
|
|
* Helper function to get params from route.
|
|
|
*
|
|
|
@@ -146,18 +187,8 @@ open class Screen {
|
|
|
defaultValue: T
|
|
|
): T = (arguments?.get(argId) as? T?) ?: defaultValue
|
|
|
|
|
|
- /**
|
|
|
- * Open screen extension.
|
|
|
- *
|
|
|
- * Function opens a screen with parameters given, if any,
|
|
|
- * from another screen.
|
|
|
- *
|
|
|
- * @param navController Nav controller
|
|
|
- * @param values Values
|
|
|
- * @param T T
|
|
|
- */
|
|
|
- inline fun <reified T : Screen> open(
|
|
|
- navController: NavHostController?,
|
|
|
+ @MainThread
|
|
|
+ inline fun <reified T : Screen> NavHostController.open(
|
|
|
vararg values: Any?
|
|
|
) {
|
|
|
val instance = T::class.createInstance()
|
|
|
@@ -171,10 +202,33 @@ open class Screen {
|
|
|
}
|
|
|
routeImpl
|
|
|
}.also { finalRoute ->
|
|
|
- val currentRoute = navController?.currentRoute
|
|
|
+ val currentRoute = currentRoute
|
|
|
val equals = currentRoute?.equals(finalRoute)
|
|
|
if (equals != true) {
|
|
|
- navController?.navigate(finalRoute)
|
|
|
+ navigate(finalRoute)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @MainThread
|
|
|
+ inline fun <reified T : Screen> NavHostController.openClear(
|
|
|
+ vararg values: Any?
|
|
|
+ ) {
|
|
|
+ val instance = T::class.createInstance()
|
|
|
+ instance.completeRoute.let { r ->
|
|
|
+ var routeImpl = r
|
|
|
+ instance.args.forEachIndexed { idx, arg ->
|
|
|
+ routeImpl = routeImpl.replace(
|
|
|
+ "{${arg.name}}",
|
|
|
+ (values[idx] ?: "").toString()
|
|
|
+ )
|
|
|
+ }
|
|
|
+ routeImpl
|
|
|
+ }.also { finalRoute ->
|
|
|
+ val currentRoute = currentRoute
|
|
|
+ val equals = currentRoute?.equals(finalRoute)
|
|
|
+ if (equals != true) {
|
|
|
+ navigate(finalRoute)
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
@@ -215,17 +269,41 @@ open class Screen {
|
|
|
*/
|
|
|
fun <T : Screen> NavGraphBuilderEx.screen(
|
|
|
route: T,
|
|
|
- isHomeScreen: Boolean = false
|
|
|
+ isHomeScreen: Boolean = false,
|
|
|
+ isStartScreen: Boolean = false,
|
|
|
+ deepLinks: List<NavDeepLink> = emptyList(),
|
|
|
+ enterTransition: (@JvmSuppressWildcards
|
|
|
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? = {
|
|
|
+ SlideInVertically
|
|
|
+ },
|
|
|
+ exitTransition: (@JvmSuppressWildcards
|
|
|
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? = {
|
|
|
+ SlideOutVertically
|
|
|
+ },
|
|
|
+ popEnterTransition: (@JvmSuppressWildcards
|
|
|
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> EnterTransition?)? =
|
|
|
+ enterTransition,
|
|
|
+ popExitTransition: (@JvmSuppressWildcards
|
|
|
+ AnimatedContentTransitionScope<NavBackStackEntry>.() -> ExitTransition?)? =
|
|
|
+ exitTransition,
|
|
|
) {
|
|
|
+ if (isStartScreen) {
|
|
|
+ splashDestinationRoute = route.completeRoute
|
|
|
+ }
|
|
|
if (isHomeScreen) {
|
|
|
- startDestinationRouteEx = route.completeRoute
|
|
|
+ homeDestinationRoute = route.completeRoute
|
|
|
}
|
|
|
route.menuItem?.also { menuItem ->
|
|
|
menuItems.add(menuItem)
|
|
|
}
|
|
|
composable(
|
|
|
route = route.completeRoute,
|
|
|
- arguments = route.args
|
|
|
+ arguments = route.args,
|
|
|
+ enterTransition = enterTransition,
|
|
|
+ exitTransition = exitTransition,
|
|
|
+ deepLinks = deepLinks,
|
|
|
+ popEnterTransition = popEnterTransition,
|
|
|
+ popExitTransition = popExitTransition
|
|
|
) { be ->
|
|
|
val rArgs = mutableMapOf<String, Any?>().apply {
|
|
|
route.args.forEach { arg ->
|