diff --git a/CHANGELOG.md b/CHANGELOG.md index e8381c00..e657782f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Navigation Changelog +## 0.5.7 + +**THIS UPDATE CONTAINS BREAKING CHANGES RELATED TO `NavigationNodeFactory` things** + +* `Code`: + * Replace `NavigationNodeFactory.Companion.Typed` factory into `NavigationNodeFactory.Typed` + * Change order of `NavigationNodeFactory.Typed` type args + * Add default aggregator for `NavigationNodeFactory` + * Add extension `changesInSubtreeFlow` for either chains and nodes + ## 0.5.6 * `Versions`: diff --git a/core/src/commonMain/kotlin/NavigationNode.kt b/core/src/commonMain/kotlin/NavigationNode.kt index b643e892..fa751300 100644 --- a/core/src/commonMain/kotlin/NavigationNode.kt +++ b/core/src/commonMain/kotlin/NavigationNode.kt @@ -167,7 +167,7 @@ abstract class NavigationNode( override val configState: StateFlow = MutableStateFlow(config).asStateFlow() companion object { - val DefaultFactory = NavigationNodeFactory.Companion.Typed { chain, config -> + val DefaultFactory = NavigationNodeFactory.Typed { chain, config -> Empty(chain, config) } } diff --git a/core/src/commonMain/kotlin/NavigationNodeFactory.kt b/core/src/commonMain/kotlin/NavigationNodeFactory.kt index 6a83d0c3..337590ca 100644 --- a/core/src/commonMain/kotlin/NavigationNodeFactory.kt +++ b/core/src/commonMain/kotlin/NavigationNodeFactory.kt @@ -8,30 +8,36 @@ fun interface NavigationNodeFactory { config: Base ): NavigationNode? - companion object { - open class Typed( - private val kClass: KClass, - private val block: (navigationChain: NavigationChain, config: T) -> NavigationNode - ) : NavigationNodeFactory { - override fun createNode( - navigationChain: NavigationChain, - config: Base - ): NavigationNode? { - return if (kClass.isInstance(config)) { - block(navigationChain, config as T) - } else { - null - } + open class Typed( + private val kClass: KClass<*>, + private val block: (navigationChain: NavigationChain, config: T) -> NavigationNode + ) : NavigationNodeFactory { + override fun createNode( + navigationChain: NavigationChain, + config: Base + ): NavigationNode? { + return if (kClass.isInstance(config)) { + block(navigationChain, config as T) + } else { + null } + } - companion object { - inline operator fun invoke( - noinline block: (navigationChain: NavigationChain, config: T) -> NavigationNode - ) = Typed( - kClass = T::class, - block - ) - } + companion object { + inline operator fun invoke( + noinline block: (navigationChain: NavigationChain, config: T) -> NavigationNode + ) = Typed( + kClass = T::class, + block + ) + } + } + + open class DefaultAggregator( + private val factories: List>, + ) : NavigationNodeFactory { + override fun createNode(navigationChain: NavigationChain, config: Base): NavigationNode? { + return factories.firstNotNullOfOrNull { it.createNode(navigationChain, config) } } } } diff --git a/core/src/commonMain/kotlin/extensions/ChainSubChanges.kt b/core/src/commonMain/kotlin/extensions/ChainSubChanges.kt index e82fefcb..7638efcb 100644 --- a/core/src/commonMain/kotlin/extensions/ChainSubChanges.kt +++ b/core/src/commonMain/kotlin/extensions/ChainSubChanges.kt @@ -7,11 +7,8 @@ import dev.inmo.navigation.core.NavigationChain import dev.inmo.navigation.core.NavigationNode import dev.inmo.navigation.core.NavigationNodeId import dev.inmo.navigation.core.onDestroyFlow -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.job +import kotlinx.coroutines.* +import kotlinx.coroutines.flow.* fun NavigationChain.onChangesInSubTree( scope: CoroutineScope, @@ -59,6 +56,9 @@ fun NavigationNode.onChangesInSubTree( ) onChangeInSubNode(this@onChangesInSubTree, it) } + configState.subscribeSafelyWithoutExceptions(subscope) { + + } onDestroyFlow.subscribeSafelyWithoutExceptions(subscope) { subscope.cancel() diff --git a/core/src/commonMain/kotlin/extensions/ChangesInSubtreeFlow.kt b/core/src/commonMain/kotlin/extensions/ChangesInSubtreeFlow.kt new file mode 100644 index 00000000..211042c8 --- /dev/null +++ b/core/src/commonMain/kotlin/extensions/ChangesInSubtreeFlow.kt @@ -0,0 +1,55 @@ +package dev.inmo.navigation.core.extensions + +import dev.inmo.micro_utils.common.* +import dev.inmo.navigation.core.NavigationChain +import dev.inmo.navigation.core.NavigationNode +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.merge + +/** + * @return [Flow] which will have one of two data variants: chain where happen changes and its stack list (list will be + * presented only if change happen there); node and its subchaines (subchaines will be presented only if their list + * have been changed) + */ +@Warning("This API is still experimental. Please, report on any wrong behaviour if any") +@OptIn(ExperimentalCoroutinesApi::class) +fun Either, NavigationNode>.changesInSubtreeFlow(): Flow< + Either< + Pair, List>>, + Pair, List>?>, + > + > { + return merge( + when (this) { + is EitherFirst -> merge( + // chain + this.t1.stackFlow.map { (this.t1 to it).either() }, + this.t1.stackFlow.flatMapLatest { + it.map { + it.either, NavigationNode>().changesInSubtreeFlow() + }.merge() + }, + ) + + is EitherSecond -> merge( + // node + this.t2.subchainsFlow.map { (this.t2 to it).either() }, + this.t2.subchainsFlow.flatMapLatest { + it.map { + it.either, NavigationNode>().changesInSubtreeFlow() + }.merge() + }, + this.t2.stateChangesFlow.map { (this.t2 to null).either() }, + this.t2.configState.map { (this.t2 to null).either() }, + ) + }, + ) +} + +@Warning("This API is still experimental. Please, report on any wrong behaviour if any") +fun NavigationChain.changesInSubTreeFlow() = either, NavigationNode>().changesInSubtreeFlow() +@Warning("This API is still experimental. Please, report on any wrong behaviour if any") +fun NavigationNode.changesInSubTreeFlow() = either, NavigationNode>().changesInSubtreeFlow() \ No newline at end of file diff --git a/core/src/commonMain/kotlin/tmp_utils/FlowDiff.kt b/core/src/commonMain/kotlin/tmp_utils/FlowDiff.kt new file mode 100644 index 00000000..f73a3e5f --- /dev/null +++ b/core/src/commonMain/kotlin/tmp_utils/FlowDiff.kt @@ -0,0 +1,23 @@ +package dev.inmo.navigation.core.tmp_utils + +import dev.inmo.micro_utils.common.Diff +import dev.inmo.micro_utils.common.Warning +import dev.inmo.micro_utils.common.diff +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.map + +@Warning("This feature can be removed in any update without notice") +val Flow>.diffFlow: Flow> + get() = flow { + var currentValue = first() + + map { + val diff = it.diff(currentValue) + + currentValue = it + + emit(diff) + } + } diff --git a/gradle.properties b/gradle.properties index 255e32b0..a0cd83b7 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,5 +10,5 @@ org.gradle.jvmargs=-Xmx1g # Project data group=dev.inmo -version=0.5.6 -android_code_version=45 +version=0.5.7 +android_code_version=46 diff --git a/sample/src/commonMain/kotlin/CommonPlugin.kt b/sample/src/commonMain/kotlin/CommonPlugin.kt index c2a9ea5f..26a95e56 100644 --- a/sample/src/commonMain/kotlin/CommonPlugin.kt +++ b/sample/src/commonMain/kotlin/CommonPlugin.kt @@ -1,18 +1,19 @@ package dev.inmo.navigation.sample import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import dev.inmo.micro_utils.common.either import dev.inmo.micro_utils.koin.singleWithRandomQualifier import dev.inmo.navigation.sample.ui.NavigationModel import dev.inmo.navigation.sample.ui.NavigationViewConfig import dev.inmo.navigation.sample.ui.NavigationViewModel import dev.inmo.micro_utils.startup.plugin.StartPlugin -import dev.inmo.navigation.compose.InjectNavigationChain -import dev.inmo.navigation.compose.InjectNavigationNode -import dev.inmo.navigation.compose.nodeFactory +import dev.inmo.navigation.compose.* import dev.inmo.navigation.core.NavigationChain import dev.inmo.navigation.core.NavigationNode import dev.inmo.navigation.core.NavigationNodeFactory import dev.inmo.navigation.core.configs.NavigationNodeDefaultConfig +import dev.inmo.navigation.core.extensions.changesInSubtreeFlow import dev.inmo.navigation.sample.ui.NavigationView import dev.inmo.navigation.sample.ui.tree.CurrentTreeViewConfig import dev.inmo.navigation.sample.ui.tree.CurrentTreeViewViewModel @@ -87,6 +88,12 @@ object CommonPlugin : StartPlugin { nodesFactory = koin.nodeFactory(), dropRedundantChainsOnRestore = true, ) { + val rootChain = getChainFromLocalProvider()!! + LaunchedEffect(rootChain) { + rootChain.either, NavigationNode>().changesInSubtreeFlow().collect { + println(it) + } + } InjectNavigationChain { InjectNavigationNode( NavigationViewConfig(id = "root", text = ">") diff --git a/sample/src/commonMain/kotlin/ui/tree/CurrentTreeViewViewModel.kt b/sample/src/commonMain/kotlin/ui/tree/CurrentTreeViewViewModel.kt index 7c78b971..e215e1ab 100644 --- a/sample/src/commonMain/kotlin/ui/tree/CurrentTreeViewViewModel.kt +++ b/sample/src/commonMain/kotlin/ui/tree/CurrentTreeViewViewModel.kt @@ -6,6 +6,7 @@ import dev.inmo.kslog.common.logger import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.navigation.core.NavigationNode import dev.inmo.navigation.core.configs.NavigationNodeDefaultConfig +import dev.inmo.navigation.core.extensions.changesInSubTreeFlow import dev.inmo.navigation.core.extensions.onChangesInSubTree import dev.inmo.navigation.core.extensions.rootChain import dev.inmo.navigation.core.visiter.walkOnNodes @@ -24,17 +25,8 @@ class CurrentTreeViewViewModel( val mermaidLines = mutableStateOf(emptyList()) private var listeningJob: Job? = null private val listeningJobsMutex = Mutex() - private val changesListeningJob = node.chain.rootChain().onChangesInSubTree(scope) { _, _ -> - listeningJobsMutex.withLock { - val flows = mutableListOf>(flowOf(Unit)) - node.chain.rootChain().walkOnNodes { - flows.add(it.statesFlow) - } - listeningJob ?.cancel() - listeningJob = flows.merge().subscribeSafelyWithoutExceptions(scope) { - updateFun() - } - } + private val changesListeningJob = node.chain.rootChain().changesInSubTreeFlow().subscribeSafelyWithoutExceptions(scope) { + updateFun() }