Skip to content

Commit

Permalink
Improve API and restoring process
Browse files Browse the repository at this point in the history
  • Loading branch information
Sergej Shafarenka committed Oct 5, 2024
1 parent dab643a commit c39c8aa
Show file tree
Hide file tree
Showing 15 changed files with 163 additions and 135 deletions.
4 changes: 2 additions & 2 deletions componental-compose/src/commonMain/kotlin/DialogSlot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import androidx.compose.runtime.Composable
import de.halfbit.componental.router.slot.Slot

@Composable
public inline fun <I : Any, C : Any> DialogSlot(
slot: Slot<I, C>,
public inline fun <C : Any> DialogSlot(
slot: Slot<C>,
content: @Composable (child: C) -> Unit,
) {
val active = slot.active
Expand Down
28 changes: 16 additions & 12 deletions componental-compose/src/commonMain/kotlin/ModalBottomSheetSlot.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@
package de.halfbit.componental.compose

import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.*
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.SheetState
import androidx.compose.material3.SheetValue
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
Expand All @@ -16,12 +20,12 @@ import de.halfbit.componental.router.RouteNode
import de.halfbit.componental.router.slot.Slot

@Composable
public fun <I : Any, C : Any> ModalBottomSheetSlot(
slot: Slot<I, C>,
public fun <C : Any> ModalBottomSheetSlot(
slot: Slot<C>,
onSlotDismissed: () -> Unit,
content: @Composable (id: I, child: C) -> Unit,
content: @Composable (child: C) -> Unit,
) {
var current by remember { mutableStateOf<BottomSheetSlotState<I, C>?>(null) }
var current by remember { mutableStateOf<BottomSheetSlotState<C>?>(null) }
val density = LocalDensity.current
val active = slot.active

Expand Down Expand Up @@ -55,12 +59,12 @@ public fun <I : Any, C : Any> ModalBottomSheetSlot(
current?.content?.invoke()
}

private fun <I : Any, C : Any> createBottomSheetSlotState(
active: RouteNode<I, C>,
private fun <C : Any> createBottomSheetSlotState(
active: RouteNode<C>,
density: Density,
onSlotDismissed: () -> Unit,
content: @Composable (id: I, child: C) -> Unit,
): BottomSheetSlotState<I, C> {
content: @Composable (child: C) -> Unit,
): BottomSheetSlotState<C> {
val sheetState = SheetState(
skipPartiallyExpanded = true,
density = density,
Expand All @@ -79,14 +83,14 @@ private fun <I : Any, C : Any> createBottomSheetSlotState(
onDismissRequest = onSlotDismissed,
dragHandle = {}, // disable drag handle
scrimColor = MaterialTheme.colorScheme.scrim.copy(scrimAlpha),
content = { content(active.route, active.child) }
content = { content(active.child) }
)
}
)
}

private class BottomSheetSlotState<out I : Any, out C : Any>(
val component: RouteNode<I, C>,
private class BottomSheetSlotState<out C : Any>(
val component: RouteNode<C>,
val sheetState: SheetState,
val content: @Composable () -> Unit,
)
4 changes: 2 additions & 2 deletions componental-compose/src/commonMain/kotlin/StackChildren.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ import androidx.compose.runtime.Composable
import de.halfbit.componental.router.stack.Stack

@Composable
public inline fun <R : Any, C : Any> StackChildren(
stack: Stack<R, C>,
public inline fun <C : Any> StackChildren(
stack: Stack<C>,
crossinline content: @Composable (active: C) -> Unit,
) {
AnimatedContent(
Expand Down
17 changes: 13 additions & 4 deletions componental-compose/src/commonMain/kotlin/ToastSlot.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
package de.halfbit.componental.compose

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -13,8 +22,8 @@ import androidx.compose.ui.unit.dp
import de.halfbit.componental.router.slot.Slot

@Composable
public inline fun <I : Any, C : Any> BoxScope.ToastSlot(
slot: Slot<I, C>,
public inline fun <C : Any> BoxScope.ToastSlot(
slot: Slot<C>,
modifier: Modifier = Modifier,
crossinline content: @Composable (child: C) -> Unit,
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
package de.halfbit.componental

public object Componental {

// logger section api
public enum class LogLevel { DEBUG, ERROR }
public typealias ComponentalLogger = (tag: String, message: String) -> Unit

public fun setLogger(logger: (LogLevel, String, Throwable?) -> Unit) {
public object Componental {
/** Set to actual logger instance. Noop by default. */
public fun setLogger(logger: ComponentalLogger) {
log = logger
}

// logger section implementation
private var log: ((LogLevel, String, Throwable?) -> Unit)? = null
internal fun log(level: LogLevel, message: String, err: Throwable? = null) {
log?.invoke(level, message, err)
}

internal fun debug(message: String, err: Throwable? = null) {
log?.invoke(LogLevel.DEBUG, message, err)
}
internal var log: ComponentalLogger = { _, _ -> }
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ package de.halfbit.componental.router
import de.halfbit.componental.lifecycle.MutableLifecycle
import de.halfbit.componental.restorator.Restorator

public data class RouteNode<out R : Any, out C : Any>(
val route: R,
public data class RouteNode<out C : Any>(
val route: Any,
val child: C,
)

public data class RuntimeRouteNode<out R : Any, out C : Any>(
val node: RouteNode<R, C>,
public data class RuntimeRouteNode<out C : Any>(
val node: RouteNode<C>,
val lifecycle: MutableLifecycle,
val restorator: Restorator,
)
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.onCompletion

public abstract class Router<Transform : Any> {
public abstract class Router<Transform : Any>(
public val name: String
) {
private val channel = Channel<Transform>(capacity = 64)

public val transformers: Flow<Transform> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,21 @@ import de.halfbit.componental.router.RestorableRoute
import de.halfbit.componental.router.RouteNode
import de.halfbit.componental.router.Router
import de.halfbit.componental.router.RuntimeRouteNode
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.KSerializer
import kotlinx.serialization.protobuf.ProtoBuf

public data class Slot<out R : Any, out C : Any>(
val active: RouteNode<R, C>? = null,
public data class Slot<out C : Any>(
val active: RouteNode<C>? = null,
)

public class SlotRouter<R : Any> : Router<TransformSlot<R>>()
public class SlotRouter<R : Any>(name: String) : Router<TransformSlot<R>>(name)
public typealias TransformSlot<R> = (active: R?) -> R?

public fun <R : Any> SlotRouter<R>.set(active: R?) {
Expand All @@ -35,11 +40,11 @@ public fun <R : Any> SlotRouter<R>.clear() {
@OptIn(ExperimentalSerializationApi::class)
public fun <Route : Any, Child : Any> ComponentContext.childSlot(
router: SlotRouter<Route>,
initial: Route? = null,
initial: () -> Route? = { null },
serializer: () -> KSerializer<Route>,
childFactory: (route: Route, context: ComponentContext) -> Child,
): StateFlow<Slot<Route, Child>> {
var runtimeNode: RuntimeRouteNode<Route, Child>? = null
): StateFlow<Slot<Child>> {
var runtimeNode: RuntimeRouteNode<Child>? = null

val restoredRoute: RestorableRoute<Route>? =
restorator.restoreRoute()?.let {
Expand All @@ -48,30 +53,33 @@ public fun <Route : Any, Child : Any> ComponentContext.childSlot(
)
}

fun createRuntimeRouteNode(route: Route): RuntimeRouteNode<Route, Child> {
val tag = "route:${route::class.simpleName.toString()}"
fun createRuntimeRouteNode(route: Route): RuntimeRouteNode<Child> {
val childLifecycle = lifecycle.createMutableChildLifecycle()
val restoredChildState = if (restoredRoute?.route == route) {
restoredRoute.consumeChildState()
} else null

val tag = "route:${route::class.simpleName.toString()}"
val context = createChildContext(
childLifecycle = childLifecycle,
childCoroutineScope = coroutineScope.createChildCoroutineScope(tag),
restorator = Restorator(restoredChildState),
)

val node = RouteNode(route, childFactory(route, context))
return RuntimeRouteNode(
node = node,
lifecycle = childLifecycle,
restorator = context.restorator,
).also { newNode ->
Componental.debug("SLOT Created: ${newNode.node.route}")
val operation = if (restoredChildState == null) "created" else "restored"
Componental.log("SLOT ${router.name}", "($operation): ${newNode.node.route}")
runtimeNode = newNode
childLifecycle.moveToState(State.Resumed)
}
}

fun Route?.asRuntimeSlot(): Slot<Route, Child> {
fun Route?.asRuntimeSlot(): Slot<Child> {
val oldNode = runtimeNode
val newNode = when (this) {
null -> {
Expand All @@ -89,14 +97,15 @@ public fun <Route : Any, Child : Any> ComponentContext.childSlot(
}

if (oldNode != null && oldNode != newNode) {
Componental.debug("SLOT Destroyed: ${oldNode.node.route}")
Componental.log("SLOT ${router.name}", "(destroyed): ${oldNode.node.route}")
oldNode.lifecycle.moveToState(State.Destroyed)
}

return Slot(active = newNode?.node)
}

var activeRoute = restoredRoute?.route ?: initial
val restoredInitial = restoredRoute?.route ?: initial()
var activeRoute = restoredInitial
restorator.storeRoute {
val route = activeRoute ?: return@storeRoute null
ProtoBuf.encodeToByteArray(
Expand All @@ -117,12 +126,12 @@ public fun <Route : Any, Child : Any> ComponentContext.childSlot(
}.distinctUntilChanged()
.map { it to it.asRuntimeSlot() }
.map { (route, slot) ->
Componental.debug("SLOT Active: $route")
Componental.log("SLOT ${router.name}", "$route")
slot
}
.stateIn(
coroutineScope,
SharingStarted.Eagerly,
initial.asRuntimeSlot(),
restoredInitial.asRuntimeSlot(),
)
}
Loading

0 comments on commit c39c8aa

Please sign in to comment.