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

commonMain.moe.tlaster.precompose.molecule.molecule.kt Maven / Gradle / Ivy

There is a newer version: 1.7.0-alpha03
Show newest version
package moe.tlaster.precompose.molecule

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import app.cash.molecule.RecompositionClock
import app.cash.molecule.launchMolecule
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.consumeAsFlow
import moe.tlaster.precompose.ui.viewModel
import moe.tlaster.precompose.viewmodel.ViewModel
import kotlin.coroutines.CoroutineContext

internal expect fun providePlatformDispatcher(): CoroutineContext

private class PresenterViewModel(
    body: @Composable () -> T,
) : ViewModel() {
    private val dispatcher = providePlatformDispatcher()
    private val clock = if (dispatcher[MonotonicFrameClock] == null) {
        RecompositionClock.Immediate
    } else {
        RecompositionClock.ContextClock
    }
    private val scope = CoroutineScope(dispatcher)
    val state = scope.launchMolecule(clock, body)

    override fun onCleared() {
        scope.cancel()
    }
}

@Composable
private fun  rememberPresenterState(
    keys: List,
    body: @Composable () -> T,
): StateFlow {
    @Suppress("UNCHECKED_CAST")
    val viewModel = viewModel(
        modelClass = PresenterViewModel::class,
        keys = keys,
        creator = { PresenterViewModel(body) }
    ) as PresenterViewModel
    return viewModel.state
}

private class ActionViewModel : ViewModel() {
    val channel = Channel()
    val pair = channel to channel.consumeAsFlow()
    override fun onCleared() {
        channel.close()
    }
}

@Composable
private fun  rememberAction(
    keys: List,
): Pair, Flow> {
    @Suppress("UNCHECKED_CAST")
    val viewModel = viewModel(
        modelClass = ActionViewModel::class,
        keys = keys,
        creator = { ActionViewModel() }
    ) as ActionViewModel
    return viewModel.pair
}

/**
 * Return pair of State and Action Channel, use it in your Compose UI
 * The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel]
 *
 * @param keys The keys to use to identify the Presenter
 * @param body The body of the molecule presenter, the flow parameter is the flow of the action channel
 * @return Pair of State and Action channel
 */
@Composable
fun  rememberPresenter(
    keys: List = emptyList(),
    body: @Composable (flow: Flow) -> T
): Pair> {
    val (channel, action) = rememberAction(keys = keys)
    val presenter = rememberPresenterState(keys = keys) { body(action) }
    val state by presenter.collectAsState()
    return state to channel
}

/**
 * Return pair of State and Action Channel, use it in your Compose UI
 * The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel]
 *
 * @param body The body of the molecule presenter, the flow parameter is the flow of the action channel
 * @return Pair of State and Action channel
 */
// @Composable
// inline fun  rememberPresenter(
//     crossinline body: @Composable (flow: Flow) -> T
// ): Pair> {
//     return rememberPresenter(keys = listOf(T::class, E::class)) {
//         body.invoke(it)
//     }
// }

/**
 * Return pair of State and Action channel, use it in your Presenter, not Compose UI
 *
 * @param body The body of the molecule presenter, the flow parameter is the flow of the action channel
 * @return Pair of State and Action channel
 */
@Composable
fun  rememberNestedPresenter(
    body: @Composable (flow: Flow) -> T
): Pair> {
    val channel = remember { Channel() }
    val flow = remember { channel.consumeAsFlow() }
    val presenter = body(flow)
    return presenter to channel
}

/**
 * Helper function to collect the action channel in your Presenter
 *
 * @param body Your action handler
 */
@Composable
fun  Flow.collectAction(
    body: suspend T.() -> Unit,
) {
    LaunchedEffect(Unit) {
        collect {
            body(it)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy