diff --git a/CHANGELOG.md b/CHANGELOG.md index 15ce8011b..b0e547d7a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## Pending changes -– +- [#551](https://github.com/bumble-tech/appyx/pull/551) - Multiplatform Navigation module --- @@ -11,7 +11,8 @@ ### Added - [#539](https://github.com/bumble-tech/appyx/pull/539) – Position alignment -- [#538](https://github.com/bumble-tech/appyx/pull/538) – Availability to observe MotionProperties from children UI +- [#538](https://github.com/bumble-tech/appyx/pull/538) – Availability to observe MotionProperties + from children UI ### Fixed diff --git a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/Cards.kt b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/Cards.kt index 2a4cd6d6f..e042a1f8c 100644 --- a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/Cards.kt +++ b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/Cards.kt @@ -2,12 +2,12 @@ package com.bumble.appyx.components.experimental.cards import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.spring -import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.interactions.core.model.BaseAppyxComponent import com.bumble.appyx.interactions.core.ui.MotionController import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory +import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig open class Cards( model: CardsModel, diff --git a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/CardsModel.kt b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/CardsModel.kt index 261bef48b..2ede13eff 100644 --- a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/CardsModel.kt +++ b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/CardsModel.kt @@ -3,13 +3,13 @@ package com.bumble.appyx.components.experimental.cards import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.InvisibleCard.Queued import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.VisibleCard.BottomCard import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.VisibleCard.TopCard -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue class CardsModel( initialItems: List = listOf(), diff --git a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VoteLike.kt b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VoteLike.kt index bfaafffd4..98de72361 100644 --- a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VoteLike.kt +++ b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VoteLike.kt @@ -2,18 +2,18 @@ package com.bumble.appyx.components.experimental.cards.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.experimental.cards.Cards -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.components.experimental.cards.CardsModel import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.InvisibleCard.VotedCard.VOTED_CARD_STATE.LIKED import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.VisibleCard.BottomCard import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.VisibleCard.TopCard import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.VisibleCard.TopCard.TOP_CARD_STATE.STANDARD +import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class VoteLike( override var mode: Operation.Mode = Operation.Mode.KEYFRAME -): TopCardOperation() { +) : TopCardOperation() { override fun createTargetState(fromState: CardsModel.State): CardsModel.State { val votedCards = fromState.votedCards diff --git a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VotePass.kt b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VotePass.kt index 27fdbc17c..e1025e46d 100644 --- a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VotePass.kt +++ b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/operation/VotePass.kt @@ -2,10 +2,10 @@ package com.bumble.appyx.components.experimental.cards.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.experimental.cards.Cards -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.components.experimental.cards.CardsModel import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.InvisibleCard.VotedCard.VOTED_CARD_STATE.PASSED +import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class VotePass( diff --git a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/ui/CardsMotionController.kt b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/ui/CardsMotionController.kt index 6f6cadcaa..760bff907 100644 --- a/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/ui/CardsMotionController.kt +++ b/appyx-components/experimental/cards/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/cards/ui/CardsMotionController.kt @@ -9,7 +9,6 @@ import com.bumble.appyx.components.experimental.cards.CardsModel import com.bumble.appyx.components.experimental.cards.CardsModel.State.Card.InvisibleCard.VotedCard.VOTED_CARD_STATE.LIKED import com.bumble.appyx.components.experimental.cards.operation.VoteLike import com.bumble.appyx.components.experimental.cards.operation.VotePass -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.gesture.Drag @@ -23,6 +22,7 @@ import com.bumble.appyx.interactions.core.ui.property.impl.Scale import com.bumble.appyx.interactions.core.ui.property.impl.ZIndex import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState import com.bumble.appyx.transitionmodel.BaseMotionController +import com.bumble.appyx.utils.multiplatform.AppyxLogger class CardsMotionController( uiContext: UiContext, diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ModalModel.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ModalModel.kt index eb56976bb..544936e5a 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ModalModel.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ModalModel.kt @@ -1,12 +1,12 @@ package com.bumble.appyx.components.modal -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.Elements import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class ModalModel( initialElements: List, diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Add.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Add.kt index 10235f894..c4443d364 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Add.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Add.kt @@ -3,11 +3,11 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue @Parcelize class Add( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Destroy.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Destroy.kt index b9960246c..7096789ed 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Destroy.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Destroy.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Destroy( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Dismiss.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Dismiss.kt index 689c6d4bb..8d2506c9a 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Dismiss.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Dismiss.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Dismiss( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/FullScreen.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/FullScreen.kt index 446243f7b..f417aeb5f 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/FullScreen.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/FullScreen.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class FullScreen( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Revert.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Revert.kt index e686d1c10..072787daa 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Revert.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Revert.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Revert( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Show.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Show.kt index 35d061792..e521fa606 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Show.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/operation/Show.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.modal.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.modal.Modal import com.bumble.appyx.components.modal.ModalModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Show( diff --git a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ui/ModalMotionController.kt b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ui/ModalMotionController.kt index 78c2d66d8..724d26612 100644 --- a/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ui/ModalMotionController.kt +++ b/appyx-components/experimental/modal/common/src/commonMain/kotlin/com/bumble/appyx/components/modal/ui/ModalMotionController.kt @@ -10,10 +10,10 @@ import com.bumble.appyx.components.modal.operation.FullScreen import com.bumble.appyx.components.modal.operation.Revert import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext -import com.bumble.appyx.interactions.core.ui.gesture.Gesture -import com.bumble.appyx.interactions.core.ui.gesture.dragVerticalDirection import com.bumble.appyx.interactions.core.ui.gesture.Drag +import com.bumble.appyx.interactions.core.ui.gesture.Gesture import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory +import com.bumble.appyx.interactions.core.ui.gesture.dragVerticalDirection import com.bumble.appyx.interactions.core.ui.property.impl.Height import com.bumble.appyx.interactions.core.ui.property.impl.Position import com.bumble.appyx.interactions.core.ui.property.impl.RoundedCorners diff --git a/appyx-components/experimental/promoter/android/src/main/kotlin/com/bumble/appyx/components/experimental/promoter/android/PromoterExperiment.kt b/appyx-components/experimental/promoter/android/src/main/kotlin/com/bumble/appyx/components/experimental/promoter/android/PromoterExperiment.kt index 67f8ca0c6..141c61891 100644 --- a/appyx-components/experimental/promoter/android/src/main/kotlin/com/bumble/appyx/components/experimental/promoter/android/PromoterExperiment.kt +++ b/appyx-components/experimental/promoter/android/src/main/kotlin/com/bumble/appyx/components/experimental/promoter/android/PromoterExperiment.kt @@ -22,13 +22,13 @@ import com.bumble.appyx.components.experimental.promoter.Promoter import com.bumble.appyx.components.experimental.promoter.PromoterModel import com.bumble.appyx.components.experimental.promoter.operation.addFirst import com.bumble.appyx.components.experimental.promoter.ui.PromoterMotionController -import com.bumble.appyx.interactions.sample.android.SampleChildren -import com.bumble.appyx.interactions.sample.android.Element import com.bumble.appyx.interactions.sample.InteractionTarget import com.bumble.appyx.interactions.sample.InteractionTarget.Child1 import com.bumble.appyx.interactions.sample.InteractionTarget.Child2 import com.bumble.appyx.interactions.sample.InteractionTarget.Child3 import com.bumble.appyx.interactions.sample.InteractionTarget.Child4 +import com.bumble.appyx.interactions.sample.android.Element +import com.bumble.appyx.interactions.sample.android.SampleChildren @ExperimentalMaterialApi diff --git a/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/PromoterModel.kt b/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/PromoterModel.kt index 12b91b3f6..3adbfda13 100644 --- a/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/PromoterModel.kt +++ b/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/PromoterModel.kt @@ -1,11 +1,11 @@ package com.bumble.appyx.components.experimental.promoter import com.bumble.appyx.components.experimental.promoter.PromoterModel.State.ElementState.DESTROYED -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class PromoterModel( savedStateMap: SavedStateMap?, diff --git a/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/operation/AddFirst.kt b/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/operation/AddFirst.kt index 1add8c253..87dc5ee60 100644 --- a/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/operation/AddFirst.kt +++ b/appyx-components/experimental/promoter/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/promoter/operation/AddFirst.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.components.experimental.promoter.operation import androidx.compose.animation.core.AnimationSpec -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue +import com.bumble.appyx.components.experimental.promoter.Promoter +import com.bumble.appyx.components.experimental.promoter.PromoterModel import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation -import com.bumble.appyx.components.experimental.promoter.Promoter -import com.bumble.appyx.components.experimental.promoter.PromoterModel +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue @Parcelize data class AddFirst( diff --git a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15.kt b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15.kt index a1b68aee9..f74a268f9 100644 --- a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15.kt +++ b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15.kt @@ -17,11 +17,11 @@ class Puzzle15( scope: CoroutineScope = CoroutineScope(SupervisorJob() + Dispatchers.Main), model: Puzzle15Model = Puzzle15Model(savedStateMap = null), motionController: (UiContext) -> MotionController = { - Puzzle15MotionController( - it - ) + Puzzle15MotionController(it) + }, + gestureFactory: (TransitionBounds) -> GestureFactory = { bounds -> + Puzzle15MotionController.Gestures(bounds) }, - gestureFactory: (TransitionBounds) -> GestureFactory = { bounds -> Puzzle15MotionController.Gestures(bounds) }, animationSpec: AnimationSpec = spring(), animateSettle: Boolean = false, ) : BaseAppyxComponent( diff --git a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15Model.kt b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15Model.kt index 9f9136c88..44bd1d522 100644 --- a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15Model.kt +++ b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/Puzzle15Model.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.components.experimental.puzzle15 import com.bumble.appyx.components.experimental.puzzle15.Puzzle15Model.Tile -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue class Puzzle15Model( savedStateMap: SavedStateMap? diff --git a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Shuffle.kt b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Shuffle.kt index e7d786e01..08cdebe08 100644 --- a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Shuffle.kt +++ b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Shuffle.kt @@ -1,9 +1,9 @@ package com.bumble.appyx.components.experimental.puzzle15.operation import com.bumble.appyx.components.experimental.puzzle15.Puzzle15Model -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Shuffle( diff --git a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Swap.kt b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Swap.kt index 9ac5ef192..58c50616a 100644 --- a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Swap.kt +++ b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/operation/Swap.kt @@ -2,10 +2,10 @@ package com.bumble.appyx.components.experimental.puzzle15.operation import com.bumble.appyx.components.experimental.puzzle15.Puzzle15Model import com.bumble.appyx.components.experimental.puzzle15.Puzzle15Model.Tile -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Swap( diff --git a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/ui/Puzzle15Ui.kt b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/ui/Puzzle15Ui.kt index ba6d83d98..9401ef519 100644 --- a/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/ui/Puzzle15Ui.kt +++ b/appyx-components/experimental/puzzle15/common/src/commonMain/kotlin/com/bumble/appyx/components/experimental/puzzle15/ui/Puzzle15Ui.kt @@ -159,25 +159,37 @@ fun Puzzle15Ui( onClick = { puzzle15.operation(Swap(direction = DOWN)) }, colors = ButtonDefaults.buttonColors(backgroundColor = accentColor), ) { - Icon(imageVector = Icons.Default.KeyboardArrowUp, contentDescription = "Move Up") + Icon( + imageVector = Icons.Default.KeyboardArrowUp, + contentDescription = "Move Up" + ) } Button( onClick = { puzzle15.operation(Swap(direction = LEFT)) }, colors = ButtonDefaults.buttonColors(backgroundColor = accentColor), ) { - Icon(imageVector = Icons.Default.KeyboardArrowRight, contentDescription = "Move Right") + Icon( + imageVector = Icons.Default.KeyboardArrowRight, + contentDescription = "Move Right" + ) } Button( onClick = { puzzle15.operation(Swap(direction = UP)) }, colors = ButtonDefaults.buttonColors(backgroundColor = accentColor), ) { - Icon(imageVector = Icons.Default.KeyboardArrowDown, contentDescription = "Move Down") + Icon( + imageVector = Icons.Default.KeyboardArrowDown, + contentDescription = "Move Down" + ) } Button( onClick = { puzzle15.operation(Swap(direction = RIGHT)) }, colors = ButtonDefaults.buttonColors(backgroundColor = accentColor), ) { - Icon(imageVector = Icons.Default.KeyboardArrowLeft, contentDescription = "Move Left") + Icon( + imageVector = Icons.Default.KeyboardArrowLeft, + contentDescription = "Move Left" + ) } } Button( diff --git a/appyx-components/experimental/puzzle15/web/src/jsMain/resources/index.html b/appyx-components/experimental/puzzle15/web/src/jsMain/resources/index.html index f125eb4ba..5d3cb39cc 100644 --- a/appyx-components/experimental/puzzle15/web/src/jsMain/resources/index.html +++ b/appyx-components/experimental/puzzle15/web/src/jsMain/resources/index.html @@ -10,6 +10,6 @@
- + diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveExperiment.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveExperiment.kt index 323108dea..118dda48c 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveExperiment.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveExperiment.kt @@ -162,7 +162,10 @@ fun TestDriveUi( modifier = Modifier .size(60.dp) .align(targetUiState.position.value.alignment) - .offset(targetUiState.position.value.offset.x, targetUiState.position.value.offset.y) + .offset( + targetUiState.position.value.offset.x, + targetUiState.position.value.offset.y + ) .border(2.dp, targetUiState.backgroundColor.value) .semantics { contentDescription = TEST_DRIVE_EXPERIMENT_TEST_HELPER diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveModel.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveModel.kt index c32304567..baced4d6f 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveModel.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/TestDriveModel.kt @@ -1,12 +1,12 @@ package com.bumble.appyx.components.internal.testdrive import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class TestDriveModel( val element: InteractionTarget, diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/MoveTo.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/MoveTo.kt index 2abd97b3e..0ea18cd29 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/MoveTo.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/MoveTo.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.internal.testdrive.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.internal.testdrive.TestDrive import com.bumble.appyx.components.internal.testdrive.TestDriveModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize data class MoveTo( diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/Next.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/Next.kt index afecbd8b8..fc7fac039 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/Next.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/operation/Next.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.internal.testdrive.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.internal.testdrive.TestDrive import com.bumble.appyx.components.internal.testdrive.TestDriveModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize data class Next( diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/rotation/TestDriveRotationMotionController.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/rotation/TestDriveRotationMotionController.kt index 5f9c21203..452f9cbc0 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/rotation/TestDriveRotationMotionController.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/rotation/TestDriveRotationMotionController.kt @@ -1,7 +1,6 @@ package com.bumble.appyx.components.internal.testdrive.ui.rotation import androidx.compose.animation.core.SpringSpec -import com.bumble.appyx.interactions.core.ui.property.impl.Position.Alignment import com.bumble.appyx.components.internal.testdrive.TestDriveModel import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.A import com.bumble.appyx.components.internal.testdrive.TestDriveModel.State.ElementState.B @@ -11,14 +10,15 @@ import com.bumble.appyx.components.internal.testdrive.ui.md_light_blue_500 import com.bumble.appyx.components.internal.testdrive.ui.md_light_green_500 import com.bumble.appyx.components.internal.testdrive.ui.md_red_500 import com.bumble.appyx.components.internal.testdrive.ui.md_yellow_500 -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.helper.DefaultAnimationSpec import com.bumble.appyx.interactions.core.ui.property.impl.BackgroundColor import com.bumble.appyx.interactions.core.ui.property.impl.Position +import com.bumble.appyx.interactions.core.ui.property.impl.Position.Alignment import com.bumble.appyx.interactions.core.ui.property.impl.RotationZ import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState import com.bumble.appyx.transitionmodel.BaseMotionController +import com.bumble.appyx.utils.multiplatform.AppyxLogger class TestDriveRotationMotionController( uiContext: UiContext, diff --git a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/simple/TestDriveSimpleMotionController.kt b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/simple/TestDriveSimpleMotionController.kt index 22f859918..8a0d42743 100644 --- a/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/simple/TestDriveSimpleMotionController.kt +++ b/appyx-components/internal/test-drive/common/src/commonMain/kotlin/com/bumble/appyx/components/internal/testdrive/ui/simple/TestDriveSimpleMotionController.kt @@ -1,7 +1,6 @@ package com.bumble.appyx.components.internal.testdrive.ui.simple import androidx.compose.animation.core.SpringSpec -import com.bumble.appyx.interactions.core.ui.property.impl.Position.Alignment import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density import com.bumble.appyx.components.internal.testdrive.TestDriveModel @@ -14,7 +13,6 @@ import com.bumble.appyx.components.internal.testdrive.ui.md_light_blue_500 import com.bumble.appyx.components.internal.testdrive.ui.md_light_green_500 import com.bumble.appyx.components.internal.testdrive.ui.md_red_500 import com.bumble.appyx.components.internal.testdrive.ui.md_yellow_500 -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.gesture.Drag.Direction8.DOWN @@ -31,8 +29,10 @@ import com.bumble.appyx.interactions.core.ui.gesture.dragDirection8 import com.bumble.appyx.interactions.core.ui.helper.DefaultAnimationSpec import com.bumble.appyx.interactions.core.ui.property.impl.BackgroundColor import com.bumble.appyx.interactions.core.ui.property.impl.Position +import com.bumble.appyx.interactions.core.ui.property.impl.Position.Alignment import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState import com.bumble.appyx.transitionmodel.BaseMotionController +import com.bumble.appyx.utils.multiplatform.AppyxLogger class TestDriveSimpleMotionController( uiContext: UiContext, diff --git a/appyx-components/stable/backstack/android/src/androidTest/kotlin/com/bumble/appyx/components/backstack/ui/parallax/BackStackParallaxTest.kt b/appyx-components/stable/backstack/android/src/androidTest/kotlin/com/bumble/appyx/components/backstack/ui/parallax/BackStackParallaxTest.kt index 282a7b974..93f6cc992 100644 --- a/appyx-components/stable/backstack/android/src/androidTest/kotlin/com/bumble/appyx/components/backstack/ui/parallax/BackStackParallaxTest.kt +++ b/appyx-components/stable/backstack/android/src/androidTest/kotlin/com/bumble/appyx/components/backstack/ui/parallax/BackStackParallaxTest.kt @@ -48,7 +48,11 @@ class BackStackParallaxTest { ) backStack = BackStack( model = backStackModel, - motionController = { buildContext -> BackStackParallax(buildContext).also { motionController = it } }, + motionController = { buildContext -> + BackStackParallax(buildContext).also { + motionController = it + } + }, scope = CoroutineScope(Dispatchers.Unconfined), ) } 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 c5f75a7ed..ae2c2a27b 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 @@ -21,7 +21,8 @@ class BackStack( animationSpec: AnimationSpec = spring(), gestureFactory: (TransitionBounds) -> GestureFactory> = { GestureFactory.Noop() }, gestureSettleConfig: GestureSettleConfig = GestureSettleConfig(), - backPressStrategy: BackPressHandlerStrategy> = PopBackstackStrategy(scope), + backPressStrategy: BackPressHandlerStrategy> = + PopBackstackStrategy(scope), disableAnimations: Boolean = false, isDebug: Boolean = false ) : BaseAppyxComponent>( diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStackModel.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStackModel.kt index 16275ec5c..c3e9fe47e 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStackModel.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/BackStackModel.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.components.backstack import com.bumble.appyx.components.backstack.BackStackModel.State -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.Elements import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class BackStackModel( initialTargets: List, @@ -46,6 +46,7 @@ class BackStackModel( initialTargets = listOf(initialTarget), savedStateMap = savedStateMap ) + override fun State.availableElements(): Set> = (created + active + stashed + destroyed).toSet() diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/NewRoot.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/NewRoot.kt index 7c274646f..5ff848533 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/NewRoot.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/NewRoot.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.components.backstack.operation import androidx.compose.animation.core.AnimationSpec -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.BackStackModel +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue /** * Operation: diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Pop.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Pop.kt index 5e0dec539..afa263e96 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Pop.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Pop.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.backstack.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.backstack.BackStack import com.bumble.appyx.components.backstack.BackStackModel.State -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize /** * Operation: diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Push.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Push.kt index 3419f671d..59772455a 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Push.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Push.kt @@ -3,11 +3,11 @@ package com.bumble.appyx.components.backstack.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.backstack.BackStack import com.bumble.appyx.components.backstack.BackStackModel -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue /** * Operation: diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Replace.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Replace.kt index e3aac3d63..09767bb84 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Replace.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/operation/Replace.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.components.backstack.operation import androidx.compose.animation.core.AnimationSpec -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue +import com.bumble.appyx.components.backstack.BackStack +import com.bumble.appyx.components.backstack.BackStackModel.State import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation -import com.bumble.appyx.components.backstack.BackStack -import com.bumble.appyx.components.backstack.BackStackModel.State +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue /** diff --git a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/ui/parallax/TargetUiState.kt b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/ui/parallax/TargetUiState.kt index 04e470dc9..34d7d84d7 100644 --- a/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/ui/parallax/TargetUiState.kt +++ b/appyx-components/stable/backstack/common/src/commonMain/kotlin/com/bumble/appyx/components/backstack/ui/parallax/TargetUiState.kt @@ -6,7 +6,6 @@ import com.bumble.appyx.interactions.core.ui.property.impl.Alpha import com.bumble.appyx.interactions.core.ui.property.impl.ColorOverlay import com.bumble.appyx.interactions.core.ui.property.impl.Position import com.bumble.appyx.interactions.core.ui.property.impl.Shadow -import com.bumble.appyx.interactions.core.ui.property.impl.ZIndex import com.bumble.appyx.interactions.core.ui.state.MutableUiStateSpecs @Suppress("unused", "MemberVisibilityCanBePrivate") diff --git a/appyx-components/stable/spotlight/android/src/androidTest/kotlin/com/bumble/appyx/components/spotlight/android/utils/SpotlightUtils.kt b/appyx-components/stable/spotlight/android/src/androidTest/kotlin/com/bumble/appyx/components/spotlight/android/utils/SpotlightUtils.kt index d9506d36a..d59906316 100644 --- a/appyx-components/stable/spotlight/android/src/androidTest/kotlin/com/bumble/appyx/components/spotlight/android/utils/SpotlightUtils.kt +++ b/appyx-components/stable/spotlight/android/src/androidTest/kotlin/com/bumble/appyx/components/spotlight/android/utils/SpotlightUtils.kt @@ -16,12 +16,12 @@ 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.ui.slider.SpotlightSlider -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.ui.helper.AppyxComponentSetup -import com.bumble.appyx.interactions.sample.android.SampleChildren -import com.bumble.appyx.interactions.sample.android.Element import com.bumble.appyx.interactions.sample.InteractionTarget +import com.bumble.appyx.interactions.sample.android.Element +import com.bumble.appyx.interactions.sample.android.SampleChildren import com.bumble.appyx.interactions.theme.appyx_dark +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers 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 22afa802d..417eb4012 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 @@ -2,12 +2,12 @@ package com.bumble.appyx.components.spotlight import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.spring -import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.interactions.core.model.BaseAppyxComponent import com.bumble.appyx.interactions.core.ui.MotionController import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory +import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.SupervisorJob diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/SpotlightModel.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/SpotlightModel.kt index 649b16088..7d87a5707 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/SpotlightModel.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/SpotlightModel.kt @@ -3,13 +3,13 @@ package com.bumble.appyx.components.spotlight import com.bumble.appyx.components.spotlight.SpotlightModel.State import com.bumble.appyx.components.spotlight.SpotlightModel.State.ElementState.DESTROYED import com.bumble.appyx.components.spotlight.SpotlightModel.State.ElementState.STANDARD -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue class SpotlightModel( items: List, diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Activate.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Activate.kt index dfd0693db..1a31c70ab 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Activate.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Activate.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.spotlight.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.spotlight.Spotlight import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Activate( diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/First.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/First.kt index 13bac5800..6e3d68c8f 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/First.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/First.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.spotlight.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.spotlight.Spotlight import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Last.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Last.kt index 160c1d89f..ec5ec1ca4 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Last.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Last.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.spotlight.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.spotlight.Spotlight import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Next.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Next.kt index e8f624c76..3b99ce926 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Next.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Next.kt @@ -1,11 +1,11 @@ package com.bumble.appyx.components.spotlight.operation import androidx.compose.animation.core.AnimationSpec -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.core.model.transition.BaseOperation -import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.components.spotlight.Spotlight import com.bumble.appyx.components.spotlight.SpotlightModel +import com.bumble.appyx.interactions.core.model.transition.BaseOperation +import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize class Next( diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Previous.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Previous.kt index 5a2afdd95..d6ca73968 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Previous.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/Previous.kt @@ -3,9 +3,9 @@ package com.bumble.appyx.components.spotlight.operation import androidx.compose.animation.core.AnimationSpec import com.bumble.appyx.components.spotlight.Spotlight import com.bumble.appyx.components.spotlight.SpotlightModel -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize @Parcelize diff --git a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/UpdateElements.kt b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/UpdateElements.kt index d2b4479c0..bf0244898 100644 --- a/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/UpdateElements.kt +++ b/appyx-components/stable/spotlight/common/src/commonMain/kotlin/com/bumble/appyx/components/spotlight/operation/UpdateElements.kt @@ -7,12 +7,12 @@ import com.bumble.appyx.components.spotlight.SpotlightModel.State.ElementState.C import com.bumble.appyx.components.spotlight.SpotlightModel.State.ElementState.DESTROYED import com.bumble.appyx.components.spotlight.SpotlightModel.State.ElementState.STANDARD import com.bumble.appyx.components.spotlight.SpotlightModel.State.Position -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue import kotlin.math.max @Parcelize diff --git a/appyx-interactions/common/build.gradle.kts b/appyx-interactions/common/build.gradle.kts index dfccb580d..3d63d4526 100644 --- a/appyx-interactions/common/build.gradle.kts +++ b/appyx-interactions/common/build.gradle.kts @@ -27,6 +27,7 @@ kotlin { api(compose.runtime) api(compose.foundation) api(compose.material) + api(project(":utils:multiplatform")) implementation(libs.kotlinx.serialization.json) } } diff --git a/appyx-interactions/common/src/androidMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/androidMain/kotlin/com/bumble/appyx/interactions/platform.kt index 4a7eb0600..2c93e9884 100644 --- a/appyx-interactions/common/src/androidMain/kotlin/com/bumble/appyx/interactions/platform.kt +++ b/appyx-interactions/common/src/androidMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -3,9 +3,3 @@ package com.bumble.appyx.interactions actual fun getPlatformName(): String { return "Android" } - -actual typealias Parcelize = kotlinx.parcelize.Parcelize - -actual typealias Parcelable = android.os.Parcelable - -actual typealias RawValue = kotlinx.parcelize.RawValue diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/Element.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/Element.kt index 0e9dd34e5..2bc89c617 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/Element.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/Element.kt @@ -2,10 +2,10 @@ package com.bumble.appyx.interactions.core import androidx.compose.runtime.Immutable -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.UUID +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue @Parcelize @Immutable 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 805ffa570..0b25986cf 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 @@ -4,7 +4,6 @@ import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.SpringSpec import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density -import com.bumble.appyx.interactions.AppyxLogger 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 @@ -28,6 +27,7 @@ import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.interactions.core.ui.helper.DefaultAnimationSpec import com.bumble.appyx.interactions.core.ui.helper.DisableAnimations import com.bumble.appyx.interactions.core.ui.output.ElementUiModel +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/AnimatedProgressController.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/AnimatedProgressController.kt index 7986c54fe..b0b1ea2aa 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/AnimatedProgressController.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/AnimatedProgressController.kt @@ -3,12 +3,12 @@ package com.bumble.appyx.interactions.core.model.progress import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.AnimationSpec import androidx.compose.animation.core.spring -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.model.transition.Keyframes import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.interactions.core.model.transition.TransitionModel import com.bumble.appyx.interactions.core.model.transition.TransitionModel.SettleDirection.COMPLETE import com.bumble.appyx.interactions.core.model.transition.TransitionModel.SettleDirection.REVERT +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch 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 index d643bc5f1..58a6390ef 100644 --- 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 @@ -4,7 +4,6 @@ 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 -import kotlinx.coroutines.CoroutineScope // FIXME class DebugProgressInputSource( diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DragProgressController.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DragProgressController.kt index 59159c051..186723f6e 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DragProgressController.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/progress/DragProgressController.kt @@ -3,7 +3,6 @@ package com.bumble.appyx.interactions.core.model.progress import androidx.compose.animation.core.AnimationSpec import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.model.transition.Keyframes import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.KEYFRAME import com.bumble.appyx.interactions.core.model.transition.TransitionModel @@ -11,6 +10,7 @@ import com.bumble.appyx.interactions.core.model.transition.TransitionModel.Settl import com.bumble.appyx.interactions.core.model.transition.TransitionModel.SettleDirection.REVERT import com.bumble.appyx.interactions.core.ui.gesture.Gesture import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory +import com.bumble.appyx.utils.multiplatform.AppyxLogger internal class DragProgressController( private val model: TransitionModel, diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/BaseTransitionModel.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/BaseTransitionModel.kt index ad2efd53d..fc66fb86c 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/BaseTransitionModel.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/BaseTransitionModel.kt @@ -1,13 +1,15 @@ package com.bumble.appyx.interactions.core.model.transition -import com.bumble.appyx.interactions.AppyxLogger -import com.bumble.appyx.interactions.Parcelable import com.bumble.appyx.interactions.core.Element -import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.* +import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.IMMEDIATE +import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.IMPOSED +import com.bumble.appyx.interactions.core.model.transition.Operation.Mode.KEYFRAME import com.bumble.appyx.interactions.core.model.transition.TransitionModel.Output import com.bumble.appyx.interactions.core.model.transition.TransitionModel.SettleDirection import com.bumble.appyx.interactions.core.state.MutableSavedStateMap import com.bumble.appyx.interactions.core.state.SavedStateMap +import com.bumble.appyx.utils.multiplatform.AppyxLogger +import com.bumble.appyx.utils.multiplatform.Parcelable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Keyframes.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Keyframes.kt index 09c218930..09ea98d8c 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Keyframes.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Keyframes.kt @@ -1,6 +1,6 @@ package com.bumble.appyx.interactions.core.model.transition -import com.bumble.appyx.interactions.AppyxLogger +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.distinctUntilChanged diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Operation.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Operation.kt index f2b86a90e..a3bf7d1ac 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Operation.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/model/transition/Operation.kt @@ -1,7 +1,7 @@ package com.bumble.appyx.interactions.core.model.transition -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize interface Operation : Parcelable { diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/context/TransitionBounds.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/context/TransitionBounds.kt index fdc36b0c1..55cd107de 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/context/TransitionBounds.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/context/TransitionBounds.kt @@ -19,4 +19,4 @@ data class TransitionBounds( val screenHeightDp: Dp = with(density) { screenHeightPx.toDp() } } -val zeroSizeTransitionBounds = TransitionBounds(Density(0f), 0, 0, 0, 0) +val zeroSizeTransitionBounds = TransitionBounds(Density(0f), 0, 0, 0, 0) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/MotionProperty.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/MotionProperty.kt index f0f87cd67..027d21be9 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/MotionProperty.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/MotionProperty.kt @@ -12,9 +12,9 @@ import androidx.compose.animation.core.LinearEasing import androidx.compose.animation.core.SpringSpec import androidx.compose.animation.core.spring import androidx.compose.ui.Modifier -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.SystemClock import com.bumble.appyx.interactions.core.ui.context.UiContext +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/impl/Position.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/impl/Position.kt index 40276c35f..0bb7cf807 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/impl/Position.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/ui/property/impl/Position.kt @@ -40,20 +40,28 @@ class Position( @Stable val TopStart = BiasAlignment(-1f, -1f) + @Stable val TopCenter = BiasAlignment(0f, -1f) + @Stable val TopEnd = BiasAlignment(1f, -1f) + @Stable val CenterStart = BiasAlignment(-1f, 0f) + @Stable val Center = BiasAlignment(0f, 0f) + @Stable val CenterEnd = BiasAlignment(1f, 0f) + @Stable val BottomStart = BiasAlignment(-1f, 1f) + @Stable val BottomCenter = BiasAlignment(0f, 1f) + @Stable val BottomEnd = BiasAlignment(1f, 1f) } diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/PermanentModel.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/PermanentModel.kt index a7c369128..4e61e6c56 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/PermanentModel.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/PermanentModel.kt @@ -1,13 +1,13 @@ package com.bumble.appyx.interactions.permanent -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.Elements import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.state.SavedStateMap import com.bumble.appyx.interactions.permanent.PermanentModel.State +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class PermanentModel( savedStateMap: SavedStateMap?, diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/operation/AddUnique.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/operation/AddUnique.kt index 72ded1213..4928a845b 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/operation/AddUnique.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/permanent/operation/AddUnique.kt @@ -1,12 +1,12 @@ package com.bumble.appyx.interactions.permanent.operation -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.asElement import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.interactions.permanent.PermanentAppyxComponent import com.bumble.appyx.interactions.permanent.PermanentModel +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue @Parcelize data class AddUnique( diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/platform.kt index 7e1a1e822..65a503653 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/platform.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -1,13 +1,3 @@ package com.bumble.appyx.interactions expect fun getPlatformName(): String - -@Target(AnnotationTarget.CLASS) -@Retention(AnnotationRetention.BINARY) -expect annotation class Parcelize() - -@Target(AnnotationTarget.TYPE) -@Retention(AnnotationRetention.BINARY) -expect annotation class RawValue() - -expect interface Parcelable diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/transitionmodel/BaseMotionController.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/transitionmodel/BaseMotionController.kt index 4b4901526..6f5efe594 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/transitionmodel/BaseMotionController.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/transitionmodel/BaseMotionController.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import com.bumble.appyx.combineState -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.model.transition.Segment import com.bumble.appyx.interactions.core.model.transition.Update @@ -20,6 +19,7 @@ import com.bumble.appyx.interactions.core.ui.output.ElementUiModel import com.bumble.appyx.interactions.core.ui.property.impl.GenericFloatProperty import com.bumble.appyx.interactions.core.ui.state.BaseMutableUiState import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState +import com.bumble.appyx.utils.multiplatform.AppyxLogger import com.bumble.appyx.withPrevious import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableSharedFlow diff --git a/appyx-interactions/common/src/commonTest/kotlin/com/bumble/appyx/interactions/core/TestTransitionModel.kt b/appyx-interactions/common/src/commonTest/kotlin/com/bumble/appyx/interactions/core/TestTransitionModel.kt index edf5cac0b..3dbe650e0 100644 --- a/appyx-interactions/common/src/commonTest/kotlin/com/bumble/appyx/interactions/core/TestTransitionModel.kt +++ b/appyx-interactions/common/src/commonTest/kotlin/com/bumble/appyx/interactions/core/TestTransitionModel.kt @@ -2,15 +2,15 @@ package com.bumble.appyx.interactions.core import androidx.compose.ui.geometry.Offset import androidx.compose.ui.unit.Density -import com.bumble.appyx.interactions.Parcelable -import com.bumble.appyx.interactions.Parcelize -import com.bumble.appyx.interactions.RawValue import com.bumble.appyx.interactions.core.TestTransitionModel.State import com.bumble.appyx.interactions.core.model.transition.BaseOperation import com.bumble.appyx.interactions.core.model.transition.BaseTransitionModel import com.bumble.appyx.interactions.core.model.transition.Operation import com.bumble.appyx.interactions.core.ui.gesture.Gesture import com.bumble.appyx.interactions.core.ui.gesture.GestureFactory +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize +import com.bumble.appyx.utils.multiplatform.RawValue class TestTransitionModel( initialElements: List, diff --git a/appyx-interactions/common/src/desktopMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/desktopMain/kotlin/com/bumble/appyx/interactions/platform.kt index ff714fd38..bfc6087d1 100644 --- a/appyx-interactions/common/src/desktopMain/kotlin/com/bumble/appyx/interactions/platform.kt +++ b/appyx-interactions/common/src/desktopMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -3,10 +3,3 @@ package com.bumble.appyx.interactions actual fun getPlatformName(): String { return "Desktop" } - -actual annotation class Parcelize - -actual interface Parcelable - -@Target(AnnotationTarget.TYPE) -actual annotation class RawValue diff --git a/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt b/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt index 2236aaf2b..b66bfb4e3 100644 --- a/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt +++ b/appyx-interactions/common/src/jsMain/kotlin/com/bumble/appyx/interactions/platform.kt @@ -1,10 +1,3 @@ package com.bumble.appyx.interactions actual fun getPlatformName(): String = "Web" - -actual annotation class Parcelize - -actual interface Parcelable - -@Target(AnnotationTarget.TYPE) -actual annotation class RawValue diff --git a/appyx-navigation/build.gradle.kts b/appyx-navigation/android/build.gradle.kts similarity index 71% rename from appyx-navigation/build.gradle.kts rename to appyx-navigation/android/build.gradle.kts index 3f54886e3..f66f2a732 100644 --- a/appyx-navigation/build.gradle.kts +++ b/appyx-navigation/android/build.gradle.kts @@ -1,16 +1,15 @@ plugins { + id("org.jetbrains.compose") id("com.android.library") - id("kotlin-android") - id("kotlin-parcelize") - id("appyx-publish-android") + kotlin("android") id("appyx-lint") + id("kotlin-parcelize") id("appyx-detekt") } android { - namespace = "com.bumble.appyx.navigation" + namespace = "com.bumble.appyx.navigation.android" compileSdk = libs.versions.androidCompileSdk.get().toInt() - defaultConfig { minSdk = libs.versions.androidMinSdk.get().toInt() targetSdk = libs.versions.androidTargetSdk.get().toInt() @@ -35,22 +34,12 @@ dependencies { val composeBom = platform(libs.compose.bom) api(composeBom) - api(project(":utils:customisations")) - api(project(":appyx-interactions:appyx-interactions")) api(libs.kotlin.coroutines.android) - api(libs.androidx.lifecycle.common) - api(libs.compose.runtime) + api(libs.compose.ui.tooling) - api(libs.compose.ui.ui) - api(libs.androidx.appcompat) implementation(composeBom) - implementation(libs.androidx.activity.compose) implementation(libs.androidx.lifecycle.java8) - implementation(libs.compose.foundation.layout) - - testImplementation(libs.kotlin.coroutines.test) - testImplementation(libs.junit) androidTestImplementation(composeBom) androidTestImplementation(libs.androidx.test.espresso.core) diff --git a/appyx-navigation/android/lint-baseline.xml b/appyx-navigation/android/lint-baseline.xml new file mode 100644 index 000000000..ab8becf45 --- /dev/null +++ b/appyx-navigation/android/lint-baseline.xml @@ -0,0 +1,5 @@ + + + + diff --git a/appyx-navigation/src/androidTest/AndroidManifest.xml b/appyx-navigation/android/src/androidTest/AndroidManifest.xml similarity index 67% rename from appyx-navigation/src/androidTest/AndroidManifest.xml rename to appyx-navigation/android/src/androidTest/AndroidManifest.xml index 307495589..424d9c3de 100644 --- a/appyx-navigation/src/androidTest/AndroidManifest.xml +++ b/appyx-navigation/android/src/androidTest/AndroidManifest.xml @@ -7,10 +7,6 @@ android:name="com.bumble.appyx.navigation.InternalAppyxTestActivity" android:theme="@style/Theme.AppCompat.Light.NoActionBar" /> - - diff --git a/appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt similarity index 81% rename from appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt rename to appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt index 99979c545..e8fd462a5 100644 --- a/appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt +++ b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/AppyxTestScenario.kt @@ -2,12 +2,14 @@ package com.bumble.appyx.navigation import androidx.annotation.WorkerThread import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.test.junit4.ComposeTestRule import androidx.compose.ui.test.junit4.createEmptyComposeRule import androidx.test.core.app.ActivityScenario import com.bumble.appyx.navigation.integration.NodeFactory import com.bumble.appyx.navigation.integration.NodeHost import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.platform.AndroidLifecycle import com.bumble.appyx.utils.testing.ui.rules.AppyxTestActivity import java.util.concurrent.CountDownLatch import java.util.concurrent.TimeUnit @@ -25,13 +27,13 @@ class AppyxTestScenario( AppyxTestActivity.composableView = { activity -> decorator { NodeHost( + lifecycle = AndroidLifecycle(LocalLifecycleOwner.current.lifecycle), integrationPoint = activity.appyxV2IntegrationPoint, - factory = { buildContext -> - node = nodeFactory.create(buildContext) - awaitNode.countDown() - node - }, - ) + ) { buildContext -> + node = nodeFactory.create(buildContext) + awaitNode.countDown() + node + } } } val scenario = ActivityScenario.launch(InternalAppyxTestActivity::class.java) diff --git a/appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/InternalAppyxTestActivity.kt b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/InternalAppyxTestActivity.kt similarity index 100% rename from appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/InternalAppyxTestActivity.kt rename to appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/InternalAppyxTestActivity.kt diff --git a/appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt similarity index 90% rename from appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt rename to appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt index 68da7f109..1a467db55 100644 --- a/appyx-navigation/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt +++ b/appyx-navigation/android/src/androidTest/kotlin/com/bumble/appyx/navigation/node/PermanentChildTest.kt @@ -1,6 +1,5 @@ package com.bumble.appyx.navigation.node -import android.os.Parcelable import androidx.compose.foundation.text.BasicText import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -14,7 +13,8 @@ import com.bumble.appyx.navigation.AppyxTestScenario import com.bumble.appyx.navigation.children.nodeOrNull import com.bumble.appyx.navigation.modality.BuildContext import com.bumble.appyx.navigation.node.PermanentChildTest.TestParentNode.InteractionTarget -import kotlinx.parcelize.Parcelize +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test @@ -59,7 +59,10 @@ class PermanentChildTest { var renderPermanentChild by mutableStateOf(true) - override fun resolve(interactionTarget: InteractionTarget, buildContext: BuildContext): Node = + override fun resolve( + interactionTarget: InteractionTarget, + buildContext: BuildContext + ): Node = node(buildContext) { modifier -> BasicText( text = interactionTarget.toString(), diff --git a/appyx-navigation/common/.gitignore b/appyx-navigation/common/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/appyx-navigation/common/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/appyx-navigation/common/build.gradle.kts b/appyx-navigation/common/build.gradle.kts new file mode 100644 index 000000000..4321ca7fb --- /dev/null +++ b/appyx-navigation/common/build.gradle.kts @@ -0,0 +1,94 @@ +plugins { + kotlin("multiplatform") + id("org.jetbrains.compose") + id("com.android.library") + id("appyx-publish-multiplatform") + id("appyx-detekt") +} + +kotlin { + android { + publishLibraryVariants("release") + } + jvm("desktop") { + compilations.all { + kotlinOptions.jvmTarget = libs.versions.jvmTarget.get() + } + } + js(IR) { + // Adding moduleName as a workaround for this issue: https://youtrack.jetbrains.com/issue/KT-51942 + moduleName = "appyx-navigation-common" + browser() + } + sourceSets { + val commonMain by getting { + dependencies { + api(compose.runtime) + api(compose.foundation) + api(compose.material) + api(project(":utils:multiplatform")) + implementation(libs.kotlinx.serialization.json) + api(project(":utils:customisations")) + api(project(":appyx-interactions:appyx-interactions")) + } + } + val commonTest by getting { + dependencies { + implementation(kotlin("test")) + } + } + val androidMain by getting { + dependencies { + api(libs.androidx.appcompat) + api(libs.androidx.core) + api(libs.compose.runtime) + api(libs.compose.ui.tooling) + + implementation(libs.androidx.activity.compose) + implementation(libs.androidx.lifecycle.java8) + + } + } + val desktopMain by getting { + dependencies { + api(compose.preview) + } + } + val jsMain by getting { + dependencies { + implementation(npm("uuid", libs.versions.uuid.get())) + } + } + } +} + +android { + namespace = "com.bumble.appyx.navigation" + compileSdk = libs.versions.androidCompileSdk.get().toInt() + sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml") + defaultConfig { + minSdk = libs.versions.androidMinSdk.get().toInt() + targetSdk = libs.versions.androidTargetSdk.get().toInt() + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.composeCompiler.get() + } + testOptions { + } + + dependencies { + val composeBom = platform(libs.compose.bom) + + api(composeBom) + + androidTestImplementation(composeBom) + androidTestImplementation(libs.androidx.test.espresso.core) + androidTestImplementation(libs.androidx.test.junit) + androidTestImplementation(libs.compose.ui.test.junit4) + androidTestImplementation(libs.compose.foundation) + androidTestImplementation(project(":utils:testing-ui")) + } +} diff --git a/appyx-navigation/src/main/AndroidManifest.xml b/appyx-navigation/common/src/androidMain/AndroidManifest.xml similarity index 100% rename from appyx-navigation/src/main/AndroidManifest.xml rename to appyx-navigation/common/src/androidMain/AndroidManifest.xml diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/ActivityIntegrationPoint.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/ActivityIntegrationPoint.kt similarity index 74% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/ActivityIntegrationPoint.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/ActivityIntegrationPoint.kt index 8f373f22c..6d0d7e7d6 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/ActivityIntegrationPoint.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/ActivityIntegrationPoint.kt @@ -1,19 +1,20 @@ -package com.bumble.appyx.navigation.integrationpoint +package com.bumble.appyx.navigation.integration import android.app.Activity import android.content.Context import android.content.ContextWrapper import android.content.Intent import android.os.Bundle -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityBoundary -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityStarter -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequestBoundary -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester +import com.bumble.appyx.navigation.integration.activitystarter.ActivityBoundary +import com.bumble.appyx.navigation.integration.activitystarter.ActivityStarter +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequestBoundary +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequester +import com.bumble.appyx.navigation.integrationpoint.IntegrationPointProvider open class ActivityIntegrationPoint( private val activity: Activity, savedInstanceState: Bundle?, -) : IntegrationPoint(savedInstanceState = savedInstanceState) { +) : AndroidIntegrationPoint(savedInstanceState = savedInstanceState) { private val activityBoundary = ActivityBoundary(activity, requestCodeRegistry) private val permissionRequestBoundary = PermissionRequestBoundary(activity, requestCodeRegistry) @@ -51,7 +52,7 @@ open class ActivityIntegrationPoint( } companion object { - fun getIntegrationPoint(context: Context): IntegrationPoint { + fun getIntegrationPoint(context: Context): AndroidIntegrationPoint { val activity = context.findActivity() checkNotNull(activity) { "Could not find an activity from the context: $context" @@ -61,7 +62,10 @@ open class ActivityIntegrationPoint( "Activity ${activity::class.qualifiedName} does not implement IntegrationPointProvider" ) - return integrationPointProvider.appyxV2IntegrationPoint + return integrationPointProvider.appyxV2IntegrationPoint as? AndroidIntegrationPoint + ?: error( + "Activity ${activity::class.qualifiedName} does not provide AndroidIntegrationPoint" + ) } @Suppress("UNCHECKED_CAST") diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidIntegrationPoint.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidIntegrationPoint.kt new file mode 100644 index 000000000..29ee8e8c7 --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidIntegrationPoint.kt @@ -0,0 +1,27 @@ +package com.bumble.appyx.navigation.integration + +import android.os.Bundle +import androidx.compose.runtime.Stable +import com.bumble.appyx.navigation.integration.activitystarter.ActivityStarter +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequester +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeRegistry +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint + +@Stable +abstract class AndroidIntegrationPoint( + protected val savedInstanceState: Bundle? +) : IntegrationPoint() { + + protected val requestCodeRegistry = + RequestCodeRegistry( + savedInstanceState + ) + + abstract val activityStarter: ActivityStarter + + abstract val permissionRequester: PermissionRequester + + fun onSaveInstanceState(outState: Bundle) { + requestCodeRegistry.onSaveInstanceState(outState) + } +} 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 new file mode 100644 index 000000000..1ef635180 --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/AndroidNodeHost.kt @@ -0,0 +1,40 @@ +package com.bumble.appyx.navigation.integration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.unit.dp +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl + + +/** + * 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( + lifecycle: Lifecycle, + integrationPoint: IntegrationPoint, + modifier: Modifier = Modifier, + customisations: NodeCustomisationDirectory = remember { NodeCustomisationDirectoryImpl() }, + factory: NodeFactory +) { + NodeHost( + lifecycle = lifecycle, + integrationPoint = integrationPoint, + modifier = modifier, + customisations = customisations, + screenSize = ScreenSize( + LocalConfiguration.current.screenWidthDp.dp, + LocalConfiguration.current.screenHeightDp.dp, + ), + factory = factory, + ) +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/LocalIntegrationPoint.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/LocalIntegrationPoint.kt similarity index 62% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/LocalIntegrationPoint.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/LocalIntegrationPoint.kt index 5150c9230..d0e7a7280 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/LocalIntegrationPoint.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/LocalIntegrationPoint.kt @@ -1,6 +1,7 @@ -package com.bumble.appyx.navigation.integrationpoint +package com.bumble.appyx.navigation.integration import androidx.compose.runtime.staticCompositionLocalOf +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint val LocalIntegrationPoint = staticCompositionLocalOf { error("CompositionLocal LocalIntegrationPoint not present") diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeActivity.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeActivity.kt similarity index 93% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeActivity.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeActivity.kt index 3071ece22..1d38d40af 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeActivity.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeActivity.kt @@ -1,8 +1,9 @@ -package com.bumble.appyx.navigation.integrationpoint +package com.bumble.appyx.navigation.integration import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import com.bumble.appyx.navigation.integrationpoint.IntegrationPointProvider /** * Helper class for root [Node] integration into projects using [AppCompatActivity]. diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeComponentActivity.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeComponentActivity.kt similarity index 94% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeComponentActivity.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeComponentActivity.kt index 495225c0c..ea8ffbdf8 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/NodeComponentActivity.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/NodeComponentActivity.kt @@ -1,8 +1,9 @@ -package com.bumble.appyx.navigation.integrationpoint +package com.bumble.appyx.navigation.integration import android.content.Intent import android.os.Bundle import androidx.activity.ComponentActivity +import com.bumble.appyx.navigation.integrationpoint.IntegrationPointProvider /** * Helper class for root [Node] integration into projects using [ComponentActivity]. diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityBoundary.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityBoundary.kt similarity index 78% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityBoundary.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityBoundary.kt index d710275ef..bf931baf9 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityBoundary.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityBoundary.kt @@ -1,13 +1,13 @@ -package com.bumble.appyx.navigation.integrationpoint.activitystarter +package com.bumble.appyx.navigation.integration.activitystarter import android.app.Activity import android.content.Context import android.content.Intent import androidx.fragment.app.Fragment -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityStarter.ActivityResultEvent -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStreamImpl -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeClient -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeRegistry +import com.bumble.appyx.navigation.integration.activitystarter.ActivityStarter.ActivityResultEvent +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStreamImpl +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeClient +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeRegistry class ActivityBoundary( private val activityStarterHost: ActivityStarterHost, diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityResultHandler.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityResultHandler.kt similarity index 67% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityResultHandler.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityResultHandler.kt index 584cf3807..7f5b205aa 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityResultHandler.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityResultHandler.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.activitystarter +package com.bumble.appyx.navigation.integration.activitystarter import android.content.Intent diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarter.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarter.kt similarity index 59% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarter.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarter.kt index eac7aac8e..ea3f19955 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarter.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarter.kt @@ -1,11 +1,11 @@ -package com.bumble.appyx.navigation.integrationpoint.activitystarter +package com.bumble.appyx.navigation.integration.activitystarter import android.content.Context import android.content.Intent -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityStarter.ActivityResultEvent -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStream -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeClient +import com.bumble.appyx.navigation.integration.activitystarter.ActivityStarter.ActivityResultEvent +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStream +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeClient /** * Start Activities. diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarterHost.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarterHost.kt similarity index 94% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarterHost.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarterHost.kt index 2a142bda0..643e4f540 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/ActivityStarterHost.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/ActivityStarterHost.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.activitystarter +package com.bumble.appyx.navigation.integration.activitystarter import android.app.Activity import android.content.Context diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/CanProvideActivityStarter.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/CanProvideActivityStarter.kt similarity index 54% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/CanProvideActivityStarter.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/CanProvideActivityStarter.kt index aac4340e6..0bd268a6e 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/activitystarter/CanProvideActivityStarter.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/activitystarter/CanProvideActivityStarter.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.activitystarter +package com.bumble.appyx.navigation.integration.activitystarter interface CanProvideActivityStarter { val activityStarter: ActivityStarter diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestBoundary.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestBoundary.kt similarity index 82% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestBoundary.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestBoundary.kt index 1915112da..7c7e7bcd1 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestBoundary.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestBoundary.kt @@ -1,14 +1,14 @@ -package com.bumble.appyx.navigation.integrationpoint.permissionrequester +package com.bumble.appyx.navigation.integration.permissionrequester import android.app.Activity import android.content.pm.PackageManager import androidx.fragment.app.Fragment -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester.CheckPermissionsResult -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester.RequestPermissionsEvent.Cancelled -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester.RequestPermissionsEvent.RequestPermissionsResult -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStreamImpl -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeClient -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeRegistry +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequester.CheckPermissionsResult +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequester.RequestPermissionsEvent.Cancelled +import com.bumble.appyx.navigation.integration.permissionrequester.PermissionRequester.RequestPermissionsEvent.RequestPermissionsResult +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStreamImpl +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeClient +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeRegistry class PermissionRequestBoundary( private val permissionRequesterHost: PermissionRequesterHost, diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestResultHandler.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestResultHandler.kt similarity index 71% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestResultHandler.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestResultHandler.kt index 11cc94fc0..67cbbbc12 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequestResultHandler.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequestResultHandler.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.permissionrequester +package com.bumble.appyx.navigation.integration.permissionrequester interface PermissionRequestResultHandler { diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequester.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequester.kt similarity index 73% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequester.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequester.kt index 6340a1154..4bf3f946a 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequester.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequester.kt @@ -1,8 +1,8 @@ -package com.bumble.appyx.navigation.integrationpoint.permissionrequester +package com.bumble.appyx.navigation.integration.permissionrequester -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStream -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeClient +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStream +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeClient interface PermissionRequester : RequestCodeBasedEventStream { diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequesterHost.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequesterHost.kt similarity index 95% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequesterHost.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequesterHost.kt index c75af7c08..129254fa7 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/permissionrequester/PermissionRequesterHost.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/permissionrequester/PermissionRequesterHost.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.permissionrequester +package com.bumble.appyx.navigation.integration.permissionrequester import android.app.Activity import android.content.pm.PackageManager diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStream.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStream.kt similarity index 80% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStream.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStream.kt index 517cbb241..698aa8f72 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStream.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStream.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.requestcode +package com.bumble.appyx.navigation.integration.requestcode import kotlinx.coroutines.flow.Flow diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStreamImpl.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStreamImpl.kt similarity index 94% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStreamImpl.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStreamImpl.kt index e426f77ef..bd2181510 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeBasedEventStreamImpl.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeBasedEventStreamImpl.kt @@ -1,6 +1,6 @@ -package com.bumble.appyx.navigation.integrationpoint.requestcode +package com.bumble.appyx.navigation.integration.requestcode -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent +import com.bumble.appyx.navigation.integration.requestcode.RequestCodeBasedEventStream.RequestCodeBasedEvent import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeClient.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeClient.kt similarity index 51% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeClient.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeClient.kt index 59b5f91cb..8fd6de352 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeClient.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeClient.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.requestcode +package com.bumble.appyx.navigation.integration.requestcode interface RequestCodeClient { diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeDoesntFitInMask.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeDoesntFitInMask.kt similarity index 58% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeDoesntFitInMask.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeDoesntFitInMask.kt index b7b57f617..dea06c428 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeDoesntFitInMask.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeDoesntFitInMask.kt @@ -1,3 +1,3 @@ -package com.bumble.appyx.navigation.integrationpoint.requestcode +package com.bumble.appyx.navigation.integration.requestcode class RequestCodeDoesntFitInMask(override val message: String?) : RuntimeException(message) diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeRegistry.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeRegistry.kt similarity index 97% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeRegistry.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeRegistry.kt index b01e5792e..903c27e26 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/requestcode/RequestCodeRegistry.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/integration/requestcode/RequestCodeRegistry.kt @@ -1,4 +1,4 @@ -package com.bumble.appyx.navigation.integrationpoint.requestcode +package com.bumble.appyx.navigation.integration.requestcode import android.os.Bundle import kotlin.math.pow diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/AndroidLifecycle.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/AndroidLifecycle.kt new file mode 100644 index 000000000..02e8a1bff --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/AndroidLifecycle.kt @@ -0,0 +1,81 @@ +package com.bumble.appyx.navigation.platform + +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.coroutineScope +import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleObserver +import kotlinx.coroutines.CoroutineScope + +class AndroidLifecycle( + val androidLifecycle: androidx.lifecycle.Lifecycle +) : Lifecycle, + DefaultLifecycleObserver, + LifecycleEventObserver { + + private val managedDefaultLifecycleObservers: MutableList = + ArrayList() + private val managedLifecycleEventObservers: MutableList = + ArrayList() + + override val currentState: Lifecycle.State + get() = androidLifecycle.currentState.toCommonState() + + override val coroutineScope: CoroutineScope = + androidLifecycle.coroutineScope + + init { + androidLifecycle.addObserver(this) + } + + override fun addObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.add(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.add(observer) + } + } + + override fun removeObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.remove(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.remove(observer) + } + } + + override fun onCreate(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onCreate() } + } + + override fun onStart(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onStart() } + } + + override fun onResume(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onResume() } + } + + override fun onPause(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onPause() } + } + + override fun onStop(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onStop() } + } + + override fun onDestroy(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onDestroy() } + } + + override fun onStateChanged(source: LifecycleOwner, event: androidx.lifecycle.Lifecycle.Event) { + val commonEvent = event.toCommonEvent() + managedLifecycleEventObservers.forEach { + it.onStateChanged( + source.lifecycle.currentState.toCommonState(), + commonEvent + ) + } + } +} diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/LifecycleCommonMappers.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/LifecycleCommonMappers.kt new file mode 100644 index 000000000..d9560f2f4 --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/LifecycleCommonMappers.kt @@ -0,0 +1,33 @@ +package com.bumble.appyx.navigation.platform + +import com.bumble.appyx.navigation.lifecycle.Lifecycle + + +fun androidx.lifecycle.Lifecycle.Event.toCommonEvent(): Lifecycle.Event = + when (this) { + androidx.lifecycle.Lifecycle.Event.ON_CREATE -> Lifecycle.Event.ON_CREATE + androidx.lifecycle.Lifecycle.Event.ON_START -> Lifecycle.Event.ON_START + androidx.lifecycle.Lifecycle.Event.ON_RESUME -> Lifecycle.Event.ON_RESUME + androidx.lifecycle.Lifecycle.Event.ON_PAUSE -> Lifecycle.Event.ON_PAUSE + androidx.lifecycle.Lifecycle.Event.ON_STOP -> Lifecycle.Event.ON_STOP + androidx.lifecycle.Lifecycle.Event.ON_DESTROY -> Lifecycle.Event.ON_DESTROY + androidx.lifecycle.Lifecycle.Event.ON_ANY -> Lifecycle.Event.ON_ANY + } + +fun Lifecycle.State.toAndroidState(): androidx.lifecycle.Lifecycle.State = + when (this) { + Lifecycle.State.INITIALIZED -> androidx.lifecycle.Lifecycle.State.INITIALIZED + Lifecycle.State.CREATED -> androidx.lifecycle.Lifecycle.State.CREATED + Lifecycle.State.STARTED -> androidx.lifecycle.Lifecycle.State.STARTED + Lifecycle.State.RESUMED -> androidx.lifecycle.Lifecycle.State.RESUMED + Lifecycle.State.DESTROYED -> androidx.lifecycle.Lifecycle.State.DESTROYED + } + +fun androidx.lifecycle.Lifecycle.State.toCommonState(): Lifecycle.State = + when (this) { + androidx.lifecycle.Lifecycle.State.DESTROYED -> Lifecycle.State.DESTROYED + androidx.lifecycle.Lifecycle.State.INITIALIZED -> Lifecycle.State.INITIALIZED + androidx.lifecycle.Lifecycle.State.CREATED -> Lifecycle.State.CREATED + androidx.lifecycle.Lifecycle.State.STARTED -> Lifecycle.State.STARTED + androidx.lifecycle.Lifecycle.State.RESUMED -> Lifecycle.State.RESUMED + } \ No newline at end of file diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt new file mode 100644 index 000000000..fb0989286 --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt @@ -0,0 +1,9 @@ +package com.bumble.appyx.navigation.platform + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable + +@Composable +actual fun PlatformBackHandler(enabled: Boolean, onBack: () -> Unit) { + BackHandler(enabled, onBack) +} diff --git a/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt new file mode 100644 index 000000000..dc93e8335 --- /dev/null +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt @@ -0,0 +1,103 @@ +package com.bumble.appyx.navigation.platform + +import android.app.Activity +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.coroutineScope +import com.bumble.appyx.navigation.lifecycle.CommonLifecycleOwner +import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleObserver +import kotlinx.coroutines.CoroutineScope + +actual class PlatformLifecycleRegistry( + androidOwner: LifecycleOwner +) : Lifecycle, androidx.lifecycle.DefaultLifecycleObserver, + androidx.lifecycle.LifecycleEventObserver { + + private var lifecycleOwner: LifecycleOwner? = androidOwner + private val androidLifecycleRegistry = LifecycleRegistry(androidOwner) + + private val managedDefaultLifecycleObservers: MutableList = + ArrayList() + private val managedLifecycleEventObservers: MutableList = + ArrayList() + + override val currentState: Lifecycle.State + get() = androidLifecycleRegistry.currentState.toCommonState() + + actual fun setCurrentState(state: Lifecycle.State) { + androidLifecycleRegistry.currentState = state.toAndroidState() + } + + override val coroutineScope: CoroutineScope = androidLifecycleRegistry.coroutineScope + + init { + androidLifecycleRegistry.addObserver(this) + } + + override fun addObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.add(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.add(observer) + } + } + + override fun removeObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.remove(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.remove(observer) + } + } + + override fun onStateChanged(source: LifecycleOwner, event: androidx.lifecycle.Lifecycle.Event) { + val commonEvent = event.toCommonEvent() + managedLifecycleEventObservers.forEach { + it.onStateChanged( + source.lifecycle.currentState.toCommonState(), commonEvent + ) + } + } + + override fun onCreate(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onCreate() } + } + + override fun onStart(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onStart() } + } + + override fun onResume(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onResume() } + } + + override fun onPause(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onPause() } + } + + override fun onStop(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onStop() } + } + + override fun onDestroy(owner: LifecycleOwner) { + managedDefaultLifecycleObservers.forEach { it.onDestroy() } + if ((owner as? Activity)?.isFinishing == true) { + lifecycleOwner = null + } + } + + actual companion object { + actual fun create(owner: CommonLifecycleOwner): PlatformLifecycleRegistry = + PlatformLifecycleRegistry(object : LifecycleOwner { + override val lifecycle: androidx.lifecycle.Lifecycle + get() = when (val platformLifecycle = owner.lifecycle) { + is AndroidLifecycle -> platformLifecycle.androidLifecycle + is PlatformLifecycleRegistry -> platformLifecycle.androidLifecycleRegistry + else -> throw (IllegalStateException( + "Unable to get android lifecycle from $platformLifecycle provided by $owner" + )) + } + }) + } +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt similarity index 72% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt rename to appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt index 30a135636..86c7cf34f 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt +++ b/appyx-navigation/common/src/androidMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt @@ -33,18 +33,3 @@ inline fun BuildContext.getRetainedInstance( disposer = disposer, factory = factory ) - -/** - * Obtains or creates an instance of a class using the identifier from the BuildContext. - */ -inline fun BuildContext.getRetainedInstance( - key: String, - noinline disposer: (T) -> Unit = {}, - noinline factory: () -> T -) = - RetainedInstanceStore.get( - storeId = identifier, - key = key, - disposer = disposer, - factory = factory - ) diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/Appyx.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/Appyx.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/Appyx.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/Appyx.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/FlowExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/FlowExt.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/FlowExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/FlowExt.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/builder/Builder.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/builder/Builder.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/Builder.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/builder/SimpleBuilder.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAware.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt similarity index 98% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt index 0728fcc63..926398fca 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareCallbackInfo.kt @@ -1,6 +1,6 @@ package com.bumble.appyx.navigation.children -import androidx.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.lifecycle.MinimumCombinedLifecycle import com.bumble.appyx.navigation.lifecycle.isDestroyed import com.bumble.appyx.navigation.node.Node diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareExt.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt similarity index 92% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt index d38d70562..d3b04d9c7 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildAwareImpl.kt @@ -1,10 +1,8 @@ package com.bumble.appyx.navigation.children -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.coroutineScope 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.Node import com.bumble.appyx.navigation.node.ParentNode @@ -94,8 +92,8 @@ class ChildAwareImpl : ChildAware { } private fun Lifecycle.removeWhenDestroyed(info: ChildAwareCallbackInfo) { - addObserver(object : DefaultLifecycleObserver { - override fun onDestroy(owner: LifecycleOwner) { + addObserver(object : DefaultPlatformLifecycleObserver { + override fun onDestroy() { callbacks.remove(info) } }) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt new file mode 100644 index 000000000..6dda7298d --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt @@ -0,0 +1,7 @@ +package com.bumble.appyx.navigation.children + +import com.bumble.appyx.navigation.lifecycle.Lifecycle + +typealias ChildrenCallback = (lifecycle: Lifecycle, child1: T1, child2: T2) -> Unit + +typealias ChildCallback = (lifecycle: Lifecycle, child: T) -> Unit diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt similarity index 84% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt index fa27bead5..22fa83b92 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntry.kt @@ -9,13 +9,15 @@ sealed class ChildEntry { abstract val key: Element override fun equals(other: Any?): Boolean = - other?.javaClass == javaClass && (other as? ChildEntry<*>)?.key == key + other != null + && other::class == this::class + && (other as? ChildEntry<*>)?.key == key override fun hashCode(): Int = key.hashCode() override fun toString(): String = - "$key@${javaClass.simpleName}" + "$key@${this::class.simpleName}" /** All public APIs should return this type of child which is ready to work with. */ class Initialized( diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryExt.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntryMap.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryMap.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildEntryMap.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildEntryMap.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt similarity index 96% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt index b112bac76..e46aae2a7 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/children/ChildNodeCreationManager.kt @@ -1,6 +1,5 @@ package com.bumble.appyx.navigation.children -import androidx.lifecycle.coroutineScope import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.state.MutableSavedStateMap import com.bumble.appyx.navigation.modality.AncestryInfo @@ -53,6 +52,7 @@ internal class ChildNodeCreationManager( appyxComponentSuspendKeys = emptySet() appyxComponentKeys = appyxComponentKeepKeys } + ChildEntry.KeepMode.SUSPEND -> { appyxComponentKeepKeys = state.onScreen @@ -61,7 +61,11 @@ internal class ChildNodeCreationManager( appyxComponentKeys = appyxComponentKeepKeys + appyxComponentSuspendKeys } } - updateChildren(appyxComponentKeys, appyxComponentKeepKeys, appyxComponentSuspendKeys) + updateChildren( + appyxComponentKeys, + appyxComponentKeepKeys, + appyxComponentSuspendKeys + ) } } } @@ -90,8 +94,8 @@ internal class ChildNodeCreationManager( } val mutableMap = map.toMutableMap() newElements.forEach { key -> - val shouldSuspend = - keepMode == ChildEntry.KeepMode.SUSPEND && appyxComponentSuspendElements.contains(key) + val shouldSuspend = keepMode == ChildEntry.KeepMode.SUSPEND + && appyxComponentSuspendElements.contains(key) mutableMap[key] = childEntry( key = key, @@ -122,6 +126,7 @@ internal class ChildNodeCreationManager( return when (child) { is ChildEntry.Initialized -> child + is ChildEntry.Suspended -> _children.updateAndGet { map -> val updateChild = map[element] diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/clienthelper/interactor/Interactor.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/collections/ImmutableList.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/collections/ImmutableList.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/collections/ImmutableList.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/collections/ImmutableList.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt similarity index 92% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt index bd3a04eb5..b0f9ae242 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt @@ -24,7 +24,6 @@ import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onPlaced -import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.IntSize @@ -38,6 +37,7 @@ import com.bumble.appyx.interactions.core.modifiers.onPointerEvent import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.output.ElementUiModel +import com.bumble.appyx.navigation.integration.LocalScreenSize import com.bumble.appyx.navigation.node.ParentNode import kotlin.math.roundToInt @@ -51,18 +51,19 @@ fun ParentNode.Ap clipToBounds: Boolean = false, gestureValidator: GestureValidator = GestureValidator.defaultValidator, gestureExtraTouchArea: Dp = defaultExtraTouch, - block: @Composable ChildrenTransitionScope.() -> Unit = { - children { child, elementUiModel -> - child() - } - } + block: @Composable (ChildrenTransitionScope.() -> Unit)? = null, ) { val density = LocalDensity.current - val screenWidthPx = (LocalConfiguration.current.screenWidthDp * density.density).roundToInt() - val screenHeightPx = (LocalConfiguration.current.screenHeightDp * density.density).roundToInt() + val screenWidthPx = (LocalScreenSize.current.widthDp * density.density).value.roundToInt() + val screenHeightPx = (LocalScreenSize.current.heightDp * density.density).value.roundToInt() val coroutineScope = rememberCoroutineScope() var uiContext by remember { mutableStateOf(null) } var boxScope: BoxScope? = null + val childrenBlock = block ?: { + children { child, _ -> + child() + } + } LaunchedEffect(uiContext) { uiContext?.let { appyxComponent.updateContext(it) } @@ -93,7 +94,7 @@ fun ParentNode.Ap .fillMaxSize() ) { boxScope = this - block( + childrenBlock( ChildrenTransitionScope(appyxComponent, gestureExtraTouchArea, gestureValidator) ) } @@ -132,7 +133,9 @@ class ChildrenTransitionScope( uiModels .forEach { elementUiModel -> key(elementUiModel.element.id) { - var transformedBoundingBox by remember(elementUiModel.element.id) { mutableStateOf(Rect.Zero) } + var transformedBoundingBox by remember(elementUiModel.element.id) { + mutableStateOf(Rect.Zero) + } var offsetCenter by remember(elementUiModel.element.id) { mutableStateOf(Offset.Zero) } var size by remember(elementUiModel.element.id) { mutableStateOf(IntSize.Zero) } val isVisible by elementUiModel.visibleState.collectAsState() @@ -179,7 +182,8 @@ class ChildrenTransitionScope( it.size.width.toFloat(), it.size.height.toFloat() ) / 2f - transformedBoundingBox = it.boundsInParent().inflate(gestureExtraTouchAreaPx) + transformedBoundingBox = + it.boundsInParent().inflate(gestureExtraTouchAreaPx) offsetCenter = transformedBoundingBox.center - localCenter } ), diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/Child.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/Child.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/Child.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/ChildRenderer.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/ChildRenderer.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/composable/ChildRenderer.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/ChildRenderer.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeFactory.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt similarity index 65% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt index 5e8b4cc1e..4ce9f4c76 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integration/NodeHost.kt @@ -1,18 +1,21 @@ package com.bumble.appyx.navigation.integration import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.State +import androidx.compose.runtime.compositionLocalOf import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver import com.bumble.appyx.navigation.modality.BuildContext import com.bumble.appyx.navigation.node.Node import com.bumble.appyx.navigation.node.build @@ -20,6 +23,10 @@ import com.bumble.appyx.navigation.state.SavedStateMap import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl +data class ScreenSize(val widthDp: Dp, val heightDp: Dp) + +val LocalScreenSize = compositionLocalOf { ScreenSize(0.dp, 0.dp) } + /** * Composable function to host [Node]. * @@ -28,24 +35,27 @@ import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl @Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda @Composable fun NodeHost( + lifecycle: Lifecycle, integrationPoint: IntegrationPoint, + screenSize: ScreenSize, modifier: Modifier = Modifier, customisations: NodeCustomisationDirectory = remember { NodeCustomisationDirectoryImpl() }, factory: NodeFactory ) { - val node by rememberNode(factory, customisations, integrationPoint) - DisposableEffect(node) { - onDispose { node.updateLifecycleState(Lifecycle.State.DESTROYED) } - } - node.Compose(modifier = modifier) - val lifecycle = LocalLifecycleOwner.current.lifecycle - DisposableEffect(lifecycle) { - node.updateLifecycleState(lifecycle.currentState) - val observer = LifecycleEventObserver { source, _ -> - node.updateLifecycleState(source.lifecycle.currentState) + CompositionLocalProvider(LocalScreenSize provides screenSize) { + val node by rememberNode(factory, customisations, integrationPoint) + DisposableEffect(node) { + onDispose { node.updateLifecycleState(Lifecycle.State.DESTROYED) } + } + node.Compose(modifier = modifier) + DisposableEffect(lifecycle) { + node.updateLifecycleState(lifecycle.currentState) + val observer = PlatformLifecycleEventObserver { newState, _ -> + node.updateLifecycleState(newState) + } + lifecycle.addObserver(observer) + onDispose { lifecycle.removeObserver(observer) } } - lifecycle.addObserver(observer) - onDispose { lifecycle.removeObserver(observer) } } } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt new file mode 100644 index 000000000..615cfbaff --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt @@ -0,0 +1,12 @@ +package com.bumble.appyx.navigation.integrationpoint + +import androidx.compose.runtime.Stable +import com.bumble.appyx.navigation.navigation.upnavigation.UpNavigationHandler + +@Stable +abstract class IntegrationPoint : UpNavigationHandler { + + abstract val isChangingConfigurations: Boolean + + abstract fun onRootFinished() +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointProvider.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointProvider.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointProvider.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointProvider.kt diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt new file mode 100644 index 000000000..e50b252dd --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt @@ -0,0 +1,22 @@ +package com.bumble.appyx.navigation.integrationpoint + +class IntegrationPointStub : IntegrationPoint() { + companion object { + private const val ERROR = "You're accessing an IntegrationPointStub. " + + "This means you're using a Node without ever integrating it to a proper IntegrationPoint. " + + "This is fine during tests with limited scope, but it looks like the code that leads here " + + "requires interfacing with a valid implementation. " + + "You may be attempting to access the IntegrationPoint before it is attached to the Node." + } + + override val isChangingConfigurations: Boolean + get() = false + + override fun handleUpNavigation() { + error(ERROR) + } + + override fun onRootFinished() { + error(ERROR) + } +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt similarity index 94% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt index c8fc083fa..339af381d 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/ChildNodeLifecycleManager.kt @@ -1,7 +1,5 @@ package com.bumble.appyx.navigation.lifecycle -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleRegistry import com.bumble.appyx.interactions.core.model.AppyxComponent import com.bumble.appyx.navigation.children.ChildEntry import com.bumble.appyx.navigation.children.ChildEntryMap @@ -15,10 +13,10 @@ import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.launch /** - * Hosts [LifecycleRegistry] to manage the current node lifecycle + * Hosts [PlatformLifecycleRegistry] to manage the current node lifecycle * and updates lifecycle of children nodes when updated. */ -internal class ChildNodeLifecycleManager( +internal class ChildNodeLifecycleManager( private val appyxComponent: AppyxComponent, private val children: StateFlow>, private val keepMode: ChildEntry.KeepMode, diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleObserver.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleObserver.kt new file mode 100644 index 000000000..a81f7363a --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleObserver.kt @@ -0,0 +1,52 @@ +package com.bumble.appyx.navigation.lifecycle + +/** + * Ported from Androidx.lifecycle + * + * Marks a class as a LifecycleObserver. Don't use this interface directly. Instead implement either DefaultLifecycleObserver or LifecycleEventObserver to be notified about lifecycle events. + * See Also: + * Lifecycle - for samples and usage patterns. + */ +interface PlatformLifecycleObserver + +/** + * Ported from Androidx.lifecycle + * + * Callback interface for listening to LifecycleOwner state changes. + * If a class implements both this interface and LifecycleEventObserver, then methods of + * DefaultLifecycleObserver will be called first, and then followed by the call of + * LifecycleEventObserver.onStateChanged(LifecycleOwner, Lifecycle.Event) + * + * If a class implements this interface and in the same time uses OnLifecycleEvent, + * then annotations will be ignored. + * + * Note that this port does not include the lifecycle owner that the lifecycle events are associated + * with, as this is a complexity that has been avoided at this point. + */ +interface DefaultPlatformLifecycleObserver : PlatformLifecycleObserver { + fun onCreate() {} + fun onStart() {} + fun onResume() {} + fun onPause() {} + fun onStop() {} + fun onDestroy() {} +} + +/** + * Ported from Androidx.lifecycle + * + * Class that can receive any lifecycle change and dispatch it to the receiver. + * + * If a class implements both this interface and + * [DefaultPlatformLifecycleObserver], then + * methods of `DefaultLifecycleObserver` will be called first, and then followed by the call + * of [PlatformLifecycleEventObserver.onStateChanged] + */ +fun interface PlatformLifecycleEventObserver : PlatformLifecycleObserver { + /** + * Called when a state transition event happens. + * + * @param event The event + */ + fun onStateChanged(newState: Lifecycle.State, event: Lifecycle.Event) +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleOwner.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleOwner.kt new file mode 100644 index 000000000..30301edfb --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/CommonLifecycleOwner.kt @@ -0,0 +1,8 @@ +package com.bumble.appyx.navigation.lifecycle + +import kotlinx.coroutines.CoroutineScope + +interface CommonLifecycleOwner { + val lifecycle: Lifecycle + val lifecycleScope: CoroutineScope +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/Lifecycle.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/Lifecycle.kt new file mode 100644 index 000000000..adc24ab6d --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/Lifecycle.kt @@ -0,0 +1,44 @@ +package com.bumble.appyx.navigation.lifecycle + +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow + +interface Lifecycle { + val currentState: State + + val coroutineScope: CoroutineScope + + fun addObserver(observer: PlatformLifecycleObserver) + + fun removeObserver(observer: PlatformLifecycleObserver) + + fun asFlow(): Flow = + callbackFlow { + val observer = PlatformLifecycleEventObserver { state, _ -> + trySend(state) + } + trySend(currentState) + addObserver(observer) + awaitClose { removeObserver(observer) } + } + + enum class State { + INITIALIZED, + CREATED, + STARTED, + RESUMED, + DESTROYED, + } + + enum class Event { + ON_CREATE, + ON_START, + ON_RESUME, + ON_PAUSE, + ON_STOP, + ON_DESTROY, + ON_ANY, + } +} 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 new file mode 100644 index 000000000..87da606c6 --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt @@ -0,0 +1,36 @@ +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 { + + + override fun onCreate() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onCreate") + } + + override fun onStart() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onStart") + } + + override fun onResume() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onResume") + } + + override fun onPause() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onPause") + } + + override fun onStop() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onStop") + } + + override fun onDestroy() { + AppyxLogger.d(LOG_TAG, "${node::class.simpleName}@${node.hashCode()} onDestroy") + } + + companion object { + private const val LOG_TAG = "Lifecycle" + } +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LocalCommonLifecycleOwner.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LocalCommonLifecycleOwner.kt new file mode 100644 index 000000000..938cceda2 --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/LocalCommonLifecycleOwner.kt @@ -0,0 +1,7 @@ +package com.bumble.appyx.navigation.lifecycle + +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.compositionLocalOf + +val LocalCommonLifecycleOwner: ProvidableCompositionLocal = + compositionLocalOf { null } diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt similarity index 61% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt index cc527467a..5271c2efe 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/MinimumCombinedLifecycle.kt @@ -1,9 +1,8 @@ package com.bumble.appyx.navigation.lifecycle -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry +import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry +import kotlinx.coroutines.CoroutineScope + /** * Combines multiple lifecycles and provides a minimum of their states. @@ -15,8 +14,8 @@ import androidx.lifecycle.LifecycleRegistry */ internal class MinimumCombinedLifecycle( vararg lifecycles: Lifecycle, -) : LifecycleOwner { - private val registry = LifecycleRegistry(this) +) : CommonLifecycleOwner { + private val registry = PlatformLifecycleRegistry.create(this) private val lifecycles = ArrayList() init { @@ -28,33 +27,33 @@ internal class MinimumCombinedLifecycle( lifecycles.sortedBy { it.currentState }.forEach { manage(it) } } - override val lifecycle: Lifecycle - get() = registry + override val lifecycle: Lifecycle = registry + override val lifecycleScope: CoroutineScope = registry.coroutineScope fun manage(lifecycle: Lifecycle) { lifecycles += lifecycle - lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { + lifecycle.addObserver(object : DefaultPlatformLifecycleObserver { + override fun onCreate() { update() } - override fun onStart(owner: LifecycleOwner) { + override fun onStart() { update() } - override fun onResume(owner: LifecycleOwner) { + override fun onResume() { update() } - override fun onPause(owner: LifecycleOwner) { + override fun onPause() { update() } - override fun onStop(owner: LifecycleOwner) { + override fun onStop() { update() } - override fun onDestroy(owner: LifecycleOwner) { + override fun onDestroy() { update() } }) @@ -65,7 +64,7 @@ internal class MinimumCombinedLifecycle( lifecycles .minByOrNull { it.currentState } ?.takeIf { it.currentState != Lifecycle.State.INITIALIZED } - ?.also { registry.currentState = it.currentState } + ?.also { registry.setCurrentState(it.currentState) } } } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt new file mode 100644 index 000000000..62571e4bb --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt @@ -0,0 +1,7 @@ +package com.bumble.appyx.navigation.lifecycle + +interface NodeLifecycle : CommonLifecycleOwner { + + fun updateLifecycleState(state: Lifecycle.State) + +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt new file mode 100644 index 000000000..2900fddff --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt @@ -0,0 +1,16 @@ +package com.bumble.appyx.navigation.lifecycle + +import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry +import kotlinx.coroutines.CoroutineScope + +internal class NodeLifecycleImpl(lifecycleOwner: CommonLifecycleOwner) : NodeLifecycle { + private val lifecycleRegistry: PlatformLifecycleRegistry = + PlatformLifecycleRegistry.create(lifecycleOwner) + + override val lifecycle: Lifecycle = lifecycleRegistry + override val lifecycleScope: CoroutineScope by lazy { lifecycleRegistry.coroutineScope } + + override fun updateLifecycleState(state: Lifecycle.State) { + lifecycleRegistry.setCurrentState(state) + } +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/PlatformLifecycleExt.kt similarity index 57% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/PlatformLifecycleExt.kt index 6a14d3404..b75f5b08b 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleExt.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/lifecycle/PlatformLifecycleExt.kt @@ -1,20 +1,16 @@ package com.bumble.appyx.navigation.lifecycle -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleEventObserver -import androidx.lifecycle.LifecycleOwner import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow -fun LifecycleOwner.asFlow(): Flow = +fun CommonLifecycleOwner.asFlow(): Flow = lifecycle.asFlow() fun Lifecycle.asFlow(): Flow = callbackFlow { - val observer = LifecycleEventObserver { source, _ -> - trySend(source.lifecycle.currentState) + val observer = PlatformLifecycleEventObserver { currentState, _ -> + trySend(currentState) } trySend(currentState) addObserver(observer) @@ -33,28 +29,28 @@ fun Lifecycle.subscribe( onDestroy: () -> Unit = {} ) { addObserver( - object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { + object : DefaultPlatformLifecycleObserver { + override fun onCreate() { onCreate() } - override fun onStart(owner: LifecycleOwner) { + override fun onStart() { onStart() } - override fun onResume(owner: LifecycleOwner) { + override fun onResume() { onResume() } - override fun onPause(owner: LifecycleOwner) { + override fun onPause() { onPause() } - override fun onStop(owner: LifecycleOwner) { + override fun onStop() { onStop() } - override fun onDestroy(owner: LifecycleOwner) { + override fun onDestroy() { onDestroy() } } diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/AncestryInfo.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt similarity index 95% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt index 91f1e7e90..5c5fe7984 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/modality/BuildContext.kt @@ -1,11 +1,11 @@ package com.bumble.appyx.navigation.modality +import com.bumble.appyx.interactions.UUID import com.bumble.appyx.interactions.core.state.MutableSavedStateMap import com.bumble.appyx.navigation.state.SavedStateMap import com.bumble.appyx.utils.customisations.NodeCustomisation import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl -import java.util.UUID data class BuildContext( val ancestryInfo: AncestryInfo, @@ -28,7 +28,7 @@ data class BuildContext( val identifier: String by lazy { if (savedStateMap == null) { - UUID.randomUUID().toString() + UUID.randomUUID() } else { savedStateMap[IDENTIFIER_KEY] as String? ?: error("onSaveInstanceState() was not called") } diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/navigation/Resolver.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/navigation/Resolver.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/navigation/Resolver.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/navigation/Resolver.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/navigation/upnavigation/UpNavigationHandler.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/navigation/upnavigation/UpNavigationHandler.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/navigation/upnavigation/UpNavigationHandler.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/navigation/upnavigation/UpNavigationHandler.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ComposableNode.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/EmptyNodeViews.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/LocalNode.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/Node.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt similarity index 84% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/Node.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt index a750507ab..f6494d84d 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/Node.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/Node.kt @@ -1,27 +1,21 @@ package com.bumble.appyx.navigation.node -import androidx.annotation.CallSuper -import androidx.annotation.VisibleForTesting import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.Stable import androidx.compose.runtime.saveable.SaverScope import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalLifecycleOwner -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.lifecycleScope 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.navigation.Appyx -import com.bumble.appyx.navigation.BuildConfig import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint import com.bumble.appyx.navigation.integrationpoint.IntegrationPointStub -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeClient +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 @@ -33,16 +27,18 @@ import com.bumble.appyx.navigation.plugin.UpNavigationHandler import com.bumble.appyx.navigation.plugin.plugins import com.bumble.appyx.navigation.state.SavedStateMap import com.bumble.appyx.navigation.store.RetainedInstanceStore +import com.bumble.appyx.utils.multiplatform.BuildFlags +import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.withContext @Suppress("TooManyFunctions") @Stable -open class Node @VisibleForTesting internal constructor( +open class Node internal constructor( private val buildContext: BuildContext, val view: NodeView = EmptyNodeView, private val retainedInstanceStore: RetainedInstanceStore, plugins: List = emptyList() -) : NodeLifecycle, NodeView by view, RequestCodeClient { +) : NodeLifecycle, NodeView by view { constructor( buildContext: BuildContext, @@ -53,6 +49,11 @@ open class Node @VisibleForTesting internal constructor( @Suppress("LeakingThis") // Implemented in the same way as in androidx.Fragment private val nodeLifecycle = NodeLifecycleImpl(this) + private var wasBuilt = false + + val id: String + get() = buildContext.identifier + val plugins: List = plugins + listOfNotNull(this as? Plugin) val ancestryInfo: AncestryInfo = @@ -67,6 +68,10 @@ open class Node @VisibleForTesting internal constructor( is AncestryInfo.Root -> null } + override val lifecycle get() = nodeLifecycle.lifecycle + + override val lifecycleScope: CoroutineScope by lazy { lifecycle.coroutineScope } + var integrationPoint: IntegrationPoint = IntegrationPointStub() get() { return if (isRoot) field @@ -79,19 +84,12 @@ open class Node @VisibleForTesting internal constructor( field = value } - private var wasBuilt = false - - val id: String - get() = buildContext.identifier - - override val requestCodeClientId: String = id - init { - if (BuildConfig.DEBUG) { - lifecycle.addObserver(LifecycleLogger) + if (BuildFlags.DEBUG) { + lifecycle.addObserver(LifecycleLogger(this)) } - lifecycle.addObserver(object : DefaultLifecycleObserver { - override fun onCreate(owner: LifecycleOwner) { + lifecycle.addObserver(object : DefaultPlatformLifecycleObserver { + override fun onCreate() { if (!wasBuilt) error("onBuilt was not invoked for $this") } }) @@ -104,7 +102,6 @@ open class Node @VisibleForTesting internal constructor( this@Node as T } - @CallSuper open fun onBuilt() { require(!wasBuilt) { "onBuilt was already invoked" } wasBuilt = true @@ -117,7 +114,7 @@ open class Node @VisibleForTesting internal constructor( fun Compose(modifier: Modifier = Modifier) { CompositionLocalProvider( LocalNode provides this, - LocalLifecycleOwner provides this, + LocalCommonLifecycleOwner provides this, ) { DerivedSetup() View(modifier) @@ -129,15 +126,12 @@ open class Node @VisibleForTesting internal constructor( } - override val lifecycle: Lifecycle - get() = nodeLifecycle.lifecycle - override fun updateLifecycleState(state: Lifecycle.State) { if (lifecycle.currentState == state) return if (lifecycle.currentState == Lifecycle.State.DESTROYED && state != Lifecycle.State.DESTROYED) { Appyx.reportException( IllegalStateException( - "Trying to change lifecycle state of already destroyed node ${this::class.qualifiedName}" + "Trying to change lifecycle state of already destroyed node ${this::class}" ) ) return @@ -160,7 +154,6 @@ open class Node @VisibleForTesting internal constructor( return writer.savedState } - @CallSuper protected open fun onSaveInstanceState(state: MutableSavedStateMap) { buildContext.onSaveInstanceState(state) } @@ -188,9 +181,8 @@ open class Node @VisibleForTesting internal constructor( } } - @CallSuper open fun performUpNavigation(): Boolean = - handleUpNavigationByPlugins() || parent?.performUpNavigation() == true + handleUpNavigationByPlugins() || (parent as? Node)?.performUpNavigation() == true private fun handleUpNavigationByPlugins(): Boolean = plugins().any { it.handleUpNavigation() } diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeExt.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/NodeView.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/NodeView.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/NodeView.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt similarity index 96% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt index d20f3f939..e02d68ece 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNode.kt @@ -1,7 +1,5 @@ package com.bumble.appyx.navigation.node -import androidx.activity.compose.BackHandler -import androidx.annotation.CallSuper import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable @@ -10,8 +8,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.lifecycleScope import com.bumble.appyx.interactions.core.Element import com.bumble.appyx.interactions.core.model.AppyxComponent import com.bumble.appyx.interactions.core.model.plus @@ -32,9 +28,11 @@ import com.bumble.appyx.navigation.children.ChildrenCallback import com.bumble.appyx.navigation.children.nodeOrNull import com.bumble.appyx.navigation.composable.ChildRenderer import com.bumble.appyx.navigation.lifecycle.ChildNodeLifecycleManager +import com.bumble.appyx.navigation.lifecycle.Lifecycle import com.bumble.appyx.navigation.mapState import com.bumble.appyx.navigation.modality.BuildContext import com.bumble.appyx.navigation.navigation.Resolver +import com.bumble.appyx.navigation.platform.PlatformBackHandler import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch @@ -80,7 +78,6 @@ abstract class ParentNode( coroutineScope = lifecycleScope, ) - @CallSuper override fun onBuilt() { super.onBuilt() childNodeCreationManager.launch(this) @@ -112,7 +109,7 @@ abstract class ParentNode( child?.let { - decorator(child = PermanentChildRender(it.node)) + decorator(PermanentChildRender(it.node)) } } @@ -143,7 +140,7 @@ abstract class ParentNode( val canHandleBack = appyxComponent .canHandeBackPress() .collectAsState(initial = false) - BackHandler(canHandleBack.value) { + PlatformBackHandler(enabled = canHandleBack.value) { appyxComponent.handleBackPress() } } @@ -170,7 +167,7 @@ abstract class ParentNode( waitForChildAttached() } checkNotNull(result) { - "Expected child of type [${T::class.java}] was not found after executing action. " + + "Expected child of type [${T::class}] was not found after executing action. " + "Check that your action actually results in the expected child." } } @@ -204,7 +201,6 @@ abstract class ParentNode( // TODO warn unhandled child } - @CallSuper // TODO save/restore state properly override fun onSaveInstanceState(state: MutableSavedStateMap) { super.onSaveInstanceState(state) diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeExt.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt similarity index 85% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt index 88d660b7c..d7aa820ba 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ParentNodeView.kt @@ -15,7 +15,7 @@ interface ParentNodeView : NodeView { @Composable override fun View(modifier: Modifier) { val node = LocalNode.current as? ParentNode - ?: error("${this::class.qualifiedName} is not provided to the appropriate 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/node/ViewFactory.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ViewFactory.kt new file mode 100644 index 000000000..100d8820b --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/ViewFactory.kt @@ -0,0 +1,5 @@ +package com.bumble.appyx.navigation.node + +fun interface ViewFactory { + operator fun invoke(): View +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt new file mode 100644 index 000000000..5e521f90d --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt @@ -0,0 +1,9 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.runtime.Composable + +@Composable +expect fun PlatformBackHandler( + enabled: Boolean = true, + onBack: () -> Unit +) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt new file mode 100644 index 000000000..8f7c43724 --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt @@ -0,0 +1,12 @@ +package com.bumble.appyx.navigation.platform + +import com.bumble.appyx.navigation.lifecycle.CommonLifecycleOwner +import com.bumble.appyx.navigation.lifecycle.Lifecycle + +expect class PlatformLifecycleRegistry : Lifecycle { + fun setCurrentState(state: Lifecycle.State) + + companion object { + fun create(owner: CommonLifecycleOwner): PlatformLifecycleRegistry + } +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/NodeAwareImpl.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt similarity index 83% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt index 0c731f1d5..d0aef44f8 100644 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/plugin/Plugins.kt @@ -1,12 +1,12 @@ package com.bumble.appyx.navigation.plugin -import androidx.activity.OnBackPressedCallback -import androidx.lifecycle.Lifecycle import com.bumble.appyx.interactions.core.plugin.Plugin +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

= - this.plugins.filterIsInstance(P::class.java) + this.plugins.filterIsInstance

() interface NodeAware : NodeReadyObserver { val node: N @@ -49,4 +49,9 @@ interface BackPressHandler : Plugin { if (isEnabled) callback.handleOnBackPressed() isEnabled } + + interface OnBackPressedCallback { + val isEnabled: Boolean + fun handleOnBackPressed() + } } diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/state/SavedStateMap.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/state/SavedStateMap.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/state/SavedStateMap.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/state/SavedStateMap.kt diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStore.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStore.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStore.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStore.kt diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreCommonExt.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreCommonExt.kt new file mode 100644 index 000000000..d112b9ba6 --- /dev/null +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreCommonExt.kt @@ -0,0 +1,18 @@ +package com.bumble.appyx.navigation.store + +import com.bumble.appyx.navigation.modality.BuildContext + +/** + * Obtains or creates an instance of a class using the identifier from the BuildContext. + */ +inline fun BuildContext.getRetainedInstance( + key: String, + noinline disposer: (T) -> Unit = {}, + noinline factory: () -> T +) = + RetainedInstanceStore.get( + storeId = identifier, + key = key, + disposer = disposer, + factory = factory + ) diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreImpl.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreImpl.kt similarity index 100% rename from appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreImpl.kt rename to appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreImpl.kt diff --git a/appyx-navigation/common/src/commonTest/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt b/appyx-navigation/common/src/commonTest/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt new file mode 100644 index 000000000..27533a3dd --- /dev/null +++ b/appyx-navigation/common/src/commonTest/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt @@ -0,0 +1,183 @@ +package com.bumble.appyx.navigation.store + +import kotlin.test.Ignore +import kotlin.test.Test +import kotlin.test.assertEquals +import kotlin.test.assertFalse +import kotlin.test.assertSame +import kotlin.test.assertTrue + +@Suppress("TestFunctionName") +class RetainedInstanceStoreTest { + + private val storeId = "storeId" + private val key = "key" + private val store = RetainedInstanceStoreImpl() + + @Test + fun GIVEN_object_stored_WHEN_get_with_same_identifier_THEN_same_instance_is_retrieved() { + val obj = Any() + store.get(storeId, key) { obj } + + val retrieved = store.get(storeId, key) { Any() } + + assertSame(obj, retrieved) + } + + @Test + fun GIVEN_object_stored_WHEN_get_with_same_identifier_THEN_factory_not_called() { + var factoryCalled = false + store.get(storeId, key) { Any() } + + store.get(storeId, key) { + factoryCalled = true + Any() + } + + assertFalse(factoryCalled) + } + + // This test requires reflection so can only be executed in JVM builds. + // TODO: move to desktop or android only tests, not common test + @Ignore + @Test + fun GIVEN_two_objects_with_different_types_stored_WHEN_get_with_same_identifier_THEN_both_objects_returned() { + store.get(storeId, key) { 1 } + store.get(storeId, key) { 2L } + + val integerValue = store.get(storeId, key) { 5 } + val longValue = store.get(storeId, key) { 6L } + + assertEquals(1, integerValue) + assertEquals(2L, longValue) + } + + @Test + fun GIVEN_two_objects_stored_with_same_type_AND_different_keys_WHEN_get_with_same_identifier_THEN_both_objects_returned() { + store.get(storeId = storeId, key = "1") { 1 } + store.get(storeId = storeId, key = "2") { 2 } + + val integerValue1 = store.get(storeId = storeId, key = "1") { 5 } + val integerValue2 = store.get(storeId = storeId, key = "2") { 6 } + + assertEquals(1, integerValue1) + assertEquals(2, integerValue2) + } + + @Test + fun GIVEN_object_stored_WHEN_clearStore_with_same_identifier_THEN_object_is_disposed() { + val obj = Any() + var disposed = false + store.get(storeId, key = key, disposer = { disposed = true }) { obj } + + store.clearStore(storeId) + + assertTrue(disposed) + } + + @Test + fun GIVEN_object_stored_WHEN_clearStore_with_different_identifier_THEN_object_is_not_disposed() { + val obj = Any() + val otherIdentifier = "other" + var disposed = false + store.get(storeId, key) { obj } + store.get(otherIdentifier, key, disposer = { disposed = true }) { obj } + + store.clearStore(storeId) + + assertFalse(disposed) + } + + @Test + fun GIVEN_object_stored_before_WHEN_checking_if_same_instance_retained_by_store_id_with_same_owner_THEN_expect_true() { + val obj = Any() + store.get(storeId, key) { obj } + + val isRetained = store.isRetainedByStoreId(storeId, obj) + + assertTrue(isRetained) + } + + @Test + fun GIVEN_object_stored_before_WHEN_checking_if_same_instance_retained_by_store_id_with_different_owner_THEN_expect_false() { + val obj = Any() + store.get(storeId, key) { obj } + + val isRetained = store.isRetainedByStoreId("different-identifier", obj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_object_stored_before_WHEN_checking_if_different_instance_retained_by_store_id_with_same_owner_THEN_expect_false() { + val obj = Any() + val otherObj = Any() + store.get(storeId, key) { obj } + + val isRetained = store.isRetainedByStoreId(storeId, otherObj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_object_stored_before_AND_then_removed_WHEN_checking_if_same_instance_retained_by_store_id_with_same_owner_THEN_expect_false() { + val obj = Any() + store.get(storeId, key) { obj } + store.clearStore(storeId) + + val isRetained = store.isRetainedByStoreId(storeId, obj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_no_object_stored_WHEN_checking_if_instance_retained_by_store_id_THEN_expect_false() { + val obj = Any() + + val isRetained = store.isRetainedByStoreId(storeId, obj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_object_stored_before_WHEN_checking_if_same_instance_retained_THEN_expect_true() { + val obj = Any() + store.get(storeId, key) { obj } + + val isRetained = store.isRetained(obj) + + assertTrue(isRetained) + } + + @Test + fun GIVEN_object_stored_before_WHEN_checking_if_different_instance_retained_THEN_expect_false() { + val obj = Any() + val otherObj = Any() + store.get(storeId, key) { obj } + + val isRetained = store.isRetained(otherObj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_object_stored_before_AND_then_removed_WHEN_checking_if_same_instance_retained_THEN_expect_false() { + val obj = Any() + store.get(storeId, key) { obj } + store.clearStore(storeId) + + val isRetained = store.isRetained(obj) + + assertFalse(isRetained) + } + + @Test + fun GIVEN_no_object_stored_WHEN_checking_if_instance_retained_THEN_expect_false() { + val obj = Any() + + val isRetained = store.isRetained(obj) + + assertFalse(isRetained) + } + +} 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 new file mode 100644 index 000000000..8dee15d44 --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/DesktopNodeHost.kt @@ -0,0 +1,71 @@ +package com.bumble.appyx.navigation.integration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.window.WindowState +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint +import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.platform.LocalOnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcher +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** + * 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 + * implementing the global "go back" functionality that is a common concept in the Appyx framework. + */ +@Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda +@Composable +fun DesktopNodeHost( + windowState: WindowState, + onBackPressedEvents: Flow, + modifier: Modifier = Modifier, + integrationPoint: IntegrationPoint = remember { MainIntegrationPoint() }, + customisations: NodeCustomisationDirectory = remember { NodeCustomisationDirectoryImpl() }, + factory: NodeFactory +) { + val screenSize = remember { + derivedStateOf { windowState.size.run { ScreenSize(width, height) } } + } + val platformLifecycleRegistry = remember { + PlatformLifecycleRegistry() + } + val onBackPressedDispatcherOwner = remember { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher = + OnBackPressedDispatcher { integrationPoint.handleUpNavigation() } + } + } + + val scope = rememberCoroutineScope() + LaunchedEffect(onBackPressedEvents) { + scope.launch { + onBackPressedEvents.collect { + onBackPressedDispatcherOwner.onBackPressedDispatcher.onBackPressed() + } + } + } + + CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides onBackPressedDispatcherOwner) { + NodeHost( + lifecycle = platformLifecycleRegistry, + integrationPoint = integrationPoint, + modifier = modifier, + customisations = customisations, + screenSize = screenSize.value, + factory = factory, + ) + } +} \ No newline at end of file diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt new file mode 100644 index 000000000..774f3432f --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt @@ -0,0 +1,17 @@ +package com.bumble.appyx.navigation.integration + +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint +import kotlin.system.exitProcess + +class MainIntegrationPoint : IntegrationPoint() { + override val isChangingConfigurations: Boolean + get() = false + + override fun onRootFinished() { + exitProcess(0) + } + + override fun handleUpNavigation() { + exitProcess(0) + } +} diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt new file mode 100644 index 000000000..416f88f2e --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt @@ -0,0 +1,59 @@ +package com.bumble.appyx.navigation.platform + +import java.util.concurrent.CopyOnWriteArrayList + +interface Cancellable { + /** + * Cancel the subscription. This call should be idempotent, making it safe to + * call multiple times. + */ + fun cancel() +} + +/** + * Create a [OnBackPressedCallback]. + * + * @param isEnabled The default enabled state for this callback. + * @see .setEnabled + */ +abstract class OnBackPressedCallback( + /** + * Set the enabled state of the callback. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @param isEnabled whether the callback should be considered enabled + */ + var isEnabled: Boolean +) { + /** + * Checks whether this callback should be considered enabled. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @return Whether this callback should be considered enabled. + */ + private val cancellables: CopyOnWriteArrayList = + CopyOnWriteArrayList() + + /** + * Removes this callback from any [OnBackPressedDispatcher] it is currently + * added to. + */ + fun remove() { + for (cancellable in cancellables) { + cancellable.cancel() + } + } + + /** + * Callback for handling the [OnBackPressedDispatcher.onBackPressed] event. + */ + abstract fun handleOnBackPressed() + + fun addCancellable(cancellable: Cancellable) { + cancellables.add(cancellable) + } + + fun removeCancellable(cancellable: Cancellable) { + cancellables.remove(cancellable) + } +} diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt new file mode 100644 index 000000000..af8c79d2c --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt @@ -0,0 +1,71 @@ +package com.bumble.appyx.navigation.platform + +import java.util.ArrayDeque + +/** + * Adapted from Android's OnBackPressedDispatcher. + * + * Create a new OnBackPressedDispatcher that dispatches [.onBackPressed] events + * to one or more [OnBackPressedCallback] instances. + * + * @param fallbackOnBackPressed The Runnable that should be triggered if + * [.onBackPressed] is called when no [OnBackPressedCallback] have been registered. + */ +class OnBackPressedDispatcher(private val fallbackOnBackPressed: (() -> Unit)? = null) { + val onBackPressedCallbacks: ArrayDeque = + ArrayDeque() + + /** + * Internal implementation of [.addCallback] that gives + * access to the [Cancellable] that specifically removes this callback from + * the dispatcher without relying on [OnBackPressedCallback.remove] which + * is what external developers should be using. + * + * @param onBackPressedCallback The callback to add + * @return a [Cancellable] which can be used to [cancel][Cancellable.cancel] + * the callback and remove it from the set of OnBackPressedCallbacks. + */ + fun addCancellableCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable { + onBackPressedCallbacks.add(onBackPressedCallback) + val cancellable = OnBackPressedCancellable(onBackPressedCallback) + onBackPressedCallback.addCancellable(cancellable) + return cancellable + } + + /** + * Trigger a call to the currently added [callbacks][OnBackPressedCallback] in reverse + * order in which they were added. Only if the most recently added callback is not + * [enabled][OnBackPressedCallback.isEnabled] + * will any previously added callback be called. + * + * + * If [.hasEnabledCallbacks] is `false` when this method is called, the + * fallback Runnable set by [the constructor][.OnBackPressedDispatcher] + * will be triggered. + */ + fun onBackPressed() { + val iterator: Iterator = onBackPressedCallbacks.descendingIterator() + while (iterator.hasNext()) { + val callback: OnBackPressedCallback = iterator.next() + if (callback.isEnabled) { + callback.handleOnBackPressed() + return + } + } + fallbackOnBackPressed?.invoke() + } + + private inner class OnBackPressedCancellable(onBackPressedCallback: OnBackPressedCallback) : + Cancellable { + private val onBackPressedCallback: OnBackPressedCallback + + init { + this.onBackPressedCallback = onBackPressedCallback + } + + override fun cancel() { + onBackPressedCallbacks.remove(onBackPressedCallback) + onBackPressedCallback.removeCancellable(this) + } + } +} diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt new file mode 100644 index 000000000..f41e0cfe9 --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt @@ -0,0 +1,55 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState + +val LocalOnBackPressedDispatcherOwner: ProvidableCompositionLocal = + compositionLocalOf { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher + get() = OnBackPressedDispatcher(null) + } + } + +interface OnBackPressedDispatcherOwner { + val onBackPressedDispatcher: OnBackPressedDispatcher +} + +@Composable +actual fun PlatformBackHandler( + enabled: Boolean, + onBack: () -> Unit +) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { + currentOnBack() + } + } + } + // On every successful composition, update the callback with the `enabled` value + SideEffect { + backCallback.isEnabled = enabled + } + + // register for back events only whilst present in the composition + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + DisposableEffect(backDispatcher) { + val cancellable = backDispatcher.addCancellableCallback(backCallback) + + onDispose { + cancellable.cancel() + } + } +} diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt new file mode 100644 index 000000000..517c831b1 --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt @@ -0,0 +1,89 @@ +package com.bumble.appyx.navigation.platform + +import com.bumble.appyx.navigation.lifecycle.CommonLifecycleOwner +import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleObserver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive + + +actual class PlatformLifecycleRegistry : Lifecycle { + + private val managedDefaultLifecycleObservers: MutableList = + ArrayList() + private val managedLifecycleEventObservers: MutableList = + ArrayList() + + private var _currentState: Lifecycle.State = Lifecycle.State.INITIALIZED + override val currentState: Lifecycle.State + get() = _currentState + + override val coroutineScope: CoroutineScope by lazy { MainScope() } + + override fun addObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.add(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.add(observer) + } + } + + override fun removeObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.remove(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.remove(observer) + } + } + + actual fun setCurrentState(state: Lifecycle.State) { + when (state) { + Lifecycle.State.INITIALIZED -> Unit + Lifecycle.State.CREATED -> { + managedDefaultLifecycleObservers.forEach { it.onCreate() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + state, + Lifecycle.Event.ON_CREATE + ) + } + } + Lifecycle.State.STARTED -> { + managedDefaultLifecycleObservers.forEach { it.onStart() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + state, + Lifecycle.Event.ON_START + ) + } + } + Lifecycle.State.RESUMED -> { + managedDefaultLifecycleObservers.forEach { it.onResume() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + state, + Lifecycle.Event.ON_RESUME + ) + } + } + Lifecycle.State.DESTROYED -> { + managedDefaultLifecycleObservers.forEach { it.onDestroy() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + state, + Lifecycle.Event.ON_DESTROY + ) + } + if (coroutineScope.isActive) coroutineScope.cancel("lifecycle was destroyed") + } + } + _currentState = state + } + + actual companion object { + actual fun create(owner: CommonLifecycleOwner): PlatformLifecycleRegistry = + PlatformLifecycleRegistry() + } +} diff --git a/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt new file mode 100644 index 000000000..86c7cf34f --- /dev/null +++ b/appyx-navigation/common/src/desktopMain/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreExt.kt @@ -0,0 +1,35 @@ +package com.bumble.appyx.navigation.store + +import com.bumble.appyx.navigation.modality.BuildContext + +/** + * Obtains or creates an instance of a class using the class name as the key. + * If you need multiple instances of an object with the same key, do not use this extension. + */ +inline fun RetainedInstanceStore.get( + storeId: String, + noinline disposer: (T) -> Unit = {}, + noinline factory: () -> T +): T = + get( + storeId = storeId, + key = T::class.java.name, + disposer = disposer, + factory = factory + ) + +/** + * Obtains or creates an instance of a class using the class name as the key, and uses the + * identifier from the BuildContext. + * + * If you need multiple instances of an object with the same key, do not use this extension. + */ +inline fun BuildContext.getRetainedInstance( + noinline disposer: (T) -> Unit = {}, + noinline factory: () -> T +) = + RetainedInstanceStore.get( + storeId = identifier, + disposer = disposer, + factory = factory + ) diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt new file mode 100644 index 000000000..2d3072c5d --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/BrowserViewportWindow.kt @@ -0,0 +1,82 @@ +// From Slack by OliverO +// See: https://kotlinlang.slack.com/archives/C01F2HV7868/p1660083429206369?thread_ts=1660083398.571449&cid=C01F2HV7868 +// adapted with scaling fix from https://github.com/OliverO2/compose-counting-grid/blob/master/src/frontendJsMain/kotlin/BrowserViewportWindow.kt + +@file:Suppress( + "INVISIBLE_MEMBER", + "INVISIBLE_REFERENCE", + "EXPOSED_PARAMETER_TYPE" +) // WORKAROUND: ComposeWindow and ComposeLayer are internal + +import androidx.compose.runtime.Composable +import androidx.compose.ui.window.ComposeWindow +import kotlinx.browser.document +import kotlinx.browser.window +import org.w3c.dom.HTMLCanvasElement +import org.w3c.dom.HTMLStyleElement +import org.w3c.dom.HTMLTitleElement + +private const val CANVAS_ELEMENT_ID = "ComposeTarget" // Hardwired into ComposeWindow + +/** + * A Skiko/Canvas-based top-level window using the browser's entire viewport. Supports resizing. + */ +fun BrowserViewportWindow( + title: String = "Untitled", + content: @Composable ComposeWindow.() -> Unit +) { + val htmlHeadElement = document.head!! + htmlHeadElement.appendChild( + (document.createElement("style") as HTMLStyleElement).apply { + type = "text/css" + appendChild( + document.createTextNode( + """ + html, body { + overflow: hidden; + margin: 0 !important; + padding: 0 !important; + } + + #$CANVAS_ELEMENT_ID { + outline: none; + } + """.trimIndent() + ) + ) + } + ) + + fun HTMLCanvasElement.fillViewportSize() { + setAttribute("width", "${window.innerWidth}") + setAttribute("height", "${window.innerHeight}") + } + + val canvas = (document.getElementById(CANVAS_ELEMENT_ID) as HTMLCanvasElement).apply { + fillViewportSize() + } + + ComposeWindow().apply { + window.addEventListener("resize", { + canvas.fillViewportSize() + layer.layer.attachTo(canvas) + layer.layer.needRedraw() + val scale = layer.layer.contentScale + layer.setSize( + (canvas.width / scale * density.density).toInt(), + (canvas.height / scale * density.density).toInt() + ) + }) + + // WORKAROUND: ComposeWindow does not implement `setTitle(title)` + val htmlTitleElement = ( + htmlHeadElement.getElementsByTagName("title").item(0) + ?: document.createElement("title").also { htmlHeadElement.appendChild(it) } + ) as HTMLTitleElement + htmlTitleElement.textContent = title + + setContent { + content(this) + } + } +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt new file mode 100644 index 000000000..079829509 --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/MainIntegrationPoint.kt @@ -0,0 +1,15 @@ +package com.bumble.appyx.navigation.integration + +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint + +class MainIntegrationPoint : IntegrationPoint() { + override val isChangingConfigurations: Boolean + get() = false + + override fun onRootFinished() { + } + + override fun handleUpNavigation() { + + } +} 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 new file mode 100644 index 000000000..4af59b070 --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/integration/WebNodeHost.kt @@ -0,0 +1,66 @@ +package com.bumble.appyx.navigation.integration + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import com.bumble.appyx.navigation.integrationpoint.IntegrationPoint +import com.bumble.appyx.navigation.node.Node +import com.bumble.appyx.navigation.platform.LocalOnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcher +import com.bumble.appyx.navigation.platform.OnBackPressedDispatcherOwner +import com.bumble.appyx.navigation.platform.PlatformLifecycleRegistry +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectory +import com.bumble.appyx.utils.customisations.NodeCustomisationDirectoryImpl +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +/** + * 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 + * that is a common concept in the Appyx framework. + */ +//@Suppress("ComposableParamOrder") // detekt complains as 'factory' param isn't a pure lambda +@Composable +fun WebNodeHost( + screenSize: ScreenSize, + onBackPressedEvents: Flow, + modifier: Modifier = Modifier, + integrationPoint: IntegrationPoint = remember { MainIntegrationPoint() }, + customisations: NodeCustomisationDirectory = remember { NodeCustomisationDirectoryImpl() }, + factory: NodeFactory +) { + val platformLifecycleRegistry = remember { + PlatformLifecycleRegistry() + } + val onBackPressedDispatcherOwner = remember { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher = + OnBackPressedDispatcher { integrationPoint.handleUpNavigation() } + } + } + + val scope = rememberCoroutineScope() + LaunchedEffect(onBackPressedEvents) { + scope.launch { + onBackPressedEvents.collect { + onBackPressedDispatcherOwner.onBackPressedDispatcher.onBackPressed() + } + } + } + + CompositionLocalProvider(LocalOnBackPressedDispatcherOwner provides onBackPressedDispatcherOwner) { + NodeHost( + lifecycle = platformLifecycleRegistry, + integrationPoint = integrationPoint, + modifier = modifier, + customisations = customisations, + screenSize = screenSize, + factory = factory, + ) + } +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt new file mode 100644 index 000000000..9ad13615c --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedCallback.kt @@ -0,0 +1,58 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.runtime.AtomicReference + +interface Cancellable { + /** + * Cancel the subscription. This call should be idempotent, making it safe to + * call multiple times. + */ + fun cancel() +} + +/** + * Create a [OnBackPressedCallback]. + * + * @param isEnabled The default enabled state for this callback. + * @see .setEnabled + */ +abstract class OnBackPressedCallback( + /** + * Set the enabled state of the callback. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @param isEnabled whether the callback should be considered enabled + */ + var isEnabled: Boolean +) { + /** + * Checks whether this callback should be considered enabled. Only when this callback + * is enabled will it receive callbacks to [.handleOnBackPressed]. + * + * @return Whether this callback should be considered enabled. + */ + private val cancellablesReference: AtomicReference> = + AtomicReference(emptyList()) + + /** + * Removes this callback from any [OnBackPressedDispatcher] it is currently + * added to. + */ + fun remove() { + for (cancellable in cancellablesReference.get()) { + cancellable.cancel() + } + } + + /** + * Callback for handling the [OnBackPressedDispatcher.onBackPressed] event. + */ + abstract fun handleOnBackPressed() + fun addCancellable(cancellable: Cancellable) { + cancellablesReference.set(cancellablesReference.get() + cancellable) + } + + fun removeCancellable(cancellable: Cancellable) { + cancellablesReference.set(cancellablesReference.get() - cancellable) + } +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt new file mode 100644 index 000000000..0bf083749 --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/OnBackPressedDispatcher.kt @@ -0,0 +1,69 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.ui.util.fastForEachReversed + +/** + * Adapted from Android's OnBackPressedDispatcher. + * + * Create a new OnBackPressedDispatcher that dispatches [.onBackPressed] events + * to one or more [OnBackPressedCallback] instances. + * + * @param fallbackOnBackPressed The Runnable that should be triggered if + * [.onBackPressed] is called when no [OnBackPressedCallback] have been registered. + */ +class OnBackPressedDispatcher(private val fallbackOnBackPressed: (() -> Unit)? = null) { + val onBackPressedCallbacks: ArrayDeque = + ArrayDeque() + + /** + * Internal implementation of [.addCallback] that gives + * access to the [Cancellable] that specifically removes this callback from + * the dispatcher without relying on [OnBackPressedCallback.remove] which + * is what external developers should be using. + * + * @param onBackPressedCallback The callback to add + * @return a [Cancellable] which can be used to [cancel][Cancellable.cancel] + * the callback and remove it from the set of OnBackPressedCallbacks. + */ + fun addCancellableCallback(onBackPressedCallback: OnBackPressedCallback): Cancellable { + onBackPressedCallbacks.add(onBackPressedCallback) + val cancellable = OnBackPressedCancellable(onBackPressedCallback) + onBackPressedCallback.addCancellable(cancellable) + return cancellable + } + + /** + * Trigger a call to the currently added [callbacks][OnBackPressedCallback] in reverse + * order in which they were added. Only if the most recently added callback is not + * [enabled][OnBackPressedCallback.isEnabled] + * will any previously added callback be called. + * + * + * If [.hasEnabledCallbacks] is `false` when this method is called, the + * fallback Runnable set by [the constructor][.OnBackPressedDispatcher] + * will be triggered. + */ + fun onBackPressed() { + onBackPressedCallbacks.fastForEachReversed { callback -> + if (callback.isEnabled) { + callback.handleOnBackPressed() + return + } + } + fallbackOnBackPressed?.invoke() + } + + private inner class OnBackPressedCancellable(onBackPressedCallback: OnBackPressedCallback) : + Cancellable { + private val onBackPressedCallback: OnBackPressedCallback + + init { + this.onBackPressedCallback = onBackPressedCallback + } + + override fun cancel() { + onBackPressedCallbacks.remove(onBackPressedCallback) + onBackPressedCallback.removeCancellable(this) + } + } +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt new file mode 100644 index 000000000..f51125ab9 --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformBackHandler.kt @@ -0,0 +1,55 @@ +package com.bumble.appyx.navigation.platform + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.SideEffect +import androidx.compose.runtime.compositionLocalOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState + +val LocalOnBackPressedDispatcherOwner: ProvidableCompositionLocal = + compositionLocalOf { + object : OnBackPressedDispatcherOwner { + override val onBackPressedDispatcher: OnBackPressedDispatcher + get() = OnBackPressedDispatcher(null) + } + } + +interface OnBackPressedDispatcherOwner { + val onBackPressedDispatcher: OnBackPressedDispatcher +} + +@Composable +actual fun PlatformBackHandler( + enabled: Boolean, + onBack: () -> Unit +) { + // Safely update the current `onBack` lambda when a new one is provided + val currentOnBack by rememberUpdatedState(onBack) + // Remember in Composition a back callback that calls the `onBack` lambda + val backCallback = remember { + object : OnBackPressedCallback(enabled) { + override fun handleOnBackPressed() { + currentOnBack() + } + } + } + // On every successful composition, update the callback with the `enabled` value + SideEffect { + backCallback.isEnabled = enabled + } + + // register for back events only whilst present in the composition + val backDispatcher = checkNotNull(LocalOnBackPressedDispatcherOwner.current) { + "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" + }.onBackPressedDispatcher + DisposableEffect(backDispatcher) { + val cancellable = backDispatcher.addCancellableCallback(backCallback) + + onDispose { + cancellable.cancel() + } + } +} diff --git a/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt new file mode 100644 index 000000000..518ca5afa --- /dev/null +++ b/appyx-navigation/common/src/jsMain/kotlin/com/bumble/appyx/navigation/platform/PlatformLifecycleRegistry.kt @@ -0,0 +1,91 @@ +package com.bumble.appyx.navigation.platform + +import com.bumble.appyx.navigation.lifecycle.CommonLifecycleOwner +import com.bumble.appyx.navigation.lifecycle.DefaultPlatformLifecycleObserver +import com.bumble.appyx.navigation.lifecycle.Lifecycle +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleEventObserver +import com.bumble.appyx.navigation.lifecycle.PlatformLifecycleObserver +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.MainScope +import kotlinx.coroutines.cancel +import kotlinx.coroutines.isActive + +actual class PlatformLifecycleRegistry : Lifecycle { + + private val managedDefaultLifecycleObservers: MutableList = + ArrayList() + private val managedLifecycleEventObservers: MutableList = + ArrayList() + + private var _currentState: Lifecycle.State = Lifecycle.State.INITIALIZED + override var currentState: Lifecycle.State + get() = _currentState + set(value) { + when (value) { + Lifecycle.State.INITIALIZED -> Unit + Lifecycle.State.CREATED -> { + managedDefaultLifecycleObservers.forEach { it.onCreate() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_CREATE + ) + } + } + Lifecycle.State.STARTED -> { + managedDefaultLifecycleObservers.forEach { it.onStart() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_START + ) + } + } + Lifecycle.State.RESUMED -> { + managedDefaultLifecycleObservers.forEach { it.onResume() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_RESUME + ) + } + } + Lifecycle.State.DESTROYED -> { + managedDefaultLifecycleObservers.forEach { it.onDestroy() } + managedLifecycleEventObservers.forEach { + it.onStateChanged( + value, + Lifecycle.Event.ON_DESTROY + ) + } + if (coroutineScope.isActive) coroutineScope.cancel("lifecycle was destroyed") + } + } + _currentState = value + } + + override val coroutineScope: CoroutineScope by lazy { MainScope() } + + override fun addObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.add(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.add(observer) + } + } + + override fun removeObserver(observer: PlatformLifecycleObserver) { + when (observer) { + is DefaultPlatformLifecycleObserver -> managedDefaultLifecycleObservers.remove(observer) + is PlatformLifecycleEventObserver -> managedLifecycleEventObservers.remove(observer) + } + } + + actual fun setCurrentState(state: Lifecycle.State) { + currentState = state + } + + actual companion object { + actual fun create(owner: CommonLifecycleOwner): PlatformLifecycleRegistry = + PlatformLifecycleRegistry() + } +} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt deleted file mode 100644 index 8662e553f..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/children/ChildCallback.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.bumble.appyx.navigation.children - -import androidx.lifecycle.Lifecycle - -typealias ChildrenCallback = (commonLifecycle: Lifecycle, child1: T1, child2: T2) -> Unit - -typealias ChildCallback = (commonLifecycle: Lifecycle, child: T) -> Unit diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt deleted file mode 100644 index cdce57eca..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPoint.kt +++ /dev/null @@ -1,28 +0,0 @@ -package com.bumble.appyx.navigation.integrationpoint - -import android.os.Bundle -import androidx.compose.runtime.Stable -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityStarter -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester -import com.bumble.appyx.navigation.integrationpoint.requestcode.RequestCodeRegistry -import com.bumble.appyx.navigation.navigation.upnavigation.UpNavigationHandler - -@Stable -abstract class IntegrationPoint( - protected val savedInstanceState: Bundle? -) : UpNavigationHandler { - - protected val requestCodeRegistry = RequestCodeRegistry(savedInstanceState) - - abstract val activityStarter: ActivityStarter - - abstract val permissionRequester: PermissionRequester - - abstract val isChangingConfigurations: Boolean - - fun onSaveInstanceState(outState: Bundle) { - requestCodeRegistry.onSaveInstanceState(outState) - } - - abstract fun onRootFinished() -} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt deleted file mode 100644 index d44503a7f..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/integrationpoint/IntegrationPointStub.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.bumble.appyx.navigation.integrationpoint - -import com.bumble.appyx.navigation.integrationpoint.activitystarter.ActivityStarter -import com.bumble.appyx.navigation.integrationpoint.permissionrequester.PermissionRequester - -class IntegrationPointStub : IntegrationPoint(savedInstanceState = null) { - companion object { - private const val ERROR = "You're accessing an IntegrationPointStub. " + - "This means you're using a Node without ever integrating it to a proper IntegrationPoint. " + - "This is fine during tests with limited scope, but it looks like the code that leads here " + - "requires interfacing with a valid implementation. " + - "You may be attempting to access the IntegrationPoint before it is attached to the Node." - } - - override val activityStarter: ActivityStarter - get() = error(ERROR) - - override val permissionRequester: PermissionRequester - get() = error(ERROR) - - override val isChangingConfigurations: Boolean - get() = false - - override fun handleUpNavigation() { - error(ERROR) - } - - override fun onRootFinished() { - error(ERROR) - } -} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt deleted file mode 100644 index 0d596587c..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/LifecycleLogger.kt +++ /dev/null @@ -1,35 +0,0 @@ -package com.bumble.appyx.navigation.lifecycle - -import android.util.Log -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.LifecycleOwner - -internal object LifecycleLogger : DefaultLifecycleObserver { - - private const val LOG_TAG = "Lifecycle" - - override fun onCreate(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onCreate") - } - - override fun onStart(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onStart") - } - - override fun onResume(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onResume") - } - - override fun onPause(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onPause") - } - - override fun onStop(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onStop") - } - - override fun onDestroy(owner: LifecycleOwner) { - Log.d(LOG_TAG, "${owner.javaClass.simpleName}@${owner.hashCode()} onDestroy") - } - -} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt deleted file mode 100644 index e99a45e43..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycle.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.bumble.appyx.navigation.lifecycle - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner - -interface NodeLifecycle : LifecycleOwner { - - fun updateLifecycleState(state: Lifecycle.State) - -} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt deleted file mode 100644 index ff34136a4..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/lifecycle/NodeLifecycleImpl.kt +++ /dev/null @@ -1,18 +0,0 @@ -package com.bumble.appyx.navigation.lifecycle - -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.LifecycleOwner -import androidx.lifecycle.LifecycleRegistry - -internal class NodeLifecycleImpl(owner: LifecycleOwner) : NodeLifecycle { - - private val lifecycleRegistry = LifecycleRegistry(owner) - - override val lifecycle: Lifecycle = - lifecycleRegistry - - override fun updateLifecycleState(state: Lifecycle.State) { - lifecycleRegistry.currentState = state - } - -} diff --git a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ViewFactory.kt b/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ViewFactory.kt deleted file mode 100644 index 0f9a47635..000000000 --- a/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/ViewFactory.kt +++ /dev/null @@ -1,3 +0,0 @@ -package com.bumble.appyx.navigation.node - -fun interface ViewFactory : () -> View diff --git a/appyx-navigation/src/test/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt b/appyx-navigation/src/test/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt deleted file mode 100644 index 49610bd5d..000000000 --- a/appyx-navigation/src/test/kotlin/com/bumble/appyx/navigation/store/RetainedInstanceStoreTest.kt +++ /dev/null @@ -1,177 +0,0 @@ -package com.bumble.appyx.navigation.store - -import org.junit.Assert.assertEquals -import org.junit.Assert.assertFalse -import org.junit.Assert.assertSame -import org.junit.Assert.assertTrue -import org.junit.Test - -class RetainedInstanceStoreTest { - - private val identifier = "identifier" - private val store = RetainedInstanceStoreImpl() - - @Test - fun `GIVEN object stored WHEN get with same identifier THEN same instance is retrieved`() { - val obj = Any() - store.get(identifier) { obj } - - val retrieved = store.get(identifier) { Any() } - - assertSame(obj, retrieved) - } - - @Test - fun `GIVEN object stored WHEN get with same identifier THEN factory not called`() { - var factoryCalled = false - store.get(identifier) { Any() } - - store.get(identifier) { - factoryCalled = true - Any() - } - - assertFalse(factoryCalled) - } - - @Test - fun `GIVEN two objects with different types stored WHEN get with same identifier THEN both objects returned`() { - store.get(identifier) { 1 } - store.get(identifier) { 2L } - - val integerValue = store.get(identifier) { 5 } - val longValue = store.get(identifier) { 6L } - - assertEquals(1, integerValue) - assertEquals(2L, longValue) - } - - @Test - fun `GIVEN two objects stored with same type AND different keys WHEN get with same identifier THEN both objects returned`() { - store.get(storeId = identifier, key = "1") { 1 } - store.get(storeId = identifier, key = "2") { 2 } - - val integerValue1 = store.get(storeId = identifier, key = "1") { 5 } - val integerValue2 = store.get(storeId = identifier, key = "2") { 6L } - - assertEquals(1, integerValue1) - assertEquals(2, integerValue2) - } - - @Test - fun `GIVEN object stored WHEN clearStore with same identifier THEN object is disposed`() { - val obj = Any() - var disposed = false - store.get(identifier, disposer = { disposed = true }) { obj } - - store.clearStore(identifier) - - assertTrue(disposed) - } - - @Test - fun `GIVEN object stored WHEN clearStore with different identifier THEN object is not disposed`() { - val obj = Any() - val otherIdentifier = "other" - var disposed = false - store.get(identifier) { obj } - store.get(otherIdentifier, disposer = { disposed = true }) { obj } - - store.clearStore(identifier) - - assertFalse(disposed) - } - - @Test - fun `GIVEN object stored before WHEN checking if same instance retained by store id with same owner THEN expect true`() { - val obj = Any() - store.get(identifier) { obj } - - val isRetained = store.isRetainedByStoreId(identifier, obj) - - assertTrue(isRetained) - } - - @Test - fun `GIVEN object stored before WHEN checking if same instance retained by store id with different owner THEN expect false`() { - val obj = Any() - store.get(identifier) { obj } - - val isRetained = store.isRetainedByStoreId("different-identifier", obj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN object stored before WHEN checking if different instance retained by store id with same owner THEN expect false`() { - val obj = Any() - val otherObj = Any() - store.get(identifier) { obj } - - val isRetained = store.isRetainedByStoreId(identifier, otherObj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN object stored before AND then removed WHEN checking if same instance retained by store id with same owner THEN expect false`() { - val obj = Any() - store.get(identifier) { obj } - store.clearStore(identifier) - - val isRetained = store.isRetainedByStoreId(identifier, obj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN no object stored WHEN checking if instance retained by store id THEN expect false`() { - val obj = Any() - - val isRetained = store.isRetainedByStoreId(identifier, obj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN object stored before WHEN checking if same instance retained THEN expect true`() { - val obj = Any() - store.get(identifier) { obj } - - val isRetained = store.isRetained(obj) - - assertTrue(isRetained) - } - - @Test - fun `GIVEN object stored before WHEN checking if different instance retained THEN expect false`() { - val obj = Any() - val otherObj = Any() - store.get(identifier) { obj } - - val isRetained = store.isRetained(otherObj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN object stored before AND then removed WHEN checking if same instance retained THEN expect false`() { - val obj = Any() - store.get(identifier) { obj } - store.clearStore(identifier) - - val isRetained = store.isRetained(obj) - - assertFalse(isRetained) - } - - @Test - fun `GIVEN no object stored WHEN checking if instance retained THEN expect false`() { - val obj = Any() - - val isRetained = store.isRetained(obj) - - assertFalse(isRetained) - } - -} 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 index 4d45dc415..c8127d5ac 100644 --- 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 @@ -13,9 +13,9 @@ 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.BackStack 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.SampleChildren @@ -31,7 +31,11 @@ fun BackStackExperimentDebug(modifier: Modifier = Modifier) { BackStack( scope = coroutineScope, model = BackStackModel( - initialTargets = listOf(InteractionTarget.Child1, InteractionTarget.Child2, InteractionTarget.Child3), + initialTargets = listOf( + InteractionTarget.Child1, + InteractionTarget.Child2, + InteractionTarget.Child3 + ), savedStateMap = null ), motionController = { BackStackFader(it) }, diff --git a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/KnobControl.kt b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/KnobControl.kt index 1eda09d4f..5cb4055bd 100644 --- a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/KnobControl.kt +++ b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/KnobControl.kt @@ -27,8 +27,8 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.theme.appyx_yellow1 +import com.bumble.appyx.utils.multiplatform.AppyxLogger import kotlin.math.roundToInt diff --git a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperiment.kt b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperiment.kt index 6a20a6881..878801428 100644 --- a/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperiment.kt +++ b/demos/appyx-interactions/android/src/main/kotlin/com/bumble/appyx/interactions/sample/SpotlightExperiment.kt @@ -27,7 +27,6 @@ import com.bumble.appyx.components.spotlight.operation.next import com.bumble.appyx.components.spotlight.operation.previous import com.bumble.appyx.components.spotlight.operation.updateElements import com.bumble.appyx.components.spotlight.ui.slider.SpotlightSlider -import com.bumble.appyx.interactions.AppyxLogger import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.gesture.GestureSettleConfig import com.bumble.appyx.interactions.core.ui.helper.AppyxComponentSetup @@ -35,6 +34,7 @@ import com.bumble.appyx.interactions.sample.android.Element import com.bumble.appyx.interactions.sample.android.SampleChildren import com.bumble.appyx.interactions.theme.appyx_dark import com.bumble.appyx.transitionmodel.BaseMotionController +import com.bumble.appyx.utils.multiplatform.AppyxLogger import com.bumble.appyx.interactions.sample.InteractionTarget as Target @ExperimentalMaterialApi 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 index 08391e5be..d09d5c30a 100644 --- 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 @@ -19,8 +19,8 @@ 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.SampleChildren import com.bumble.appyx.interactions.sample.android.Element +import com.bumble.appyx.interactions.sample.android.SampleChildren import com.bumble.appyx.interactions.theme.appyx_dark @ExperimentalMaterialApi diff --git a/demos/appyx-interactions/desktop/src/desktopMain/kotlin/com/bumble/appyx/interactions/widgets/Widgets.kt b/demos/appyx-interactions/desktop/src/desktopMain/kotlin/com/bumble/appyx/interactions/widgets/Widgets.kt index b149f832a..ca94b843f 100644 --- a/demos/appyx-interactions/desktop/src/desktopMain/kotlin/com/bumble/appyx/interactions/widgets/Widgets.kt +++ b/demos/appyx-interactions/desktop/src/desktopMain/kotlin/com/bumble/appyx/interactions/widgets/Widgets.kt @@ -110,7 +110,7 @@ private fun WidgetsUi( .fillMaxWidth() .requiredHeight(240.dp) ) - } + }, ) } diff --git a/demos/appyx-navigation/build.gradle.kts b/demos/appyx-navigation/android/build.gradle.kts similarity index 89% rename from demos/appyx-navigation/build.gradle.kts rename to demos/appyx-navigation/android/build.gradle.kts index abb580f6d..d46fbafd3 100644 --- a/demos/appyx-navigation/build.gradle.kts +++ b/demos/appyx-navigation/android/build.gradle.kts @@ -56,14 +56,11 @@ dependencies { val composeBom = platform(libs.compose.bom) implementation(composeBom) - implementation(project(":appyx-navigation")) implementation(project(":demos:common")) + implementation(project(":demos:appyx-navigation:common")) implementation(project(":appyx-interactions:appyx-interactions")) - implementation(project(":appyx-components:stable:spotlight:spotlight")) implementation(project(":appyx-components:stable:backstack:backstack")) - implementation(project(":appyx-components:experimental:modal:modal")) implementation(project(":appyx-components:experimental:cards:cards")) - implementation(project(":appyx-components:experimental:promoter:promoter")) implementation(libs.androidx.activity.compose) implementation(libs.androidx.appcompat) diff --git a/demos/appyx-navigation/debug.keystore b/demos/appyx-navigation/android/debug.keystore similarity index 100% rename from demos/appyx-navigation/debug.keystore rename to demos/appyx-navigation/android/debug.keystore diff --git a/demos/appyx-navigation/detekt-baseline.xml b/demos/appyx-navigation/android/detekt-baseline.xml similarity index 100% rename from demos/appyx-navigation/detekt-baseline.xml rename to demos/appyx-navigation/android/detekt-baseline.xml diff --git a/demos/appyx-navigation/detekt.yml b/demos/appyx-navigation/android/detekt.yml similarity index 100% rename from demos/appyx-navigation/detekt.yml rename to demos/appyx-navigation/android/detekt.yml diff --git a/demos/appyx-navigation/lint-baseline.xml b/demos/appyx-navigation/android/lint-baseline.xml similarity index 100% rename from demos/appyx-navigation/lint-baseline.xml rename to demos/appyx-navigation/android/lint-baseline.xml diff --git a/demos/appyx-navigation/lint.xml b/demos/appyx-navigation/android/lint.xml similarity index 100% rename from demos/appyx-navigation/lint.xml rename to demos/appyx-navigation/android/lint.xml diff --git a/demos/appyx-navigation/proguard-rules.pro b/demos/appyx-navigation/android/proguard-rules.pro similarity index 100% rename from demos/appyx-navigation/proguard-rules.pro rename to demos/appyx-navigation/android/proguard-rules.pro diff --git a/demos/appyx-navigation/src/main/AndroidManifest.xml b/demos/appyx-navigation/android/src/main/AndroidManifest.xml similarity index 100% rename from demos/appyx-navigation/src/main/AndroidManifest.xml rename to demos/appyx-navigation/android/src/main/AndroidManifest.xml diff --git a/demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt b/demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt similarity index 77% rename from demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt rename to demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt index e72ff6f89..5e4621b83 100644 --- a/demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt +++ b/demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/MainActivity.kt @@ -8,13 +8,15 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.runtime.Composable import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.ExperimentalUnitApi import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import com.bumble.appyx.navigation.integration.NodeActivity import com.bumble.appyx.navigation.integration.NodeHost -import com.bumble.appyx.navigation.integrationpoint.NodeActivity import com.bumble.appyx.navigation.modality.BuildContext import com.bumble.appyx.navigation.node.container.ContainerNode +import com.bumble.appyx.navigation.platform.AndroidLifecycle import com.bumble.appyx.navigation.ui.AppyxSampleAppTheme @ExperimentalUnitApi @@ -29,12 +31,13 @@ class MainActivity : NodeActivity() { AppyxSampleAppTheme { // A surface container using the 'background' color from the theme Surface(color = MaterialTheme.colorScheme.background) { - Column { - NodeHost(integrationPoint = appyxV2IntegrationPoint) { - ContainerNode( - buildContext = it, - ) - } + NodeHost( + AndroidLifecycle(LocalLifecycleOwner.current.lifecycle), + integrationPoint = appyxV2IntegrationPoint, + ) { + ContainerNode( + buildContext = it, + ) } } } diff --git a/demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt b/demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt similarity index 95% rename from demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt rename to demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt index de5352b6a..51787f0a6 100644 --- a/demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt +++ b/demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/node/datingcards/DatingCardsNode.kt @@ -1,6 +1,5 @@ package com.bumble.appyx.navigation.node.datingcards -import android.os.Parcelable import androidx.compose.foundation.background import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding @@ -18,7 +17,8 @@ import com.bumble.appyx.navigation.node.datingcards.DatingCardsNode.InteractionT import com.bumble.appyx.navigation.node.profilecard.ProfileCardNode import com.bumble.appyx.navigation.ui.appyx_dark import com.bumble.appyx.samples.common.profile.Profile -import kotlinx.parcelize.Parcelize +import com.bumble.appyx.utils.multiplatform.Parcelable +import com.bumble.appyx.utils.multiplatform.Parcelize class DatingCardsNode( buildContext: BuildContext, diff --git a/demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/profilecard/ProfileCardNode.kt b/demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/node/profilecard/ProfileCardNode.kt similarity index 100% rename from demos/appyx-navigation/src/main/kotlin/com/bumble/appyx/navigation/node/profilecard/ProfileCardNode.kt rename to demos/appyx-navigation/android/src/main/kotlin/com/bumble/appyx/navigation/node/profilecard/ProfileCardNode.kt diff --git a/demos/appyx-navigation/src/main/res/drawable/appyx.xml b/demos/appyx-navigation/android/src/main/res/drawable/appyx.xml similarity index 100% rename from demos/appyx-navigation/src/main/res/drawable/appyx.xml rename to demos/appyx-navigation/android/src/main/res/drawable/appyx.xml diff --git a/demos/appyx-navigation/src/main/res/values-night/themes.xml b/demos/appyx-navigation/android/src/main/res/values-night/themes.xml similarity index 95% rename from demos/appyx-navigation/src/main/res/values-night/themes.xml rename to demos/appyx-navigation/android/src/main/res/values-night/themes.xml index 156cbcc42..4ee3696c4 100644 --- a/demos/appyx-navigation/src/main/res/values-night/themes.xml +++ b/demos/appyx-navigation/android/src/main/res/values-night/themes.xml @@ -1,4 +1,4 @@ - +