diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d8a8572ac1..0d4c555406c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,18 @@ # Changelog +## 0.9.17 + +* `Common`: + * New extensions `Element#onVisibilityChanged`, `Element#onVisible` and `Element#onInvisible` +* `Coroutines`: + * New extension `Element.visibilityFlow()` +* `FSM`: + * Now it is possible to resolve conflicts on `startChain` + ## 0.9.16 +* `Versions`: + * `Klock`: `2.6.3` -> `2.7.0` * `Common`: * New extension `Node#onRemoved` * `Compose`: diff --git a/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/HTMLElementDomChanged.kt b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/HTMLElementDomChanged.kt index 5d803dca1fb..825414a3587 100644 --- a/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/HTMLElementDomChanged.kt +++ b/common/src/jsMain/kotlin/dev/inmo/micro_utils/common/HTMLElementDomChanged.kt @@ -3,7 +3,7 @@ package dev.inmo.micro_utils.common import kotlinx.browser.document import org.w3c.dom.* -fun Node.onRemoved(block: () -> Unit) { +fun Node.onRemoved(block: () -> Unit): MutationObserver { lateinit var observer: MutationObserver observer = MutationObserver { _, _ -> @@ -18,4 +18,44 @@ fun Node.onRemoved(block: () -> Unit) { } observer.observe(document, MutationObserverInit(childList = true, subtree = true)) + return observer +} + +fun Element.onVisibilityChanged(block: IntersectionObserverEntry.(Float, IntersectionObserver) -> Unit): IntersectionObserver { + var previousIntersectionRatio = -1f + val observer = IntersectionObserver { entries, observer -> + entries.forEach { + if (previousIntersectionRatio != it.intersectionRatio) { + previousIntersectionRatio = it.intersectionRatio.toFloat() + it.block(previousIntersectionRatio, observer) + } + } + } + + observer.observe(this) + return observer +} + +fun Element.onVisible(block: Element.(IntersectionObserver) -> Unit) { + var previous = -1f + onVisibilityChanged { intersectionRatio, observer -> + if (previous != intersectionRatio) { + if (intersectionRatio > 0 && previous == 0f) { + block(observer) + } + previous = intersectionRatio + } + } +} + +fun Element.onInvisible(block: Element.(IntersectionObserver) -> Unit): IntersectionObserver { + var previous = -1f + return onVisibilityChanged { intersectionRatio, observer -> + if (previous != intersectionRatio) { + if (intersectionRatio == 0f && previous != 0f) { + block(observer) + } + previous = intersectionRatio + } + } } diff --git a/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/ElementVisibilityFlow.kt b/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/ElementVisibilityFlow.kt new file mode 100644 index 00000000000..54b217527ee --- /dev/null +++ b/coroutines/src/jsMain/kotlin/dev.inmo.micro_utils.coroutines/ElementVisibilityFlow.kt @@ -0,0 +1,28 @@ +package dev.inmo.micro_utils.coroutines + +import dev.inmo.micro_utils.common.onRemoved +import dev.inmo.micro_utils.common.onVisibilityChanged +import kotlinx.coroutines.flow.* +import org.w3c.dom.Element + +fun Element.visibilityFlow(): Flow = channelFlow { + var previousData: Boolean? = null + + val observer = onVisibilityChanged { intersectionRatio, _ -> + val currentData = intersectionRatio > 0 + if (currentData != previousData) { + trySend(currentData) + } + previousData = currentData + } + + val removeObserver = onRemoved { + observer.disconnect() + close() + } + + invokeOnClose { + observer.disconnect() + removeObserver.disconnect() + } +} diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt index 4c67a5fbd31..b6e9556855c 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/DefaultStatesManager.kt @@ -39,13 +39,17 @@ interface DefaultStatesManagerRepo { * @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 * [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] + * @param onStartContextsConflictResolver Receive current [State] and the state passed with [startChain]. In case when + * this callback will return true, currently placed on the [State.context] [State] will be replaced by new state + * with [endChain] with current state + * @param onUpdateContextsConflictResolver 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 */ open class DefaultStatesManager( protected val repo: DefaultStatesManagerRepo = InMemoryDefaultStatesManagerRepo(), - protected val onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } + protected val onStartContextsConflictResolver: suspend (current: T, new: T) -> Boolean = { _, _ -> true }, + protected val onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } ) : StatesManager { protected val _onChainStateUpdated = MutableSharedFlow>(0) override val onChainStateUpdated: Flow> = _onChainStateUpdated.asSharedFlow() @@ -56,6 +60,14 @@ open class DefaultStatesManager( protected val mapMutex = Mutex() + constructor( + repo: DefaultStatesManagerRepo, + onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean + ) : this ( + repo, + onUpdateContextsConflictResolver = onContextsConflictResolver + ) + override suspend fun update(old: T, new: T) = mapMutex.withLock { val stateByOldContext: T? = repo.getContextState(old.context) when { @@ -67,7 +79,7 @@ open class DefaultStatesManager( } else -> { val stateOnNewOneContext = repo.getContextState(new.context) - if (stateOnNewOneContext == null || onContextsConflictResolver(old, new, stateOnNewOneContext)) { + if (stateOnNewOneContext == null || onUpdateContextsConflictResolver(old, new, stateOnNewOneContext)) { stateOnNewOneContext ?.let { endChainWithoutLock(it) } repo.removeState(old) repo.set(new) @@ -78,7 +90,11 @@ open class DefaultStatesManager( } override suspend fun startChain(state: T) = mapMutex.withLock { - if (!repo.contains(state.context)) { + val stateOnContext = repo.getContextState(state.context) + if (stateOnContext == null || onStartContextsConflictResolver(stateOnContext, state)) { + stateOnContext ?.let { + endChainWithoutLock(it) + } repo.set(state) _onStartChain.emit(state) } diff --git a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt index 323e895b315..3cfe75323ad 100644 --- a/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt +++ b/fsm/common/src/commonMain/kotlin/dev/inmo/micro_utils/fsm/common/managers/InMemoryStatesManager.kt @@ -12,5 +12,6 @@ import kotlinx.coroutines.flow.* */ @Deprecated("Use DefaultStatesManager instead", ReplaceWith("DefaultStatesManager")) fun InMemoryStatesManager( - onContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } -) = DefaultStatesManager(onContextsConflictResolver = onContextsConflictResolver) + onStartContextsConflictResolver: suspend (old: T, new: T) -> Boolean = { _, _ -> true }, + onUpdateContextsConflictResolver: suspend (old: T, new: T, currentNew: T) -> Boolean = { _, _, _ -> true } +) = DefaultStatesManager(onStartContextsConflictResolver = onStartContextsConflictResolver, onUpdateContextsConflictResolver = onUpdateContextsConflictResolver) diff --git a/gradle.properties b/gradle.properties index 192c37e235a..bc19ff604f3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -14,5 +14,5 @@ crypto_js_version=4.1.1 # Project data group=dev.inmo -version=0.9.16 -android_code_version=106 +version=0.9.17 +android_code_version=107 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9490cefd914..25021e34a23 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ jb-compose = "1.1.1" jb-exposed = "0.37.3" jb-dokka = "1.6.10" -klock = "2.6.3" +klock = "2.7.0" uuid = "0.4.0" ktor = "1.6.8"