Repository: arkivanov/Decompose
Pull request #237 (2022-10-14 08:46:53)

Update compose-darwin branch


Patch:
diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api
index 585ea94d..7bdc249e 100644
--- a/decompose/api/android/decompose.api
+++ b/decompose/api/android/decompose.api
@@ -177,6 +177,8 @@ public final class com/arkivanov/decompose/router/stack/StackNavigatorExtKt {
 	public static final fun popWhile (Lcom/arkivanov/decompose/router/stack/StackNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
 	public static final fun push (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
 	public static synthetic fun push$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
+	public static final fun replaceAll (Lcom/arkivanov/decompose/router/stack/StackNavigator;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
+	public static synthetic fun replaceAll$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
 	public static final fun replaceCurrent (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
 	public static synthetic fun replaceCurrent$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
 }
diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api
index 7647fc31..55cd07ca 100644
--- a/decompose/api/jvm/decompose.api
+++ b/decompose/api/jvm/decompose.api
@@ -164,6 +164,8 @@ public final class com/arkivanov/decompose/router/stack/StackNavigatorExtKt {
 	public static final fun popWhile (Lcom/arkivanov/decompose/router/stack/StackNavigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)V
 	public static final fun push (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
 	public static synthetic fun push$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
+	public static final fun replaceAll (Lcom/arkivanov/decompose/router/stack/StackNavigator;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
+	public static synthetic fun replaceAll$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;[Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
 	public static final fun replaceCurrent (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)V
 	public static synthetic fun replaceCurrent$default (Lcom/arkivanov/decompose/router/stack/StackNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)V
 }
diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
index 4602ca42..06ec9ddc 100644
--- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
+++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/stack/StackNavigatorExt.kt
@@ -65,6 +65,15 @@ fun <C : Any> StackNavigator<C>.replaceCurrent(configuration: C, onComplete: ()
     )
 }
 
+/**
+ * Replaces the whole stack with the provided [configurations].
+ *
+ * @param onComplete called when the navigation is finished (either synchronously or asynchronously).
+ */
+fun <C : Any> StackNavigator<C>.replaceAll(vararg configurations: C, onComplete: () -> Unit = { }) {
+    navigate(transformer = { configurations.toList() }, onComplete = { _, _ -> onComplete() })
+}
+
 /**
  * Removes all components with configurations of [configuration]'s class, and adds the provided [configuration] to the top of the stack.
  * The operation is performed as one transaction. If there is already a component with the same configuration, it will not be recreated.
diff --git a/deps.versions.toml b/deps.versions.toml
index 9c9c149b..7d89938a 100644
--- a/deps.versions.toml
+++ b/deps.versions.toml
@@ -1,16 +1,16 @@
 [versions]
 
-decompose = "1.0.0-alpha-06-native-compose"
-kotlin = "1.7.10"
+decompose = "1.0.0-alpha-07-native-compose"
+kotlin = "1.7.20"
 essenty = "0.6.0"
 reaktive = "1.2.1"
 junit = "4.13.2"
-jetbrainsCompose = "1.2.0-beta02"
+jetbrainsCompose = "1.2.0"
 jetbrainsKotlinWrappers = "0.0.1-pre.325-kotlin-1.6.10"
 jetbrainsKotlinxCoroutines = "1.6.1"
 jetbrainsBinaryCompatibilityValidator = "0.10.0"
 jetpackCompose = "1.2.1"
-jetpackComposeCompiler = "1.3.1"
+jetpackComposeCompiler = "1.3.2"
 androidGradle = "7.2.2"
 androidMaterial = "1.3.0"
 androidPlay = "1.10.0"
diff --git a/docs/component/custom-component-context.md b/docs/component/custom-component-context.md
index 0a08da92..e692c0b3 100644
--- a/docs/component/custom-component-context.md
+++ b/docs/component/custom-component-context.md
@@ -22,64 +22,7 @@ class DefaultAppComponentContext(
 }
 ```
 
-## How to initialize Child Stack with custom ComponentContext
+## Navigation with custom ComponentContext
 
-In order to pass this `AppComponentContext` to child components, make an extension function on the `AppComponentContext` interface. This custom extension function will initialize the `Child Stack` and provide child `AppComponentContext`.
-
-```kotlin
-fun <C : Parcelable, T : Any> AppComponentContext.appChildStack(
-    source: StackNavigationSource<C>,
-    initialStack: () -> List<C>,
-    configurationClass: KClass<out C>,
-    key: String = "DefaultStack",
-    handleBackButton: Boolean = false,
-    childFactory: (configuration: C, AppComponentContext) -> T
-): Value<ChildStack<C, T>> =
-    childStack(
-        source = source,
-        initialStack = initialStack,
-        configurationClass = configurationClass,
-        key = key,
-        handleBackButton = handleBackButton
-    ) { configuration, componentContext ->
-        childFactory(
-            configuration,
-            DefaultAppComponentContext(
-                componentContext = componentContext
-            )
-        )
-    }
-
-inline fun <reified C : Parcelable, T : Any> AppComponentContext.appChildStack(
-    source: StackNavigationSource<C>,
-    noinline initialStack: () -> List<C>,
-    key: String = "DefaultStack",
-    handleBackButton: Boolean = false,
-    noinline childFactory: (configuration: C, AppComponentContext) -> T
-): Value<ChildStack<C, T>> =
-    appChildStack(
-        source = source,
-        initialStack = initialStack,
-        configurationClass = C::class,
-        key = key,
-        handleBackButton = handleBackButton,
-        childFactory = childFactory,
-    )
-```
-
-Finally, in your components you can create the new extension function that will utilize the new custom `AppComponentContext`.
-
-```kotlin
-class MyComponent(componentContext: AppComponentContext) : AppComponentContext by componentContext {
-
-    private val navigation = StackNavigation<Configuration>()
-
-    private val childStack = appChildStack(
-        source = navigation,
-        initialStack = { listOf(Configuration.Home) },
-        childFactory = { configuration, appComponentContext ->
-            // return child components using the custom component context
-        }
-    )
-}
-```
+- [Using Child Stack](../navigation/stack/component-context.md)
+- [Using Child Overlay](../navigation/overlay/component-context.md)
diff --git a/docs/navigation/overlay/component-context.md b/docs/navigation/overlay/component-context.md
new file mode 100644
index 00000000..5600a7d9
--- /dev/null
+++ b/docs/navigation/overlay/component-context.md
@@ -0,0 +1,90 @@
+# Child Overlay with custom `ComponentContext`
+
+Custom `ComponentContext` allows passing extra data and functionality to every child component. See [Custom ComponentContext](../../component/custom-component-context.md) page for more information about creating custom `AppComponentContext`.
+
+In order to pass custom component context like `AppComponentContext` to child overlay components, make an extension function on your `AppComponentContext` interface. This custom extension function will initialize the `Child Overlay` and provide every child an `AppComponentContext`.
+
+```kotlin
+fun <C : Parcelable, T : Any> AppComponentContext.appChildOverlay(
+    source: OverlayNavigationSource<C>,
+    initialConfiguration: () -> C?,
+    configurationClass: KClass<out C>,
+    key: String = "DefaultOverlay",
+    handleBackButton: Boolean = false,
+    persistent: Boolean = false,
+    childFactory: (configuration: C, AppComponentContext) -> T
+): Value<ChildOverlay<C, T>> =
+    childOverlay(
+        source = source,
+        configurationClass = configurationClass,
+        key = key,
+        handleBackButton = handleBackButton,
+        initialConfiguration = initialConfiguration,
+        persistent = persistent
+    ) { configuration, componentContext ->
+        childFactory(
+            configuration,
+            DefaultAppComponentContext(
+                componentContext = componentContext,
+                bundleId,
+                settings,
+                boarding
+            )
+        )
+    }
+
+inline fun <reified C : Parcelable, T : Any> AppComponentContext.appChildStack(
+    source: OverlayNavigationSource<C>,
+    noinline  initialConfiguration: () -> C?,
+    key: String = "DefaultOverlay",
+    handleBackButton: Boolean = false,
+    persistent: Boolean = false,
+    noinline childFactory: (configuration: C, AppComponentContext) -> T
+): Value<ChildOverlay<C, T>> =
+    appChildOverlay(
+        source = source,
+        initialConfiguration = initialConfiguration,
+        configurationClass = C::class,
+        key = key,
+        handleBackButton = handleBackButton,
+        persistent = persistent,
+        childFactory = childFactory,
+    )
+```
+
+Finally, in your components you can use the new extension function that will utilize the custom `AppComponentContext`.
+
+```kotlin
+interface RootComponent {
+    val dialog: Value<ChildOverlay<*, DialogComponent>>
+}
+
+class DefaultRootComponent(
+    componentContext: AppComponentContext,
+) : RootComponent, AppComponentContext by componentContext {
+
+    private val dialogNavigation = OverlayNavigation<DialogConfig>()
+
+    private val _dialog =
+        appChildOverlay(
+            source = dialogNavigation,
+            // persistent = false, // Disable navigation state saving, if needed
+            handleBackButton = true, // Close the dialog on back button press
+        ) { config, componentContext ->
+            DefaultDialogComponent(
+                componentContext = componentContext,
+                message = config.message,
+                onDismissed = dialogNavigation::dismiss,
+            )
+        }
+
+    override val dialog: Value<ChildOverlay<*, DialogComponent>> = _dialog
+
+    private fun showDialog(message: String) {
+        dialogNavigation.activate(DialogConfig(message = message))
+    }
+
+    @Parcelize
+    private data class DialogConfig(val message: String) : Parcelable
+}
+```
diff --git a/docs/navigation/stack/component-context.md b/docs/navigation/stack/component-context.md
new file mode 100644
index 00000000..737d8c7d
--- /dev/null
+++ b/docs/navigation/stack/component-context.md
@@ -0,0 +1,94 @@
+# Child Stack with custom `ComponentContext`
+
+Custom `ComponentContext` allows passing extra data and functionality to every child component. See [Custom ComponentContext](../../component/custom-component-context.md) page for more information about creating custom `AppComponentContext`.
+
+In order to pass custom component context like `AppComponentContext` to child stack components, make an extension function on your `AppComponentContext` interface. This custom extension function will initialize the `Child Stack` and provide every child an `AppComponentContext`.
+
+```kotlin
+fun <C : Parcelable, T : Any> AppComponentContext.appChildStack(
+    source: StackNavigationSource<C>,
+    initialStack: () -> List<C>,
+    configurationClass: KClass<out C>,
+    key: String = "DefaultStack",
+    handleBackButton: Boolean = false,
+    childFactory: (configuration: C, AppComponentContext) -> T
+): Value<ChildStack<C, T>> =
+    childStack(
+        source = source,
+        initialStack = initialStack,
+        configurationClass = configurationClass,
+        key = key,
+        handleBackButton = handleBackButton
+    ) { configuration, componentContext ->
+        childFactory(
+            configuration,
+            DefaultAppComponentContext(
+                componentContext = componentContext
+            )
+        )
+    }
+
+inline fun <reified C : Parcelable, T : Any> AppComponentContext.appChildStack(
+    source: StackNavigationSource<C>,
+    noinline initialStack: () -> List<C>,
+    key: String = "DefaultStack",
+    handleBackButton: Boolean = false,
+    noinline childFactory: (configuration: C, AppComponentContext) -> T
+): Value<ChildStack<C, T>> =
+    appChildStack(
+        source = source,
+        initialStack = initialStack,
+        configurationClass = C::class,
+        key = key,
+        handleBackButton = handleBackButton,
+        childFactory = childFactory,
+    )
+```
+
+Finally, in your components you can use the new extension function that will utilize the custom `AppComponentContext`.
+
+```kotlin
+interface Root {
+
+    val childStack: Value<ChildStack<*, Child>>
+
+    sealed class Child {
+        class List(val component: ItemList) : Child()
+        class Details(val component: ItemDetails) : Child()
+    }
+}
+
+class RootComponent(
+    componentContext: AppComponentContext
+) : Root, AppComponentContext by componentContext {
+
+    private val navigation = StackNavigation<Config>()
+
+    private val _childStack =
+        childStack(
+            source = navigation,
+            initialConfiguration = Config.List,
+            handleBackButton = true, // Pop the back stack on back button press
+            childFactory = ::createChild,
+        )
+
+    override val childStack: Value<ChildStack<*, Root.Child>> = _childStack
+
+    private fun createChild(config: Config, componentContext: AppComponentContext): Root.Child =
+        TODO('Initialize child based on config with the custom component context')
+
+    private fun itemList(componentContext: AppComponentContext): ItemList =
+        TODO('Initialize ItemDetails with the custom component context')
+
+    private fun itemDetails(componentContext: AppComponentContext, config: Config.Details): ItemDetails =
+        TODO('Initialize ItemDetails with the custom component context')
+
+    private sealed class Config : Parcelable {
+        @Parcelize
+        object List : Config()
+
+        @Parcelize
+        data class Details(val itemId: Long) : Config()
+    }
+}
+```
diff --git a/docs/navigation/stack/navigation.md b/docs/navigation/stack/navigation.md
index f919be43..33f80484 100644
--- a/docs/navigation/stack/navigation.md
+++ b/docs/navigation/stack/navigation.md
@@ -41,7 +41,7 @@ sealed class Configuration {
 val navigation = StackNavigation<Configuration>()
 ```
 
-### Push
+### push
 
 Pushes the provided `Configuration` at the top of the stack.
 
@@ -50,7 +50,7 @@ navigation.push(Configuration.B)
 navigation.push(Configuration.C)
 ```
 
-![](../media/RouterPush.png)
+![](../../media/RouterPush.png)
 
 ### pop
 
@@ -69,7 +69,7 @@ navigation.pop { isSuccess ->
 }
 ```
 
-![](../media/RouterPop.png)
+![](../../media/RouterPop.png)
 
 ### popWhile
 
@@ -79,7 +79,7 @@ Drops the configurations at the top of the stack while the provided predicate re
 navigation.popWhile { topOfStack: Configuration -> topOfStack !is B }
 ```
 
-![](../media/RouterPopWhile.png)
+![](Decompose/media/RouterPopWhile.png)
 
 ### replaceCurrent
 
@@ -89,7 +89,7 @@ Replaces the current configuration at the top of the stack with the provided `Co
 navigation.replaceCurrent(Configuration.D)
 ```
 
-![](../media/RouterReplaceCurrent.png)
+![](../../media/RouterReplaceCurrent.png)
 
 ### bringToFront
 
@@ -102,4 +102,4 @@ Removes all components with configurations of the provided `Configuration`'s cla
 navigation.bringToFront(Configuration.B)
 ```
 
-![](../media/RouterBringToFront.png)
+![](../../media/RouterBringToFront.png)
diff --git a/mkdocs.yml b/mkdocs.yml
index 23cc94d1..4fa4b4da 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -37,9 +37,11 @@ nav:
       - Navigation: navigation/stack/navigation.md
       - Deep linking: navigation/stack/deeplinking.md
       - Web browser history: navigation/stack/browser-history.md
+      - Custom ComponentContext: navigation/stack/component-context.md
     - Child Overlay:
       - Overview: navigation/overlay/overview.md
       - Navigation: navigation/overlay/navigation.md
+      - Custom ComponentContext: navigation/overlay/component-context.md
 
   - Extensions:
     - Overview: extensions/overview.md
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 82dea982..98dd7ba7 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -18,7 +18,7 @@ pluginManagement {
     resolutionStrategy {
         eachPlugin {
             if (requested.id.toString() == "com.arkivanov.gradle.setup") {
-                useModule("com.github.arkivanov:gradle-setup-plugin:f5e40dcc04")
+                useModule("com.github.arkivanov:gradle-setup-plugin:60ac46054c")
             }
         }
     }


Test patch:
diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/RouterReplaceAllTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/RouterReplaceAllTest.kt
new file mode 100644
index 00000000..fb89ff58
--- /dev/null
+++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/stack/RouterReplaceAllTest.kt
@@ -0,0 +1,28 @@
+package com.arkivanov.decompose.router.stack
+
+import kotlin.test.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertTrue
+
+@Suppress("TestFunctionName")
+class RouterReplaceAllTest {
+
+    @Test
+    fun WHEN_replaceAll_THEN_configurations_correctly_applied() {
+        val navigator = TestStackNavigator(listOf(1, 2, 3))
+
+        navigator.replaceAll(3, 4, 5)
+
+        assertEquals(listOf(3, 4, 5), navigator.configurations)
+    }
+
+    @Test
+    fun WHEN_replace_all_THEN_onComplete_called() {
+        val navigator = TestStackNavigator(listOf(1, 2, 3))
+        var isCalled = false
+
+        navigator.replaceAll(4) { isCalled = true }
+
+        assertTrue(isCalled)
+    }
+}