Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.5.6 #49

Merged
merged 8 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# Navigation Changelog

## 0.5.6

* `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
100 changes: 74 additions & 26 deletions compose/src/commonMain/kotlin/ComposeNavigationExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ 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

Expand All @@ -17,8 +15,23 @@ import kotlinx.coroutines.flow.take
@Composable
internal fun <Base> NavigationChain<Base>.defaultStackHandling() {
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
val stack = stackFlow.collectAsState()
val latestNode = stack.value.lastOrNull()
latestNode ?.StartInCompose()
key(stack.value.lastOrNull()) {
val latestNode = stack.value.lastOrNull()
if (latestNode != null) {
CompositionLocalProvider(LocalNavigationNodeProvider<Base>() provides latestNode) {
when {
latestNode is ComposeNode -> {
val drawState = latestNode.drawerState.collectAsState()
drawState.value ?.invoke()
}

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

@Composable
Expand All @@ -38,7 +51,7 @@ fun <Base> NavigationNode<out Base, Base>.defaultSubchainsHandling(filter: (Navi
@Composable
internal fun <Base> NavigationChain<Base>.StartInCompose(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
block: @Composable NavigationChain<Base>.() -> Unit = { defaultStackHandling() }
block: @Composable NavigationChain<Base>.() -> Unit = { }
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
) {
key(onDismiss) {
onDismiss ?.let {
Expand All @@ -58,58 +71,69 @@ internal fun <Base> NavigationChain<Base>.StartInCompose(

CompositionLocalProvider(LocalNavigationChainProvider<Base>() provides this) {
block()
defaultStackHandling()
}
}

/**
* Creates [NavigationChain] in current composition and call [StartInCompose]
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
*/
@Composable
fun <Base> NavigationNode<*, Base>.NavigationSubChain(
fun <Base> NavigationNode<*, Base>?.NavigationSubChain(
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
block: @Composable NavigationChain<Base>.() -> Unit
) {
remember { createEmptySubChain() }.StartInCompose(onDismiss, block)
val factory = LocalNavigationNodeFactory<Base>().current
val chain = remember(this, factory) {
this ?.createEmptySubChain() ?: NavigationChain<Base>(null, factory)
}
chain.StartInCompose(onDismiss, block)
}

/**
* Trying to get [NavigationNode] using [LocalNavigationNodeProvider] and calling [NavigationSubChain] with it
* Trying to get [InjectNavigationNode] using [LocalNavigationNodeProvider] and calling [NavigationSubChain] with it
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
*/
@Composable
fun <Base> NavigationSubChain(
fun <Base> InjectNavigationChain(
onDismiss: (suspend NavigationChain<Base>.() -> Unit)? = null,
block: @Composable NavigationChain<Base>.() -> Unit
) {
LocalNavigationNodeProvider<Base>().current.NavigationSubChain(onDismiss, block)
}

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

/**
* **If [this] is [ComposeNode]** provides [this] with [LocalNavigationNodeProvider] in [CompositionLocalProvider] and
* calls [this] [ComposeNode.drawerState] value invoke
*
* @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>.StartInCompose(
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
additionalCodeInNodeContext: (@Composable () -> Unit)? = null,
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
onDismiss: (suspend NavigationNode<*, Base>.() -> 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) {
CompositionLocalProvider(LocalNavigationNodeProvider<Base>() provides this) {
additionalCodeInNodeContext ?.invoke()
}
}

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

node.onDestroyFlow.take(1).subscribeSafelyWithoutExceptions(scope) {
Expand All @@ -122,26 +146,50 @@ internal fun <Base> NavigationNode<*, Base>?.StartInCompose(
}

/**
* Trying to create [NavigationNode] in [this] [NavigationChain] and do [StartInCompose] with passing of [onDismiss] in
* Trying to create [InjectNavigationNode] in [this] [NavigationChain] and do [StartInCompose] with passing of [onDismiss] in
* this call
*/
@Composable
fun <Base> NavigationChain<Base>.NavigationSubNode(
InsanusMokrassar marked this conversation as resolved.
Show resolved Hide resolved
config: Base,
additionalCodeInNodeContext: (@Composable () -> Unit)? = null,
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
) {
val node: NavigationNode<out Base, Base>? = remember {
push(config)
}
node ?.StartInCompose(additionalCodeInNodeContext, onDismiss)
}

/**
* Trying to get current [NavigationChain] using [LocalNavigationChainProvider] and calls [NavigationSubNode] with
* passing both [config] and [onDismiss]
*/
@Composable
fun <Base> InjectNavigationNode(
config: Base,
additionalCodeInNodeContext: (@Composable () -> Unit)? = null,
onDismiss: (suspend NavigationNode<*, Base>.() -> Unit)? = null,
) {
val node: NavigationNode<out Base, Base>? = remember { push(config) }
node.StartInCompose(onDismiss)
val chain = LocalNavigationChainProvider<Base>().current ?: run {
InjectNavigationChain<Base> {
InjectNavigationNode(config, additionalCodeInNodeContext, onDismiss)
}
return@InjectNavigationNode
}
chain.NavigationSubNode(config, additionalCodeInNodeContext, onDismiss)
}

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

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ComposeCompilerApi
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.compositionLocalOf
import dev.inmo.navigation.core.NavigationChain
Expand All @@ -11,17 +9,14 @@ 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>>
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>>
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>>
fun <Base> LocalNavigationNodeProvider(): ProvidableCompositionLocal<NavigationNode<out Base, Base>?> = InternalLocalNavigationNodeProvider as ProvidableCompositionLocal<NavigationNode<out Base, Base>?>
90 changes: 83 additions & 7 deletions compose/src/commonMain/kotlin/InitNavigation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,14 @@ import kotlinx.coroutines.CoroutineScope
* @param rootChain Default root chain where navigation will be rooted
*/
@Composable
fun <Base> initNavigation(
defaultStartChain: ConfigHolder.Chain<Base>,
private fun <Base> initNavigation(
defaultStartChain: ConfigHolder.Chain<Base>?,
configsRepo: NavigationConfigsRepo<Base>,
nodesFactory: NavigationNodeFactory<Base>,
scope: CoroutineScope = rememberCoroutineScope(),
dropRedundantChainsOnRestore: Boolean = false,
rootChain: NavigationChain<Base> = NavigationChain(null, nodesFactory)
rootChain: NavigationChain<Base> = NavigationChain(null, nodesFactory),
defaultInitBlock: @Composable () -> Unit = {}
) {
val logger = TagLogger("NavigationJob")
val subscope = scope.LinkedSupervisorScope()
Expand All @@ -56,7 +57,7 @@ fun <Base> initNavigation(
}
}
}
CompositionLocalProvider(InternalLocalNavigationNodeFactory<Base>() provides resultNodesFactory) {
CompositionLocalProvider(LocalNavigationNodeFactory<Base>() provides resultNodesFactory) {
logger.d { "Hierarchy saving enabled" }

val existsChain = configsRepo.get()
Expand All @@ -71,13 +72,88 @@ fun <Base> initNavigation(

val restoredRootChain = remember {
restoreHierarchy<Base>(
existsChain ?: defaultStartChain,
existsChain ?: defaultStartChain ?: ConfigHolder.Chain<Base>(null),
factory = resultNodesFactory,
rootChain = rootChain,
dropRedundantChainsOnRestore = dropRedundantChainsOnRestore
)
}
restoredRootChain ?.start(subscope)
restoredRootChain ?.StartInCompose({ savingJob.cancel() })
restoredRootChain ?.let {
if (existsChain == null && defaultStartChain == null) {
CompositionLocalProvider(LocalNavigationChainProvider<Base>() provides it) {
defaultInitBlock()
}
} else {
it.defaultStackHandling()
}

it.start(subscope)
}
}
}

/**
* Creates root of navigation in current place
*
* @param defaultStartChain Config of default tree for navigation in case [configsRepo] contains no any information
* about last used navigation
* @param configsRepo Contains information about last saved navigation tree
* @param nodesFactory Provides opportunity to create [dev.inmo.navigation.core.NavigationNode] from their configs
* @param scope Will be used to create [LinkedSupervisorScope] which will be the root [CoroutineScope] for all navigation
* operations
* @param dropRedundantChainsOnRestore Drops chains with empty content
* @param rootChain Default root chain where navigation will be rooted
*/
@Composable
fun <Base> initNavigation(
defaultStartChain: ConfigHolder.Chain<Base>,
configsRepo: NavigationConfigsRepo<Base>,
nodesFactory: NavigationNodeFactory<Base>,
scope: CoroutineScope = rememberCoroutineScope(),
dropRedundantChainsOnRestore: Boolean = false,
rootChain: NavigationChain<Base> = NavigationChain(null, nodesFactory),
) = initNavigation(
defaultStartChain = defaultStartChain,
configsRepo = configsRepo,
nodesFactory = nodesFactory,
scope = scope,
dropRedundantChainsOnRestore = dropRedundantChainsOnRestore,
rootChain = rootChain,
defaultInitBlock = {}
)


/**
* Creates root of navigation in current place
*
* @param defaultStartChain Config of default tree for navigation in case [configsRepo] contains no any information
* about last used navigation
* @param configsRepo Contains information about last saved navigation tree
* @param nodesFactory Provides opportunity to create [dev.inmo.navigation.core.NavigationNode] from their configs
* @param scope Will be used to create [LinkedSupervisorScope] which will be the root [CoroutineScope] for all navigation
* operations
* @param dropRedundantChainsOnRestore Drops chains with empty content
* @param rootChain Default root chain where navigation will be rooted
*/
@Composable
fun <Base> initNavigation(
rootNodeConfig: Base,
configsRepo: NavigationConfigsRepo<Base>,
nodesFactory: NavigationNodeFactory<Base>,
scope: CoroutineScope = rememberCoroutineScope(),
dropRedundantChainsOnRestore: Boolean = false,
rootChain: NavigationChain<Base> = NavigationChain(null, nodesFactory),
defaultInitBlock: @Composable () -> Unit
) = initNavigation(
defaultStartChain = null,
configsRepo = configsRepo,
nodesFactory = nodesFactory,
scope = scope,
dropRedundantChainsOnRestore = dropRedundantChainsOnRestore,
rootChain = rootChain
) {
InjectNavigationNode(
rootNodeConfig,
defaultInitBlock
)
}
7 changes: 7 additions & 0 deletions core/src/commonMain/kotlin/NavigationNode.kt
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,13 @@ abstract class NavigationNode<Config : Base, Base>(
config: T,
id: NavigationNodeId = NavigationNodeId()
) : NavigationNode<T, Base>(id) {
object Config
override val configState: StateFlow<T> = MutableStateFlow(config).asStateFlow()

companion object {
val DefaultFactory = NavigationNodeFactory.Companion.Typed<Any, Config> { chain, config ->
Empty(chain, config)
}
}
}
}
Loading
Loading