Initial commit

This commit is contained in:
vfsfitvnm
2022-06-02 18:59:18 +02:00
commit 1e673ad582
160 changed files with 10800 additions and 0 deletions

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"/>

View File

@@ -0,0 +1,111 @@
package it.vfsfitvnm.route
import androidx.compose.runtime.*
import androidx.compose.runtime.saveable.SaverScope
import androidx.compose.runtime.saveable.rememberSaveable
@Stable
open class Route internal constructor(val tag: String) {
override fun equals(other: Any?): Boolean {
return when {
this === other -> true
other is Route -> tag == other.tag
else -> false
}
}
override fun hashCode(): Int {
return tag.hashCode()
}
object GlobalEmitter {
var listener: ((Route) -> Unit)? = null
}
object Saver : androidx.compose.runtime.saveable.Saver<Route?, String> {
override fun restore(value: String): Route? = value.takeIf(String::isNotEmpty)?.let(::Route)
override fun SaverScope.save(value: Route?): String = value?.tag ?: ""
}
}
@Composable
fun rememberRoute(route: Route? = null): MutableState<Route?> {
return rememberSaveable(stateSaver = Route.Saver) {
mutableStateOf(route)
}
}
@Stable
class Route0(
tag: String
) : Route(tag) {
context(RouteHandlerScope)
@Composable
inline operator fun invoke(content: @Composable () -> Unit) {
if (this == route) {
content()
}
}
fun global() {
GlobalEmitter.listener?.invoke(this)
}
}
@Stable
class Route1<P0>(
tag: String,
state0: MutableState<P0>
) : Route(tag) {
var p0 by state0
context(RouteHandlerScope)
@Composable
inline operator fun invoke(content: @Composable (P0) -> Unit) {
if (this == route) {
if (route is Route1<*>) {
@Suppress("UNCHECKED_CAST")
(route as Route1<P0>).let { route ->
this.p0 = route.p0
}
}
content(this.p0)
}
}
fun global(p0: P0 = this.p0) {
this.p0 = p0
GlobalEmitter.listener?.invoke(this)
}
}
@Stable
class Route2<P0, P1>(
tag: String,
state0: MutableState<P0>,
state1: MutableState<P1>
) : Route(tag) {
var p0 by state0
var p1 by state1
context(RouteHandlerScope)
@Composable
inline operator fun invoke(content: @Composable (P0, P1) -> Unit) {
if (this == route) {
if (route is Route2<*, *>) {
@Suppress("UNCHECKED_CAST")
(route as Route2<P0, P1>).let { route ->
this.p0 = route.p0
this.p1 = route.p1
}
}
content(this.p0, this.p1)
}
}
fun global(p0: P0 = this.p0, p1: P1 = this.p1) {
this.p0 = p0
this.p1 = p1
GlobalEmitter.listener?.invoke(this)
}
}

View File

@@ -0,0 +1,73 @@
package it.vfsfitvnm.route
import androidx.activity.compose.BackHandler
import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.animation.core.updateTransition
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ExperimentalAnimationApi
@Composable
fun RouteHandler(
modifier: Modifier = Modifier,
listenToGlobalEmitter: Boolean = false,
handleBackPress: Boolean = true,
transitionSpec: AnimatedContentScope<RouteHandlerScope>.() -> ContentTransform = { fastFade },
content: @Composable RouteHandlerScope.() -> Unit
) {
var route by rememberRoute()
RouteHandler(
route = route,
onRouteChanged = { route = it },
listenToGlobalEmitter = listenToGlobalEmitter,
handleBackPress = handleBackPress,
transitionSpec = transitionSpec,
modifier = modifier,
content = content
)
}
@ExperimentalAnimationApi
@Composable
fun RouteHandler(
route: Route?,
onRouteChanged: (Route?) -> Unit,
modifier: Modifier = Modifier,
listenToGlobalEmitter: Boolean = false,
handleBackPress: Boolean = true,
transitionSpec: AnimatedContentScope<RouteHandlerScope>.() -> ContentTransform = { fastFade },
content: @Composable RouteHandlerScope.() -> Unit
) {
val backDispatcher = LocalOnBackPressedDispatcherOwner.current?.onBackPressedDispatcher
val scope = remember(route) {
RouteHandlerScope(
route = route,
push = onRouteChanged,
pop = { if (handleBackPress) backDispatcher?.onBackPressed() else onRouteChanged(null) }
)
}
if (listenToGlobalEmitter) {
LaunchedEffect(route) {
Route.GlobalEmitter.listener = if (route == null) onRouteChanged else null
}
}
BackHandler(enabled = handleBackPress && route != null) {
onRouteChanged(null)
}
updateTransition(targetState = scope, label = null).AnimatedContent(
transitionSpec = transitionSpec,
contentKey = { it.route?.tag },
modifier = modifier,
) {
it.content()
}
}

View File

@@ -0,0 +1,40 @@
package it.vfsfitvnm.route
import android.annotation.SuppressLint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Immutable
@Immutable
class RouteHandlerScope(
val route: Route?,
private val push: (Route?) -> Unit,
val pop: () -> Unit,
) {
@SuppressLint("ComposableNaming")
@Composable
inline fun host(content: @Composable () -> Unit) {
if (route == null) {
content()
}
}
operator fun Route0.invoke() {
push(this)
}
operator fun <P0> Route1<P0>.invoke(
p0: P0 = this.p0
) {
this.p0 = p0
push(this)
}
operator fun <P0, P1> Route2<P0, P1>.invoke(
p0: P0 = this.p0,
p1: P1 = this.p1
) {
this.p0 = p0
this.p1 = p1
push(this)
}
}

View File

@@ -0,0 +1,13 @@
package it.vfsfitvnm.route
import androidx.compose.animation.*
import androidx.compose.animation.core.tween
@ExperimentalAnimationApi
val AnimatedContentScope<RouteHandlerScope>.fastFade: ContentTransform
get() = fadeIn(tween(200)) with fadeOut(tween(200))
@ExperimentalAnimationApi
val AnimatedContentScope<RouteHandlerScope>.empty: ContentTransform
get() = EnterTransition.None with ExitTransition.None