Skip to content

Commit

Permalink
Merge pull request #49 from InsanusMokrassar/0.5.6
Browse files Browse the repository at this point in the history
0.5.6
  • Loading branch information
InsanusMokrassar authored Oct 3, 2024
2 parents 3e87ffa + 607d590 commit fae87b7
Show file tree
Hide file tree
Showing 13 changed files with 293 additions and 122 deletions.
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# Navigation Changelog

## 0.5.6

* `Versions`:
* `MicroUtils`: `0.22.3` -> `0.22.4`
* `Core`:
* Add default config for `Empty` node (just empty config object)
* Add default factory for `Empty` node, based on default config
* Add `NavigationNodeFactory.Typed` default node factory with inline factory fun
* `Compose`:
* Add opportunity to setup initial chains and nodes in `initNavigation`
* A lot of fixes in Compose Navigation Extensions

## 0.5.5

* `Compose`:
Expand Down
174 changes: 112 additions & 62 deletions compose/src/commonMain/kotlin/ComposeNavigationExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,143 +5,193 @@ import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.navigation.core.NavigationChain
import dev.inmo.navigation.core.NavigationNode
import dev.inmo.navigation.core.extensions.onChainRemovedFlow
import dev.inmo.navigation.core.extensions.onNodeRemovedFlow
import dev.inmo.navigation.core.onDestroyFlow
import kotlinx.coroutines.flow.dropWhile
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.take

/**
* Collecting [NavigationChain.stackFlow] and always [StartInCompose] only the last element in [NavigationChain.stackFlow]
* Collecting [NavigationChain.stackFlow] and always [Draw] only the latest element in [NavigationChain.stackFlow]
*/
@Composable
internal fun <Base> NavigationChain<Base>.defaultStackHandling() {
internal fun <Base> NavigationChain<Base>.DrawStackNodes() {
val stack = stackFlow.collectAsState()
val latestNode = stack.value.lastOrNull()
latestNode ?.StartInCompose()
key(stack.value.lastOrNull()) {
val latestNode = stack.value.lastOrNull()
if (latestNode != null) {
doWithNodeInLocalProvider(latestNode) {
when {
latestNode is ComposeNode -> {
val drawState = latestNode.drawerState.collectAsState()
drawState.value ?.invoke()
}

else -> {
latestNode.SubchainsHandling()
}
}
}
}
}
}

/**
* Calls [Draw] on each [NavigationChain] in [NavigationNode.subchainsFlow] of [this]
*/
@Composable
fun <Base> NavigationNode<out Base, Base>.defaultSubchainsHandling(filter: (NavigationChain<Base>) -> Boolean = { true }) {
internal fun <Base> NavigationNode<out Base, Base>.SubchainsHandling(filter: (NavigationChain<Base>) -> Boolean = { true }) {
val subchainsState = subchainsFlow.collectAsState()
val rawSubchains = subchainsState.value
val filteredSubchains = rawSubchains.filter(filter)
filteredSubchains.forEach {
it.StartInCompose()
it.Draw()
}
}

/**
* @param onDismiss Will be called when [this] [NavigationChain] will be removed from its parent. [onDismiss] will
* never be called if [this] [NavigationChain] is the root one
* Main purpose of this function - is to call [DrawStackNodes] to provide stack drawing
*
* @param onDismiss Will be called when [this] [NavigationChain] must be dropped
* @param beforeNodes Will be called **before** [DrawStackNodes] will be called
*/
@Composable
internal fun <Base> NavigationChain<Base>.StartInCompose(
internal fun <Base> NavigationChain<Base>.Draw(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
block: @Composable NavigationChain<Base>.() -> Unit = { defaultStackHandling() }
beforeNodes: @Composable NavigationChain<Base>.() -> Unit = { }
) {
key(onDismiss) {
key(onDismiss, parentNode) {
onDismiss ?.let {
key (parentNode) {
parentNode ?.let { parentNode ->
val scope = rememberCoroutineScope()

remember {
parentNode.onChainRemovedFlow.filter { it.any { it.value === this@StartInCompose } }.subscribeSafelyWithoutExceptions(scope) {
onDismiss(this)
}
parentNode ?.let { parentNode ->
val scope = rememberCoroutineScope()

remember {
parentNode.onChainRemovedFlow.filter { it.any { it.value === this@Draw } }.subscribeSafelyWithoutExceptions(scope) {
onDismiss(this)
}
}
}
}
}

CompositionLocalProvider(LocalNavigationChainProvider<Base>() provides this) {
block()
doWithChainInLocalProvider(this) {
beforeNodes()
DrawStackNodes()
}
}

/**
* Using [getNodesFactoryFromLocalProvider] to get navigation nodes factory, creating new subchain with
* [NavigationNode.createEmptySubChain] if [this] [NavigationNode] is not null, and creating new one without parent node
* with [NavigationChain] constructor. After chain created it will call [Draw] on it
*/
@Composable
internal fun <Base> NavigationNode<*, Base>?.SubChain(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
beforeNodes: @Composable NavigationChain<Base>.() -> Unit
) {
val factory = getNodesFactoryFromLocalProvider<Base>()
val chain = remember(this, factory) {
this ?.createEmptySubChain() ?: NavigationChain<Base>(null, factory)
}
chain.Draw(onDismiss, beforeNodes)
}

/**
* Creates [NavigationChain] in current composition and call [StartInCompose]
* Calling [getNodeFromLocalProvider] and creates [SubChain] on it with passing [onDismiss] and [beforeNodes]
*/
@Composable
fun <Base> NavigationNode<*, Base>.NavigationSubChain(
fun <Base> InjectNavigationChain(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
block: @Composable NavigationChain<Base>.() -> Unit
beforeNodes: @Composable NavigationChain<Base>.() -> Unit
) {
remember { createEmptySubChain() }.StartInCompose(onDismiss, block)
getNodeFromLocalProvider<Base>().SubChain(onDismiss, beforeNodes)
}

/**
* Trying to get [NavigationNode] using [LocalNavigationNodeProvider] and calling [NavigationSubChain] with it
* Just calls [InjectNavigationChain]
*/
@Composable
fun <Base> NavigationSubChain(
@Deprecated("Renamed", ReplaceWith("InjectNavigationChain(onDismiss, block)", "dev.inmo.navigation.compose.InjectNavigationChain"))
fun <Base> SubChain(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
block: @Composable NavigationChain<Base>.() -> Unit
beforeNodes: @Composable NavigationChain<Base>.() -> Unit
) {
LocalNavigationNodeProvider<Base>().current.NavigationSubChain(onDismiss, block)
InjectNavigationChain(onDismiss, beforeNodes)
}

/**
* **If [this] is [ComposeNode]** provides [this] with [LocalNavigationNodeProvider] in [CompositionLocalProvider] and
* calls [this] [ComposeNode.drawerState] value invoke
* **If [this] is [ComposeNode]** provides [this] with [doWithNodeInLocalProvider] for [actionInContext]
*
* @param onDismiss Will be called when [this] [NavigationNode] will be removed from its [NavigationChain]
* @param onDismiss Will be called when [this] [InjectNavigationNode] will be removed from its [NavigationChain]
*/
@Composable
internal fun <Base> NavigationNode<*, Base>?.StartInCompose(
internal fun <Base> NavigationNode<*, Base>.Use(
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
actionInContext: @Composable() (() -> Unit)? = null,
) {
val nodeAsComposeNode = this as? ComposeNode

key(nodeAsComposeNode) {
this ?.let { view ->
CompositionLocalProvider(LocalNavigationNodeProvider<Base>() provides view) {
nodeAsComposeNode ?.let { view ->
val drawState = view.drawerState.collectAsState()
drawState.value ?.invoke()
} ?: view.defaultSubchainsHandling()
}
key(this, actionInContext) {
doWithNodeInLocalProvider(this) {
actionInContext ?.invoke()
}
}

key(onDismiss) {
key(this) {
onDismiss ?.let { onDismiss ->
this ?.let { node ->
val scope = rememberCoroutineScope()
key(this, onDismiss) {
onDismiss ?.let { onDismiss ->
val scope = rememberCoroutineScope()

node.onDestroyFlow.take(1).subscribeSafelyWithoutExceptions(scope) {
onDismiss(node)
}
}
onDestroyFlow.take(1).subscribeSafelyWithoutExceptions(scope) {
onDismiss(this)
}
}
}
}

/**
* Trying to create [NavigationNode] in [this] [NavigationChain] and do [StartInCompose] with passing of [onDismiss] in
* this call
* Calls [NavigationChain.push] on [this] receiver and [Use] returned [NavigationNode] with passing of
* [onDismiss] and [additionalCodeInNodeContext] to it
*/
@Composable
fun <Base> NavigationChain<Base>.NavigationSubNode(
internal fun <Base> NavigationChain<Base>.NodeInStack(
config: Base,
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
additionalCodeInNodeContext: @Composable() (() -> Unit)? = null,
) {
val node: NavigationNode<out Base, Base>? = remember { push(config) }
node.StartInCompose(onDismiss)
val node: NavigationNode<out Base, Base>? = remember {
push(config)
}
node ?.Use(onDismiss, additionalCodeInNodeContext)
}

/**
* Trying to get current [NavigationChain] using [getChainFromLocalProvider] and calls [NodeInStack] with
* passing both [config], [onDismiss] and [additionalCodeInNodeContext]
*
* If [NavigationChain] is absent in context, will create new one with [InjectNavigationChain] and pass calling of [NodeInStack]
* as [NodeInStack]
*/
@Composable
fun <Base> InjectNavigationNode(
config: Base,
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
additionalCodeInNodeContext: @Composable() (() -> Unit)? = null,
) {
val chain = getChainFromLocalProvider<Base>() ?: run {
InjectNavigationChain<Base> {
NodeInStack(config, onDismiss, additionalCodeInNodeContext)
}
return@InjectNavigationNode
}
chain.NodeInStack(config, onDismiss, additionalCodeInNodeContext)
}

/**
* Trying to get current [NavigationChain] using [LocalNavigationChainProvider] and calls [NavigationSubNode] with
* passing both [config] and [onDismiss]
* Just calling [InjectNavigationNode] with passing arguments as is
*/
@Composable
@Deprecated("Renamed", ReplaceWith("InjectNavigationNode(config, onDismiss)", "dev.inmo.navigation.compose.InjectNavigationNode"))
fun <Base> NavigationSubNode(
config: Base,
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
additionalCodeInNodeContext: @Composable() (() -> Unit)? = null,
) {
LocalNavigationChainProvider<Base>().current.NavigationSubNode(config, onDismiss)
InjectNavigationNode(config, onDismiss, additionalCodeInNodeContext)
}
5 changes: 1 addition & 4 deletions compose/src/commonMain/kotlin/ComposeNode.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
package dev.inmo.navigation.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import dev.inmo.kslog.common.logger
import dev.inmo.micro_utils.coroutines.SpecialMutableStateFlow
import dev.inmo.navigation.core.NavigationChain
import dev.inmo.navigation.core.NavigationNode
import dev.inmo.navigation.core.NavigationNodeId
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlin.jvm.JvmName

/**
* Provides [onDraw] open function which will be called by the navigation system to draw content in the place it added
Expand Down Expand Up @@ -45,7 +42,7 @@ abstract class ComposeNode<Config : Base, Base>(
*/
@Composable
protected open fun SubchainsHost(filter: (NavigationChain<Base>) -> Boolean) {
defaultSubchainsHandling(filter)
SubchainsHandling(filter)
}

@Composable
Expand Down
38 changes: 28 additions & 10 deletions compose/src/commonMain/kotlin/ComposeProviders.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package dev.inmo.navigation.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeCompilerApi
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import dev.inmo.navigation.core.NavigationChain
Expand All @@ -11,17 +11,35 @@ import dev.inmo.navigation.core.NavigationNodeFactory
val LocalNavigationNodeFactory = compositionLocalOf<NavigationNodeFactory<*>> { NavigationNodeFactory<Any> { _, _ -> null } }

internal val InternalLocalNavigationNodeFactory: ProvidableCompositionLocal<NavigationNodeFactory<*>> = compositionLocalOf<NavigationNodeFactory<*>> { NavigationNodeFactory<Any> { _, _ -> null } }
internal val InternalLocalNavigationChainProvider: ProvidableCompositionLocal<NavigationChain<*>> = compositionLocalOf<NavigationChain<*>> { NavigationChain<Any>(null, { _, _ -> null }) }
internal val InternalLocalNavigationNodeProvider: ProvidableCompositionLocal<NavigationNode<*, *>> = compositionLocalOf {
NavigationNode.Empty<Any, Any>(
NavigationChain(null, { _, _ -> null}),
Unit
)
internal val InternalLocalNavigationChainProvider: ProvidableCompositionLocal<NavigationChain<*>?> = compositionLocalOf<NavigationChain<*>?> { NavigationChain<Any>(null, { _, _ -> null }) }
internal val InternalLocalNavigationNodeProvider: ProvidableCompositionLocal<NavigationNode<*, *>?> = compositionLocalOf {
null
}

@Suppress("UNCHECKED_CAST")
internal fun <Base> InternalLocalNavigationNodeFactory(): ProvidableCompositionLocal<NavigationNodeFactory<Base>> = InternalLocalNavigationNodeFactory as ProvidableCompositionLocal<NavigationNodeFactory<Base>>
private fun <Base> LocalNavigationNodeFactory(): ProvidableCompositionLocal<NavigationNodeFactory<Base>> = InternalLocalNavigationNodeFactory as ProvidableCompositionLocal<NavigationNodeFactory<Base>>
@Suppress("UNCHECKED_CAST")
internal fun <Base> LocalNavigationChainProvider(): ProvidableCompositionLocal<NavigationChain<Base>> = InternalLocalNavigationChainProvider as ProvidableCompositionLocal<NavigationChain<Base>>
private fun <Base> LocalNavigationChainProvider(): ProvidableCompositionLocal<NavigationChain<Base>?> = InternalLocalNavigationChainProvider as ProvidableCompositionLocal<NavigationChain<Base>?>
@Suppress("UNCHECKED_CAST")
internal fun <Base> LocalNavigationNodeProvider(): ProvidableCompositionLocal<NavigationNode<out Base, Base>> = InternalLocalNavigationNodeProvider as ProvidableCompositionLocal<NavigationNode<out Base, Base>>
private fun <Base> LocalNavigationNodeProvider(): ProvidableCompositionLocal<NavigationNode<out Base, Base>?> = InternalLocalNavigationNodeProvider as ProvidableCompositionLocal<NavigationNode<out Base, Base>?>

@Composable
fun <Base> getNodeFromLocalProvider(): NavigationNode<out Base, Base>? = LocalNavigationNodeProvider<Base>().current
@Composable
fun <Base> doWithNodeInLocalProvider(node: NavigationNode<out Base, Base>, block: @Composable () -> Unit) {
CompositionLocalProvider(LocalNavigationNodeProvider<Base>() provides node, block)
}

@Composable
fun <Base> getChainFromLocalProvider(): NavigationChain<Base>? = LocalNavigationChainProvider<Base>().current
@Composable
fun <Base> doWithChainInLocalProvider(chain: NavigationChain<Base>, block: @Composable () -> Unit) {
CompositionLocalProvider(LocalNavigationChainProvider<Base>() provides chain, block)
}

@Composable
fun <Base> getNodesFactoryFromLocalProvider(): NavigationNodeFactory<Base> = LocalNavigationNodeFactory<Base>().current
@Composable
fun <Base> doWithNodesFactoryInLocalProvider(factory: NavigationNodeFactory<Base>, block: @Composable () -> Unit) {
CompositionLocalProvider(LocalNavigationNodeFactory<Base>() provides factory, block)
}
Loading

0 comments on commit fae87b7

Please sign in to comment.