All Downloads are FREE. Search and download functionalities are using the official Maven repository.

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(),
    )
}