diff --git a/CHANGELOG.md b/CHANGELOG.md index cb3edc6db..44be48b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,9 @@ Please refer to [2.0.0-alpha10 – Migration guide](2.0.0-alpha10.md) - [#642](https://github.com/bumble-tech/appyx/pull/642) – Renamings - [#643](https://github.com/bumble-tech/appyx/pull/643) – Unify AppyxComponent composable between appyx-navigation and appyx-interactions modules - [#651](https://github.com/bumble-tech/appyx/pull/651) - Keep only one instance of SaveStateMap typealias and moved it to `com.bumble.appyx.utils.multiplatform` package -- [#654](https://github.com/bumble-tech/appyx/pull/654) - Renamings - [#652](https://github.com/bumble-tech/appyx/pull/652) - KSP processor renamed from `mutable-ui-processor` to `appyx-processor` +- [#654](https://github.com/bumble-tech/appyx/pull/654) - Renamings +- [#657](https://github.com/bumble-tech/appyx/pull/657) - Rename ParentNode & Node to Node and LeafNode - [#644](https://github.com/bumble-tech/appyx/pull/644) – Refactor AppyxComponent and application of draggable modifier ### Fixed diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/Modal.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/Modal.kt index 4f9b01542..018942362 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/Modal.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/Modal.kt @@ -30,7 +30,6 @@ class Modal( revertGestureSpec = animationSpec, ), disableAnimations: Boolean = false, - isDebug: Boolean = false, ) : BaseAppyxComponent>( scope = scope, model = model, @@ -39,6 +38,5 @@ class Modal( backPressStrategy = backPressStrategy, defaultAnimationSpec = animationSpec, gestureSettleConfig = gestureSettleConfig, - disableAnimations = disableAnimations, - isDebug = isDebug + disableAnimations = disableAnimations ) diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStack.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStack.kt index e73fc662c..bd25093fa 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStack.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStack.kt @@ -26,7 +26,6 @@ class BackStack( backPressStrategy: BackPressHandlerStrategy> = PopBackstackStrategy(scope), disableAnimations: Boolean = false, - isDebug: Boolean = false ) : BaseAppyxComponent>( scope = scope, model = model, @@ -35,7 +34,6 @@ class BackStack( gestureSettleConfig = gestureSettleConfig, backPressStrategy = backPressStrategy, defaultAnimationSpec = animationSpec, - disableAnimations = disableAnimations, - isDebug = isDebug + disableAnimations = disableAnimations ) diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/Spotlight.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/Spotlight.kt index 539cb345c..1f742a2b4 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/Spotlight.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/Spotlight.kt @@ -28,7 +28,6 @@ open class Spotlight( revertGestureSpec = animationSpec, ), disableAnimations: Boolean = false, - isDebug: Boolean = false ) : BaseAppyxComponent>( scope = scope, model = model, @@ -36,8 +35,7 @@ open class Spotlight( gestureFactory = gestureFactory, defaultAnimationSpec = animationSpec, gestureSettleConfig = gestureSettleConfig, - disableAnimations = disableAnimations, - isDebug = isDebug + disableAnimations = disableAnimations ) { val activeIndex: StateFlow = model.output .mapState(scope) { it.currentTargetState.activeIndex } diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/BaseAppyxComponent.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/BaseAppyxComponent.kt index f599e72ae..0ef3488e6 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/BaseAppyxComponent.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/BaseAppyxComponent.kt @@ -8,7 +8,6 @@ import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.model.backpresshandlerstrategies.BackPressHandlerStrategy import com.bumble.appyx.interactions.core.model.backpresshandlerstrategies.DontHandleBackPress import com.bumble.appyx.interactions.core.model.progress.AnimatedProgressController -import com.bumble.appyx.interactions.core.model.progress.DebugProgressInputSource import com.bumble.appyx.interactions.core.model.progress.DragProgressController import com.bumble.appyx.interactions.core.model.progress.Draggable import com.bumble.appyx.interactions.core.model.progress.HasDefaultAnimationSpec @@ -17,12 +16,12 @@ import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.IMMEDIATE import com.bumble.appyx.interactions.core.model.transition.TransitionModel import com.bumble.appyx.interactions.core.state.MutableSavedStateMap +import com.bumble.appyx.interactions.core.ui.DefaultAnimationSpec import com.bumble.appyx.interactions.core.ui.Visualisation import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.TransitionBoundsAware import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.context.UiContextAware -import com.bumble.appyx.interactions.core.ui.DefaultAnimationSpec import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.interactions.core.ui.output.ElementUiModel @@ -56,7 +55,6 @@ open class BaseAppyxComponent( private val backPressStrategy: BackPressHandlerStrategy = DontHandleBackPress(), private val animateSettle: Boolean = false, private val disableAnimations: Boolean = false, - private val isDebug: Boolean = false ) : AppyxComponent, HasDefaultAnimationSpec, Draggable, @@ -82,7 +80,6 @@ open class BaseAppyxComponent( private val instant = InstantProgressController(model = model) private var animated: AnimatedProgressController? = null - private var debug: DebugProgressInputSource? = null private val drag = DragProgressController( model = model, gestureFactory = { _gestureFactory }, @@ -141,12 +138,11 @@ open class BaseAppyxComponent( override fun onAddedToComposition(scope: CoroutineScope) { animationScope = scope createAnimatedProgressController(scope) - createdDebugInputSource() } override fun onRemovedFromComposition() { // TODO finish unfinished transitions - if (isDebug) debug?.stopModel() else animated?.stopModel() + animated?.stopModel() animationScope?.cancel() } @@ -159,12 +155,6 @@ open class BaseAppyxComponent( ) } - private fun createdDebugInputSource() { - debug = DebugProgressInputSource( - transitionModel = model, - ) - } - override fun updateContext(uiContext: UiContext) { if (this.uiContext != uiContext) { this.uiContext = uiContext @@ -238,9 +228,7 @@ open class BaseAppyxComponent( animationSpec ) val animatedSource = animated - val debugSource = debug when { - (isDebug && debugSource != null) -> debugSource.operation(operation) animatedSource == null || disableAnimations -> instant.operation( operation ) @@ -282,15 +270,11 @@ open class BaseAppyxComponent( } private fun settle(gestureSettleConfig: GestureSettleConfig) { - if (isDebug) { - debug?.settle() - } else { - animated?.settle( - completionThreshold = gestureSettleConfig.completionThreshold, - completeGestureSpec = gestureSettleConfig.completeGestureSpec, - revertGestureSpec = gestureSettleConfig.revertGestureSpec, - ) - } + animated?.settle( + completionThreshold = gestureSettleConfig.completionThreshold, + completeGestureSpec = gestureSettleConfig.completeGestureSpec, + revertGestureSpec = gestureSettleConfig.revertGestureSpec, + ) } // TODO plugin?! @@ -300,10 +284,6 @@ open class BaseAppyxComponent( scope.cancel() } - fun setNormalisedProgress(progress: Float) { - debug?.setNormalisedProgress(progress) - } - override fun handleBackPress(): Boolean = backPressStrategy.handleBackPress() override fun canHandeBackPress(): StateFlow = backPressStrategy.canHandleBackPress diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DebugProgressInputSource.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DebugProgressInputSource.kt deleted file mode 100644 index f356410a0..000000000 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DebugProgressInputSource.kt +++ /dev/null @@ -1,52 +0,0 @@ -package com.bumble.appyx.interactions.core.model.progress - -import androidx.compose.animation.core.AnimationResult -import androidx.compose.animation.core.AnimationVector1D -import com.bumble.appyx.interactions.core.model.transition.Operation -import com.bumble.appyx.interactions.core.model.transition.TransitionModel - -// FIXME -class DebugProgressInputSource( - private val transitionModel: TransitionModel, -) : ProgressController { - // TODO this should >not< use its own animatable that's independent of AnimatedInputSource -// private val animatable = Animatable(0f) - @Suppress("UnusedPrivateMember") - private lateinit var result: AnimationResult - @Suppress("UnusedPrivateMember") - private var progress: Float = 1f - - override fun operation(operation: Operation) { - // Regardless of operation.mode, only enqueue makes sense - transitionModel.operation(operation, Operation.Mode.KEYFRAME) - } - - @Suppress("UnusedPrivateMember") - fun setNormalisedProgress(progress: Float) { - // FIXME -// this.progress = progress.coerceIn(0f, 1f) -// // TODO enforce min 1f in NavModel as a hidden detail rather than here: -// transitionModel.setProgress(1f + this.progress * (transitionModel.maxProgress - 1f)) - } - - fun settle() { -// Logger.log(TAG, "Settle ${progress} to: ${progress.roundToInt().toFloat()}") -// coroutineScope.launch { -// animatable.snapTo(progress) -// result = animatable.animateTo(progress.roundToInt().toFloat(), spring()) { -// setNormalisedProgress(this.value) -// } -// } - } - - fun stopModel() { -// coroutineScope.launch(Dispatchers.Main) { -// animatable.snapTo(transitionModel.currentProgress) -// } - } - - private companion object { - @Suppress("UnusedPrivateMember") - private const val TAG = "DebugProgressInputSource" - } -} diff --git a/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt index 9daabe49c..f04c53c25 100644 --- a/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt +++ b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt @@ -14,7 +14,7 @@ import com.bumble.appyx.utils.testing.ui.rules.AppyxTestActivity import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit -class AppyxTestScenario( +class AppyxTestScenario>( private val composeTestRule: ComposeTestRule = createEmptyComposeRule(), /** Add decorations like custom theme or CompositionLocalProvider. Do not forget to invoke `content()`. */ private val decorator: (@Composable (content: @Composable () -> Unit) -> Unit) = { content -> content() }, diff --git a/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt index abd205820..ad22676a7 100644 --- a/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt +++ b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt @@ -13,7 +13,7 @@ import com.bumble.appyx.navigation.AppyxTestScenario import com.bumble.appyx.navigation.children.nodeOrNull import com.bumble.appyx.navigation.composable.PermanentChild import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.PermanentChildTest.TestParentNode.Child +import com.bumble.appyx.navigation.node.PermanentChildTest.TestNode.Child import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize import org.junit.Assert.assertEquals @@ -22,8 +22,8 @@ import org.junit.Test class PermanentChildTest { - var nodeFactory: (nodeContext: NodeContext) -> TestParentNode = { - TestParentNode(nodeContext = it) + var nodeFactory: (nodeContext: NodeContext) -> TestNode = { + TestNode(nodeContext = it) } @get:Rule @@ -65,7 +65,7 @@ class PermanentChildTest { private fun createPermanentAppyxComponentWithInteractionKey() { nodeFactory = { - TestParentNode( + TestNode( nodeContext = it, permanentAppyxComponent = PermanentAppyxComponent( savedStateMap = null, @@ -76,11 +76,11 @@ class PermanentChildTest { } - class TestParentNode( + class TestNode( nodeContext: NodeContext, private val permanentAppyxComponent: PermanentAppyxComponent = PermanentAppyxComponent(savedStateMap = nodeContext.savedStateMap) - ) : ParentNode( + ) : Node( nodeContext = nodeContext, appyxComponent = permanentAppyxComponent ) { @@ -90,7 +90,7 @@ class PermanentChildTest { var renderPermanentChild by mutableStateOf(true) - override fun buildChildNode(navTarget: Child, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: Child, nodeContext: NodeContext): Node<*> = node(nodeContext) { modifier -> BasicText( text = navTarget.toString(), diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidNodeHost.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidNodeHost.kt index 7e367f51c..15e1cdde5 100644 --- a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidNodeHost.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidNodeHost.kt @@ -12,13 +12,13 @@ import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl /** - * Composable function to host [Node]. + * Composable function to host [Node<*>]. * * This wrapper uses [LocalConfiguration] to provide [ScreenSize] automatically. */ @Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda @Composable -fun NodeHost( +fun > NodeHost( lifecycle: Lifecycle, integrationPoint: IntegrationPoint, modifier: Modifier = Modifier, diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/node/AndroidNodeExt.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/node/AndroidNodeExt.kt index 74acff9bc..e1770ae83 100644 --- a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/node/AndroidNodeExt.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/node/AndroidNodeExt.kt @@ -2,5 +2,5 @@ package com.bumble.appyx.navigation.node import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry -val Node.androidLifecycle: androidx.lifecycle.Lifecycle +val Node<*>.androidLifecycle: androidx.lifecycle.Lifecycle get() = (lifecycle as PlatformLifecycleRegistry).androidLifecycleRegistry diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt index 18ae9b336..0d785e9a2 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt @@ -6,5 +6,5 @@ import com.bumble.appyx.navigation.node.Node // Changing this to an interface would be a breaking change @Suppress("UnnecessaryAbstractClass") abstract class Builder

{ - abstract fun build(nodeContext: NodeContext, payload: P): Node + abstract fun build(nodeContext: NodeContext, payload: P): Node<*> } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt index 52d94edb5..c13dfa1ed 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt @@ -6,5 +6,5 @@ import com.bumble.appyx.navigation.node.Node // Changing this to an interface would be a breaking change @Suppress("UnnecessaryAbstractClass") abstract class SimpleBuilder { - abstract fun build(nodeContext: NodeContext): Node + abstract fun build(nodeContext: NodeContext): Node<*> } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt index e43d6c524..80f632867 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt @@ -4,7 +4,7 @@ import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.plugin.NodeAware import kotlin.reflect.KClass -interface ChildAware : NodeAware { +interface ChildAware> : NodeAware { fun whenChildAttached( child: KClass, diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt index 926398fca..c6585cbd6 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt @@ -10,7 +10,7 @@ import kotlin.reflect.safeCast internal sealed class ChildAwareCallbackInfo { - abstract fun onRegistered(activeNodes: List) + abstract fun onRegistered(activeNodes: List>) class Single( private val child: KClass, @@ -18,7 +18,7 @@ internal sealed class ChildAwareCallbackInfo { private val parentLifecycle: Lifecycle, ) : ChildAwareCallbackInfo() { - fun onNewNodeAppeared(newNode: Node) { + fun onNewNodeAppeared(newNode: Node<*>) { if (parentLifecycle.isDestroyed) return val castedNode = child.safeCast(newNode) if (castedNode != null) { @@ -31,7 +31,7 @@ internal sealed class ChildAwareCallbackInfo { } } - override fun onRegistered(activeNodes: List) { + override fun onRegistered(activeNodes: List>) { activeNodes.forEach { node -> onNewNodeAppeared(node) } @@ -47,9 +47,9 @@ internal sealed class ChildAwareCallbackInfo { ) : ChildAwareCallbackInfo() { fun onNewNodeAppeared( - activeNodes: Collection, - newNode: Node, - ignoreNodes: Set, + activeNodes: Collection>, + newNode: Node<*>, + ignoreNodes: Set>, ) { val second = getOther(newNode) ?: return activeNodes @@ -57,7 +57,7 @@ internal sealed class ChildAwareCallbackInfo { .forEach { notify(newNode, it) } } - override fun onRegistered(activeNodes: List) { + override fun onRegistered(activeNodes: List>) { activeNodes.forEachIndexed { index, node -> onNewNodeAppeared( // Do not include already handled nodes to avoid call duplication @@ -68,7 +68,7 @@ internal sealed class ChildAwareCallbackInfo { } } - private fun notify(node1: Node, node2: Node) { + private fun notify(node1: Node<*>, node2: Node<*>) { if (parentLifecycle.isDestroyed) return val lifecycle = MinimumCombinedLifecycle( @@ -83,7 +83,7 @@ internal sealed class ChildAwareCallbackInfo { } } - private fun getOther(node: Node): KClass<*>? = + private fun getOther(node: Node<*>): KClass<*>? = when { child1.isInstance(node) -> child2 child2.isInstance(node) -> child1 diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt index 360a42a1a..09a12fa03 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt @@ -2,13 +2,13 @@ package com.bumble.appyx.navigation.children import com.bumble.appyx.navigation.node.Node -inline fun ChildAware<*>.whenChildAttached( +inline fun > ChildAware<*>.whenChildAttached( noinline callback: ChildCallback, ) { whenChildAttached(T::class, callback) } -inline fun ChildAware<*>.whenChildrenAttached( +inline fun , reified T2 : Node<*>> ChildAware<*>.whenChildrenAttached( noinline callback: ChildrenCallback, ) { whenChildrenAttached(T1::class, T2::class, callback) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt index d3b04d9c7..363b4025f 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt @@ -4,8 +4,8 @@ import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.lifecycle.isDestroyed +import com.bumble.appyx.navigation.node.LeafNode import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.withPrevious import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.MutableStateFlow @@ -14,7 +14,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import kotlin.reflect.KClass -class ChildAwareImpl : ChildAware { +class ChildAwareImpl> : ChildAware { private val callbacks: MutableList = ArrayList() @@ -29,11 +29,11 @@ class ChildAwareImpl : ChildAware { this.node = node lifecycle = node.lifecycle coroutineScope = lifecycle.coroutineScope - if (node is ParentNode<*>) { + if (node is LeafNode) { + children = MutableStateFlow(emptyMap()) + } else { children = node.children coroutineScope.launch { observeChanges() } - } else { - children = MutableStateFlow(emptyMap()) } } @@ -43,7 +43,7 @@ class ChildAwareImpl : ChildAware { .withPrevious() .collect { (previous, current) -> val newNodes = current - previous.orEmpty() - val visitedSet = HashSet() + val visitedSet = HashSet>() newNodes.forEach { node -> notifyWhenChanged(node, current, visitedSet) visitedSet.add(node) @@ -78,7 +78,7 @@ class ChildAwareImpl : ChildAware { callback.onRegistered(getCreatedNodes(children.value)) } - private fun notifyWhenChanged(child: Node, nodes: Collection, ignore: Set) { + private fun notifyWhenChanged(child: Node<*>, nodes: Collection>, ignore: Set>) { for (callback in callbacks) { when (callback) { is ChildAwareCallbackInfo.Double<*, *> -> callback.onNewNodeAppeared( diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt index 3e9912355..733eb4693 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt @@ -22,7 +22,7 @@ sealed class ChildEntry { /** All public APIs should return this type of child which is ready to work with. */ class Initialized( override val key: Element, - val node: Node, + val node: Node<*>, ) : ChildEntry() class Suspended( diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt index 893aa89e8..30e42d2f1 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt @@ -2,7 +2,7 @@ package com.bumble.appyx.navigation.children import com.bumble.appyx.navigation.node.Node -val ChildEntry.nodeOrNull: Node? +val ChildEntry.nodeOrNull: Node<*>? get() = when (this) { is ChildEntry.Initialized -> node diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeBuilder.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeBuilder.kt index 55a05630f..ec8f10163 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeBuilder.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeBuilder.kt @@ -5,5 +5,5 @@ import com.bumble.appyx.navigation.node.Node fun interface ChildNodeBuilder { - fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node + fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt index 48892c2e8..ff5197b41 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt @@ -4,7 +4,7 @@ import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.state.MutableSavedStateMap import com.bumble.appyx.navigation.modality.AncestryInfo import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.node.build import com.bumble.appyx.utils.multiplatform.SavedStateMap import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory @@ -25,23 +25,23 @@ internal class ChildNodeCreationManager( private val customisations: NodeCustomisationDirectory, private val keepMode: ChildEntry.KeepMode, ) { - private lateinit var parentNode: ParentNode + private lateinit var node: Node private val _children = MutableStateFlow, ChildEntry>>(emptyMap()) val children: StateFlow> = _children.asStateFlow() - fun launch(parentNode: ParentNode) { - this.parentNode = parentNode + fun launch(node: Node) { + this.node = node savedStateMap.restoreChildren()?.also { restoredMap -> _children.update { restoredMap } savedStateMap = null } - syncAppyxComponentWithChildren(parentNode) + syncAppyxComponentWithChildren(node) } - private fun syncAppyxComponentWithChildren(parentNode: ParentNode) { - parentNode.lifecycle.coroutineScope.launch { - parentNode.appyxComponent.elements.collect { state -> + private fun syncAppyxComponentWithChildren(node: Node) { + node.lifecycle.coroutineScope.launch { + node.appyxComponent.elements.collect { state -> val appyxComponentKeepKeys: Set> val appyxComponentSuspendKeys: Set> val appyxComponentKeys: Set> @@ -165,9 +165,9 @@ internal class ChildNodeCreationManager( private fun childContext(savedState: SavedStateMap?): NodeContext = NodeContext( - ancestryInfo = AncestryInfo.Child(parentNode), + ancestryInfo = AncestryInfo.Child(node), savedStateMap = savedState, - customisations = customisations.getSubDirectoryOrSelf(parentNode::class), + customisations = customisations.getSubDirectoryOrSelf(node::class), ) private fun childEntry( @@ -180,7 +180,7 @@ internal class ChildNodeCreationManager( } else { ChildEntry.Initialized( key = key, - node = parentNode + node = node .buildChildNode(key.interactionTarget, childContext(savedState)) .build() ) @@ -192,7 +192,7 @@ internal class ChildNodeCreationManager( is ChildEntry.Suspended -> ChildEntry.Initialized( key = key, - node = parentNode.buildChildNode( + node = node.buildChildNode( navTarget = key.interactionTarget, nodeContext = childContext(savedState), ).build() diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt index 57a73e253..ec40fe7e2 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt @@ -7,7 +7,7 @@ import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.plugin.NodeAware import com.bumble.appyx.navigation.plugin.NodeLifecycleAware -open class Interactor( +open class Interactor>( private val childAwareImpl: ChildAware = ChildAwareImpl() ) : NodeAware, NodeLifecycleAware, diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxNavigationContainer.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxNavigationContainer.kt index 827174c6d..6c5ddacd1 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxNavigationContainer.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxNavigationContainer.kt @@ -11,18 +11,20 @@ import com.bumble.appyx.interactions.core.GesturesReferencePoint import com.bumble.appyx.interactions.core.gesture.GestureValidator import com.bumble.appyx.interactions.core.model.BaseAppyxComponent import com.bumble.appyx.navigation.integration.LocalScreenSize -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.LocalNode +import com.bumble.appyx.navigation.node.Node import kotlin.math.roundToInt internal val defaultExtraTouch = 48.dp +@Suppress("UNCHECKED_CAST") @Composable -fun ParentNode.AppyxNavigationContainer( +fun AppyxNavigationContainer( appyxComponent: BaseAppyxComponent, modifier: Modifier = Modifier, clipToBounds: Boolean = false, - gestureValidator: GestureValidator = GestureValidator.permissiveValidator, + gestureValidator: GestureValidator = GestureValidator.defaultValidator, gestureExtraTouchArea: Dp = defaultExtraTouch, gestureRelativeTo: GesturesReferencePoint = GesturesReferencePoint.Container, decorator: @Composable (child: ChildRenderer, element: Element) -> Unit = { child, _ -> @@ -32,17 +34,24 @@ fun ParentNode.AppyxNavigationCon val density = LocalDensity.current val screenWidthPx = (LocalScreenSize.current.widthDp * density.density).value.roundToInt() val screenHeightPx = (LocalScreenSize.current.heightDp * density.density).value.roundToInt() + val node = LocalNode.current as? Node + ?: error( + "AppyxNavigationContainer called from outside the expected Node tree;" + + "LocalNode.current=${LocalNode.current}" + ) AppyxInteractionsContainer( - appyxComponent, - screenWidthPx, - screenHeightPx, - modifier, - clipToBounds, - gestureValidator, - gestureExtraTouchArea, - gestureRelativeTo + appyxComponent = appyxComponent, + screenWidthPx = screenWidthPx, + screenHeightPx = screenHeightPx, + modifier = modifier, + clipToBounds = clipToBounds, + gestureValidator = gestureValidator, + gestureExtraTouchArea = gestureExtraTouchArea, + gestureRelativeTo = gestureRelativeTo ) { element -> - Child(element, decorator) + with(node) { + Child(element, decorator) + } } } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt index a761b84f5..cca2acbd4 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt @@ -5,10 +5,9 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode @Composable -fun ParentNode.Child( +fun Node.Child( element: Element, decorator: @Composable (child: ChildRenderer, element: Element) -> Unit ) { @@ -22,7 +21,7 @@ fun ParentNode.Child( } private class ChildRendererImpl( - private val node: Node + private val node: Node<*>, ) : ChildRenderer { @Suppress("ComposableNaming") // This wants to be 'Invoke' but that won't work with 'operator'. diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/PermanentChild.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/PermanentChild.kt index e947fda64..543265fd8 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/PermanentChild.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/PermanentChild.kt @@ -8,11 +8,11 @@ import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import com.bumble.appyx.interactions.permanent.PermanentAppyxComponent import com.bumble.appyx.navigation.mapState -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.Node import kotlinx.coroutines.flow.SharingStarted @Composable -fun ParentNode.PermanentChild( +fun Node.PermanentChild( permanentAppyxComponent: PermanentAppyxComponent, navTarget: NavTarget, modifier: Modifier = Modifier diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt index b00ed06e1..7320f5f22 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt @@ -5,6 +5,6 @@ import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node @Stable -fun interface NodeFactory { +fun interface NodeFactory> { fun create(nodeContext: NodeContext): N } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt index aa71f5b1d..18369b4b2 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt @@ -20,13 +20,13 @@ import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl /** - * Composable function to host [Node]. + * Composable function to host [Node<*>]. * * Aligns lifecycle and manages state restoration. */ @Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda @Composable -fun NodeHost( +fun > NodeHost( lifecycle: Lifecycle, integrationPoint: IntegrationPoint, screenSize: ScreenSize, @@ -52,7 +52,7 @@ fun NodeHost( } @Composable -internal fun rememberNode( +internal fun > rememberNode( factory: NodeFactory, customisations: NodeCustomisationDirectory, integrationPoint: IntegrationPoint, diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt index 87da606c6..97c5f683d 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt @@ -3,7 +3,7 @@ package com.bumble.appyx.navigation.lifecycle import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.utils.multiplatform.AppyxLogger -internal class LifecycleLogger(private val node: Node) : DefaultPlatformLifecycleObserver { +internal class LifecycleLogger(private val node: Node<*>) : DefaultPlatformLifecycleObserver { override fun onCreate() { diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt index 8d30aa096..be02f1cda 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.navigation.modality -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.Node sealed class AncestryInfo { object Root : AncestryInfo() data class Child( - val anchor: ParentNode<*>, + val anchor: Node<*>, ) : AncestryInfo() } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt index f003991dc..759e0ee41 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt @@ -7,7 +7,7 @@ import com.bumble.appyx.navigation.modality.NodeContext open class ComposableNode( nodeContext: NodeContext, private val composable: @Composable (Modifier) -> Unit -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { @@ -17,5 +17,5 @@ open class ComposableNode( } } -fun node(nodeContext: NodeContext, composable: @Composable (Modifier) -> Unit): Node = +fun node(nodeContext: NodeContext, composable: @Composable (Modifier) -> Unit): Node<*> = ComposableNode(nodeContext, composable) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeView.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeView.kt new file mode 100644 index 000000000..78e0b9b25 --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeView.kt @@ -0,0 +1,10 @@ +package com.bumble.appyx.navigation.node + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +class EmptyNodeView : NodeView { + + @Composable + override fun Content(modifier: Modifier) = Unit +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt deleted file mode 100644 index 18bc50174..000000000 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.bumble.appyx.navigation.node - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -object EmptyNodeView : NodeView { - - @Composable - override fun Content(modifier: Modifier) = Unit -} - -class EmptyParentNodeView : ParentNodeView { - - @Composable - override fun ParentNode.NodeView(modifier: Modifier) = Unit - -} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LeafNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LeafNode.kt new file mode 100644 index 000000000..12435540c --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LeafNode.kt @@ -0,0 +1,20 @@ +package com.bumble.appyx.navigation.node + +import com.bumble.appyx.interactions.core.model.EmptyAppyxComponent +import com.bumble.appyx.interactions.core.plugin.Plugin +import com.bumble.appyx.navigation.modality.NodeContext + +open class LeafNode( + nodeContext: NodeContext, + view: NodeView = EmptyNodeView(), + plugins: List = listOf(), +) : Node( + appyxComponent = EmptyAppyxComponent(), + nodeContext = nodeContext, + view = view, + plugins = plugins, +) { + override fun buildChildNode(navTarget: Nothing, nodeContext: NodeContext): Node<*> { + error("A leaf node should never have to build a child node") + } +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt index f85650758..cb6f85012 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt @@ -2,4 +2,4 @@ package com.bumble.appyx.navigation.node import androidx.compose.runtime.compositionLocalOf -val LocalNode = compositionLocalOf { null } +val LocalNode = compositionLocalOf?> { null } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt index 07047bf4e..5dbe2409f 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt @@ -3,50 +3,85 @@ package com.bumble.appyx.navigation.node import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.saveable.SaverScope import androidx.compose.ui.Modifier +import com.bumble.appyx.interactions.core.Element +import com.bumble.appyx.interactions.core.model.AppyxComponent import com.bumble.appyx.interactions.core.plugin.Plugin import com.bumble.appyx.interactions.core.plugin.SavesInstanceState import com.bumble.appyx.interactions.core.state.MutableSavedStateMap import com.bumble.appyx.interactions.core.state.MutableSavedStateMapImpl +import com.bumble.appyx.interactions.core.ui.helper.AppyxComponentSetup import com.bumble.appyx.navigation.Appyx +import com.bumble.appyx.navigation.children.ChildAware +import com.bumble.appyx.navigation.children.ChildAwareImpl +import com.bumble.appyx.navigation.children.ChildCallback +import com.bumble.appyx.navigation.children.ChildEntry +import com.bumble.appyx.navigation.children.ChildEntryMap +import com.bumble.appyx.navigation.children.ChildNodeCreationManager +import com.bumble.appyx.navigation.children.ChildrenCallback +import com.bumble.appyx.navigation.children.nodeOrNull +import com.bumble.appyx.navigation.lifecycle.ChildNodeLifecycleManager +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.modality.NodeContext +import com.bumble.appyx.navigation.children.ChildNodeBuilder import com.bumble.appyx.navigation.integration.IntegrationPoint import com.bumble.appyx.navigation.integration.IntegrationPointStub import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver -import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.lifecycle.LifecycleLogger import com.bumble.appyx.navigation.lifecycle.LocalCommonLifecycleOwner import com.bumble.appyx.navigation.lifecycle.NodeLifecycle import com.bumble.appyx.navigation.lifecycle.NodeLifecycleImpl import com.bumble.appyx.navigation.modality.AncestryInfo -import com.bumble.appyx.navigation.modality.NodeContext +import com.bumble.appyx.navigation.platform.PlatformBackHandler import com.bumble.appyx.navigation.plugin.Destroyable import com.bumble.appyx.navigation.plugin.NodeLifecycleAware import com.bumble.appyx.navigation.plugin.NodeReadyObserver import com.bumble.appyx.navigation.plugin.UpNavigationHandler import com.bumble.appyx.navigation.plugin.plugins -import com.bumble.appyx.utils.multiplatform.SavedStateMap import com.bumble.appyx.navigation.store.RetainedInstanceStore import com.bumble.appyx.utils.multiplatform.BuildFlags +import com.bumble.appyx.utils.multiplatform.SavedStateMap import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.withContext -import kotlin.experimental.ExperimentalObjCRefinement -import kotlin.native.HiddenFromObjC +import kotlinx.coroutines.withTimeoutOrNull +import kotlin.coroutines.resume +import kotlin.reflect.KClass @Suppress("TooManyFunctions") @Stable -open class Node internal constructor( +abstract class Node( + val appyxComponent: AppyxComponent, private val nodeContext: NodeContext, - val view: NodeView = EmptyNodeView, + view: NodeView = EmptyNodeView(), + childKeepMode: ChildEntry.KeepMode = Appyx.defaultChildKeepMode, + private val childAware: ChildAware> = ChildAwareImpl(), private val retainedInstanceStore: RetainedInstanceStore, - plugins: List = emptyList() -) : NodeLifecycle, NodeView by view { + plugins: List = listOf(), +) : NodeLifecycle, + NodeView by view, + ChildNodeBuilder { constructor( + appyxComponent: AppyxComponent, nodeContext: NodeContext, - view: NodeView = EmptyNodeView, + view: NodeView = EmptyNodeView(), + childKeepMode: ChildEntry.KeepMode = Appyx.defaultChildKeepMode, + childAware: ChildAware> = ChildAwareImpl(), plugins: List = emptyList() - ) : this(nodeContext, view, RetainedInstanceStore, plugins) + ) : this( + appyxComponent, + nodeContext, + view, + childKeepMode, + childAware, + RetainedInstanceStore, + plugins + ) @Suppress("LeakingThis") // Implemented in the same way as in androidx.Fragment private val nodeLifecycle = NodeLifecycleImpl(this) @@ -64,7 +99,7 @@ open class Node internal constructor( val isRoot: Boolean = ancestryInfo == AncestryInfo.Root - val parent: ParentNode<*>? = + val parent: Node<*>? = when (ancestryInfo) { is AncestryInfo.Child -> ancestryInfo.anchor is AncestryInfo.Root -> null @@ -86,6 +121,21 @@ open class Node internal constructor( field = value } + private val childNodeCreationManager = ChildNodeCreationManager( + savedStateMap = nodeContext.savedStateMap, + customisations = nodeContext.customisations, + keepMode = childKeepMode, + ) + val children: StateFlow> + get() = childNodeCreationManager.children + + private val childNodeLifecycleManager = ChildNodeLifecycleManager( + appyxComponent = this.appyxComponent, + children = children, + keepMode = childKeepMode, + coroutineScope = lifecycleScope, + ) + init { if (BuildFlags.DEBUG) { lifecycle.addObserver(LifecycleLogger(this)) @@ -97,43 +147,18 @@ open class Node internal constructor( }) } - protected suspend inline fun executeAction( - crossinline action: suspend () -> Unit - ): T = withContext(lifecycleScope.coroutineContext) { - action() - this@Node as T - } - open fun onBuilt() { require(!wasBuilt) { "onBuilt was already invoked" } wasBuilt = true updateLifecycleState(Lifecycle.State.CREATED) - plugins>().forEach { it.init(this) } + plugins>>().forEach { it.init(this) } plugins().forEach { it.onCreate(lifecycle) } + childNodeCreationManager.launch(this) + childNodeLifecycleManager.launch() } - @Composable - fun Compose(modifier: Modifier = Modifier) { - CompositionLocalProvider( - LocalNode provides this, - LocalCommonLifecycleOwner provides this, - ) { - DerivedSetup() - Content(modifier) - } - } - - /** - * Exposing this method to ObjC causes compilation time errors while targeting iOS, that's why - * we've to annotate this method with @HiddenFromObjC. More details in the thread below: - * https://kotlinlang.slack.com/archives/C05FPNA6P27/p1699883454768869. - */ - @Composable - @OptIn(ExperimentalObjCRefinement::class) - @HiddenFromObjC - protected open fun DerivedSetup() { - - } + fun childOrCreate(element: Element): ChildEntry.Initialized = + childNodeCreationManager.childOrCreate(element) override fun updateLifecycleState(state: Lifecycle.State) { if (lifecycle.currentState == state) return @@ -152,23 +177,109 @@ open class Node internal constructor( } plugins().forEach { it.destroy() } } + childNodeLifecycleManager.propagateLifecycleToChildren(state) + + // TODO move to plugins + if (state == Lifecycle.State.DESTROYED) { + appyxComponent.destroy() + } } - fun saveInstanceState(scope: SaverScope): SavedStateMap { - val writer = MutableSavedStateMapImpl(saverScope = scope) - onSaveInstanceState(writer) - plugins - .filterIsInstance() - .forEach { it.saveInstanceState(writer) } - return writer.savedState + @Composable + fun Compose(modifier: Modifier = Modifier) { + CompositionLocalProvider( + LocalNode provides this, + LocalCommonLifecycleOwner provides this, + ) { + DerivedSetup() + Content(modifier) + } } - protected open fun onSaveInstanceState(state: MutableSavedStateMap) { - nodeContext.onSaveInstanceState(state) + @Composable + private fun DerivedSetup() { + AppyxComponentSetup(appyxComponent = appyxComponent) + BackHandler() } - fun finish() { - parent?.onChildFinished(this) ?: integrationPoint.onRootFinished() + @Composable + private fun BackHandler() { + //todo support delegating to plugins + val canHandleBack = appyxComponent + .canHandeBackPress() + .collectAsState(initial = false) + PlatformBackHandler(enabled = canHandleBack.value) { + appyxComponent.handleBackPress() + } + } + + fun performUpNavigation(): Boolean = + appyxComponent.handleBackPress() || + handleUpNavigationByPlugins() || + parent?.performUpNavigation() == true + + private fun handleUpNavigationByPlugins(): Boolean = + plugins().any { it.handleUpNavigation() } + + + protected suspend inline fun > executeAction( + crossinline action: suspend () -> Unit + ): T = withContext(lifecycleScope.coroutineContext) { + action() + this@Node as T + } + + /** + * attachChild executes provided action e.g. backstack.push(NodeANavTarget) and waits for the specific + * Node of type T to appear in the ParentNode's children list. It should happen almost immediately because it happens + * on the main thread, but the order of actions is not preserved as lifecycleScope uses Dispatchers.Main.immediate. + * As the result we're doing it asynchronously with timeout after which exception is thrown if + * expected node has not appeared in the children list. + */ + protected suspend inline fun > attachChild( + timeout: Long = ATTACH_WORKFLOW_SYNC_TIMEOUT, + crossinline action: () -> Unit + ): T = withContext(lifecycleScope.coroutineContext) { + action() + + // after executing action waiting for the children to sync with navModel and + // throw an exception after short timeout if desired child was not found + val result = withTimeoutOrNull(timeout) { + waitForChildAttached() + } + checkNotNull(result) { + "Expected child of type [${T::class}] was not found after executing action. " + + "Check that your action actually results in the expected child." + } + } + + /** + * waitForChildAttached waits for the specific child of type T to be attached. For instance, we may + * want to wait until user logs in to perform a certain action. Since we don't have control over + * when this happens this job can hang indefinitely therefore you need to provide timeout if + * you need one. + */ + protected suspend inline fun > waitForChildAttached(): T = + suspendCancellableCoroutine { continuation -> + lifecycleScope.launch { + children.collect { childMap -> + val childNodeOfExpectedType = childMap.entries + .mapNotNull { it.value.nodeOrNull } + .filterIsInstance() + .takeIf { it.isNotEmpty() } + ?.last() + + if (childNodeOfExpectedType != null && !continuation.isCompleted) { + continuation.resume(childNodeOfExpectedType) + } + } + }.invokeOnCompletion { + continuation.cancel() + } + } + + open fun onChildFinished(child: Node<*>) { + // TODO warn unhandled child } /** @@ -190,9 +301,55 @@ open class Node internal constructor( } } - open fun performUpNavigation(): Boolean = - handleUpNavigationByPlugins() || (parent as? Node)?.performUpNavigation() == true + fun saveInstanceState(scope: SaverScope): SavedStateMap { + val writer = MutableSavedStateMapImpl(saverScope = scope) + onSaveInstanceState(writer) + plugins + .filterIsInstance() + .forEach { it.saveInstanceState(writer) } + return writer.savedState + } + + fun finish() { + parent?.onChildFinished(this) ?: integrationPoint.onRootFinished() + } + + // TODO save/restore state properly + fun onSaveInstanceState(state: MutableSavedStateMap) { + nodeContext.onSaveInstanceState(state) + childNodeCreationManager.saveChildrenState(state) + } + + // region ChildAware + + protected fun whenChildAttached(child: KClass, callback: ChildCallback) { + childAware.whenChildAttached(child, callback) + } + + protected fun whenChildrenAttached( + child1: KClass, + child2: KClass, + callback: ChildrenCallback + ) { + childAware.whenChildrenAttached(child1, child2, callback) + } + + protected inline fun whenChildAttached( + noinline callback: ChildCallback, + ) { + whenChildAttached(T::class, callback) + } + + protected inline fun whenChildrenAttached( + noinline callback: ChildrenCallback, + ) { + whenChildrenAttached(T1::class, T2::class, callback) + } + + // endregion + + companion object { + const val ATTACH_WORKFLOW_SYNC_TIMEOUT = 5000L + } - private fun handleUpNavigationByPlugins(): Boolean = - plugins().any { it.handleUpNavigation() } } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt index a0eab53e4..7b53c6e1b 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt @@ -1,3 +1,17 @@ package com.bumble.appyx.navigation.node -fun T.build(): T = also { it.onBuilt() } +import com.bumble.appyx.navigation.children.nodeOrNull + +fun > T.build(): T = also { it.onBuilt() } + +fun Node<*>.children(): List> { + return children.value.values.mapNotNull { it.nodeOrNull } +} + +inline fun > Node<*>.childrenOfType(): List { + return children.value.values.mapNotNull { it.nodeOrNull as? N } +} + +inline fun > Node<*>.firstChildOfType(): N? { + return children.value.values.firstOrNull { it.nodeOrNull is N }?.nodeOrNull as N? +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt index c35b4e4a0..3f21dda33 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt @@ -1,10 +1,8 @@ package com.bumble.appyx.navigation.node import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable import androidx.compose.ui.Modifier -@Stable interface NodeView { @Composable diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt deleted file mode 100644 index 3105a2b11..000000000 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt +++ /dev/null @@ -1,193 +0,0 @@ -package com.bumble.appyx.navigation.node - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.collectAsState -import com.bumble.appyx.interactions.core.Element -import com.bumble.appyx.interactions.core.model.AppyxComponent -import com.bumble.appyx.interactions.core.plugin.Plugin -import com.bumble.appyx.interactions.core.state.MutableSavedStateMap -import com.bumble.appyx.interactions.core.ui.helper.AppyxComponentSetup -import com.bumble.appyx.navigation.Appyx -import com.bumble.appyx.navigation.children.ChildAware -import com.bumble.appyx.navigation.children.ChildAwareImpl -import com.bumble.appyx.navigation.children.ChildCallback -import com.bumble.appyx.navigation.children.ChildEntry -import com.bumble.appyx.navigation.children.ChildEntryMap -import com.bumble.appyx.navigation.children.ChildNodeCreationManager -import com.bumble.appyx.navigation.children.ChildrenCallback -import com.bumble.appyx.navigation.children.nodeOrNull -import com.bumble.appyx.navigation.lifecycle.ChildNodeLifecycleManager -import com.bumble.appyx.navigation.lifecycle.Lifecycle -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.children.ChildNodeBuilder -import com.bumble.appyx.navigation.platform.PlatformBackHandler -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.withContext -import kotlinx.coroutines.withTimeoutOrNull -import kotlin.coroutines.resume -import kotlin.reflect.KClass - -@Suppress("TooManyFunctions") -@Stable -abstract class ParentNode( - val appyxComponent: AppyxComponent, - nodeContext: NodeContext, - view: ParentNodeView = EmptyParentNodeView(), - childKeepMode: ChildEntry.KeepMode = Appyx.defaultChildKeepMode, - private val childAware: ChildAware> = ChildAwareImpl(), - plugins: List = listOf(), -) : Node( - view = view, - nodeContext = nodeContext, - plugins = plugins + appyxComponent + childAware -), ChildNodeBuilder { - - private val childNodeCreationManager = ChildNodeCreationManager( - savedStateMap = nodeContext.savedStateMap, - customisations = nodeContext.customisations, - keepMode = childKeepMode, - ) - val children: StateFlow> - get() = childNodeCreationManager.children - - private val childNodeLifecycleManager = ChildNodeLifecycleManager( - appyxComponent = this.appyxComponent, - children = children, - keepMode = childKeepMode, - coroutineScope = lifecycleScope, - ) - - override fun onBuilt() { - super.onBuilt() - childNodeCreationManager.launch(this) - childNodeLifecycleManager.launch() - } - - fun childOrCreate(element: Element): ChildEntry.Initialized = - childNodeCreationManager.childOrCreate(element) - - override fun updateLifecycleState(state: Lifecycle.State) { - super.updateLifecycleState(state) - childNodeLifecycleManager.propagateLifecycleToChildren(state) - - // TODO move to plugins - if (state == Lifecycle.State.DESTROYED) { - appyxComponent.destroy() - } - } - - @Composable - override fun DerivedSetup() { - AppyxComponentSetup(appyxComponent = appyxComponent) - BackHandler() - } - - @Composable - private fun BackHandler() { - //todo support delegating to plugins - val canHandleBack = appyxComponent - .canHandeBackPress() - .collectAsState(initial = false) - PlatformBackHandler(enabled = canHandleBack.value) { - appyxComponent.handleBackPress() - } - } - - override fun performUpNavigation(): Boolean = - appyxComponent.handleBackPress() || super.performUpNavigation() - - /** - * attachChild executes provided action e.g. backstack.push(NodeANavTarget) and waits for the specific - * Node of type T to appear in the ParentNode's children list. It should happen almost immediately because it happens - * on the main thread, but the order of actions is not preserved as lifecycleScope uses Dispatchers.Main.immediate. - * As the result we're doing it asynchronously with timeout after which exception is thrown if - * expected node has not appeared in the children list. - */ - protected suspend inline fun attachChild( - timeout: Long = ATTACH_WORKFLOW_SYNC_TIMEOUT, - crossinline action: () -> Unit - ): T = withContext(lifecycleScope.coroutineContext) { - action() - - // after executing action waiting for the children to sync with navModel and - // throw an exception after short timeout if desired child was not found - val result = withTimeoutOrNull(timeout) { - waitForChildAttached() - } - checkNotNull(result) { - "Expected child of type [${T::class}] was not found after executing action. " + - "Check that your action actually results in the expected child." - } - } - - /** - * waitForChildAttached waits for the specific child of type T to be attached. For instance, we may - * want to wait until user logs in to perform a certain action. Since we don't have control over - * when this happens this job can hang indefinitely therefore you need to provide timeout if - * you need one. - */ - protected suspend inline fun waitForChildAttached(): T = - suspendCancellableCoroutine { continuation -> - lifecycleScope.launch { - children.collect { childMap -> - val childNodeOfExpectedType = childMap.entries - .mapNotNull { it.value.nodeOrNull } - .filterIsInstance() - .takeIf { it.isNotEmpty() } - ?.last() - - if (childNodeOfExpectedType != null && !continuation.isCompleted) { - continuation.resume(childNodeOfExpectedType) - } - } - }.invokeOnCompletion { - continuation.cancel() - } - } - - open fun onChildFinished(child: Node) { - // TODO warn unhandled child - } - - // TODO save/restore state properly - override fun onSaveInstanceState(state: MutableSavedStateMap) { - super.onSaveInstanceState(state) - childNodeCreationManager.saveChildrenState(state) - } - - // region ChildAware - - protected fun whenChildAttached(child: KClass, callback: ChildCallback) { - childAware.whenChildAttached(child, callback) - } - - protected fun whenChildrenAttached( - child1: KClass, - child2: KClass, - callback: ChildrenCallback - ) { - childAware.whenChildrenAttached(child1, child2, callback) - } - - protected inline fun whenChildAttached( - noinline callback: ChildCallback, - ) { - whenChildAttached(T::class, callback) - } - - protected inline fun whenChildrenAttached( - noinline callback: ChildrenCallback, - ) { - whenChildrenAttached(T1::class, T2::class, callback) - } - - // endregion - - companion object { - const val ATTACH_WORKFLOW_SYNC_TIMEOUT = 5000L - } - -} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt deleted file mode 100644 index 0b363b8c4..000000000 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.bumble.appyx.navigation.node - -import com.bumble.appyx.navigation.children.nodeOrNull - -fun ParentNode<*>.children(): List { - return children.value.values.mapNotNull { it.nodeOrNull } -} - -inline fun ParentNode<*>.childrenOfType(): List { - return children.value.values.mapNotNull { it.nodeOrNull as? N } -} - -inline fun ParentNode<*>.firstChildOfType(): N? { - return children.value.values.firstOrNull { it.nodeOrNull is N }?.nodeOrNull as N? -} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt deleted file mode 100644 index 035cfc705..000000000 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.bumble.appyx.navigation.node - -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier - -interface ParentNodeView : NodeView { - - @Composable - fun ParentNode.NodeView(modifier: Modifier) - - /** - * Do not override this function. Parent views should implement NodeView method. - */ - @Suppress("UNCHECKED_CAST") - @Composable - override fun Content(modifier: Modifier) { - val node = LocalNode.current as? ParentNode - ?: error("${this::class} is not provided to the appropriate ParentNode") - node.NodeView(modifier = modifier) - } -} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt index 3e8bfa7ef..8727cbf23 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt @@ -3,7 +3,7 @@ package com.bumble.appyx.navigation.plugin import com.bumble.appyx.navigation.node.Node -class NodeAwareImpl : NodeAware { +class NodeAwareImpl> : NodeAware { override lateinit var node: N private set diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt index bc083a573..f335d72bd 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt @@ -5,14 +5,17 @@ import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.plugin.BackPressHandler.OnBackPressedCallback -inline fun Node.plugins(): List

= +inline fun Node<*>.plugins(): List

= this.plugins.filterIsInstance

() -interface NodeAware : NodeReadyObserver { +//inline fun Node<*>.plugins(): List

= +// this.plugins.filterIsInstance

() + +interface NodeAware> : NodeReadyObserver { val node: N } -fun interface NodeReadyObserver : Plugin { +fun interface NodeReadyObserver> : Plugin { fun init(node: N) } @@ -33,7 +36,7 @@ fun interface Destroyable : Plugin { * * Implement either [onBackPressedCallback] or [onBackPressedCallbackList], not both. * In case if both implemented, [onBackPressedCallback] will be ignored. - * There is runtime check in [Node] to verify correctness. + * There is runtime check in [Node<*>] to verify correctness. */ interface BackPressHandler : Plugin { diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/DesktopNodeHost.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/DesktopNodeHost.kt index 1ff67e052..c3f2fa06a 100644 --- a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/DesktopNodeHost.kt +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/DesktopNodeHost.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch /** - * Composable function to host [Node]. + * Composable function to host [Node<*>]. * * This convenience wrapper uses [WindowState] to provide [ScreenSize] and provides an * [OnBackPressedDispatcherOwner] hooked up to the [.onBackPressedEvents] flow to simplify @@ -27,7 +27,7 @@ import kotlinx.coroutines.launch */ @Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda @Composable -fun DesktopNodeHost( +fun > DesktopNodeHost( windowState: WindowState, onBackPressedEvents: Flow, modifier: Modifier = Modifier, diff --git a/appyx-navigation/common/src/iosMain/kotlin/com/bumble/appyx/navigation/integration/IosNodeHost.kt b/appyx-navigation/common/src/iosMain/kotlin/com/bumble/appyx/navigation/integration/IosNodeHost.kt index 6c5ef8ba6..6f88232d7 100644 --- a/appyx-navigation/common/src/iosMain/kotlin/com/bumble/appyx/navigation/integration/IosNodeHost.kt +++ b/appyx-navigation/common/src/iosMain/kotlin/com/bumble/appyx/navigation/integration/IosNodeHost.kt @@ -23,7 +23,7 @@ import platform.UIKit.UIScreen @OptIn(ExperimentalForeignApi::class) @Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda @Composable -fun IosNodeHost( +fun > IosNodeHost( onBackPressedEvents: Flow, modifier: Modifier = Modifier, integrationPoint: IntegrationPoint, diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt index ca9cb00d1..b2b2b30d4 100644 --- a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt @@ -17,7 +17,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.launch /** - * Composable function to host [Node]. + * Composable function to host [Node<*>]. * * This convenience wrapper provides an [OnBackPressedDispatcherOwner] hooked up to the * [.onBackPressedEvents] flow to simplify implementing the global "go back" functionality @@ -25,7 +25,7 @@ import kotlinx.coroutines.launch */ @Suppress("ComposableParamOrder") @Composable -fun WebNodeHost( +fun > WebNodeHost( screenSize: ScreenSize, onBackPressedEvents: Flow, modifier: Modifier = Modifier, diff --git a/benchmark/benchmark-app/src/main/kotlin/com/bumble/appyx/benchmark/app/node/MosaicNode.kt b/benchmark/benchmark-app/src/main/kotlin/com/bumble/appyx/benchmark/app/node/MosaicNode.kt index 1705c1c08..be0c29712 100644 --- a/benchmark/benchmark-app/src/main/kotlin/com/bumble/appyx/benchmark/app/node/MosaicNode.kt +++ b/benchmark/benchmark-app/src/main/kotlin/com/bumble/appyx/benchmark/app/node/MosaicNode.kt @@ -38,7 +38,6 @@ import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.KEYFRA import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import kotlin.random.Random @@ -65,12 +64,12 @@ class MosaicNode( savedStateMap = nodeContext.savedStateMap, defaultAnimationSpec = animationSpec ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = mosaic ) { - override fun buildChildNode(mosaicPiece: MosaicPiece, nodeContext: NodeContext): Node = + override fun buildChildNode(mosaicPiece: MosaicPiece, nodeContext: NodeContext): Node<*> = node(nodeContext) { modifier -> Box( modifier = modifier diff --git a/demos/appyx-interactions/android/build.gradle.kts b/demos/appyx-interactions/android/build.gradle.kts index fd81ac4fc..49fa2d055 100644 --- a/demos/appyx-interactions/android/build.gradle.kts +++ b/demos/appyx-interactions/android/build.gradle.kts @@ -16,7 +16,6 @@ dependencies { implementation(composeBom) implementation(project(":appyx-interactions:android")) - implementation(project(":appyx-components:stable:backstack:backstack")) implementation(project(":appyx-components:stable:spotlight:spotlight")) implementation(project(":appyx-components:experimental:cards:android")) implementation(project(":appyx-components:experimental:modal:modal")) diff --git a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/BackstackExperimentDebug.kt b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/BackstackExperimentDebug.kt deleted file mode 100644 index 8f6a58010..000000000 --- a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/BackstackExperimentDebug.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.bumble.appyx.interactions.sample - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.BackStackModel -import com.bumble.appyx.components.backstack.operation.pop -import com.bumble.appyx.components.backstack.ui.fader.BackStackFader -import com.bumble.appyx.interactions.core.model.transition.Operation -import com.bumble.appyx.interactions.sample.android.SampleAppyxContainer -import com.bumble.appyx.interactions.theme.appyx_dark - - -@ExperimentalMaterialApi -@Composable -fun BackStackExperimentDebug(modifier: Modifier = Modifier) { - val coroutineScope = rememberCoroutineScope() - - val backStack = remember { - BackStack( - scope = coroutineScope, - model = BackStackModel( - initialTargets = listOf( - InteractionTarget.Child1, - InteractionTarget.Child2, - InteractionTarget.Child3 - ), - savedStateMap = null - ), - visualisation = { BackStackFader(it) }, - isDebug = false - ) - } - - LaunchedEffect(Unit) { -// backStack.push(Child2) -// backStack.replace(Child6) -// backStack.pop() -// backStack.pop() -// backStack.newRoot(Child1) - } - - Column( - modifier = Modifier - .fillMaxWidth() - .background(appyx_dark) - ) { -// KnobControl(onValueChange = { -// backStack.setNormalisedProgress(it) -// }) - Button(onClick = { - backStack.pop(Operation.Mode.IMMEDIATE) - } - ) { - Text("POP") - } - - SampleAppyxContainer( - modifier = Modifier - .padding( - horizontal = 64.dp, - vertical = 12.dp - ), - appyxComponent = backStack, - ) - } -} diff --git a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperimentDebug.kt b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperimentDebug.kt deleted file mode 100644 index 768657661..000000000 --- a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperimentDebug.kt +++ /dev/null @@ -1,85 +0,0 @@ -package com.bumble.appyx.interactions.sample - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import com.bumble.appyx.components.spotlight.Spotlight -import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.components.spotlight.operation.first -import com.bumble.appyx.components.spotlight.operation.last -import com.bumble.appyx.components.spotlight.operation.next -import com.bumble.appyx.components.spotlight.operation.previous -import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider -import com.bumble.appyx.interactions.core.ui.helper.AppyxComponentSetup -import com.bumble.appyx.interactions.sample.android.Element -import com.bumble.appyx.interactions.sample.android.SampleAppyxContainer -import com.bumble.appyx.interactions.theme.appyx_dark - -@ExperimentalMaterialApi -@Composable -fun SpotlightExperimentDebug(modifier: Modifier = Modifier) { - val model = remember { - SpotlightModel( - items = listOf( - InteractionTarget.Child1, - InteractionTarget.Child2, - InteractionTarget.Child3, - InteractionTarget.Child4, - InteractionTarget.Child5, - InteractionTarget.Child6, - InteractionTarget.Child7 - ), - savedStateMap = null - ) - } - val spotlight = remember { - Spotlight( - model = model, - visualisation = { SpotlightSlider(it, model.currentState) }, - gestureFactory = { SpotlightSlider.Gestures(it) }, - isDebug = true - ) - } - - AppyxComponentSetup(spotlight) - - - LaunchedEffect(Unit) { - spotlight.next() - spotlight.next() - spotlight.next() - spotlight.previous() - spotlight.last() - spotlight.first() - } - - Column( - Modifier - .fillMaxWidth() - .background(appyx_dark) - ) { - KnobControl(onValueChange = { - spotlight.setNormalisedProgress(it) - }) - - SampleAppyxContainer( - modifier = Modifier.padding( - horizontal = 64.dp, - vertical = 12.dp - ), - appyxComponent = spotlight, - elementUi = { element -> - Element( - element = element - ) - } - ) - } -} diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/component/spotlighthero/SpotlightHero.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/component/spotlighthero/SpotlightHero.kt index ae192e9d0..ee562e119 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/component/spotlighthero/SpotlightHero.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/component/spotlighthero/SpotlightHero.kt @@ -34,7 +34,6 @@ open class SpotlightHero( revertGestureSpec = animationSpec, ), disableAnimations: Boolean = false, - isDebug: Boolean = false ) : BaseAppyxComponent>( scope = scope, model = model, @@ -43,8 +42,7 @@ open class SpotlightHero( defaultAnimationSpec = animationSpec, gestureSettleConfig = gestureSettleConfig, disableAnimations = disableAnimations, - backPressStrategy = ExitHeroModeStrategy(scope), - isDebug = isDebug + backPressStrategy = ExitHeroModeStrategy(scope) ) { val currentState: State get() = model.currentState diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeBackdropNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeBackdropNode.kt index 2f11cba0c..8b4f23217 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeBackdropNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeBackdropNode.kt @@ -17,16 +17,17 @@ import com.bumble.appyx.demos.navigation.component.spotlighthero.visualisation.p import com.bumble.appyx.interactions.core.ui.math.smoothstep import com.bumble.appyx.interactions.core.ui.property.motionPropertyRenderValue import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode class CakeBackdropNode( nodeContext: NodeContext, private val cake: Cake, private val onClick: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { + @Composable override fun Content(modifier: Modifier) { val heroProgress = motionPropertyRenderValue() ?: 0f diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeImageNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeImageNode.kt index c6d6bf63c..ddf8161f4 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeImageNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeImageNode.kt @@ -15,13 +15,13 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.zIndex import com.bumble.appyx.demos.navigation.ui.EmbeddableResourceImage import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode class CakeImageNode( nodeContext: NodeContext, private val cake: Cake, private val onClick: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeListNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeListNode.kt index 219f1daec..9e438b7b1 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeListNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/cakes/CakeListNode.kt @@ -33,7 +33,6 @@ import com.bumble.appyx.interactions.core.ui.math.lerpFloat import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize import kotlinx.coroutines.delay @@ -56,7 +55,7 @@ class CakeListNode( visualisation = { SpotlightHeroDefaultVisualisation(it, model.currentState) }, gestureFactory = { SpotlightHeroGestures(it) } ), -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = spotlight ) { @@ -75,7 +74,7 @@ class CakeListNode( ) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Backdrop -> CakeBackdropNode(nodeContext, navTarget.cake) { toggleHeroMode() diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/AddressNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/AddressNode.kt index b9583a816..5d552d8dc 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/AddressNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/AddressNode.kt @@ -25,7 +25,7 @@ import com.bumble.appyx.demos.navigation.node.checkout.Address.AddressThree import com.bumble.appyx.demos.navigation.node.checkout.Address.AddressTwo import com.bumble.appyx.navigation.collections.toImmutableList import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode sealed class Address(override val value: String): CheckoutFormField { object AddressOne : Address("Address one") @@ -42,7 +42,7 @@ private val addresses = listOf( class AddressNode( nodeContext: NodeContext, private val onAddressSelected: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext ) { @Composable diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CartItemsNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CartItemsNode.kt index 8b334e5ae..947941c3a 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CartItemsNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CartItemsNode.kt @@ -22,13 +22,13 @@ import com.bumble.appyx.demos.navigation.node.cakes.Cake import com.bumble.appyx.demos.navigation.node.cart.Cart import com.bumble.appyx.navigation.collections.toImmutableMap import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode class CartItemsNode( nodeContext: NodeContext, private val cart: Cart, private val onCheckout: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CheckoutNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CheckoutNode.kt index 6097c0060..2be9ce819 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CheckoutNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/CheckoutNode.kt @@ -16,7 +16,6 @@ import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -43,7 +42,7 @@ class CheckoutNode( } } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack ) { @@ -64,7 +63,7 @@ class CheckoutNode( object Success : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.CartItems -> CartItemsNode(nodeContext, cart) { backStack.push(NavTarget.Address) diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/OrderConfirmedNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/OrderConfirmedNode.kt index 6deaadf22..3cdb87f44 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/OrderConfirmedNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/OrderConfirmedNode.kt @@ -17,14 +17,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode import kotlinx.coroutines.delay import kotlin.time.Duration.Companion.seconds class OrderConfirmedNode( nodeContext: NodeContext, private val postViewAction: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/PaymentNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/PaymentNode.kt index a67d58d11..b4ba60d24 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/PaymentNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/PaymentNode.kt @@ -26,7 +26,7 @@ import com.bumble.appyx.demos.navigation.node.checkout.PaymentOption.Monopoly import com.bumble.appyx.demos.navigation.node.checkout.PaymentOption.PiggyBank import com.bumble.appyx.navigation.collections.toImmutableList import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode sealed class PaymentOption(override val value: String) : CheckoutFormField { object Card : PaymentOption("Card") @@ -45,7 +45,7 @@ private val paymentOptions = listOf( class PaymentNode( nodeContext: NodeContext, private val onPaymentConfirmed: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/ShippingDetailsNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/ShippingDetailsNode.kt index 809d3e79c..e8639bbad 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/ShippingDetailsNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/checkout/ShippingDetailsNode.kt @@ -26,7 +26,7 @@ import com.bumble.appyx.demos.navigation.node.checkout.ShippingMethod.Snail import com.bumble.appyx.demos.navigation.node.checkout.ShippingMethod.Teleportation import com.bumble.appyx.navigation.collections.toImmutableList import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode sealed class ShippingMethod(override val value: String): CheckoutFormField { object Pony : ShippingMethod("Pony express") @@ -45,7 +45,7 @@ private val shippingMethods = listOf( class ShippingDetailsNode( nodeContext: NodeContext, private val onShippingConfirmed: () -> Unit, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/home/HomeNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/home/HomeNode.kt index af6ce6393..555eac2f9 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/home/HomeNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/home/HomeNode.kt @@ -16,11 +16,11 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.bumble.appyx.demos.navigation.navigator.LocalNavigator import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode class HomeNode( nodeContext: NodeContext, -) : Node( +) : LeafNode( nodeContext = nodeContext, ) { diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/loggedout/LoggedOutNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/loggedout/LoggedOutNode.kt index 3ae930f4e..9b7cc4026 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/loggedout/LoggedOutNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/loggedout/LoggedOutNode.kt @@ -31,7 +31,6 @@ import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -59,7 +58,7 @@ class LoggedOutNode( } } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack ) { @@ -71,7 +70,7 @@ class LoggedOutNode( object Login : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Splash -> node(nodeContext) { modifier -> SplashScreen(modifier) diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/profile/ProfileNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/profile/ProfileNode.kt index 009344dc2..757e4d955 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/profile/ProfileNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/profile/ProfileNode.kt @@ -19,7 +19,6 @@ import com.bumble.appyx.demos.navigation.node.profile.ProfileNode.NavTarget import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -35,7 +34,7 @@ class ProfileNode( ), visualisation = { BackStackSlider(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack ) { @@ -44,7 +43,7 @@ class ProfileNode( object ProfileChild : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.ProfileChild -> node(nodeContext) { modifier -> Column( diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/root/RootNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/root/RootNode.kt index 6a78a825e..fa3211a35 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/root/RootNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/navigation/node/root/RootNode.kt @@ -14,7 +14,6 @@ import com.bumble.appyx.interactions.core.plugin.Plugin import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -34,7 +33,7 @@ class RootNode( visualisation = { BackStackFader(it) } ), plugins: List = listOf(), -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack, plugins = plugins @@ -54,7 +53,7 @@ class RootNode( ) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.LoggedOut -> LoggedOutNode( nodeContext = nodeContext, diff --git a/demos/navigation-compose/src/main/kotlin/com/bumble/appyx/sample/navigtion/compose/ComposeNavigationContainerNode.kt b/demos/navigation-compose/src/main/kotlin/com/bumble/appyx/sample/navigtion/compose/ComposeNavigationContainerNode.kt index 43a450b47..281ac6934 100644 --- a/demos/navigation-compose/src/main/kotlin/com/bumble/appyx/sample/navigtion/compose/ComposeNavigationContainerNode.kt +++ b/demos/navigation-compose/src/main/kotlin/com/bumble/appyx/sample/navigtion/compose/ComposeNavigationContainerNode.kt @@ -14,7 +14,6 @@ import com.bumble.appyx.components.backstack.ui.slider.BackStackSlider import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import kotlinx.parcelize.Parcelize @@ -28,7 +27,7 @@ internal class ComposeNavigationContainerNode( ), visualisation = { BackStackSlider(it) } ) -) : ParentNode( +) : Node( appyxComponent = backStack, nodeContext = nodeContext, ) { @@ -38,7 +37,7 @@ internal class ComposeNavigationContainerNode( object Main : InteractionTarget() } - override fun buildChildNode(navTarget: InteractionTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: InteractionTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is InteractionTarget.Main -> node(nodeContext) { Column( diff --git a/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/datingcards/DatingCardsNode.kt b/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/datingcards/DatingCardsNode.kt index 0bfbdb3df..b7866def4 100644 --- a/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/datingcards/DatingCardsNode.kt +++ b/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/datingcards/DatingCardsNode.kt @@ -17,7 +17,6 @@ import com.bumble.appyx.demos.sandbox.navigation.ui.appyx_dark import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -35,7 +34,7 @@ class DatingCardsNode( gestureFactory = { CardsVisualisation.Gestures(it) }, ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = cards ) { @@ -45,7 +44,7 @@ class DatingCardsNode( class ProfileCard(val profile: Profile) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = ProfileCardNode(nodeContext, (navTarget as ProfileCard).profile) @Composable diff --git a/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/profilecard/ProfileCardNode.kt b/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/profilecard/ProfileCardNode.kt index 31001165a..4d5a67ab3 100644 --- a/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/profilecard/ProfileCardNode.kt +++ b/demos/sandbox-appyx-navigation/android/src/main/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/profilecard/ProfileCardNode.kt @@ -21,12 +21,14 @@ import coil.compose.AsyncImage import coil.request.ImageRequest import com.bumble.appyx.demos.common.profile.Profile import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.node.LeafNode class ProfileCardNode( nodeContext: NodeContext, private val profile: Profile -) : Node(nodeContext) { +) : LeafNode( + nodeContext = nodeContext +) { @Composable @Override diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/MainNavItem.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/MainNavItem.kt index de9328b2d..3515b9406 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/MainNavItem.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/MainNavItem.kt @@ -22,9 +22,9 @@ enum class MainNavItem : Parcelable { BACKSTACK, SPOTLIGHT, PROMOTER; companion object { - val resolver: (com.bumble.appyx.demos.sandbox.navigation.MainNavItem) -> AppyxNavItem = { navBarItem -> + val resolver: (MainNavItem) -> AppyxNavItem = { navBarItem -> when (navBarItem) { - com.bumble.appyx.demos.sandbox.navigation.MainNavItem.BACKSTACK -> AppyxNavItem( + BACKSTACK -> AppyxNavItem( text = "Back stack", unselectedIcon = Outlined.WebStories, selectedIcon = Filled.WebStories, @@ -32,14 +32,14 @@ enum class MainNavItem : Parcelable { node = { BackStackExamplesNode(it) } ) - com.bumble.appyx.demos.sandbox.navigation.MainNavItem.SPOTLIGHT -> AppyxNavItem( + SPOTLIGHT -> AppyxNavItem( text = "Spotlight", unselectedIcon = Outlined.ViewCarousel, selectedIcon = Filled.ViewCarousel, node = { SpotlightNode(it) } ) - com.bumble.appyx.demos.sandbox.navigation.MainNavItem.PROMOTER -> AppyxNavItem( + PROMOTER -> AppyxNavItem( text = "Promoter", unselectedIcon = Outlined.GridView, selectedIcon = Filled.GridViewCustom, diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackExamplesNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackExamplesNode.kt index 5c2d6c54d..29458efb5 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackExamplesNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackExamplesNode.kt @@ -27,7 +27,6 @@ import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -42,7 +41,7 @@ class BackStackExamplesNode( ), visualisation = { BackStackSlider(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack ) { @@ -66,7 +65,7 @@ class BackStackExamplesNode( object BackStack3D : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.BackStackPicker -> node(nodeContext) { BackStackPicker(it) diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackNode.kt index 8c05f0e1c..9d52e6915 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/BackStackNode.kt @@ -37,7 +37,6 @@ import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -61,7 +60,7 @@ class BackStackNode( gestureFactory = gestureFactory, gestureSettleConfig = gestureSettleConfig, ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack, ) { @@ -70,7 +69,7 @@ class BackStackNode( class Child(val index: Int) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Child -> node(nodeContext) { val backgroundColor = diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/debug/BackstackDebugNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/debug/BackstackDebugNode.kt deleted file mode 100644 index 7e3f59b84..000000000 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/backstack/debug/BackstackDebugNode.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.bumble.appyx.demos.sandbox.navigation.node.backstack.debug - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.BackStackModel -import com.bumble.appyx.components.backstack.operation.newRoot -import com.bumble.appyx.components.backstack.operation.pop -import com.bumble.appyx.components.backstack.operation.push -import com.bumble.appyx.components.backstack.operation.replace -import com.bumble.appyx.components.backstack.ui.slider.BackStackSlider -import com.bumble.appyx.demos.sandbox.navigation.colors -import com.bumble.appyx.demos.sandbox.navigation.composable.KnobControl -import com.bumble.appyx.demos.sandbox.navigation.node.backstack.debug.BackstackDebugNode.NavTarget -import com.bumble.appyx.demos.sandbox.navigation.ui.appyx_dark -import com.bumble.appyx.navigation.composable.AppyxNavigationContainer -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode -import com.bumble.appyx.navigation.node.node -import com.bumble.appyx.utils.multiplatform.Parcelable -import com.bumble.appyx.utils.multiplatform.Parcelize - -class BackstackDebugNode( - nodeContext: NodeContext, - private val backStack: BackStack = BackStack( - model = BackStackModel( - initialTargets = listOf(NavTarget.Child(1)), - savedStateMap = nodeContext.savedStateMap, - ), - visualisation = { BackStackSlider(it) } - ) -) : ParentNode( - nodeContext = nodeContext, - appyxComponent = backStack -) { - - init { - backStack.push(NavTarget.Child(2)) - backStack.push(NavTarget.Child(3)) - backStack.push(NavTarget.Child(4)) - backStack.push(NavTarget.Child(5)) - backStack.replace(NavTarget.Child(6)) - backStack.pop() - backStack.pop() - backStack.newRoot(NavTarget.Child(1)) - } - - sealed class NavTarget : Parcelable { - @Parcelize - class Child(val index: Int) : NavTarget() - } - - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = - when (navTarget) { - is NavTarget.Child -> node(nodeContext) { - val backgroundColor = remember { colors.shuffled().random() } - - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(5)) - .background(backgroundColor) - .padding(24.dp) - ) { - Text( - text = navTarget.index.toString(), - fontSize = 21.sp, - color = Color.Black, - fontWeight = FontWeight.Bold - ) - } - } - } - - @ExperimentalMaterialApi - @Composable - override fun Content(modifier: Modifier) { - Column( - modifier = modifier - .fillMaxWidth() - .background(appyx_dark) - ) { - KnobControl(onValueChange = { - backStack.setNormalisedProgress(it) - }) - AppyxNavigationContainer( - appyxComponent = backStack, - modifier = Modifier - .fillMaxSize() - .background(appyx_dark) - .padding(16.dp), - ) - } - } -} diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/container/ContainerNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/container/ContainerNode.kt index fff6626cd..ee59e23be 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/container/ContainerNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/container/ContainerNode.kt @@ -16,7 +16,6 @@ import com.bumble.appyx.components.backstack.BackStackModel import com.bumble.appyx.components.backstack.operation.push import com.bumble.appyx.components.backstack.ui.slider.BackStackSlider import com.bumble.appyx.demos.sandbox.navigation.node.backstack.BackStackExamplesNode -import com.bumble.appyx.demos.sandbox.navigation.node.backstack.debug.BackstackDebugNode import com.bumble.appyx.demos.sandbox.navigation.node.container.ContainerNode.NavTarget import com.bumble.appyx.demos.sandbox.navigation.node.modal.ModalExamplesNode import com.bumble.appyx.demos.sandbox.navigation.node.permanentchild.PermanentChildNode @@ -27,7 +26,6 @@ import com.bumble.appyx.demos.sandbox.navigation.ui.TextButton import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -42,7 +40,7 @@ class ContainerNode( visualisation = { BackStackSlider(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack ) { @@ -53,18 +51,12 @@ class ContainerNode( @Parcelize object PermanentChild : NavTarget() -// @Parcelize -// object DatingCards : NavTarget() - @Parcelize object SpotlightExperiment : NavTarget() @Parcelize object ObservingTransitionsExample : NavTarget() - @Parcelize - object BackStackExperimentDebug : NavTarget() - @Parcelize object BackStack : NavTarget() @@ -76,21 +68,19 @@ class ContainerNode( } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Selector -> node(nodeContext) { modifier -> Selector(modifier) } is NavTarget.PermanentChild -> PermanentChildNode(nodeContext) -// is NavTarget.DatingCards -> DatingCardsNode(nodeContext) is NavTarget.SpotlightExperiment -> SpotlightNode(nodeContext) is NavTarget.ObservingTransitionsExample -> SpotlightObserveTransitionsExampleNode( nodeContext ) is NavTarget.BackStack -> BackStackExamplesNode(nodeContext) - is NavTarget.BackStackExperimentDebug -> BackstackDebugNode(nodeContext) is NavTarget.Modal -> ModalExamplesNode(nodeContext) is NavTarget.PromoterExperiment -> PromoterNode(nodeContext) } @@ -110,9 +100,6 @@ class ContainerNode( verticalArrangement = Arrangement.spacedBy(24.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { -// TextButton(text = "Dating Cards") { -//// backStack.push(NavTarget.DatingCards) -// } TextButton(text = "Spotlight") { backStack.push(NavTarget.SpotlightExperiment) } @@ -122,9 +109,6 @@ class ContainerNode( TextButton(text = "Backstack Examples") { backStack.push(NavTarget.BackStack) } - TextButton(text = "Backstack Debug") { - backStack.push(NavTarget.BackStackExperimentDebug) - } TextButton(text = "Promoter") { backStack.push(NavTarget.PromoterExperiment) } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/modal/ModalExamplesNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/modal/ModalExamplesNode.kt index c35d47eed..1867ba817 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/modal/ModalExamplesNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/modal/ModalExamplesNode.kt @@ -29,7 +29,6 @@ import com.bumble.appyx.demos.sandbox.navigation.ui.appyx_dark import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -45,7 +44,7 @@ class ModalExamplesNode( ), visualisation = { ModalVisualisation(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = modal ) { @@ -55,7 +54,7 @@ class ModalExamplesNode( object Child : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Child -> node(nodeContext) { val backgroundColor = remember { colors.shuffled().random() } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/permanentchild/PermanentChildNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/permanentchild/PermanentChildNode.kt index d35a14d19..efe1fb3f3 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/permanentchild/PermanentChildNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/permanentchild/PermanentChildNode.kt @@ -22,7 +22,6 @@ import com.bumble.appyx.interactions.permanent.PermanentAppyxComponent import com.bumble.appyx.navigation.composable.PermanentChild import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -37,7 +36,7 @@ class PermanentChildNode( NavTarget.Child2 ) ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = permanentAppyxComponent ) { @@ -49,7 +48,7 @@ class PermanentChildNode( object Child2 : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Child1 -> node(nodeContext) { val backgroundColor = remember { colors.shuffled().random() } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/promoter/PromoterNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/promoter/PromoterNode.kt index e2b7d0673..773dc51b6 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/promoter/PromoterNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/promoter/PromoterNode.kt @@ -38,7 +38,6 @@ import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.KEYFRA import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -56,7 +55,7 @@ class PromoterNode( }, animationSpec = spring(stiffness = Spring.StiffnessVeryLow / 20) ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = promoter ) { @@ -73,7 +72,7 @@ class PromoterNode( class Child(val index: Int) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is Child -> node(nodeContext) { val backgroundColor = remember { colors.shuffled().random() } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightNode.kt index 8cda73e19..3a2441fb6 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightNode.kt @@ -39,7 +39,6 @@ import com.bumble.appyx.demos.sandbox.navigation.ui.appyx_dark import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -56,7 +55,7 @@ class SpotlightNode( visualisation = { SpotlightSlider(it, model.currentState) }, gestureFactory = { SpotlightSlider.Gestures(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = spotlight ) { @@ -67,7 +66,7 @@ class SpotlightNode( class Child(val index: Int) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Child -> node(nodeContext) { modifier -> val backgroundColorIdx = rememberSaveable { colors.shuffled().indices.random() } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightObserveTransitionsExampleNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightObserveTransitionsExampleNode.kt index f950ee289..f6affcb0c 100644 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightObserveTransitionsExampleNode.kt +++ b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/SpotlightObserveTransitionsExampleNode.kt @@ -38,7 +38,6 @@ import com.bumble.appyx.interactions.core.ui.property.motionPropertyRenderValue import com.bumble.appyx.navigation.composable.AppyxNavigationContainer import com.bumble.appyx.navigation.modality.NodeContext import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode import com.bumble.appyx.navigation.node.node import com.bumble.appyx.utils.multiplatform.Parcelable import com.bumble.appyx.utils.multiplatform.Parcelize @@ -55,7 +54,7 @@ class SpotlightObserveTransitionsExampleNode( visualisation = { SpotlightSliderRotation(it, model.currentState) }, gestureFactory = { SpotlightSlider.Gestures(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = spotlight ) { @@ -66,7 +65,7 @@ class SpotlightObserveTransitionsExampleNode( class Child(val index: Int) : NavTarget() } - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = when (navTarget) { is NavTarget.Child -> node(nodeContext) { modifier -> val backgroundColor = remember { colors.shuffled().random() } diff --git a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/debug/SpotlightDebugNode.kt b/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/debug/SpotlightDebugNode.kt deleted file mode 100644 index 5bc48854a..000000000 --- a/demos/sandbox-appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/demos/sandbox/navigation/node/spotlight/debug/SpotlightDebugNode.kt +++ /dev/null @@ -1,111 +0,0 @@ -package com.bumble.appyx.demos.sandbox.navigation.node.spotlight.debug - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.bumble.appyx.components.spotlight.Spotlight -import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.components.spotlight.operation.first -import com.bumble.appyx.components.spotlight.operation.last -import com.bumble.appyx.components.spotlight.operation.next -import com.bumble.appyx.components.spotlight.operation.previous -import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider -import com.bumble.appyx.demos.sandbox.navigation.colors -import com.bumble.appyx.demos.sandbox.navigation.composable.KnobControl -import com.bumble.appyx.demos.sandbox.navigation.node.spotlight.debug.SpotlightDebugNode.NavTarget -import com.bumble.appyx.demos.sandbox.navigation.ui.appyx_dark -import com.bumble.appyx.navigation.composable.AppyxNavigationContainer -import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode -import com.bumble.appyx.navigation.node.node -import com.bumble.appyx.utils.multiplatform.Parcelable -import com.bumble.appyx.utils.multiplatform.Parcelize - -class SpotlightDebugNode( - nodeContext: NodeContext, - private val model: SpotlightModel = SpotlightModel( - items = List(7) { NavTarget.Child(it + 1) }, - initialActiveIndex = 0f, - savedStateMap = nodeContext.savedStateMap - ), - private val spotlight: Spotlight = Spotlight( - model = model, - visualisation = { SpotlightSlider(it, model.currentState) }, - isDebug = true - ) -) : ParentNode( - nodeContext = nodeContext, - appyxComponent = spotlight -) { - - init { - spotlight.next() - spotlight.next() - spotlight.next() - spotlight.previous() - spotlight.last() - spotlight.first() - } - - sealed class NavTarget : Parcelable { - @Parcelize - class Child(val index: Int) : NavTarget() - } - - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = - when (navTarget) { - is NavTarget.Child -> node(nodeContext) { - val backgroundColor = remember { colors.shuffled().random() } - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(5)) - .background(backgroundColor) - .padding(24.dp) - ) { - Text( - text = navTarget.index.toString(), - fontSize = 21.sp, - color = Color.Black, - fontWeight = FontWeight.Bold - ) - } - } - } - - @ExperimentalMaterialApi - @Composable - override fun Content(modifier: Modifier) { - Column( - modifier - .fillMaxWidth() - .background(appyx_dark) - ) { - KnobControl(onValueChange = { - spotlight.setNormalisedProgress(it) - }) - AppyxNavigationContainer( - appyxComponent = spotlight, - modifier = Modifier.padding( - horizontal = 64.dp, - vertical = 12.dp - ) - ) - } - } -} diff --git a/documentation/interactions/usage.md b/documentation/interactions/usage.md index 7e57f4397..c91822c39 100644 --- a/documentation/interactions/usage.md +++ b/documentation/interactions/usage.md @@ -102,7 +102,7 @@ For client code usage they're almost identical. However, you should always use t Also note: -- This composable is only accessible inside of a `ParentNode`. +- This composable is only accessible inside of a `Node`. - You should use it inside the `View` composable. - You don't need to specify screen dimensions. @@ -110,7 +110,7 @@ Also note: ```kotlin class YourNode( /*...*/ -) : ParentNode { +) : Node { @Composable override fun Content(modifier: Modifier) { diff --git a/documentation/navigation/concepts/composable-navigation.md b/documentation/navigation/concepts/composable-navigation.md index bdb7c1e21..1f99e7869 100644 --- a/documentation/navigation/concepts/composable-navigation.md +++ b/documentation/navigation/concepts/composable-navigation.md @@ -27,7 +27,7 @@ This allows you to make your app's business logic also composable by leveraging ## Parent nodes, child nodes -`ParentNodes` can have other `Nodes` as children. This means you can represent your whole application as a tree of Appyx nodes. +`Nodes` can have other `Nodes` as children. This means you can represent your whole application as a tree of Appyx nodes. @@ -36,7 +36,7 @@ You can go as granular or as high-level as it fits you. This allows to keep the ## ChildAware API -A `ParentNode` can react to dynamically added child `Nodes` in the tree: [ChildAware API](../features/childaware.md) +A `Node` can react to dynamically added child `Nodes` in the tree: [ChildAware API](../features/childaware.md) ## Navigation in the tree @@ -58,7 +58,7 @@ See [Implicit navigation](implicit-navigation.md) and [Explicit navigation](expl `AppyxComponent` operations will typically result in: -- Adding or removing child `Nodes` of a `ParentNode` +- Adding or removing child `Nodes` of a `Node` - Move them on and off the screen - Change their states @@ -69,7 +69,7 @@ As an illustration: Here: - `Back stack` illustrates adding and removing child `Nodes` -- `Tiles` illustrates changing the state of children and removing them from the `ParentNode` +- `Tiles` illustrates changing the state of children and removing them from the `Node` These are just two examples, you're of course not limited to using them. diff --git a/documentation/navigation/concepts/explicit-navigation.md b/documentation/navigation/concepts/explicit-navigation.md index 16b3a7c9a..7b1bb8edf 100644 --- a/documentation/navigation/concepts/explicit-navigation.md +++ b/documentation/navigation/concepts/explicit-navigation.md @@ -8,8 +8,8 @@ When [Implicit navigation](implicit-navigation.md) doesn't fit your use case, yo !!! info "Relevant methods" - - ParentNode.attachChild() - - ParentNode.waitForChildAttached() + - Node.attachChild() + - Node.waitForChildAttached() Using these methods we can chain together a path which leads from the root of the tree to a specific `Node`. @@ -43,7 +43,7 @@ First, we need to define how to programmatically attach `Onboarding` to the `Roo class RootNode( nodeContext: NodeContext, backStack: BackStack -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack, ) { @@ -89,7 +89,7 @@ Unlike `Root`, `Onboarding` uses [Spotlight](../../components/spotlight.md) inst class OnboardingNode( nodeContext: NodeContext, spotlight: Spotlight -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = spotlight, ) { @@ -172,7 +172,7 @@ It can pass it further down the tree as a dependency to other nodes. Those nodes There might be cases when we want to wait for a certain action to be _performed by the user_, rather than us, to result in a child being attached. -In these cases we can use `ParentNode.waitForChildAttached()` instead. +In these cases we can use `Node.waitForChildAttached()` instead. ### Use case – Wait for login @@ -183,7 +183,7 @@ A typical case building an explicit navigation chain that relies on `Logged in` ```kotlin class RootNode( nodeContext: NodeContext, -) : ParentNode( +) : Node( nodeContext = nodeContext ) { diff --git a/documentation/navigation/concepts/implicit-navigation.md b/documentation/navigation/concepts/implicit-navigation.md index 17c998d1f..36702bf3d 100644 --- a/documentation/navigation/concepts/implicit-navigation.md +++ b/documentation/navigation/concepts/implicit-navigation.md @@ -10,7 +10,7 @@ How can we go from one part of the tree to another? In almost all cases navigati !!! info "Relevant methods" - - `ParentNode.onChildFinished(child: Node)` can be overridden by client code to handle a child finishing + - `Node.onChildFinished(child: Node)` can be overridden by client code to handle a child finishing - `Node.finish()` invokes the above method on its parent diff --git a/documentation/navigation/features/plugins.md b/documentation/navigation/features/plugins.md index 6d70296f5..0f77ddde4 100644 --- a/documentation/navigation/features/plugins.md +++ b/documentation/navigation/features/plugins.md @@ -73,7 +73,7 @@ class SomeClass( } ``` -⚠️ Note: the reference to ```node``` is set by ```Node``` automatically, and isn't available immediately after constructing your object, but only after the construction of the ```Node``` itself. +⚠️ Note: the reference to `node` is set by `Node` automatically, and isn't available immediately after constructing your object, but only after the construction of the `Node` itself. ### Navigation plugins @@ -102,9 +102,9 @@ You can read more about it [here](https://developer.android.com/guide/navigation ## Using Plugins -All plugins are designed to have empty ```{}``` default implementations (or other sensible defaults when a return value is defined), so it's convenient to implement them only if you need. +All plugins are designed to have empty `{}` default implementations (or other sensible defaults when a return value is defined), so it's convenient to implement them only if you need. -Don't forget to pass your ```Plugins``` to your ```Node```: +Don't forget to pass your `Plugins` to your `Node`: ```kotlin internal class MyNode( @@ -118,4 +118,4 @@ internal class MyNode( ) ``` -⚠️ Note: ```plugins``` is a ```List```, as the order matters here. All ```Plugin``` instances are invoked in the order they appear in the list. +⚠️ Note: `plugins` is a `List`, as the order matters here. All `Plugin` instances are invoked in the order they appear in the list. diff --git a/documentation/navigation/quick-start.md b/documentation/navigation/quick-start.md index b5d4c22d3..c916c17c7 100644 --- a/documentation/navigation/quick-start.md +++ b/documentation/navigation/quick-start.md @@ -35,7 +35,7 @@ For the scope of this quick start guide, you will need to add dependencies for: ```kotlin class RootNode( nodeContext: NodeContext -) : Node( +) : LeafNode( nodeContext = nodeContext ) { @Composable @@ -85,18 +85,18 @@ sealed class NavTarget : Parcelable { } ``` -Next, let's modify `RootNode` so it extends `ParentNode`: +Next, let's modify `RootNode` so it extends `Node` instead of `LeafNode`: ```kotlin class RootNode( nodeContext: NodeContext -) : ParentNode( +) : Node( appyxComponent = TODO("We will come back to this in Step 5"), nodeContext = nodeContext ) { ``` -`ParentNode` expects us to implement the abstract method `buildChildNode`. This is how we relate navigation targets to actual children. Let's use these helper methods to define some placeholders for the time being – we'll soon make them more appealing: +`Node` expects us to implement the abstract method `buildChildNode`. This is how we relate navigation targets to actual children. Let's use these helper methods to define some placeholders for the time being – we'll soon make them more appealing: ```kotlin override fun buildChildNode(reference: NavTarget, nodeContext: NodeContext): Node = @@ -111,7 +111,7 @@ Great! With this mapping created, we can now just refer to children using the se ## 5. Add a back stack -The project wouldn't compile just yet. `ParentNode` expects us to pass an instance of an `AppyxComponent` – the main control structure in any case when we want to add children. No need to worry now – for simplicity, let's just go with a simple `BackStack` implementation here: +The project wouldn't compile just yet. `Node` expects us to pass an instance of an `AppyxComponent` – the main control structure in any case when we want to add children. No need to worry now – for simplicity, let's just go with a simple `BackStack` implementation here: ```kotlin class RootNode( @@ -123,7 +123,7 @@ class RootNode( ), visualisation = { BackStackFader(it) } ) -) : ParentNode( +) : Node( nodeContext = nodeContext, appyxComponent = backStack // pass it here ) { @@ -137,7 +137,7 @@ backStack.replace(NavTarget.Child3) // will replace the currently active child backStack.pop() // will remove the currently active child and restore the one before it ``` -Since we passed the back stack to the `ParentNode`, all such changes will be immediately reflected. We only need to add it to the composition: +Since we passed the back stack to the `Node`, all such changes will be immediately reflected. We only need to add it to the composition: ```kotlin @Composable @@ -243,11 +243,11 @@ override fun buildChildNode(reference: NavTarget, nodeContext: NodeContext): Nod Congrats, you've just built your first Appyx tree! -You can repeat the same pattern and make any embedded children also a `ParentNode` with their own children, navigation models, and transitions. As complexity grows, generally you would: +You can repeat the same pattern and make any embedded children also a `Node` with their own children, navigation models, and transitions. As complexity grows, generally you would: -1. Have a `Node` -2. At some point make it a `ParentNode` and add children to it -3. At some point extract the increasing complexity from a placeholder to another `Node` +1. Have a `LeafNode` +2. At some point make it a `Node` and add children to it +3. At some point extract the increasing complexity from a placeholder to another `LeafNode` 4. Repeat the same on children, go to `1.` diff --git a/documentation/releases/2.0.0-alpha10.md b/documentation/releases/2.0.0-alpha10.md index 21a8f3c1c..7eae42872 100644 --- a/documentation/releases/2.0.0-alpha10.md +++ b/documentation/releases/2.0.0-alpha10.md @@ -21,11 +21,34 @@ title: 2.0.0-alpha10 – Migration guide +import com.bumble.appyx.utils.multiplatform.SavedStateMap ``` +## Rename `Node` & `ParentNode` + +```diff +class YourNode( + /*...*/ +-) : Node( ++) : LeafNode( + /*...*/ +) { +} +``` + +```diff +class YourNode( + /*...*/ +-) : ParentNode( ++) : Node( + /*...*/ +) { +} +``` + + ## Rename `View` composable ```diff class YourNode( /*...*/ -) : ParentNode { +) : Node { @Composable - override fun View(modifier: Modifier) { @@ -44,7 +67,7 @@ When used in the scope of Appyx Navigation: class YourNode( /*...*/ -) : ParentNode { +) : Node { @Composable override fun Content(modifier: Modifier) { @@ -81,6 +104,7 @@ fun SomeComposable() { ) } ``` + ## Rename `BuildContext` & `buildContext` ```diff @@ -91,7 +115,7 @@ class YourNode( - buildContext: BuildContext, + nodeContext: NodeContext, /*...*/ -) : ParentNode { +) : Node { - buildContext = buildContext + nodeContext = nodeContext /*...*/ @@ -104,7 +128,7 @@ class YourNode( ```diff class YourNode( /*...*/ -) : ParentNode { +) : Node { /*...*/ ) { sealed class NavTarget : Parcelable { @@ -118,9 +142,39 @@ class YourNode( } ``` +  + +# Changes unlikely to affect you directly + +## Changes to `NodeView` + +You only need to care about these changes if you separated out your view to a class implementing the `NodeView` interface. + +If you implemented your `@Composable override fun Content(modifier: Modifier)` directly in your `Node`, you don't need to do anything. + +1. **We've dropped generics from `NodeView` interface** + + ```diff + -interface NodeView + +interface NodeView + ``` + + If you had an implementation of this interface, drop the generic from your class too. + +2. **We've unified `NodeView` and `ParentNodeView`** + + If you used `ParentNodeView`, just replace the usage with `NodeView`. + +3. **We've unified `EmptyNodeView` and `EmptyParentNodeView`** + + If you used `EmptyParentNodeView`, just replace the usage with `EmptyNodeView`. + ## Swap the order of `TargetUiState`, `MutableUiState` +You only need to do this if you implemented your own `Visualisation`. If you only relied on the ones supplied by the library, you don't need to do anything. + + ```diff class SomeVisualisation( /*...*/ @@ -131,7 +185,13 @@ class SomeVisualisation( ## KSP generated method name change +You only need to do this if you implemented your own `Visualisation`. If you only relied on the ones supplied by the library, you don't need to do anything. + ```diff --targetUiState.toMutableState() -+targetUiState.toMutableUiState() +override fun mutableUiStateFor( + uiContext: UiContext, + targetUiState: TargetUiState +): MutableUiState = +- targetUiState.toMutableState(uiContext) ++ targetUiState.toMutableUiState(uiContext) ``` diff --git a/utils/interop-ribs/src/androidTest/kotlin/com/bumble/appyx/utils/interop/ribs/RibsNode.kt b/utils/interop-ribs/src/androidTest/kotlin/com/bumble/appyx/utils/interop/ribs/RibsNode.kt index 908ef28e7..1e6bfc9c0 100644 --- a/utils/interop-ribs/src/androidTest/kotlin/com/bumble/appyx/utils/interop/ribs/RibsNode.kt +++ b/utils/interop-ribs/src/androidTest/kotlin/com/bumble/appyx/utils/interop/ribs/RibsNode.kt @@ -96,7 +96,7 @@ class RibsNodeRouter( class AppyxNode( nodeContext: NodeContext, private val s: String, -) : com.bumble.appyx.navigation.node.Node( +) : com.bumble.appyx.navigation.node.LeafNode( nodeContext, ) { var shouldInterceptBackPress by mutableStateOf(true) diff --git a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropBuilder.kt b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropBuilder.kt index 914513174..9862edb05 100644 --- a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropBuilder.kt +++ b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropBuilder.kt @@ -9,7 +9,7 @@ import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.node.build import com.bumble.appyx.utils.interop.ribs.InteropNodeImpl.Companion.InteropNodeKey -class InteropBuilder( +class InteropBuilder>( private val nodeFactory: NodeFactory, private val integrationPoint: IntegrationPoint ) : SimpleBuilder>() { diff --git a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropNode.kt b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropNode.kt index 01e1de9be..eb6b59059 100644 --- a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropNode.kt +++ b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropNode.kt @@ -13,7 +13,7 @@ import com.bumble.appyx.utils.customisations.NodeCustomisation import com.bumble.appyx.utils.interop.ribs.InteropNode.Customisation import com.bumble.appyx.utils.interop.ribs.InteropViewImpl.Factory -interface InteropNode : Rib { +interface InteropNode> : Rib { val appyxNode: N class Customisation( @@ -21,7 +21,7 @@ interface InteropNode : Rib { ) : NodeCustomisation } -internal class InteropNodeImpl( +internal class InteropNodeImpl>( buildParams: BuildParams<*>, override val appyxNode: N, private val backPressHandler: InteropBackPressHandler = InteropBackPressHandler(), diff --git a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropView.kt b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropView.kt index 481956d59..408f91d31 100644 --- a/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropView.kt +++ b/utils/interop-ribs/src/main/kotlin/com/bumble/appyx/utils/interop/ribs/InteropView.kt @@ -16,7 +16,7 @@ import com.bumble.appyx.utils.interop.ribs.InteropView.Dependency interface InteropView : RibView { - interface Dependency { + interface Dependency> { val appyxNode: N val onBackPressedDispatcherOwner: OnBackPressedDispatcherOwner } @@ -25,7 +25,7 @@ interface InteropView : RibView { internal class InteropViewImpl private constructor( override val context: Context, lifecycle: Lifecycle, - private val appyxNode: Node, + private val appyxNode: Node<*>, private val onBackPressedDispatcherOwner: OnBackPressedDispatcherOwner, ) : InteropView, ComposeRibView(context, lifecycle) { @@ -38,7 +38,7 @@ internal class InteropViewImpl private constructor( } } - class Factory : ViewFactoryBuilder, InteropView> { + class Factory> : ViewFactoryBuilder, InteropView> { override fun invoke(deps: Dependency): ViewFactory = ViewFactory { InteropViewImpl( diff --git a/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxMaterial3NavNode.kt b/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxMaterial3NavNode.kt index 0a69e58af..8c83bc090 100644 --- a/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxMaterial3NavNode.kt +++ b/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxMaterial3NavNode.kt @@ -41,10 +41,9 @@ import com.bumble.appyx.navigation.integration.LocalScreenSize import com.bumble.appyx.navigation.integration.ScreenSize import com.bumble.appyx.navigation.integration.ScreenSize.WindowSizeClass.COMPACT import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.EmptyParentNodeView +import com.bumble.appyx.navigation.node.EmptyNodeView import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.ParentNode -import com.bumble.appyx.navigation.node.ParentNodeView +import com.bumble.appyx.navigation.node.NodeView @OptIn(ExperimentalMaterial3Api::class) open class AppyxMaterial3NavNode( @@ -69,11 +68,11 @@ open class AppyxMaterial3NavNode( ), visualisation = visualisation ), - view: ParentNodeView = EmptyParentNodeView(), + view: NodeView = EmptyNodeView(), childKeepMode: ChildEntry.KeepMode = Appyx.defaultChildKeepMode, - childAware: ChildAware> = ChildAwareImpl(), + childAware: ChildAware> = ChildAwareImpl(), plugins: List = listOf(), -) : ParentNode( +) : Node( appyxComponent = spotlight, nodeContext = nodeContext, view = view, @@ -82,7 +81,7 @@ open class AppyxMaterial3NavNode( plugins = plugins ) { - override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node = + override fun buildChildNode(navTarget: NavTarget, nodeContext: NodeContext): Node<*> = navTargetResolver .invoke(navTarget) .node diff --git a/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxNavItem.kt b/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxNavItem.kt index f5e948d96..6ab0273d2 100644 --- a/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxNavItem.kt +++ b/utils/material3/src/commonMain/kotlin/com/bumble/appyx/utils/material3/AppyxNavItem.kt @@ -26,7 +26,7 @@ import kotlinx.coroutines.flow.MutableStateFlow class AppyxNavItem( val text: @Composable (isSelected: Boolean) -> Unit, val icon: @Composable (isSelected: Boolean) -> Unit, - val node: (nodeContext: NodeContext) -> Node + val node: (nodeContext: NodeContext) -> Node<*> ) { constructor( text: String, @@ -35,7 +35,7 @@ class AppyxNavItem( badgeText: Flow = MutableStateFlow(null), modifier: Modifier = Modifier, hasScaleAnimation: Boolean = true, - node: (nodeContext: NodeContext) -> Node + node: (nodeContext: NodeContext) -> Node<*> ) : this( text = { Text( diff --git a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxActivityTestRule.kt b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxActivityTestRule.kt index 4de4dc229..781cc8d28 100644 --- a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxActivityTestRule.kt +++ b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxActivityTestRule.kt @@ -14,7 +14,7 @@ import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement -open class AppyxActivityTestRule( +open class AppyxActivityTestRule>( private val launchActivity: Boolean = true, private val composeTestRule: ComposeTestRule = createEmptyComposeRule(), /** Add decorations like custom theme or CompositionLocalProvider. Do not forget to invoke `content()`. */ diff --git a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxViewTestRule.kt b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxViewTestRule.kt index 87041092b..43b5151c6 100644 --- a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxViewTestRule.kt +++ b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/rules/AppyxViewTestRule.kt @@ -10,7 +10,7 @@ import com.bumble.appyx.navigation.node.LocalNode import com.bumble.appyx.navigation.node.NodeView import com.bumble.appyx.navigation.node.ViewFactory import com.bumble.appyx.navigation.node.build -import com.bumble.appyx.utils.testing.ui.utils.DummyParentNode +import com.bumble.appyx.utils.testing.ui.utils.DummyNode import org.junit.rules.TestRule import org.junit.runner.Description import org.junit.runners.model.Statement @@ -48,7 +48,7 @@ open class AppyxViewTestRule( override fun beforeActivityLaunched() { AppyxTestActivity.composableView = { CompositionLocalProvider( - LocalNode provides DummyParentNode().build(), + LocalNode provides DummyNode().build(), ) { view.Content(modifier = Modifier) } diff --git a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyParentNode.kt b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyNode.kt similarity index 76% rename from utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyParentNode.kt rename to utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyNode.kt index af3528992..9e516c306 100644 --- a/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyParentNode.kt +++ b/utils/testing-ui/src/main/kotlin/com/bumble/appyx/utils/testing/ui/utils/DummyNode.kt @@ -2,10 +2,10 @@ package com.bumble.appyx.utils.testing.ui.utils import com.bumble.appyx.interactions.core.model.EmptyAppyxComponent import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.node.node -class DummyParentNode : ParentNode( +class DummyNode : Node( appyxComponent = EmptyAppyxComponent(), nodeContext = NodeContext.root(savedStateMap = null) ) { diff --git a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/NodeTestHelper.kt b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/NodeTestHelper.kt index f59e33394..369183ab0 100644 --- a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/NodeTestHelper.kt +++ b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/NodeTestHelper.kt @@ -4,9 +4,9 @@ import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.node.build -fun N.nodeTestHelper() = NodeTestHelper(this) +fun > N.nodeTestHelper() = NodeTestHelper(this) -open class NodeTestHelper(private val node: N) { +open class NodeTestHelper>(private val node: N) { private val nodeLifecycle get() = node.lifecycle diff --git a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/ParentNodeTestHelper.kt b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/ParentNodeTestHelper.kt index 0ed8593a2..e702df7b0 100644 --- a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/ParentNodeTestHelper.kt +++ b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/helper/ParentNodeTestHelper.kt @@ -2,14 +2,14 @@ package com.bumble.appyx.utils.testing.unit.common.helper import com.bumble.appyx.navigation.children.nodeOrNull import com.bumble.appyx.navigation.lifecycle.Lifecycle -import com.bumble.appyx.navigation.node.ParentNode +import com.bumble.appyx.navigation.node.Node import kotlin.test.assertEquals import kotlin.test.assertNull -fun > N.parentNodeTestHelper() = +fun > N.parentNodeTestHelper() = ParentNodeTestHelper(this) -class ParentNodeTestHelper>( +class ParentNodeTestHelper>( private val node: N ) : NodeTestHelper( node = node diff --git a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropBuilderStub.kt b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropBuilderStub.kt index bb975a9b8..26b37586d 100644 --- a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropBuilderStub.kt +++ b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropBuilderStub.kt @@ -8,16 +8,16 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class InteropBuilderStub

( - val delegate: (NodeContext, P) -> Node = { _, _ -> NodeStub() }, + val delegate: (NodeContext, P) -> Node<*> = { _, _ -> NodeStub() }, ) : Builder

() { - var lastNode: Node? = null + var lastNode: Node<*>? = null private set var lastParam: P? = null private set - override fun build(nodeContext: NodeContext, payload: P): Node = + override fun build(nodeContext: NodeContext, payload: P): Node<*> = delegate(nodeContext, payload).also { lastNode = it lastParam = payload diff --git a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropSimpleBuilderStub.kt b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropSimpleBuilderStub.kt index 8445e80fe..d7c2c6e96 100644 --- a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropSimpleBuilderStub.kt +++ b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/InteropSimpleBuilderStub.kt @@ -8,13 +8,13 @@ import kotlin.test.assertEquals import kotlin.test.assertTrue class InteropSimpleBuilderStub( - val delegate: (NodeContext) -> Node = { _ -> NodeStub() }, + val delegate: (NodeContext) -> Node<*> = { _ -> NodeStub() }, ) : SimpleBuilder() { - var lastNode: Node? = null + var lastNode: Node<*>? = null private set - override fun build(nodeContext: NodeContext): Node = + override fun build(nodeContext: NodeContext): Node<*> = delegate(nodeContext).also { lastNode = it } diff --git a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/NodeStub.kt b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/NodeStub.kt index 320b846a2..6c01ab39f 100644 --- a/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/NodeStub.kt +++ b/utils/testing-unit-common/src/main/kotlin/com/bumble/appyx/utils/testing/unit/common/util/NodeStub.kt @@ -1,17 +1,10 @@ package com.bumble.appyx.utils.testing.unit.common.util -import com.bumble.appyx.interactions.core.plugin.Plugin import com.bumble.appyx.navigation.modality.NodeContext -import com.bumble.appyx.navigation.node.EmptyNodeView -import com.bumble.appyx.navigation.node.Node -import com.bumble.appyx.navigation.node.NodeView +import com.bumble.appyx.navigation.node.LeafNode class NodeStub( nodeContext: NodeContext = NodeContext.root(savedStateMap = null), - plugins: List = emptyList(), - view: NodeView = EmptyNodeView, -) : Node( +) : LeafNode( nodeContext = nodeContext, - plugins = plugins, - view = view, )