diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt index 4f37fa5b4..a40b3c0f3 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt @@ -3,7 +3,7 @@ package com.arkivanov.decompose.router.panels import com.arkivanov.decompose.ExperimentalDecomposeApi /** - * Determines how lifecycles of the panels within the Child Panels navigation model are changing. + * Determines how lifecycles of the child components within the Child Panels navigation model are changing. * * @see com.arkivanov.essenty.lifecycle.Lifecycle.State */ diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt index 6e68973c8..3d9d1eaf7 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt @@ -58,7 +58,7 @@ fun PanelsNavigator.navigate( } /** - * Activates the [Main][ChildPanels.main] component represented by the specified [main], + * Activates the [Main][ChildPanels.main] component represented by the specified [main] configuration, * and dismisses (destroys) any currently active Main component. * * @param main a configuration of the Main component being activated. @@ -76,7 +76,7 @@ fun PanelsNavigator.activateMain( } /** - * Activates the [Details][ChildPanels.details] component represented by the specified [details], + * Activates the [Details][ChildPanels.details] component represented by the specified [details] configuration, * and dismisses (destroys) any currently active Details component. * * @param details a configuration of the Details component being activated. @@ -109,7 +109,7 @@ fun PanelsNavigator.dismissDetails( } /** - * Activates the [Extra][ChildPanels.extra] component represented by the specified [extra], + * Activates the [Extra][ChildPanels.extra] component represented by the specified [extra] configuration, * and dismisses (destroys) any currently active Extra component. * * @param extra a configuration of the Extra component being activated. diff --git a/docs/extensions/compose.md b/docs/extensions/compose.md index 480324653..98935a840 100644 --- a/docs/extensions/compose.md +++ b/docs/extensions/compose.md @@ -207,17 +207,15 @@ fun DialogContent(component: DialogComponent) { Child Slot might not be suitable for a Navigation Drawer. This is because the Navigation Drawer can be opened by a drag gesture at any time. The corresponding component should be [always created](https://arkivanov.github.io/Decompose/component/child-components/#adding-a-child-component-manually) so that it's always ready to be rendered. -## Pager-like navigation - -!!!warning - This navigation model is experimental, the API is subject to change. +## Child Pages navigation with Compose The [Child Pages](../navigation/pages/overview.md) navigation model provides [ChildPages](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/pages/ChildPages.kt) as `Value` that can be observed in a `Composable` component. The Compose extension module provides the [ChildPages(...)](https://github.com/arkivanov/Decompose/blob/master/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/pages/ChildPages.kt) function which has the following features: - It listens for the `ChildPages` changes and displays child components using `HorizontalPager` or `VerticalPager` (see the related Jetpack Compose [documentation](https://developer.android.com/jetpack/compose/layouts/pager)). -- It animates page changes if there is an `animation` spec provided. +- It animates page changes if there is a `scrollAnimation` spec provided. +- It supports displaying either just two panels (Main and Details) or three panels (Main, Details and Extra). === "Before version 3.2.0-alpha03" @@ -267,6 +265,87 @@ The Compose extension module provides the [ChildPages(...)](https://github.com/a } ``` +## Child Panels navigation with Compose + +!!!warning + This navigation model is experimental since version `3.2.0-beta01`, the API is subject to change. + +The [Child Panels](../navigation/panels/overview.md) navigation model provides [ChildPanels](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanels.kt) as `Value` that can be observed in a `Composable` component. + +The experimental Compose extension module provides the [ChildPanels(...)](https://github.com/arkivanov/Decompose/blob/master/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt) function which has the following features: + +- It listens for the `ChildPanels` changes and displays child components (panels) using the provided `layout`. +- It animates panel changes using the provided `animators` and `predictiveBackParams` specs. + +The following arguments are supported. + +- `panels` - an observable [ChildPanels] to be displayed. +- `mainChild` - a `Composable` function that displays the provided Main component. +- `detailsChild` - a `Composable` function that displays the provided Details component. +- `extraChild` - a `Composable` function that displays the provided Extra component. +- `modifier` - a `Modifier` to be applied to a wrapping container. +- `layout` - an implementation of [ChildPanelsLayout](https://github.com/arkivanov/Decompose/blob/master/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout.kt) responsible for laying out panels. The default layout is [HorizontalChildPanelsLayout](https://github.com/arkivanov/Decompose/blob/master/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt). +- `animators` - a [ChildPanelsAnimators](https://github.com/arkivanov/Decompose/blob/master/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators.kt) containing panel animators for different kinds of layouts. +- `predictiveBackParams` - a function that returns `PredictiveBackParams` for the specified `ChildPanels`, or `null`. The predictive back gesture is enabled if the value returned for the specified `ChildStack` is not `null`, and disabled if the returned value is `null`. Only works if `ChildPanels.mode` is `SINGLE`. Also see the related docs below. + +The default `HorizontalChildPanelsLayout` layout places child components (panels) in the following ways. + +- If the `mode` is `SINGLE`, all panels are displayed in a stack. The Main panel, then the Details panel on top (if any), and finally the Extra panel (if any). +- If the `mode` is `DUAL`, the Main panel is always displayed on the left side, and then the Details and the Extra panels are displayed in a stack on the right side (next to the Main panel). +- If the `mode` is `TRIPLE`, all panels are displayed horizontally side by side. + +```kotlin title="Basic example" +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanels + +@Composable +fun PanelsContent(component: PanelsComponent) { + ChildPanels( + panels = component.panels, + mainChild = { MainContent(it.instance) }, + detailsChild = { DetailsContent(it.instance) }, + ) +} + +@Composable +fun MainContent(component: MainComponent) { + // Omitted code +} + +@Composable +fun DetailsContent(component: DetailsComponent) { + // Omitted code +} +``` + +```kotlin title="Example with animations" +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanels +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.plus +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.scale +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.materialPredictiveBackAnimatable + +@Composable +fun PanelsContent(component: PanelsComponent) { + ChildPanels( + panels = component.panels, + mainChild = { MainContent(it.instance) }, + detailsChild = { DetailsContent(it.instance) }, + animators = ChildPanelsAnimators(single = fade() + scale(), dual = fade() to fade()), + predictiveBackParams = { // See the docs below + PredictiveBackParams( + backHandler = component.backHandler, + onBack = component::onBackClicked, + animatable = ::materialPredictiveBackAnimatable, + ) + }, + ) +} +``` + ## Animations Decompose provides [Child Animation API](https://github.com/arkivanov/Decompose/tree/master/extensions-compose/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/stack/animation) for Compose, as well as some predefined animation specs. To enable child animations you need to pass the `animation` argument to the `Children` function. There are predefined animators provided by Decompose. diff --git a/docs/navigation/pages/navigation.md b/docs/navigation/pages/navigation.md index c20277284..989164aa6 100644 --- a/docs/navigation/pages/navigation.md +++ b/docs/navigation/pages/navigation.md @@ -12,6 +12,7 @@ All navigation in `Child Pages` is performed using the [`PagesNavigator`](https: There is also `navigate` extension function without the `onComplete` callback, for convenience. ```kotlin title="Creating the navigation" +// In your component class val navigation = PagesNavigation() ``` diff --git a/docs/navigation/pages/overview.md b/docs/navigation/pages/overview.md index df335ad85..251bc85ce 100644 --- a/docs/navigation/pages/overview.md +++ b/docs/navigation/pages/overview.md @@ -1,17 +1,14 @@ # Child Pages overview -## The Child Pages (experimental) +## The Child Pages `Child Pages` is a navigation model for managing a list of components (pages) with one selected (active) component. The list can be empty. -!!!warning - This navigation model is experimental, the API is subject to change. - Similarly to `Child Stack`, each component has its own `Lifecycle`. By default, the currently selected page is `ACTIVE`, its two neighbours are `INACTIVE`, and the rest are `DESTROYED`. You can implement your own logic, for example with circular behaviour. It is possible to have more than one `Child Pages` navigation model in a component, nested navigation is also supported. -The `Child Pages` navigation model consists of two main entities: +The `Child Pages` navigation model consists of three main entities: - [Pages](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/pages/Pages.kt) - represents a state of the `Child Pages` navigation model. The navigation is performed by creating a new navigation state from the previous one. - `Pages#items` - the list of child configurations, must be unique, can be empty. @@ -49,7 +46,7 @@ There are three steps to initialize `Child Pages`: ### Displaying pages with Compose -`Child Pages` state can be observed and displayed in Compose by using the `Pager` `Composable` function from the Compose extensions module provided by Decompose. Please see the [related documentation](../../extensions/compose.md#pager-like-navigation) for more information. +`Child Pages` state can be observed and displayed in Compose by using the `ChildPages` `Composable` function from the Compose extensions module provided by Decompose. Please see the [related documentation](../../extensions/compose.md#child-pages-navigation-with-compose) for more information. ## Example diff --git a/docs/navigation/panels/navigation.md b/docs/navigation/panels/navigation.md new file mode 100644 index 000000000..6254013de --- /dev/null +++ b/docs/navigation/panels/navigation.md @@ -0,0 +1,333 @@ +# Navigation with Child Stack + +## The PanelsNavigator + +All navigation in `Child Panels` is performed using the [`PanelsNavigator`](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigator.kt) interface, which is extended by the `PanelsNavigation` interface. + +`PanelsNavigator` contains the `navigate` method with the following arguments: + +- `transformer` - converts the current `Panels` state to a new one. +- `onComplete` - called when navigation is finished. + +There is also `navigate` extension function without the `onComplete` callback, for convenience. + +```kotlin title="Creating the navigation" +// In your component class +val navigation = PanelsNavigation() +``` + +### The navigation process + +During the navigation process, the `Child Panels` navigation model compares the new state with the previous one. It ensures that all removed components are destroyed, and all created components have correct lifecycle states + +The `Child Panels` navigation model usually performs the navigation synchronously, which means that by the time the `navigate` method returns, the navigation is finished and all component lifecycles are moved into required states. However, the navigation is performed asynchronously in case of recursive invocations - e.g. `navigate` is called from `onResume` lifecycle callback of a component being pushed. All recursive invocations are queued and performed one by one once the current navigation is finished. + +## PanelsNavigator extension functions + +There are `PanelsNavigator` [extension functions](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt) to simplify the navigation. Some of which were already used in the [Child Panels Overview example](overview.md#example). + +### navigate(main, details, extra) + +Sets the provided Main, Details and Extra configurations. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(main = Main2, details = Details2, extra = Extra2) + ``` + + ```title="After" + {Main2, Details2, Extra2, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(main = Main2, details = null, extra = null) + ``` + + ```title="After" + {Main2, null, null, SINGLE} + ``` + +### navigate(details, extra) + +Sets the provided Details and Extra configurations. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(details = Details2, extra = Extra2) + ``` + + ```title="After" + {Main1, Details2, Extra2, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(details = null, extra = null) + ``` + + ```title="After" + {Main1, null, null, SINGLE} + ``` + +### navigate(extra) + +Sets the provided Extra configuration. + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(extra = Extra2) + ``` + + ```title="After" + {Main1, Details1, Extra2, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.navigate(extra = null) + ``` + + ```title="After" + {Main1, Details1, null, SINGLE} + ``` + +### activateMain(main) + +Activates the Main component represented by the specified `main` configuration, and dismisses (destroys) any currently active Main component. + +!!! note "Illustration" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.activateMain(main = Main2) + ``` + + ```title="After" + {Main2, Details1, Extra1, SINGLE} + ``` + +### activateDetails(details) + +Activates the Details component represented by the specified `details` configuration, and dismisses (destroys) any currently active Details component. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, null, Extra1, SINGLE} + ``` + + ```kotlin + navigation.activateDetails(details = Details1) + ``` + + ```title="After" + {Main1, Details1, Extra1, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.activateDetails(details = Details2) + ``` + + ```title="After" + {Main1, Details2, Extra1, SINGLE} + ``` + +### dismissDetails() + +Dismisses (destroys) the currently active Details component, if any. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.dismissDetails() + ``` + + ```title="After" + {Main1, null, Extra1, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, null, Extra1, SINGLE} + ``` + + ```kotlin + navigation.dismissDetails() + ``` + + ```title="After" + {Main1, null, Extra1, SINGLE} + ``` + +### activateExtra(extra) + +Activates the Extra component represented by the specified `extra` configuration, and dismisses (destroys) any currently active Extra component. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, null, SINGLE} + ``` + + ```kotlin + navigation.activateExtra(extra = Extra1) + ``` + + ```title="After" + {Main1, Details1, Extra1, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.activateExtra(extra = Extra2) + ``` + + ```title="After" + {Main1, Details1, Extra2, SINGLE} + ``` + +### dismissExtra() + +Dismisses (destroys) the currently active Extra component, if any. + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.dismissExtra() + ``` + + ```title="After" + {Main1, Details1, null, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, null, SINGLE} + ``` + + ```kotlin + navigation.dismissExtra() + ``` + + ```title="After" + {Main1, Details1, null, SINGLE} + ``` + +### pop() + +Dismisses the Extra component (if it exists) or the Details component (if it exists). + +!!! note "Illustration 1" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.pop() + ``` + + ```title="After" + {Main1, Details1, null, SINGLE} + ``` + +!!! note "Illustration 2" + + ```title="Before" + {Main1, Details1, null, SINGLE} + ``` + + ```kotlin + navigation.pop() + ``` + + ```title="After" + {Main1, null, null, SINGLE} + ``` + +!!! note "Illustration 3" + + ```title="Before" + {Main1, null, null, SINGLE} + ``` + + ```kotlin + navigation.pop() + ``` + + ```title="After" + {Main1, null, null, SINGLE} + ``` + +### setMode(mode) + +Sets `Panels.mode` to the specified `mode` value and updates component lifecycles accordingly. + +!!! note "Illustration" + + ```title="Before" + {Main1, Details1, Extra1, SINGLE} + ``` + + ```kotlin + navigation.setMode(ChildPanelsMode.DUAL) + ``` + + ```title="After" + {Main1, Details1, Extra1, DUAL} + ``` diff --git a/docs/navigation/panels/overview.md b/docs/navigation/panels/overview.md new file mode 100644 index 000000000..8ec648282 --- /dev/null +++ b/docs/navigation/panels/overview.md @@ -0,0 +1,136 @@ +# Child Panels overview + +## The Child Panels + +`Child Panels` is a navigation model for managing a set of up to three child components (panels): Main (required), Details (optional) and Extra (optional). This navigation model can be compared with Compose [List-Details Layout](https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail). + +!!!warning + This navigation model is experimental since version `3.2.0-beta01`, the API is subject to change. + +Similarly to `Child Stack`, each component has its own `Lifecycle` automatically controlled by the navigation model depending on the current `ChildPanelsMode`. + +It is possible to have more than one `Child Panels` navigation model in a component, nested navigation is also supported. + +The `Child Panels` navigation model consists of the following main entities: + +- [Panels](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/Panels.kt) - represents a state of the `Child Panels` navigation model. The navigation is performed by creating a new navigation state from the previous one. + - `Panels#main` - a configuration of the Main panel. + - `Panels#details` - an optional configuration of the Details panel, default value is `null`. + - `Panels#extra` - an optional configuration of the Extra panel, default value is `null`. + - `Panels#mode` - the current `ChildPanelsMode`, determines how lifecycles of the panels within the Child Panels navigation model are changing, default value is `ChildPanelsMode.SINGLE`. +- [ChildPanels](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanels.kt) - a simple data class that stores all child components (panels) and their configurations, as well as the current mode. + - `ChildPanels#main` - a Main child component. + - `ChildPanels#details` - an optional Details child component. + - `ChildPanels#extra` - an optional Extra child component. + - `ChildPanels#mode` - the current `ChildPanelsMode`. +- [ChildPanelsMode](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt) - determines how lifecycles of the child components (panels) within the `Child Panels` navigation model are changing. + - `SINGLE` - there is only one `RESUMED` panel at a time. If the Extra panel exists, then it is `RESUMED` and all other panels are `CREATED`. Otherwise, if the Details panel exists, then it is `RESUMED` and the Main panel is `CREATED`. Otherwise, the Main panel is `RESUMED`. + - `DUAL` - there are at most two panels `RESUMED` at a time. The Main panel is always `RESUMED`. If the Extra panel exists, then it is `RESUMED` and the Details panel (if exists) is `CREATED`. Otherwise, if the Details panel exists, then it is `RESUMED`. + - `TRIPLE` - any existing panel is always `RESUMED`. +- [PanelsNavigation](https://github.com/arkivanov/Decompose/blob/master/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigation.kt) - an interface that accepts navigation commands and forwards them to all subscribed observers. + +### Component Configurations + +Similarly to `Child Stack`, each component created and managed by the `Child Panels` has a configuration, please read the documentation about [child configurations](../overview.md#component-configurations-and-child-factories). + +### Initializing Child Panels + +There are three steps to initialize `Child Panels`: + +- Create a new instance of `PanelsNavigation` and assign it to a variable or a property. +- Initialize the `Child Panels` navigation model using the `ComponentContext#childPanels` extension function and pass `PanelsNavigation` into it along with other arguments. +- The `childPanels` function returns `Value` that can be observed in the UI. Assign the returned `Value` to another property or a variable. + +### Displaying panels with Compose + +`Child Panels` state can be observed and displayed in Compose by using the `ChildPanels` `Composable` function from the experimental Compose extensions module provided by Decompose. Please see the [related documentation](../../extensions/compose.md#child-panels-navigation-with-compose) for more information. + +## Example + +Here is a very basic example of a list-details navigation: + +```kotlin title="Child components" +import com.arkivanov.decompose.ComponentContext + +interface MainComponent + +class DefaultMainComponent( + componentContext: ComponentContext, + onItemSelected: (id: Long) -> Unit, +) : MainComponent, ComponentContext by componentContext + +interface DetailsComponent + +class DefaultDetailsComponent( + componentContext: ComponentContext, + itemId: Long, + onFinished: () -> Unit, +) : DetailsComponent, ComponentContext by componentContext +``` + +```kotlin title="PanelsComponent" +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.Panels +import com.arkivanov.decompose.router.panels.PanelsNavigation +import com.arkivanov.decompose.router.panels.activateDetails +import com.arkivanov.decompose.router.panels.childPanels +import com.arkivanov.decompose.router.panels.dismissDetails +import com.arkivanov.decompose.router.panels.setMode +import com.arkivanov.decompose.value.Value +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer + +interface PanelsComponent { + + val panels: Value> + + fun setMode(mode: ChildPanelsMode) +} + +class DefaultPanelsComponent( + componentContext: ComponentContext, +) : PanelsComponent, ComponentContext by componentContext { + + private val nav = PanelsNavigation() + + override val panels: Value> = + childPanels( + source = nav, + serializers = Unit.serializer() to DetailsConfig.serializer(), + initialPanels = { Panels(main = Unit) }, + handleBackButton = true, + mainFactory = { _, ctx -> + DefaultMainComponent( + componentContext = ctx, + onItemSelected = { nav.activateDetails(details = DetailsConfig(itemId = it)) }, + ) + }, + detailsFactory = { cfg, ctx -> + DefaultDetailsComponent( + componentContext = ctx, + itemId = cfg.itemId, + onFinished = nav::dismissDetails, + ) + }, + ) + + override fun setMode(mode: ChildPanelsMode) { + nav.setMode(mode) + } + + @Serializable + private data class DetailsConfig(val itemId: Long) +} +``` + +## Screen recreation and process death on (not only) Android + +`Child Panels` automatically preserves the state when a configuration change or process death occurs. To disable state preservation completely, pass `serializers = null` argument. When navigation state saving is disabled, the state is reset to the initial value when recreated. + +Components are created in their order. I.e. the Main component is created first, then the Details component is created (if exists), and lastly the Extra component is created (if exists). Components are destroyed in reverse order. + +## Multiple Child Panels in a component + +When multiple `Child Panels` are used in one component, each such `Child Panels` must have a unique `key` argument associated. The keys are required to be unique only within the hosting component, so it is ok for different components to have `Child Panels` with same keys. An exception will be thrown if multiple `Child Panels` with the same key are detected in a component. diff --git a/docs/navigation/slot/navigation.md b/docs/navigation/slot/navigation.md index c63bafeb3..38606a13c 100644 --- a/docs/navigation/slot/navigation.md +++ b/docs/navigation/slot/navigation.md @@ -12,6 +12,7 @@ All navigation in `Child Slot` is performed using the [`SlotNavigator`](https:// There is also `navigate` extension function without the `onComplete` callback, for convenience. ```kotlin title="Creating the navigation" +// In your component class val navigation = SlotNavigation() ``` diff --git a/docs/navigation/stack/navigation.md b/docs/navigation/stack/navigation.md index 3ca13c639..1ce3981ab 100644 --- a/docs/navigation/stack/navigation.md +++ b/docs/navigation/stack/navigation.md @@ -6,7 +6,7 @@ All navigation in `Child Stack` is performed using the [`StackNavigator`](https: `StackNavigator` contains the `navigate` method with the following arguments: -- `transformer` - converts the current stack of configurations into a new one. The stack is represented as `List`, where the last element is the top of the stack, and the first element is the bottom of the stack. +- `transformer` - converts the current stack of configurations to a new one. The stack is represented as `List`, where the last element is the top of the stack, and the first element is the bottom of the stack. - `onComplete` - called when navigation is finished. There is also `navigate` extension function without the `onComplete` callback, for convenience. @@ -15,14 +15,15 @@ There is also `navigate` extension function without the `onComplete` callback, f The configuration stack returned by the `transformer` function must not be empty. ```kotlin title="Creating the navigation" +// In your component class val navigation = StackNavigation() ``` ### The navigation process -During the navigation process, the `Child Stack` compares the new stack of configurations with the previous one. The `Child Stack` ensures that all removed components are destroyed, and that there is only one component resumed at a time - the top one. All components in the back stack are always either stopped or destroyed. +During the navigation process, the `Child Stack` navigation model compares the new stack of configurations with the previous one. It ensures that all removed components are destroyed, and that there is only one component resumed at a time - the top one. All components in the back stack are always either stopped or destroyed. -The `Child Stack` usually performs the navigation synchronously, which means that by the time the `navigate` method returns, the navigation is finished and all component lifecycles are moved into required states. However, the navigation is performed asynchronously in case of recursive invocations - e.g. `pop` is called from `onResume` lifecycle callback of a component being pushed. All recursive invocations are queued and performed one by one once the current navigation is finished. +The `Child Stack` navigation model usually performs the navigation synchronously, which means that by the time the `navigate` method returns, the navigation is finished and all component lifecycles are moved into required states. However, the navigation is performed asynchronously in case of recursive invocations - e.g. `pop` is called from `onResume` lifecycle callback of a component being pushed. All recursive invocations are queued and performed one by one once the current navigation is finished. ## StackNavigator extension functions diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt index 42bf75013..b26523bfa 100644 --- a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt @@ -33,6 +33,7 @@ import com.arkivanov.decompose.value.Value * @param detailsChild a `Composable` function that displays the provided Details component. * @param modifier a [Modifier] to applied to a wrapping container. * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. + * The default layout is [HorizontalChildPanelsLayout]. * @param animators a [ChildPanelsAnimators] containing panel animators for different * kinds of layouts. * @param predictiveBackParams a function that returns [PredictiveBackParams] for the specified [ChildPanels], @@ -116,7 +117,7 @@ fun ChildPanels( * @param mainChild a `Composable` function that displays the provided Main component. * @param detailsChild a `Composable` function that displays the provided Details component. * @param extraChild a `Composable` function that displays the provided Extra component. - * @param modifier a [Modifier] to applied to a wrapping container. + * @param modifier a [Modifier] to be applied to a wrapping container. * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. * @param animators a [ChildPanelsAnimators] containing panel animators for different * kinds of layouts. diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt index 475a66af4..110110b3e 100644 --- a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt @@ -13,7 +13,36 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi import com.arkivanov.decompose.router.panels.ChildPanelsMode /** - * An implementation of [ChildPanelsLayout] for laying out panels horizontally. + * An implementation of [ChildPanelsLayout] for laying out panels in the following ways. + * + * - If the `mode` is `SINGLE`, all panels are displayed in a stack. The Main panel, then + * the Details panel on top (if any), and finally the Extra panel (if any). + * - If the `mode` is `DUAL`, the Main panel is always displayed on the left side, and then + * the Details and the Extra panels are displayed in a stack on the right side (next to the Main panel). + * - If the `mode` is `TRIPLE`, all panels are displayed horizontally side by side. + * + * ``` + * SINGLE mode + * +-----------------------------+ + * | Main | + * | Details | + * | Extra | + * +-----------------------------+ + * + * DUAL mode + * +-----------------------------+ + * | | Details | + * | Main | Extra | + * | | | + * +-----------------------------+ + * + * TRIPLE mode + * +-----------------------------+ + * | | | | + * | Main | Details | Extra | + * | | | | + * +-----------------------------+ + * ``` * * @param dualWeights a [Pair] of weights for two panels to be used in * [DUAL][com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL] mode. diff --git a/mkdocs.yml b/mkdocs.yml index e900cbdbc..e0ed136a6 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -44,6 +44,9 @@ nav: - Child Pages: - Overview: navigation/pages/overview.md - Navigation: navigation/pages/navigation.md + - Child Panels: + - Overview: navigation/panels/overview.md + - Navigation: navigation/panels/navigation.md - Generic Navigation: - Overview: navigation/children/overview.md