commonMain.de.halfbit.componental.router.slot.SlotRouter.kt Maven / Gradle / Ivy
package de.halfbit.componental.router.slot
import de.halfbit.componental.ComponentContext
import de.halfbit.componental.coroutines.createChildCoroutineScope
import de.halfbit.componental.createChildContext
import de.halfbit.componental.lifecycle.Lifecycle.State
import de.halfbit.componental.lifecycle.createMutableChildLifecycle
import de.halfbit.componental.restorator.Restorator
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 de.halfbit.componental.router.slot.SlotRouter.Event
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.stateIn
import kotlinx.serialization.KSerializer
import kotlinx.serialization.protobuf.ProtoBuf
public data class Slot(
val active: RouteNode? = null,
)
public class SlotRouter : Router>() {
public class Event(
public val transform: (active: I?) -> I?,
)
}
public fun SlotRouter.set(active: I?) {
route(
event = Event { _ -> active }
)
}
public fun ComponentContext.childSlot(
router: SlotRouter,
initial: Id? = null,
serializer: () -> KSerializer,
childFactory: (id: Id, context: ComponentContext) -> Child,
): StateFlow> {
var runtimeNode: RuntimeRouteNode? = null
val restoredRoute: RestorableRoute? =
restorator.restoreRoute()?.let {
ProtoBuf.decodeFromByteArray(
RestorableRoute.serializer(serializer()), it
)
}
fun createRuntimeRouteNode(id: Id): RuntimeRouteNode {
val tag = "id:${id::class.simpleName.toString()}"
val childLifecycle = lifecycle.createMutableChildLifecycle()
val restoredChildState = if (restoredRoute?.id == id) {
restoredRoute.consumeChildState()
} else null
val context = createChildContext(
childLifecycle = childLifecycle,
childCoroutineScope = coroutineScope.createChildCoroutineScope(tag),
restorator = Restorator(restoredChildState)
)
val node = RouteNode(id, childFactory(id, context))
return RuntimeRouteNode(
node = node,
lifecycle = childLifecycle,
restorator = context.restorator,
).also { newNode ->
runtimeNode = newNode
childLifecycle.moveToState(State.Resumed)
}
}
fun Id?.asSlot(): Slot {
val oldNode = runtimeNode
val newNode = when (this) {
null -> {
runtimeNode = null
null
}
else -> when {
oldNode == null || this != oldNode.node.id -> {
createRuntimeRouteNode(this)
}
else -> oldNode
}
}
if (oldNode != null && oldNode != newNode) {
oldNode.lifecycle.moveToState(State.Destroyed)
}
return Slot(active = newNode?.node)
}
var activeId = restoredRoute?.id ?: initial
restorator.storeRoute {
val id = activeId ?: return@storeRoute null
ProtoBuf.encodeToByteArray(
RestorableRoute.serializer(serializer()),
RestorableRoute(
id = id,
childState = runtimeNode?.restorator?.storeAll()
)
)
}
return flow {
emit(activeId.asSlot())
router.events.collect { event ->
activeId = event.transform(activeId)
emit(activeId.asSlot())
}
}.stateIn(
coroutineScope,
SharingStarted.Eagerly,
initial.asSlot(),
)
}