Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package moe.tlaster.precompose.molecule
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MonotonicFrameClock
import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import app.cash.molecule.RecompositionMode
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.stateholder.LocalStateHolder
import moe.tlaster.precompose.stateholder.StateHolder
import kotlin.coroutines.CoroutineContext
internal expect fun providePlatformDispatcher(): CoroutineContext
@OptIn(ExperimentalStdlibApi::class)
private class PresenterHolder(
useImmediateClock: Boolean,
body: @Composable () -> T,
) : AutoCloseable {
private val dispatcher = providePlatformDispatcher()
private val clock = if (useImmediateClock || dispatcher[MonotonicFrameClock] == null) {
RecompositionMode.Immediate
} else {
RecompositionMode.ContextClock
}
private val scope = CoroutineScope(dispatcher)
val state = scope.launchMolecule(clock, body)
override fun close() {
scope.cancel()
}
}
@OptIn(ExperimentalStdlibApi::class)
private class ActionViewHolder : AutoCloseable {
val channel = Channel(Channel.UNLIMITED)
val pair = channel to channel.consumeAsFlow()
override fun close() {
channel.close()
}
}
@Composable
private fun rememberAction(
keys: List,
): Pair, Flow> {
val stateHolder = LocalStateHolder.current
val key = remember(keys) {
(keys.map { it.hashCode().toString() } + ActionViewHolder::class.simpleName).joinToString()
}
return stateHolder.getOrPut(key) {
ActionViewHolder()
}.pair
}
/**
* Return StateFlow, use it in your Compose UI
* The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
* @param keys The keys to use to identify the Presenter
* @param body The body of the molecule presenter
* @return StateFlow
*/
@Composable
private fun rememberPresenterState(
keys: List = emptyList(),
useImmediateClock: Boolean,
body: @Composable () -> T,
): StateFlow {
val stateHolder = LocalStateHolder.current
val key = remember(keys) {
(keys.map { it.hashCode().toString() } + PresenterHolder::class.simpleName).joinToString()
}
return stateHolder.getOrPut(key) {
PresenterHolder(useImmediateClock, body)
}.state
}
/**
* Return State, use it in your Compose UI
* The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder]
* @param keys The keys to use to identify the Presenter
* @param useImmediateClock Use immediate clock or not, for text input, you should set it to true
* @param body The body of the molecule presenter
* @return State
*/
@Composable
fun producePresenter(
keys: List = emptyList(),
useImmediateClock: Boolean = false,
body: @Composable () -> T,
): State {
val presenter = rememberPresenterState(keys = keys, useImmediateClock = useImmediateClock) { body() }
return presenter.collectAsState()
}
/**
* 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 [StateHolder], so it has the same lifecycle as the [StateHolder]
*
* @param keys The keys to use to identify the Presenter
* @param useImmediateClock Use immediate clock or not, for text input, you should set it to true
* @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(),
useImmediateClock: Boolean = false,
body: @Composable (flow: Flow) -> T,
): Pair> {
val (channel, action) = rememberAction(keys = keys)
val presenter = rememberPresenterState(keys = keys, useImmediateClock = useImmediateClock) { 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 [StateHolder], so it has the same lifecycle as the [StateHolder]
*
* @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(Channel.UNLIMITED) }
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)
}
}
}