mirror of
https://github.com/InsanusMokrassar/MicroUtils.git
synced 2025-10-14 11:50:28 +00:00
Compare commits
9 Commits
Author | SHA1 | Date | |
---|---|---|---|
f377ebea88 | |||
7373fef964 | |||
41ef86dbda | |||
7bc2b2336d | |||
0a615e6d78 | |||
8f928e16e1 | |||
72bc8da1e7 | |||
51e349a5db | |||
ae3a5bf45d |
17
CHANGELOG.md
17
CHANGELOG.md
@@ -1,5 +1,22 @@
|
||||
# Changelog
|
||||
|
||||
## 0.9.16
|
||||
|
||||
* `Common`:
|
||||
* New extension `Node#onRemoved`
|
||||
* `Compose`:
|
||||
* New extension `Composition#linkWithRoot` for removing of composition with root element
|
||||
* `Coroutines`:
|
||||
* `Compose`:
|
||||
* New function `renderComposableAndLinkToContextAndRoot` with linking of composition to root element
|
||||
|
||||
## 0.9.15
|
||||
|
||||
* `FSM`:
|
||||
* Rename `DefaultUpdatableStatesMachine#compare` to `DefaultUpdatableStatesMachine#shouldReplaceJob`
|
||||
* `DefaultStatesManager` now is extendable
|
||||
* `DefaultStatesMachine` will stop all jobs of states which was removed from `statesManager`
|
||||
|
||||
## 0.9.14
|
||||
|
||||
* `Versions`:
|
||||
|
@@ -0,0 +1,9 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import androidx.compose.runtime.Composition
|
||||
import dev.inmo.micro_utils.common.onRemoved
|
||||
import org.w3c.dom.Element
|
||||
|
||||
fun Composition.linkWithElement(element: Element) {
|
||||
element.onRemoved { dispose() }
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
package dev.inmo.micro_utils.common.compose
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import org.jetbrains.compose.web.dom.DOMScope
|
||||
import org.w3c.dom.Element
|
||||
|
||||
fun <TElement : Element> renderComposableAndLinkToRoot(
|
||||
root: TElement,
|
||||
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
|
||||
content: @Composable DOMScope<TElement>.() -> Unit
|
||||
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
|
||||
linkWithElement(root)
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
package dev.inmo.micro_utils.common
|
||||
|
||||
import kotlinx.browser.document
|
||||
import org.w3c.dom.*
|
||||
|
||||
fun Node.onRemoved(block: () -> Unit) {
|
||||
lateinit var observer: MutationObserver
|
||||
|
||||
observer = MutationObserver { _, _ ->
|
||||
fun checkIfRemoved(node: Node): Boolean {
|
||||
return node.parentNode != document && (node.parentNode ?.let { checkIfRemoved(it) } ?: true)
|
||||
}
|
||||
|
||||
if (checkIfRemoved(this)) {
|
||||
observer.disconnect()
|
||||
block()
|
||||
}
|
||||
}
|
||||
|
||||
observer.observe(document, MutationObserverInit(childList = true, subtree = true))
|
||||
}
|
@@ -13,6 +13,7 @@ kotlin {
|
||||
dependencies {
|
||||
api libs.kt.coroutines
|
||||
api project(":micro_utils.coroutines")
|
||||
api project(":micro_utils.common.compose")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package dev.inmo.micro_utils.coroutines.compose
|
||||
|
||||
import androidx.compose.runtime.*
|
||||
import dev.inmo.micro_utils.common.compose.linkWithElement
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.compose.web.dom.DOMScope
|
||||
import org.w3c.dom.Element
|
||||
@@ -14,3 +15,12 @@ suspend fun <TElement : Element> renderComposableAndLinkToContext(
|
||||
currentCoroutineContext()
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun <TElement : Element> renderComposableAndLinkToContextAndRoot(
|
||||
root: TElement,
|
||||
monotonicFrameClock: MonotonicFrameClock = DefaultMonotonicFrameClock,
|
||||
content: @Composable DOMScope<TElement>.() -> Unit
|
||||
): Composition = org.jetbrains.compose.web.renderComposable(root, monotonicFrameClock, content).apply {
|
||||
linkWithContext(currentCoroutineContext())
|
||||
linkWithElement(root)
|
||||
}
|
||||
|
@@ -105,6 +105,16 @@ open class DefaultStatesMachine <T: State>(
|
||||
statesManager.onChainStateUpdated.subscribeSafelyWithoutExceptions(this) {
|
||||
launch { performStateUpdate(Optional.presented(it.first), it.second, scope.LinkedSupervisorScope()) }
|
||||
}
|
||||
statesManager.onEndChain.subscribeSafelyWithoutExceptions(this) { removedState ->
|
||||
launch {
|
||||
statesJobsMutex.withLock {
|
||||
val stateInMap = statesJobs.keys.firstOrNull { stateInMap -> stateInMap == removedState }
|
||||
if (stateInMap === removedState) {
|
||||
statesJobs[stateInMap] ?.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
statesManager.getActiveStates().forEach {
|
||||
launch { performStateUpdate(Optional.absent(), it, scope.LinkedSupervisorScope()) }
|
||||
|
@@ -26,6 +26,12 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
), UpdatableStatesMachine<T> {
|
||||
protected val jobsStates = mutableMapOf<Job, T>()
|
||||
|
||||
/**
|
||||
* Realization of this update will use the [Job] of [previousState] in [statesJobs] and [jobsStates] if
|
||||
* [previousState] is [Optional.presented] and [shouldReplaceJob] has returned true for [previousState] and [actualState]. In
|
||||
* other words, [Job] of [previousState] WILL NOT be replaced with the new one if they are "equal". Equality of
|
||||
* states is solved in [shouldReplaceJob] and can be rewritten in subclasses
|
||||
*/
|
||||
override suspend fun performStateUpdate(previousState: Optional<T>, actualState: T, scope: CoroutineScope) {
|
||||
statesJobsMutex.withLock {
|
||||
if (compare(previousState, actualState)) {
|
||||
@@ -52,7 +58,13 @@ open class DefaultUpdatableStatesMachine<T : State>(
|
||||
}
|
||||
}
|
||||
|
||||
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||
/**
|
||||
* Compare if [previous] potentially lead to the same behaviour with [new]
|
||||
*/
|
||||
protected open suspend fun shouldReplaceJob(previous: Optional<T>, new: T): Boolean = previous.dataOrNull() != new
|
||||
|
||||
@Deprecated("Overwrite shouldReplaceJob instead")
|
||||
protected open suspend fun compare(previous: Optional<T>, new: T): Boolean = shouldReplaceJob(previous, new)
|
||||
|
||||
override suspend fun updateChain(currentState: T, newState: T) {
|
||||
statesManager.update(currentState, newState)
|
||||
|
@@ -37,30 +37,31 @@ interface DefaultStatesManagerRepo<T : State> {
|
||||
|
||||
/**
|
||||
* @param repo This repo will be used as repository for storing states. All operations with this repo will happen BEFORE
|
||||
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default will be used
|
||||
* any event will be sent to [onChainStateUpdated], [onStartChain] or [onEndChain]. By default, will be used
|
||||
* [InMemoryDefaultStatesManagerRepo] or you may create custom [DefaultStatesManagerRepo] and pass as [repo] parameter
|
||||
* @param onContextsConflictResolver Receive old [State], new one and the state currently placed on new [State.context]
|
||||
* key. In case when this callback will returns true, the state placed on [State.context] of new will be replaced by
|
||||
* new state by using [endChain] with that state
|
||||
*/
|
||||
class DefaultStatesManager<T : State>(
|
||||
private val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||
private val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
open class DefaultStatesManager<T : State>(
|
||||
protected val repo: DefaultStatesManagerRepo<T> = InMemoryDefaultStatesManagerRepo(),
|
||||
protected val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true }
|
||||
) : StatesManager<T> {
|
||||
private val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||
protected val _onChainStateUpdated = MutableSharedFlow<Pair<T, T>>(0)
|
||||
override val onChainStateUpdated: Flow<Pair<T, T>> = _onChainStateUpdated.asSharedFlow()
|
||||
private val _onStartChain = MutableSharedFlow<T>(0)
|
||||
protected val _onStartChain = MutableSharedFlow<T>(0)
|
||||
override val onStartChain: Flow<T> = _onStartChain.asSharedFlow()
|
||||
private val _onEndChain = MutableSharedFlow<T>(0)
|
||||
protected val _onEndChain = MutableSharedFlow<T>(0)
|
||||
override val onEndChain: Flow<T> = _onEndChain.asSharedFlow()
|
||||
|
||||
private val mapMutex = Mutex()
|
||||
protected val mapMutex = Mutex()
|
||||
|
||||
override suspend fun update(old: T, new: T) = mapMutex.withLock {
|
||||
val stateByOldContext: T? = repo.getContextState(old.context)
|
||||
when {
|
||||
stateByOldContext != old -> return@withLock
|
||||
stateByOldContext == null || old.context == new.context -> {
|
||||
repo.removeState(old)
|
||||
repo.set(new)
|
||||
_onChainStateUpdated.emit(old to new)
|
||||
}
|
||||
@@ -83,7 +84,7 @@ class DefaultStatesManager<T : State>(
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun endChainWithoutLock(state: T) {
|
||||
protected open suspend fun endChainWithoutLock(state: T) {
|
||||
if (repo.getContextState(state.context) == state) {
|
||||
repo.removeState(state)
|
||||
_onEndChain.emit(state)
|
||||
|
@@ -14,5 +14,5 @@ crypto_js_version=4.1.1
|
||||
# Project data
|
||||
|
||||
group=dev.inmo
|
||||
version=0.9.14
|
||||
android_code_version=104
|
||||
version=0.9.16
|
||||
android_code_version=106
|
||||
|
Reference in New Issue
Block a user