diff --git a/decompose/api/android/decompose.api b/decompose/api/android/decompose.api index 0e49fac26..829c49e0a 100644 --- a/decompose/api/android/decompose.api +++ b/decompose/api/android/decompose.api @@ -234,6 +234,126 @@ public final class com/arkivanov/decompose/router/pages/PagesNavigatorExtKt { public static synthetic fun selectPrev$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } +public final class com/arkivanov/decompose/router/panels/ChildPanels { + public fun (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)V + public synthetic fun (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component2 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component3 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component4 ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public final fun copy (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Lcom/arkivanov/decompose/router/panels/ChildPanels; + public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/panels/ChildPanels; + public fun equals (Ljava/lang/Object;)Z + public final fun getDetails ()Lcom/arkivanov/decompose/Child$Created; + public final fun getExtra ()Lcom/arkivanov/decompose/Child$Created; + public final fun getMain ()Lcom/arkivanov/decompose/Child$Created; + public final fun getMode ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsFactoryKt { + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Pair;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Triple;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Pair;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Triple;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsMode : java/lang/Enum { + public static final field DUAL Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static final field SINGLE Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static final field TRIPLE Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static fun values ()[Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsModeKt { + public static final fun isDual (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z + public static final fun isSingle (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z + public static final fun isTriple (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z +} + +public final class com/arkivanov/decompose/router/panels/Panels { + public static final field Companion Lcom/arkivanov/decompose/router/panels/Panels$Companion; + public fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)V + public synthetic fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Ljava/lang/Object; + public final fun component3 ()Ljava/lang/Object; + public final fun component4 ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public final fun copy (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Lcom/arkivanov/decompose/router/panels/Panels; + public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/panels/Panels;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/panels/Panels; + public fun equals (Ljava/lang/Object;)Z + public final fun getDetails ()Ljava/lang/Object; + public final fun getExtra ()Ljava/lang/Object; + public final fun getMain ()Ljava/lang/Object; + public final fun getMode ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public synthetic class com/arkivanov/decompose/router/panels/Panels$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public fun (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/arkivanov/decompose/router/panels/Panels; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/arkivanov/decompose/router/panels/Panels;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/arkivanov/decompose/router/panels/Panels$Companion { + public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +} + +public abstract interface class com/arkivanov/decompose/router/panels/PanelsNavigation : com/arkivanov/decompose/router/children/NavigationSource, com/arkivanov/decompose/router/panels/PanelsNavigator { +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigation$Event { + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getOnComplete ()Lkotlin/jvm/functions/Function2; + public final fun getTransformer ()Lkotlin/jvm/functions/Function1; +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigationKt { + public static final fun PanelsNavigation ()Lcom/arkivanov/decompose/router/panels/PanelsNavigation; +} + +public abstract interface class com/arkivanov/decompose/router/panels/PanelsNavigator { + public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigatorExtKt { + public static final fun activateDetails (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateDetails$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun activateExtra (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateExtra$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun activateMain (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateMain$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun dismissDetails (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun dismissDetails$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun dismissExtra (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun dismissExtra$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun pop (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun pop$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun setMode (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun setMode$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + public final class com/arkivanov/decompose/router/slot/ChildSlot { public fun ()V public fun (Lcom/arkivanov/decompose/Child$Created;)V diff --git a/decompose/api/decompose.klib.api b/decompose/api/decompose.klib.api index 35d62aaae..59791561f 100644 --- a/decompose/api/decompose.klib.api +++ b/decompose/api/decompose.klib.api @@ -22,6 +22,18 @@ open annotation class com.arkivanov.decompose/InternalDecomposeApi : kotlin/Anno constructor () // com.arkivanov.decompose/InternalDecomposeApi.|(){}[0] } +final enum class com.arkivanov.decompose.router.panels/ChildPanelsMode : kotlin/Enum { // com.arkivanov.decompose.router.panels/ChildPanelsMode|null[0] + enum entry DUAL // com.arkivanov.decompose.router.panels/ChildPanelsMode.DUAL|null[0] + enum entry SINGLE // com.arkivanov.decompose.router.panels/ChildPanelsMode.SINGLE|null[0] + enum entry TRIPLE // com.arkivanov.decompose.router.panels/ChildPanelsMode.TRIPLE|null[0] + + final val entries // com.arkivanov.decompose.router.panels/ChildPanelsMode.entries|#static{}entries[0] + final fun (): kotlin.enums/EnumEntries // com.arkivanov.decompose.router.panels/ChildPanelsMode.entries.|#static(){}[0] + + final fun valueOf(kotlin/String): com.arkivanov.decompose.router.panels/ChildPanelsMode // com.arkivanov.decompose.router.panels/ChildPanelsMode.valueOf|valueOf#static(kotlin.String){}[0] + final fun values(): kotlin/Array // com.arkivanov.decompose.router.panels/ChildPanelsMode.values|values#static(){}[0] +} + final enum class com.arkivanov.decompose.value/ObserveLifecycleMode : kotlin/Enum { // com.arkivanov.decompose.value/ObserveLifecycleMode|null[0] enum entry CREATE_DESTROY // com.arkivanov.decompose.value/ObserveLifecycleMode.CREATE_DESTROY|null[0] enum entry RESUME_PAUSE // com.arkivanov.decompose.value/ObserveLifecycleMode.RESUME_PAUSE|null[0] @@ -42,6 +54,21 @@ abstract fun interface com.arkivanov.decompose/Cancellation { // com.arkivanov.d abstract fun cancel() // com.arkivanov.decompose/Cancellation.cancel|cancel(){}[0] } +abstract interface <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> com.arkivanov.decompose.router.panels/PanelsNavigation : com.arkivanov.decompose.router.children/NavigationSource>, com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C> { // com.arkivanov.decompose.router.panels/PanelsNavigation|null[0] + final class <#A1: kotlin/Any, #B1: kotlin/Any, #C1: kotlin/Any> Event { // com.arkivanov.decompose.router.panels/PanelsNavigation.Event|null[0] + constructor (kotlin/Function1, com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1>>, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/PanelsNavigation.Event.|(kotlin.Function1,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>>;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>,kotlin.Unit>){}[0] + + final val onComplete // com.arkivanov.decompose.router.panels/PanelsNavigation.Event.onComplete|{}onComplete[0] + final fun (): kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1>, kotlin/Unit> // com.arkivanov.decompose.router.panels/PanelsNavigation.Event.onComplete.|(){}[0] + final val transformer // com.arkivanov.decompose.router.panels/PanelsNavigation.Event.transformer|{}transformer[0] + final fun (): kotlin/Function1, com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1>> // com.arkivanov.decompose.router.panels/PanelsNavigation.Event.transformer.|(){}[0] + } +} + +abstract interface <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> com.arkivanov.decompose.router.panels/PanelsNavigator { // com.arkivanov.decompose.router.panels/PanelsNavigator|null[0] + abstract fun navigate(kotlin/Function1, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>>, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit>) // com.arkivanov.decompose.router.panels/PanelsNavigator.navigate|navigate(kotlin.Function1,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>>;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>,kotlin.Unit>){}[0] +} + abstract interface <#A: kotlin/Any> com.arkivanov.decompose.router.pages/PagesNavigation : com.arkivanov.decompose.router.children/NavigationSource>, com.arkivanov.decompose.router.pages/PagesNavigator<#A> { // com.arkivanov.decompose.router.pages/PagesNavigation|null[0] final class <#A1: kotlin/Any> Event { // com.arkivanov.decompose.router.pages/PagesNavigation.Event|null[0] constructor (kotlin/Function1, com.arkivanov.decompose.router.pages/Pages<#A1>>, kotlin/Function2, com.arkivanov.decompose.router.pages/Pages<#A1>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.pages/PagesNavigation.Event.|(kotlin.Function1,com.arkivanov.decompose.router.pages.Pages<1:0>>;kotlin.Function2,com.arkivanov.decompose.router.pages.Pages<1:0>,kotlin.Unit>){}[0] @@ -158,6 +185,74 @@ final class <#A: kotlin/Any> com.arkivanov.decompose.router.children/SimpleNavig final fun subscribe(kotlin/Function1<#A, kotlin/Unit>): com.arkivanov.decompose/Cancellation // com.arkivanov.decompose.router.children/SimpleNavigation.subscribe|subscribe(kotlin.Function1<1:0,kotlin.Unit>){}[0] } +final class <#A: out kotlin/Any, #B: out kotlin/Any, #C: out kotlin/Any, #D: out kotlin/Any, #E: out kotlin/Any, #F: out kotlin/Any> com.arkivanov.decompose.router.panels/ChildPanels { // com.arkivanov.decompose.router.panels/ChildPanels|null[0] + constructor (com.arkivanov.decompose/Child.Created<#A, #B>, com.arkivanov.decompose/Child.Created<#C, #D>? = ..., com.arkivanov.decompose/Child.Created<#E, #F>? = ..., com.arkivanov.decompose.router.panels/ChildPanelsMode = ...) // com.arkivanov.decompose.router.panels/ChildPanels.|(com.arkivanov.decompose.Child.Created<1:0,1:1>;com.arkivanov.decompose.Child.Created<1:2,1:3>?;com.arkivanov.decompose.Child.Created<1:4,1:5>?;com.arkivanov.decompose.router.panels.ChildPanelsMode){}[0] + + final val details // com.arkivanov.decompose.router.panels/ChildPanels.details|{}details[0] + final fun (): com.arkivanov.decompose/Child.Created<#C, #D>? // com.arkivanov.decompose.router.panels/ChildPanels.details.|(){}[0] + final val extra // com.arkivanov.decompose.router.panels/ChildPanels.extra|{}extra[0] + final fun (): com.arkivanov.decompose/Child.Created<#E, #F>? // com.arkivanov.decompose.router.panels/ChildPanels.extra.|(){}[0] + final val main // com.arkivanov.decompose.router.panels/ChildPanels.main|{}main[0] + final fun (): com.arkivanov.decompose/Child.Created<#A, #B> // com.arkivanov.decompose.router.panels/ChildPanels.main.|(){}[0] + final val mode // com.arkivanov.decompose.router.panels/ChildPanels.mode|{}mode[0] + final fun (): com.arkivanov.decompose.router.panels/ChildPanelsMode // com.arkivanov.decompose.router.panels/ChildPanels.mode.|(){}[0] + + final fun component1(): com.arkivanov.decompose/Child.Created<#A, #B> // com.arkivanov.decompose.router.panels/ChildPanels.component1|component1(){}[0] + final fun component2(): com.arkivanov.decompose/Child.Created<#C, #D>? // com.arkivanov.decompose.router.panels/ChildPanels.component2|component2(){}[0] + final fun component3(): com.arkivanov.decompose/Child.Created<#E, #F>? // com.arkivanov.decompose.router.panels/ChildPanels.component3|component3(){}[0] + final fun component4(): com.arkivanov.decompose.router.panels/ChildPanelsMode // com.arkivanov.decompose.router.panels/ChildPanels.component4|component4(){}[0] + final fun copy(com.arkivanov.decompose/Child.Created<#A, #B> = ..., com.arkivanov.decompose/Child.Created<#C, #D>? = ..., com.arkivanov.decompose/Child.Created<#E, #F>? = ..., com.arkivanov.decompose.router.panels/ChildPanelsMode = ...): com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F> // com.arkivanov.decompose.router.panels/ChildPanels.copy|copy(com.arkivanov.decompose.Child.Created<1:0,1:1>;com.arkivanov.decompose.Child.Created<1:2,1:3>?;com.arkivanov.decompose.Child.Created<1:4,1:5>?;com.arkivanov.decompose.router.panels.ChildPanelsMode){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose.router.panels/ChildPanels.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // com.arkivanov.decompose.router.panels/ChildPanels.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // com.arkivanov.decompose.router.panels/ChildPanels.toString|toString(){}[0] +} + +final class <#A: out kotlin/Any, #B: out kotlin/Any, #C: out kotlin/Any> com.arkivanov.decompose.router.panels/Panels { // com.arkivanov.decompose.router.panels/Panels|null[0] + constructor (#A, #B? = ..., #C? = ..., com.arkivanov.decompose.router.panels/ChildPanelsMode = ...) // com.arkivanov.decompose.router.panels/Panels.|(1:0;1:1?;1:2?;com.arkivanov.decompose.router.panels.ChildPanelsMode){}[0] + + final val details // com.arkivanov.decompose.router.panels/Panels.details|{}details[0] + final fun (): #B? // com.arkivanov.decompose.router.panels/Panels.details.|(){}[0] + final val extra // com.arkivanov.decompose.router.panels/Panels.extra|{}extra[0] + final fun (): #C? // com.arkivanov.decompose.router.panels/Panels.extra.|(){}[0] + final val main // com.arkivanov.decompose.router.panels/Panels.main|{}main[0] + final fun (): #A // com.arkivanov.decompose.router.panels/Panels.main.|(){}[0] + final val mode // com.arkivanov.decompose.router.panels/Panels.mode|{}mode[0] + final fun (): com.arkivanov.decompose.router.panels/ChildPanelsMode // com.arkivanov.decompose.router.panels/Panels.mode.|(){}[0] + + final fun component1(): #A // com.arkivanov.decompose.router.panels/Panels.component1|component1(){}[0] + final fun component2(): #B? // com.arkivanov.decompose.router.panels/Panels.component2|component2(){}[0] + final fun component3(): #C? // com.arkivanov.decompose.router.panels/Panels.component3|component3(){}[0] + final fun component4(): com.arkivanov.decompose.router.panels/ChildPanelsMode // com.arkivanov.decompose.router.panels/Panels.component4|component4(){}[0] + final fun copy(#A = ..., #B? = ..., #C? = ..., com.arkivanov.decompose.router.panels/ChildPanelsMode = ...): com.arkivanov.decompose.router.panels/Panels<#A, #B, #C> // com.arkivanov.decompose.router.panels/Panels.copy|copy(1:0;1:1?;1:2?;com.arkivanov.decompose.router.panels.ChildPanelsMode){}[0] + final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.decompose.router.panels/Panels.equals|equals(kotlin.Any?){}[0] + final fun hashCode(): kotlin/Int // com.arkivanov.decompose.router.panels/Panels.hashCode|hashCode(){}[0] + final fun toString(): kotlin/String // com.arkivanov.decompose.router.panels/Panels.toString|toString(){}[0] + + final class <#A1: kotlin/Any?, #B1: kotlin/Any?, #C1: kotlin/Any?> $serializer : kotlinx.serialization.internal/GeneratedSerializer> { // com.arkivanov.decompose.router.panels/Panels.$serializer|null[0] + constructor (kotlinx.serialization/KSerializer<#A1>, kotlinx.serialization/KSerializer<#B1>, kotlinx.serialization/KSerializer<#C1>) // com.arkivanov.decompose.router.panels/Panels.$serializer.|(kotlinx.serialization.KSerializer<1:0>;kotlinx.serialization.KSerializer<1:1>;kotlinx.serialization.KSerializer<1:2>){}[0] + + final val descriptor // com.arkivanov.decompose.router.panels/Panels.$serializer.descriptor|{}descriptor[0] + final fun (): kotlinx.serialization.descriptors/SerialDescriptor // com.arkivanov.decompose.router.panels/Panels.$serializer.descriptor.|(){}[0] + final val typeSerial0 // com.arkivanov.decompose.router.panels/Panels.$serializer.typeSerial0|{}typeSerial0[0] + final val typeSerial1 // com.arkivanov.decompose.router.panels/Panels.$serializer.typeSerial1|{}typeSerial1[0] + final val typeSerial2 // com.arkivanov.decompose.router.panels/Panels.$serializer.typeSerial2|{}typeSerial2[0] + + final fun childSerializers(): kotlin/Array> // com.arkivanov.decompose.router.panels/Panels.$serializer.childSerializers|childSerializers(){}[0] + final fun deserialize(kotlinx.serialization.encoding/Decoder): com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1> // com.arkivanov.decompose.router.panels/Panels.$serializer.deserialize|deserialize(kotlinx.serialization.encoding.Decoder){}[0] + final fun serialize(kotlinx.serialization.encoding/Encoder, com.arkivanov.decompose.router.panels/Panels<#A1, #B1, #C1>) // com.arkivanov.decompose.router.panels/Panels.$serializer.serialize|serialize(kotlinx.serialization.encoding.Encoder;com.arkivanov.decompose.router.panels.Panels<1:0,1:1,1:2>){}[0] + final fun typeParametersSerializers(): kotlin/Array> // com.arkivanov.decompose.router.panels/Panels.$serializer.typeParametersSerializers|typeParametersSerializers(){}[0] + } + + final object Companion : kotlinx.serialization.internal/SerializerFactory { // com.arkivanov.decompose.router.panels/Panels.Companion|null[0] + final val $cachedDescriptor // com.arkivanov.decompose.router.panels/Panels.Companion.$cachedDescriptor|{}$cachedDescriptor[0] + final fun (): kotlinx.serialization.descriptors/SerialDescriptor // com.arkivanov.decompose.router.panels/Panels.Companion.$cachedDescriptor.|(){}[0] + final val $childSerializers // com.arkivanov.decompose.router.panels/Panels.Companion.$childSerializers|{}$childSerializers[0] + + final fun <#A2: kotlin/Any?, #B2: kotlin/Any?, #C2: kotlin/Any?> serializer(kotlinx.serialization/KSerializer<#A2>, kotlinx.serialization/KSerializer<#B2>, kotlinx.serialization/KSerializer<#C2>): kotlinx.serialization/KSerializer> // com.arkivanov.decompose.router.panels/Panels.Companion.serializer|serializer(kotlinx.serialization.KSerializer<0:0>;kotlinx.serialization.KSerializer<0:1>;kotlinx.serialization.KSerializer<0:2>){0§;1§;2§}[0] + final fun serializer(kotlin/Array>...): kotlinx.serialization/KSerializer<*> // com.arkivanov.decompose.router.panels/Panels.Companion.serializer|serializer(kotlin.Array>...){}[0] + } +} + final class <#A: out kotlin/Any, #B: out kotlin/Any> com.arkivanov.decompose.router.pages/ChildPages { // com.arkivanov.decompose.router.pages/ChildPages|null[0] constructor () // com.arkivanov.decompose.router.pages/ChildPages.|(){}[0] constructor (kotlin.collections/List>, kotlin/Int) // com.arkivanov.decompose.router.pages/ChildPages.|(kotlin.collections.List>;kotlin.Int){}[0] @@ -313,6 +408,12 @@ final object com.arkivanov.decompose/DecomposeExperimentFlags { // com.arkivanov final fun (kotlin/Boolean) // com.arkivanov.decompose/DecomposeExperimentFlags.duplicateConfigurationsEnabled.|(kotlin.Boolean){}[0] } +final val com.arkivanov.decompose.router.panels/isDual // com.arkivanov.decompose.router.panels/isDual|@com.arkivanov.decompose.router.panels.ChildPanelsMode{}isDual[0] + final fun (com.arkivanov.decompose.router.panels/ChildPanelsMode).(): kotlin/Boolean // com.arkivanov.decompose.router.panels/isDual.|@com.arkivanov.decompose.router.panels.ChildPanelsMode(){}[0] +final val com.arkivanov.decompose.router.panels/isSingle // com.arkivanov.decompose.router.panels/isSingle|@com.arkivanov.decompose.router.panels.ChildPanelsMode{}isSingle[0] + final fun (com.arkivanov.decompose.router.panels/ChildPanelsMode).(): kotlin/Boolean // com.arkivanov.decompose.router.panels/isSingle.|@com.arkivanov.decompose.router.panels.ChildPanelsMode(){}[0] +final val com.arkivanov.decompose.router.panels/isTriple // com.arkivanov.decompose.router.panels/isTriple|@com.arkivanov.decompose.router.panels.ChildPanelsMode{}isTriple[0] + final fun (com.arkivanov.decompose.router.panels/ChildPanelsMode).(): kotlin/Boolean // com.arkivanov.decompose.router.panels/isTriple.|@com.arkivanov.decompose.router.panels.ChildPanelsMode(){}[0] final val com.arkivanov.decompose.router.slot/child // com.arkivanov.decompose.router.slot/child|@com.arkivanov.decompose.value.Value>{0§;1§}child[0] final fun <#A1: kotlin/Any, #B1: kotlin/Any> (com.arkivanov.decompose.value/Value>).(): com.arkivanov.decompose/Child.Created<#A1, #B1>? // com.arkivanov.decompose.router.slot/child.|@com.arkivanov.decompose.value.Value>(){0§;1§}[0] final val com.arkivanov.decompose.router.stack/active // com.arkivanov.decompose.router.stack/active|@com.arkivanov.decompose.value.Value>{0§;1§}active[0] @@ -328,6 +429,10 @@ final var com.arkivanov.decompose.errorhandler/onDecomposeError // com.arkivanov final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: com.arkivanov.decompose.router.children/NavState<#B>, #F: kotlin/Any> (#A).com.arkivanov.decompose.router.children/children(com.arkivanov.decompose.router.children/NavigationSource<#D>, kotlin/String, kotlin/Function0<#E>, kotlin/Function1<#E, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1, kotlin/Function2<#E, #D, #E>, kotlin/Function2<#E, kotlin.collections/List>, #F>, kotlin/Function2<#E, #E?, kotlin/Unit> = ..., kotlin/Function3<#D, #E, #E, kotlin/Unit> = ..., kotlin/Function1<#E, kotlin/Function0<#E>?> = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<#F> // com.arkivanov.decompose.router.children/children|children@0:0(com.arkivanov.decompose.router.children.NavigationSource<0:3>;kotlin.String;kotlin.Function0<0:4>;kotlin.Function1<0:4,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1;kotlin.Function2<0:4,0:3,0:4>;kotlin.Function2<0:4,kotlin.collections.List>,0:5>;kotlin.Function2<0:4,0:4?,kotlin.Unit>;kotlin.Function3<0:3,0:4,0:4,kotlin.Unit>;kotlin.Function1<0:4,kotlin.Function0<0:4>?>;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§;3§;4§>;5§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: com.arkivanov.decompose.router.children/NavState<#B>, #F: kotlin/Any> (#A).com.arkivanov.decompose.router.children/children(com.arkivanov.decompose.router.children/NavigationSource<#D>, kotlinx.serialization/KSerializer<#E>?, kotlin/Function0<#E>, kotlin/String, kotlin/Function2<#E, #D, #E>, kotlin/Function2<#E, kotlin.collections/List>, #F>, kotlin/Function2<#E, #E?, kotlin/Unit> = ..., kotlin/Function3<#D, #E, #E, kotlin/Unit> = ..., kotlin/Function1<#E, kotlin/Function0<#E>?> = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value<#F> // com.arkivanov.decompose.router.children/children|children@0:0(com.arkivanov.decompose.router.children.NavigationSource<0:3>;kotlinx.serialization.KSerializer<0:4>?;kotlin.Function0<0:4>;kotlin.String;kotlin.Function2<0:4,0:3,0:4>;kotlin.Function2<0:4,kotlin.collections.List>,0:5>;kotlin.Function2<0:4,0:4?,kotlin.Unit>;kotlin.Function3<0:3,0:4,0:4,kotlin.Unit>;kotlin.Function1<0:4,kotlin.Function0<0:4>?>;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§;3§;4§>;5§}[0] +final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any, #G: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Function0>, kotlin/Function1, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1?>, kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Function0>;kotlin.Function1,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1?>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>){0§>;1§;2§;3§;4§;5§;6§}[0] +final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any, #G: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Function0>, kotlin/Function1, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1?>, kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>, kotlin/Function2<#F, #A, #G>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Function0>;kotlin.Function1,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1?>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>;kotlin.Function2<0:5,0:0,0:6>){0§>;1§;2§;3§;4§;5§;6§}[0] +final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any, #G: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Triple, kotlinx.serialization/KSerializer<#D>, kotlinx.serialization/KSerializer<#F>>?, kotlin/Function0>, kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#B, #D, #F>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>, kotlin/Function2<#F, #A, #G>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Triple,kotlinx.serialization.KSerializer<0:3>,kotlinx.serialization.KSerializer<0:5>>?;kotlin.Function0>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,0:5>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>;kotlin.Function2<0:5,0:0,0:6>){0§>;1§;2§;3§;4§;5§;6§}[0] +final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any> (#A).com.arkivanov.decompose.router.panels/childPanels(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Pair, kotlinx.serialization/KSerializer<#D>>?, kotlin/Function0>, kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#B, #D, kotlin/Nothing>?, kotlin/Unit> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>, kotlin/Function2<#D, #A, #E>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.panels/childPanels|childPanels@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Pair,kotlinx.serialization.KSerializer<0:3>>?;kotlin.Function0>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:1,0:3,kotlin.Nothing>?,kotlin.Unit>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>;kotlin.Function2<0:3,0:0,0:4>){0§>;1§;2§;3§;4§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.pages/childPages(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Function0>, kotlin/Function1, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1?>, kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.children/ChildNavState.Status> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.pages/childPages|childPages@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Function0>;kotlin.Function1,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1?>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.children.ChildNavState.Status>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.pages/childPages(com.arkivanov.decompose.router.children/NavigationSource>, kotlinx.serialization/KSerializer<#B>?, kotlin/Function0> = ..., kotlin/String = ..., kotlin/Function2, com.arkivanov.decompose.router.children/ChildNavState.Status> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.pages/childPages|childPages@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlinx.serialization.KSerializer<0:1>?;kotlin.Function0>;kotlin.String;kotlin.Function2,com.arkivanov.decompose.router.children.ChildNavState.Status>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.slot/childSlot(com.arkivanov.decompose.router.children/NavigationSource>, kotlin/Function1<#B?, com.arkivanov.essenty.statekeeper/SerializableContainer?>, kotlin/Function1, kotlin/String = ..., kotlin/Function0<#B?> = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.slot/childSlot|childSlot@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlin.Function1<0:1?,com.arkivanov.essenty.statekeeper.SerializableContainer?>;kotlin.Function1;kotlin.String;kotlin.Function0<0:1?>;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§}[0] @@ -336,6 +441,18 @@ final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/A final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.stack/childStack(com.arkivanov.decompose.router.children/NavigationSource>, kotlinx.serialization/KSerializer<#B>?, #B, kotlin/String = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.stack/childStack|childStack@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlinx.serialization.KSerializer<0:1>?;0:1;kotlin.String;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>, #B: kotlin/Any, #C: kotlin/Any> (#A).com.arkivanov.decompose.router.stack/childStack(com.arkivanov.decompose.router.children/NavigationSource>, kotlinx.serialization/KSerializer<#B>?, kotlin/Function0>, kotlin/String = ..., kotlin/Boolean = ..., kotlin/Function2<#B, #A, #C>): com.arkivanov.decompose.value/Value> // com.arkivanov.decompose.router.stack/childStack|childStack@0:0(com.arkivanov.decompose.router.children.NavigationSource>;kotlinx.serialization.KSerializer<0:1>?;kotlin.Function0>;kotlin.String;kotlin.Boolean;kotlin.Function2<0:1,0:0,0:2>){0§>;1§;2§}[0] final fun <#A: com.arkivanov.decompose/GenericComponentContext<#A>> (#A).com.arkivanov.decompose/childContext(kotlin/String, com.arkivanov.essenty.lifecycle/Lifecycle? = ...): #A // com.arkivanov.decompose/childContext|childContext@0:0(kotlin.String;com.arkivanov.essenty.lifecycle.Lifecycle?){0§>}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/activateDetails(#B, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/activateDetails|activateDetails@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:1;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/activateExtra(#C, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/activateExtra|activateExtra@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:2;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/activateMain(#A, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/activateMain|activateMain@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:0;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/dismissDetails(kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/dismissDetails|dismissDetails@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/dismissExtra(kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/dismissExtra|dismissExtra@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/navigate(#A, #B?, #C?, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/navigate|navigate@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:0;0:1?;0:2?;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/navigate(#B?, #C?, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/navigate|navigate@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:1?;0:2?;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/navigate(#C?, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/navigate|navigate@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(0:2?;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/navigate(kotlin/Function1, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>>) // com.arkivanov.decompose.router.panels/navigate|navigate@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(kotlin.Function1,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/pop(kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/pop|pop@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> (com.arkivanov.decompose.router.panels/PanelsNavigator<#A, #B, #C>).com.arkivanov.decompose.router.panels/setMode(com.arkivanov.decompose.router.panels/ChildPanelsMode, kotlin/Function2, com.arkivanov.decompose.router.panels/Panels<#A, #B, #C>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.panels/setMode|setMode@com.arkivanov.decompose.router.panels.PanelsNavigator<0:0,0:1,0:2>(com.arkivanov.decompose.router.panels.ChildPanelsMode;kotlin.Function2,com.arkivanov.decompose.router.panels.Panels<0:0,0:1,0:2>,kotlin.Unit>){0§;1§;2§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any> com.arkivanov.decompose.router.panels/PanelsNavigation(): com.arkivanov.decompose.router.panels/PanelsNavigation<#A, #B, #C> // com.arkivanov.decompose.router.panels/PanelsNavigation|PanelsNavigation(){0§;1§;2§}[0] final fun <#A: kotlin/Any, #B: kotlin/Any> (com.arkivanov.decompose.value/Value<#A>).com.arkivanov.decompose.value.operator/map(kotlin/Function1<#A, #B>): com.arkivanov.decompose.value/Value<#B> // com.arkivanov.decompose.value.operator/map|map@com.arkivanov.decompose.value.Value<0:0>(kotlin.Function1<0:0,0:1>){0§;1§}[0] final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/clear(kotlin/Function2, com.arkivanov.decompose.router.pages/Pages<#A>, kotlin/Unit> = ...) // com.arkivanov.decompose.router.pages/clear|clear@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function2,com.arkivanov.decompose.router.pages.Pages<0:0>,kotlin.Unit>){0§}[0] final fun <#A: kotlin/Any> (com.arkivanov.decompose.router.pages/PagesNavigator<#A>).com.arkivanov.decompose.router.pages/navigate(kotlin/Function1, com.arkivanov.decompose.router.pages/Pages<#A>>) // com.arkivanov.decompose.router.pages/navigate|navigate@com.arkivanov.decompose.router.pages.PagesNavigator<0:0>(kotlin.Function1,com.arkivanov.decompose.router.pages.Pages<0:0>>){0§}[0] diff --git a/decompose/api/jvm/decompose.api b/decompose/api/jvm/decompose.api index 9361550d2..81673ba37 100644 --- a/decompose/api/jvm/decompose.api +++ b/decompose/api/jvm/decompose.api @@ -214,6 +214,126 @@ public final class com/arkivanov/decompose/router/pages/PagesNavigatorExtKt { public static synthetic fun selectPrev$default (Lcom/arkivanov/decompose/router/pages/PagesNavigator;ZLkotlin/jvm/functions/Function2;ILjava/lang/Object;)V } +public final class com/arkivanov/decompose/router/panels/ChildPanels { + public fun (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)V + public synthetic fun (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component2 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component3 ()Lcom/arkivanov/decompose/Child$Created; + public final fun component4 ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public final fun copy (Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Lcom/arkivanov/decompose/router/panels/ChildPanels; + public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/Child$Created;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/panels/ChildPanels; + public fun equals (Ljava/lang/Object;)Z + public final fun getDetails ()Lcom/arkivanov/decompose/Child$Created; + public final fun getExtra ()Lcom/arkivanov/decompose/Child$Created; + public final fun getMain ()Lcom/arkivanov/decompose/Child$Created; + public final fun getMode ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsFactoryKt { + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Pair;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Triple;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static final fun childPanels (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Pair;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/Triple;Lkotlin/jvm/functions/Function0;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; + public static synthetic fun childPanels$default (Lcom/arkivanov/decompose/GenericComponentContext;Lcom/arkivanov/decompose/router/children/NavigationSource;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Ljava/lang/String;Lkotlin/jvm/functions/Function2;ZLkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/arkivanov/decompose/value/Value; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsMode : java/lang/Enum { + public static final field DUAL Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static final field SINGLE Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static final field TRIPLE Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static fun getEntries ()Lkotlin/enums/EnumEntries; + public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public static fun values ()[Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; +} + +public final class com/arkivanov/decompose/router/panels/ChildPanelsModeKt { + public static final fun isDual (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z + public static final fun isSingle (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z + public static final fun isTriple (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Z +} + +public final class com/arkivanov/decompose/router/panels/Panels { + public static final field Companion Lcom/arkivanov/decompose/router/panels/Panels$Companion; + public fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)V + public synthetic fun (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun component1 ()Ljava/lang/Object; + public final fun component2 ()Ljava/lang/Object; + public final fun component3 ()Ljava/lang/Object; + public final fun component4 ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public final fun copy (Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;)Lcom/arkivanov/decompose/router/panels/Panels; + public static synthetic fun copy$default (Lcom/arkivanov/decompose/router/panels/Panels;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;ILjava/lang/Object;)Lcom/arkivanov/decompose/router/panels/Panels; + public fun equals (Ljava/lang/Object;)Z + public final fun getDetails ()Ljava/lang/Object; + public final fun getExtra ()Ljava/lang/Object; + public final fun getMain ()Ljava/lang/Object; + public final fun getMode ()Lcom/arkivanov/decompose/router/panels/ChildPanelsMode; + public fun hashCode ()I + public fun toString ()Ljava/lang/String; +} + +public synthetic class com/arkivanov/decompose/router/panels/Panels$$serializer : kotlinx/serialization/internal/GeneratedSerializer { + public fun (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)V + public final fun childSerializers ()[Lkotlinx/serialization/KSerializer; + public final fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lcom/arkivanov/decompose/router/panels/Panels; + public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object; + public final fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor; + public final fun serialize (Lkotlinx/serialization/encoding/Encoder;Lcom/arkivanov/decompose/router/panels/Panels;)V + public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V + public final fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer; +} + +public final class com/arkivanov/decompose/router/panels/Panels$Companion { + public final fun serializer (Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;Lkotlinx/serialization/KSerializer;)Lkotlinx/serialization/KSerializer; +} + +public abstract interface class com/arkivanov/decompose/router/panels/PanelsNavigation : com/arkivanov/decompose/router/children/NavigationSource, com/arkivanov/decompose/router/panels/PanelsNavigator { +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigation$Event { + public fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V + public synthetic fun (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getOnComplete ()Lkotlin/jvm/functions/Function2; + public final fun getTransformer ()Lkotlin/jvm/functions/Function1; +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigationKt { + public static final fun PanelsNavigation ()Lcom/arkivanov/decompose/router/panels/PanelsNavigation; +} + +public abstract interface class com/arkivanov/decompose/router/panels/PanelsNavigator { + public abstract fun navigate (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function2;)V +} + +public final class com/arkivanov/decompose/router/panels/PanelsNavigatorExtKt { + public static final fun activateDetails (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateDetails$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun activateExtra (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateExtra$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun activateMain (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun activateMain$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun dismissDetails (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun dismissDetails$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun dismissExtra (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun dismissExtra$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;)V + public static final fun navigate (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function1;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static synthetic fun navigate$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Ljava/lang/Object;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun pop (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun pop$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V + public static final fun setMode (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;)V + public static synthetic fun setMode$default (Lcom/arkivanov/decompose/router/panels/PanelsNavigator;Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)V +} + public final class com/arkivanov/decompose/router/slot/ChildSlot { public fun ()V public fun (Lcom/arkivanov/decompose/Child$Created;)V diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanels.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanels.kt new file mode 100644 index 000000000..0aafac765 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanels.kt @@ -0,0 +1,21 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ExperimentalDecomposeApi + +/** + * A state holder for Child Panels. + * + * @param main a Main child component. + * @param details an optional Details child component. + * @param extra an optional Extra child component. + * @param mode determines how lifecycles of the panels within the Child Panels navigation model are changing, + * default value is [ChildPanelsMode.SINGLE], see [ChildPanelsMode]. + */ +@ExperimentalDecomposeApi +data class ChildPanels( + val main: Child.Created, + val details: Child.Created? = null, + val extra: Child.Created? = null, + val mode: ChildPanelsMode = ChildPanelsMode.SINGLE, +) diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsFactory.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsFactory.kt new file mode 100644 index 000000000..fab9a708c --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsFactory.kt @@ -0,0 +1,304 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.GenericComponentContext +import com.arkivanov.decompose.router.children.ChildNavState +import com.arkivanov.decompose.router.children.ChildNavState.Status +import com.arkivanov.decompose.router.children.NavState +import com.arkivanov.decompose.router.children.NavigationSource +import com.arkivanov.decompose.router.children.SimpleChildNavState +import com.arkivanov.decompose.router.children.children +import com.arkivanov.decompose.router.panels.PanelsNavigation.Event +import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.statekeeper.SerializableContainer +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.builtins.NothingSerializer + +/** + * Initializes and manages a set of up to two child components (panels): Main (required) and + * Details (optional). The Extra component is unused. See [ChildPanelsMode] for documentation about + * how child components lifecycles are controlled. + * + * **It is strongly recommended to call this method on the Main thread.** + * + * @param source a source of navigation events. + * @param serializers an optional [Pair] of [KSerializer] (Main and Details) to be used for + * serializing and deserializing configurations. If `null` then the navigation state will not be preserved. + * @param initialPanels an initial state of Child Panels that should be set if there is no saved state. + * See [Panels] for more information. + * @param key a key of the navigation, must be unique if there are multiple Child Panels + * used in the same component. + * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when + * called first time during initialisation. + * @param handleBackButton determines whether the previous component should be automatically + * selected on back button press or not, default is `false`. + * Only works if [Panels.mode] is [ChildPanelsMode.SINGLE]. + * @param mainFactory a factory function that creates new instances of the Main component. + * @param detailsFactory a factory function that creates new instances of the Details component. + * @return an observable [Value] of [ChildPanels]. + */ +@ExperimentalSerializationApi +@ExperimentalDecomposeApi +fun , MC : Any, MT : Any, DC : Any, DT : Any> Ctx.childPanels( + source: NavigationSource>, + serializers: Pair, KSerializer>?, + initialPanels: () -> Panels, + key: String = "DefaultChildPanels", + onStateChanged: (newState: Panels, oldState: Panels?) -> Unit = { _, _ -> }, + handleBackButton: Boolean = false, + mainFactory: (configuration: MC, Ctx) -> MT, + detailsFactory: (configuration: DC, Ctx) -> DT, +): Value> = + childPanels( + source = source, + initialPanels = initialPanels, + serializers = serializers?.let { Triple(it.first, it.second, NothingSerializer()) }, + key = key, + onStateChanged = onStateChanged, + handleBackButton = handleBackButton, + mainFactory = mainFactory, + detailsFactory = detailsFactory, + extraFactory = { _, _ -> error("Can't instantiate Nothing") }, + ) + +/** + * Initializes and manages a set of up to two child components (panels): Main (required) and + * Details (optional). The Extra component is unused. See [ChildPanelsMode] for documentation about + * how child components lifecycles are controlled. + * + * **It is strongly recommended to call this method on the Main thread.** + * + * @param source a source of navigation events. + * @param initialPanels an initial state of Child Panels that should be set if there is no saved state. + * See [Panels] for more information. + * @param savePanels a function that saves the provided [Panels] state into [SerializableContainer]. + * The navigation state is not saved if `null` is returned. + * @param restorePanels a function that restores the [Panels] state from the provided [SerializableContainer]. + * If `null` is returned then [initialPanels] is used instead. + * The restored [Panels] state must have exactly the same configurations. + * @param key a key of the navigation, must be unique if there are multiple Child Panels + * used in the same component. + * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when + * called first time during initialisation. + * @param handleBackButton determines whether the previous component should be automatically + * selected on back button press or not, default is `false`. + * Only works if [Panels.mode] is [ChildPanelsMode.SINGLE]. + * @param mainFactory a factory function that creates new instances of the Main component. + * @param detailsFactory a factory function that creates new instances of the Details component. + * @return an observable [Value] of [ChildPanels]. + */ +@ExperimentalDecomposeApi +fun , MC : Any, MT : Any, DC : Any, DT : Any, EC : Any, ET : Any> Ctx.childPanels( + source: NavigationSource>, + initialPanels: () -> Panels, + savePanels: (Panels) -> SerializableContainer?, + restorePanels: (SerializableContainer) -> Panels?, + key: String = "DefaultChildPanels", + onStateChanged: (newState: Panels, oldState: Panels?) -> Unit = { _, _ -> }, + handleBackButton: Boolean = false, + mainFactory: (configuration: MC, Ctx) -> MT, + detailsFactory: (configuration: DC, Ctx) -> DT, +): Value> = + childPanels( + source = source, + initialPanels = initialPanels, + savePanels = savePanels, + restorePanels = restorePanels, + key = key, + onStateChanged = onStateChanged, + handleBackButton = handleBackButton, + mainFactory = mainFactory, + detailsFactory = detailsFactory, + extraFactory = { _, _ -> error("Can't instantiate Nothing") }, + ) + +/** + * Initializes and manages a set of up to three child components (panels): Main (required), + * Details (optional) and Extra (optional). See [ChildPanelsMode] for documentation about + * how child components lifecycles are controlled. + * + * **It is strongly recommended to call this method on the Main thread.** + * + * @param source a source of navigation events. + * @param serializers an optional [Pair] of [KSerializer] (Main and Details) to be used for + * serializing and deserializing configurations. If `null` then the navigation state will not be preserved. + * @param initialPanels an initial state of Child Panels that should be set if there is no saved state. + * See [Panels] for more information. + * @param key a key of the navigation, must be unique if there are multiple Child Panels + * used in the same component. + * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when + * called first time during initialisation. + * @param handleBackButton determines whether the previous component should be automatically + * selected on back button press or not, default is `false`. + * Only works if [Panels.mode] is [ChildPanelsMode.SINGLE]. + * @param mainFactory a factory function that creates new instances of the Main component. + * @param detailsFactory a factory function that creates new instances of the Details component. + * @param extraFactory a factory function that creates new instances of the Extra component. + * @return an observable [Value] of [ChildPanels]. + */ +@ExperimentalDecomposeApi +fun , MC : Any, MT : Any, DC : Any, DT : Any, EC : Any, ET : Any> Ctx.childPanels( + source: NavigationSource>, + serializers: Triple, KSerializer, KSerializer>?, + initialPanels: () -> Panels, + key: String = "DefaultChildPanels", + onStateChanged: (newState: Panels, oldState: Panels?) -> Unit = { _, _ -> }, + handleBackButton: Boolean = false, + mainFactory: (configuration: MC, Ctx) -> MT, + detailsFactory: (configuration: DC, Ctx) -> DT, + extraFactory: (configuration: EC, Ctx) -> ET, +): Value> = + childPanels( + source = source, + initialPanels = initialPanels, + savePanels = savePanels@{ panels -> + val (mainSerializer, detailsSerializer, extraSerializer) = serializers ?: return@savePanels null + SerializableContainer( + value = panels, + strategy = Panels.serializer(mainSerializer, detailsSerializer, extraSerializer), + ) + }, + restorePanels = restorePanels@{ container -> + val (mainSerializer, detailsSerializer, extraSerializer) = serializers ?: return@restorePanels null + container.consume(Panels.serializer(mainSerializer, detailsSerializer, extraSerializer)) + }, + key = key, + onStateChanged = onStateChanged, + handleBackButton = handleBackButton, + mainFactory = mainFactory, + detailsFactory = detailsFactory, + extraFactory = extraFactory, + ) + +/** + * Initializes and manages a set of up to three child components (panels): Main (required), + * Details (optional) and Extra (optional). See [ChildPanelsMode] for documentation about + * how child components lifecycles are controlled. + * + * **It is strongly recommended to call this method on the Main thread.** + * + * @param source a source of navigation events. + * @param initialPanels an initial state of Child Panels that should be set if there is no saved state. + * See [Panels] for more information. + * @param savePanels a function that saves the provided [Panels] state into [SerializableContainer]. + * The navigation state is not saved if `null` is returned. + * @param restorePanels a function that restores the [Panels] state from the provided [SerializableContainer]. + * If `null` is returned then [initialPanels] is used instead. + * The restored [Panels] state must have exactly the same configurations. + * @param key a key of the navigation, must be unique if there are multiple Child Panels + * used in the same component. + * @param onStateChanged called every time the navigation state changes, `oldState` is `null` when + * called first time during initialisation. + * @param handleBackButton determines whether the previous component should be automatically + * selected on back button press or not, default is `false`. + * Only works if [Panels.mode] is [ChildPanelsMode.SINGLE]. + * @param mainFactory a factory function that creates new instances of the Main component. + * @param detailsFactory a factory function that creates new instances of the Details component. + * @param extraFactory a factory function that creates new instances of the Extra component. + * @return an observable [Value] of [ChildPanels]. + */ +@ExperimentalDecomposeApi +fun , MC : Any, MT : Any, DC : Any, DT : Any, EC : Any, ET : Any> Ctx.childPanels( + source: NavigationSource>, + initialPanels: () -> Panels, + savePanels: (Panels) -> SerializableContainer?, + restorePanels: (SerializableContainer) -> Panels?, + key: String = "DefaultChildPanels", + onStateChanged: (newState: Panels, oldState: Panels?) -> Unit = { _, _ -> }, + handleBackButton: Boolean = false, + mainFactory: (configuration: MC, Ctx) -> MT, + detailsFactory: (configuration: DC, Ctx) -> DT, + extraFactory: (configuration: EC, Ctx) -> ET, +): Value> = + children( + source = source, + key = key, + initialState = { PanelsNavState(initialPanels()) }, + saveState = { savePanels(it.panels) }, + restoreState = { restorePanels(it)?.let(::PanelsNavState) }, + navTransformer = { state, event -> PanelsNavState(event.transformer(state.panels)) }, + stateMapper = { state, children -> + val main = children.firstNotNullOf { it.instance as? Panel.Main } + val details = children.firstNotNullOfOrNull { it.instance as? Panel.Details } + val extra = children.firstNotNullOfOrNull { it.instance as? Panel.Extra } + + ChildPanels( + main = Child.Created(configuration = main.config, instance = main.instance), + details = details?.let { Child.Created(configuration = it.config, instance = it.instance) }, + extra = extra?.let { Child.Created(configuration = it.config, instance = it.instance) }, + mode = state.panels.mode, + ) + }, + onStateChanged = { newState, oldState -> onStateChanged(newState.panels, oldState?.panels) }, + onEventComplete = { event, newState, oldState -> event.onComplete(newState.panels, oldState.panels) }, + backTransformer = { state -> + val panels = state.panels + + when { + !handleBackButton -> null + + (panels.mode == ChildPanelsMode.SINGLE) && (panels.extra != null) -> { + { state.copy(panels = panels.copy(extra = null)) } + } + + (panels.mode == ChildPanelsMode.SINGLE) && (panels.details != null) -> { + { state.copy(panels = panels.copy(details = null)) } + } + + else -> null + } + }, + childFactory = { config, ctx -> + when (config) { + is Config.Main -> Panel.Main(config.config, mainFactory(config.config, ctx)) + is Config.Details -> Panel.Details(config.config, detailsFactory(config.config, ctx)) + is Config.Extra -> Panel.Extra(config.config, extraFactory(config.config, ctx)) + } + }, + ) + +private sealed interface Config { + data class Main(val config: MC) : Config + data class Details(val config: DC) : Config + data class Extra(val config: EC) : Config +} + +private sealed interface Panel { + data class Main(val config: MC, val instance: MT) : Panel + data class Details(val config: DC, val instance: DT) : Panel + data class Extra(val config: EC, val instance: ET) : Panel +} + +private data class PanelsNavState( + val panels: Panels, +) : NavState> { + override val children: List>> = + listOfNotNull( + SimpleChildNavState( + configuration = Config.Main(panels.main), + status = when { + panels.mode != ChildPanelsMode.SINGLE -> Status.RESUMED + (panels.details == null) && (panels.extra == null) -> Status.RESUMED + else -> Status.CREATED + }, + ), + panels.details?.let { + SimpleChildNavState( + configuration = Config.Details(it), + status = when { + panels.mode == ChildPanelsMode.TRIPLE -> Status.RESUMED + panels.extra == null -> Status.RESUMED + else -> Status.CREATED + }, + ) + }, + panels.extra?.let { + SimpleChildNavState( + configuration = Config.Extra(it), + status = Status.RESUMED, + ) + }, + ) +} diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt new file mode 100644 index 000000000..4f37fa5b4 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsMode.kt @@ -0,0 +1,38 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi + +/** + * Determines how lifecycles of the panels within the Child Panels navigation model are changing. + * + * @see com.arkivanov.essenty.lifecycle.Lifecycle.State + */ +@ExperimentalDecomposeApi +enum class ChildPanelsMode { + + /** + * There is only one `RESUMED` panel at a time. + * If the Extra panel exists, then it is `RESUMED` and all other panels are `CREATED`. + * Otherwise, if the Details panel exists, then it is `RESUMED` and the Main panel is `CREATED`. + * Otherwise, the Main panel is `RESUMED`. + */ + SINGLE, + + /** + * There are at most two panels `RESUMED` at a time. The Main panel is always `RESUMED`. + * If the Extra panel exists, then it is `RESUMED` and the Details panel (if exists) is `CREATED`. + * Otherwise, if the Details panel exists, then it is `RESUMED`. + */ + DUAL, + + /** + * Any existing panel is always `RESUMED`. + */ + TRIPLE, +} + +val ChildPanelsMode.isSingle: Boolean get() = this == ChildPanelsMode.SINGLE + +val ChildPanelsMode.isDual: Boolean get() = this == ChildPanelsMode.DUAL + +val ChildPanelsMode.isTriple: Boolean get() = this == ChildPanelsMode.TRIPLE diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/DefaultPanelsNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/DefaultPanelsNavigation.kt new file mode 100644 index 000000000..28b3ac307 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/DefaultPanelsNavigation.kt @@ -0,0 +1,20 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.Cancellation +import com.arkivanov.decompose.Relay +import com.arkivanov.decompose.router.panels.PanelsNavigation.Event + +internal class DefaultPanelsNavigation : PanelsNavigation { + + private val relay = Relay>() + + override fun subscribe(observer: (Event) -> Unit): Cancellation = + relay.subscribe(observer) + + override fun navigate( + transformer: (Panels) -> Panels, + onComplete: (newState: Panels, oldState: Panels) -> Unit, + ) { + relay.accept(Event(transformer, onComplete)) + } +} diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/Panels.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/Panels.kt new file mode 100644 index 000000000..97e00e8f9 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/Panels.kt @@ -0,0 +1,22 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import kotlinx.serialization.Serializable + +/** + * Represents a state of Child Panels navigation model. + * + * @param main a configuration of the Main panel. + * @param details an optional configuration of the Details panel, default value is `null`. + * @param extra an optional configuration of the Extra panel, default value is `null`. + * @param mode determines how lifecycles of the panels within the Child Panels navigation model are changing, + * default value is [ChildPanelsMode.SINGLE], see [ChildPanelsMode]. + */ +@ExperimentalDecomposeApi +@Serializable +data class Panels( + val main: MC, + val details: DC? = null, + val extra: EC? = null, + val mode: ChildPanelsMode = ChildPanelsMode.SINGLE, +) diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigation.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigation.kt new file mode 100644 index 000000000..04ae082a5 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigation.kt @@ -0,0 +1,25 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.children.NavigationSource +import com.arkivanov.decompose.router.panels.PanelsNavigation.Event + +/** + * Represents [PanelsNavigator] and [NavigationSource] at the same time. + */ +@ExperimentalDecomposeApi +interface PanelsNavigation : PanelsNavigator, NavigationSource> { + + class Event( + val transformer: (Panels) -> Panels, + val onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, + ) +} + +/** + * Returns a default implementation of [PanelsNavigation]. + * Broadcasts navigation events to all subscribed observers. + */ +@ExperimentalDecomposeApi +fun PanelsNavigation(): PanelsNavigation = + DefaultPanelsNavigation() diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigator.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigator.kt new file mode 100644 index 000000000..3936f9f5f --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigator.kt @@ -0,0 +1,31 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi + +@ExperimentalDecomposeApi +interface PanelsNavigator { + + /** + * Transforms the current [Panels] state to a new one. + * + * During the navigation process, the Child Panels navigation model compares the new [Panels] state + * with the previous one. The navigation model ensures that all removed components are destroyed, + * and updates lifecycles of the existing components to match the new state. + * + * The navigation is usually performed synchronously, which means that by the time + * the `navigate` method returns, the navigation is finished and all component lifecycles are + * moved into required states. However, the navigation is performed asynchronously in case of + * recursive invocations - e.g. `activeDetails` is called from `onResume` lifecycle callback of a + * component being shown. All recursive invocations are queued and performed one by one once + * the current navigation is finished. + * + * Should be called on the main thread. + * + * @param transformer transforms the current [Panels] state to a new one. + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ + fun navigate( + transformer: (Panels) -> Panels, + onComplete: (newState: Panels, oldState: Panels) -> Unit, + ) +} diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt new file mode 100644 index 000000000..6e68973c8 --- /dev/null +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/panels/PanelsNavigatorExt.kt @@ -0,0 +1,180 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi + +/** + * A convenience method for [PanelsNavigator.navigate]. + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.navigate( + transformer: (Panels) -> Panels, +) { + navigate(transformer = transformer, onComplete = { _, _ -> }) +} + +/** + * Sets the provided [main], [details] and [extra] configurations. + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.navigate( + main: MC, + details: DC?, + extra: EC?, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(main = main, details = details, extra = extra) }, + onComplete = onComplete, + ) +} + +/** + * Sets the provided [details] and [extra] configurations. + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.navigate( + details: DC?, + extra: EC?, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(details = details, extra = extra) }, + onComplete = onComplete, + ) +} + +/** + * Sets the provided [extra] configuration. + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.navigate( + extra: EC?, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(extra = extra) }, + onComplete = onComplete, + ) +} + +/** + * Activates the [Main][ChildPanels.main] component represented by the specified [main], + * and dismisses (destroys) any currently active Main component. + * + * @param main a configuration of the Main component being activated. + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.activateMain( + main: MC, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(main = main) }, + onComplete = onComplete, + ) +} + +/** + * Activates the [Details][ChildPanels.details] component represented by the specified [details], + * and dismisses (destroys) any currently active Details component. + * + * @param details a configuration of the Details component being activated. + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.activateDetails( + details: DC, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(details = details) }, + onComplete = onComplete, + ) +} + +/** + * Dismisses (destroys) the currently active [Details][ChildPanels.details] component, if any. + * + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.dismissDetails( + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(details = null) }, + onComplete = onComplete, + ) +} + +/** + * Activates the [Extra][ChildPanels.extra] component represented by the specified [extra], + * and dismisses (destroys) any currently active Extra component. + * + * @param extra a configuration of the Extra component being activated. + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.activateExtra( + extra: EC, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(extra = extra) }, + onComplete = onComplete, + ) +} + +/** + * Dismisses (destroys) the currently active [Extra][ChildPanels.extra] component, if any. + * + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.dismissExtra( + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(extra = null) }, + onComplete = onComplete, + ) +} + +/** + * Dismisses the Extra component (if it exists) or the Details component (if it exists). + * + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.pop( + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { state -> + when { + state.extra != null -> state.copy(extra = null) + state.details != null -> state.copy(details = null) + else -> state + } + }, + onComplete = onComplete, + ) +} + +/** + * Sets [Panels.mode] to the specified [mode] value and updates component lifecycles accordingly. + * + * @param mode a new [ChildPanelsMode] to be set. + * @param onComplete called when the navigation is finished (either synchronously or asynchronously). + */ +@ExperimentalDecomposeApi +fun PanelsNavigator.setMode( + mode: ChildPanelsMode, + onComplete: (newState: Panels, oldState: Panels) -> Unit = { _, _ -> }, +) { + navigate( + transformer = { it.copy(mode = mode) }, + onComplete = onComplete, + ) +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/Component.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/Component.kt new file mode 100644 index 000000000..392db77c0 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/Component.kt @@ -0,0 +1,8 @@ +package com.arkivanov.decompose.router + +import com.arkivanov.decompose.ComponentContext + +class Component( + val config: T, + componentContext: ComponentContext, +) : ComponentContext by componentContext diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/BaseChildPanelsTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/BaseChildPanelsTest.kt new file mode 100644 index 000000000..85da3b4be --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/BaseChildPanelsTest.kt @@ -0,0 +1,102 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.router.Component +import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher +import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.backhandler.BackDispatcher +import com.arkivanov.essenty.lifecycle.Lifecycle.State.CREATED +import com.arkivanov.essenty.lifecycle.Lifecycle.State.RESUMED +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import com.arkivanov.essenty.lifecycle.resume +import kotlinx.serialization.builtins.serializer +import kotlin.test.BeforeTest +import kotlin.test.assertEquals + +abstract class BaseChildPanelsTest { + + protected val nav: PanelsNavigation = PanelsNavigation() + protected val lifecycle: LifecycleRegistry = LifecycleRegistry() + protected val stateKeeper: TestStateKeeperDispatcher = TestStateKeeperDispatcher() + protected val backDispatcher: BackDispatcher = BackDispatcher() + + @BeforeTest + fun before() { + lifecycle.resume() + } + + protected fun ChildPanels, Int, Component, Int, Component>.assertPanels( + main: Int, + details: Int?, + extra: Int?, + mode: ChildPanelsMode, + ) { + val mainComponent = this.main.instance + val detailsComponent = this.details?.instance + val extraComponent = this.extra?.instance + + assertChildConfiguration(this.main, main) + assertChildConfiguration(this.details, details) + assertChildConfiguration(this.extra, extra) + assertEquals(mode, this.mode) + + when (mode) { + ChildPanelsMode.SINGLE -> { + assertEquals(if ((details == null) && (extra == null)) RESUMED else CREATED, mainComponent.lifecycle.state) + + assertEquals( + when { + details == null -> null + extra == null -> RESUMED + else -> CREATED + }, + detailsComponent?.lifecycle?.state, + ) + + assertEquals(if (extra != null) RESUMED else null, extraComponent?.lifecycle?.state) + } + + ChildPanelsMode.DUAL -> { + assertEquals(RESUMED, mainComponent.lifecycle.state) + + assertEquals( + when { + details == null -> null + extra == null -> RESUMED + else -> CREATED + }, + detailsComponent?.lifecycle?.state, + ) + + assertEquals(if (extra != null) RESUMED else null, extraComponent?.lifecycle?.state) + } + + ChildPanelsMode.TRIPLE -> { + assertEquals(RESUMED, mainComponent.lifecycle.state) + assertEquals(if (details != null) RESUMED else null, detailsComponent?.lifecycle?.state) + assertEquals(if (extra != null) RESUMED else null, extraComponent?.lifecycle?.state) + } + } + } + + private fun assertChildConfiguration(child: Child>?, config: Int?) { + assertEquals(config, child?.configuration) + assertEquals(config, child?.instance?.config) + } + + protected fun ComponentContext.childPanels( + initialPanels: Panels = Panels(main = 1), + persistent: Boolean = true, + handleBackButton: Boolean = false, + ): Value, Int, Component, Int, Component>> = + childPanels( + source = nav, + initialPanels = { initialPanels }, + serializers = if (persistent) Triple(Int.serializer(), Int.serializer(), Int.serializer()) else null, + handleBackButton = handleBackButton, + mainFactory = ::Component, + detailsFactory = ::Component, + extraFactory = ::Component, + ) +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsBackButtonTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsBackButtonTest.kt new file mode 100644 index 000000000..e1dd3a0f4 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsBackButtonTest.kt @@ -0,0 +1,127 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL +import com.arkivanov.decompose.router.panels.ChildPanelsMode.SINGLE +import com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE +import com.arkivanov.decompose.value.getValue +import kotlin.test.Test +import kotlin.test.assertFalse +import kotlin.test.assertTrue + +@Suppress("TestFunctionName") +class ChildPanelsBackButtonTest : BaseChildPanelsTest() { + + private val context = DefaultComponentContext(lifecycle = lifecycle, backHandler = backDispatcher) + + @Test + fun WHEN_single_with_main_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, mode = SINGLE), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_single_with_main_and_details_THEN_isEnabled_true() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = SINGLE), handleBackButton = true) + + assertTrue(backDispatcher.isEnabled) + } + + @Test + fun WHEN_single_with_main_and_extra_THEN_isEnabled_true() { + context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = SINGLE), handleBackButton = true) + + assertTrue(backDispatcher.isEnabled) + } + + @Test + fun WHEN_single_with_all_panels_THEN_isEnabled_true() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE), handleBackButton = true) + + assertTrue(backDispatcher.isEnabled) + } + + @Test + fun GIVEN_single_with_main_and_details_WHEN_back_THEN_isEnabled_true() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = SINGLE), handleBackButton = true) + + backDispatcher.back() + + panels.assertPanels(main = 1, details = null, extra = null, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_main_and_extra_WHEN_back_THEN_isEnabled_true() { + val panels by context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = SINGLE), handleBackButton = true) + + backDispatcher.back() + + panels.assertPanels(main = 1, details = null, extra = null, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_back_THEN_isEnabled_true() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE), handleBackButton = true) + + backDispatcher.back() + + panels.assertPanels(main = 1, details = 2, extra = null, mode = SINGLE) + } + + @Test + fun WHEN_dual_with_main_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, mode = DUAL), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_dual_with_main_and_details_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = DUAL), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_dual_with_main_and_extra_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = DUAL), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_dual_with_all_panels_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_triple_with_main_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, mode = TRIPLE), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_triple_with_main_and_details_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = TRIPLE), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_triple_with_main_and_extra_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = TRIPLE), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } + + @Test + fun WHEN_triple_with_all_panels_THEN_isEnabled_false() { + context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE), handleBackButton = true) + + assertFalse(backDispatcher.isEnabled) + } +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationExtTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationExtTest.kt new file mode 100644 index 000000000..97e574780 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationExtTest.kt @@ -0,0 +1,308 @@ +package com.arkivanov.decompose.router.panels + +import kotlin.test.Test +import kotlin.test.assertEquals + +@Suppress("TestFunctionName") +class ChildPanelsNavigationExtTest { + + @Test + fun WHEN_navigate_with_main_and_details_and_extra_THEN_panels_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.navigate(main = 11, details = 22, extra = 33) + + nav.assertPanels(Panels(main = 11, details = 22, extra = 33)) + } + + @Test + fun WHEN_navigate_with_main_and_details_and_extra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.navigate(main = 11, details = 22, extra = 33) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 11, details = 22, extra = 33, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_navigate_with_details_and_extra_THEN_panels_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.navigate(details = 22, extra = 33) + + nav.assertPanels(Panels(main = 1, details = 22, extra = 33)) + } + + @Test + fun WHEN_navigate_with_details_and_extra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.navigate(details = 22, extra = 33) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 22, extra = 33, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_navigate_with_extra_THEN_panels_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.navigate(extra = 33) + + nav.assertPanels(Panels(main = 1, details = 2, extra = 33)) + } + + @Test + fun WHEN_navigate_with_extra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.navigate(extra = 33) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = 33, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_activateMain_THEN_main_panel_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.activateMain(main = 11) + + nav.assertPanels(Panels(main = 11, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun WHEN_activateMain_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.activateMain(main = 11) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 11, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_activateDetails_THEN_details_panel_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.activateDetails(details = 22) + + nav.assertPanels(Panels(main = 1, details = 22, extra = 3, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun WHEN_activateDetails_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.activateDetails(details = 22) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 22, extra = 3, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_dismissDetails_THEN_details_panel_dismissed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.dismissDetails() + + nav.assertPanels(Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun GIVEN_details_active_WHEN_dismissDetails_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.dismissDetails { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun GIVEN_details_not_active_WHEN_dismissDetails_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.dismissDetails { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_activateExtra_THEN_extra_panel_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.activateExtra(extra = 33) + + nav.assertPanels(Panels(main = 1, details = 2, extra = 33, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun WHEN_activateExtra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.activateExtra(extra = 33) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = 33, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_dismissExtra_THEN_extra_panel_dismissed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.dismissExtra() + + nav.assertPanels(Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun GIVEN_extra_active_WHEN_dismissExtra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.dismissExtra { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun GIVEN_extra_not_active_WHEN_dismissExtra_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.dismissExtra { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun GIVEN_details_and_extra_panels_exist_WHEN_pop_THEN_extra_panel_dismissed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.pop() + + nav.assertPanels(Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun GIVEN_extra_panel_exists_WHEN_pop_THEN_extra_panel_dismissed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = null, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.pop() + + nav.assertPanels(Panels(main = 1, details = null, extra = null, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun GIVEN_details_panel_exists_WHEN_pop_THEN_details_panel_dismissed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE)) + + nav.pop() + + nav.assertPanels(Panels(main = 1, details = null, extra = null, mode = ChildPanelsMode.SINGLE)) + } + + @Test + fun WHEN_pop_THEN_onComplete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.pop { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = null, mode = ChildPanelsMode.SINGLE), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } + + @Test + fun WHEN_setMode_THEN_mode_changed() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + + nav.setMode(ChildPanelsMode.DUAL) + + nav.assertPanels(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.DUAL)) + } + + @Test + fun WHEN_setMode_THEN_complete_called() { + val nav = TestPanelsNavigator(Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE)) + var complete: Any? = null + + nav.setMode(ChildPanelsMode.DUAL) { newState, oldState -> complete = newState to oldState } + + assertEquals( + Pair( + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.DUAL), + Panels(main = 1, details = 2, extra = 3, mode = ChildPanelsMode.SINGLE), + ), + complete, + ) + } +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationTest.kt new file mode 100644 index 000000000..71647b326 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsNavigationTest.kt @@ -0,0 +1,322 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL +import com.arkivanov.decompose.router.panels.ChildPanelsMode.SINGLE +import com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE +import com.arkivanov.decompose.value.getValue +import com.arkivanov.essenty.lifecycle.Lifecycle.State.DESTROYED +import kotlin.test.Test +import kotlin.test.assertEquals + +@Suppress("TestFunctionName") +class ChildPanelsNavigationTest : BaseChildPanelsTest() { + + private val context = DefaultComponentContext(lifecycle = lifecycle) + + @Test + fun GIVEN_single_with_main_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = SINGLE)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = null, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_main_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = SINGLE)) + + nav.navigate { it.copy(extra = 2) } + + panels.assertPanels(main = 1, details = null, extra = 2, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_main_WHEN_activate_details_and_extra_THEN_details_and_extra_panels_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = SINGLE)) + + nav.navigate { it.copy(details = 2, extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_main_and_details_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = SINGLE)) + + nav.navigate { it.copy(extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_main_and_extra_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = SINGLE)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_main_THEN_main_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE)) + + nav.navigate { it.copy(main = 11) } + + panels.assertPanels(main = 11, details = 2, extra = 3, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_main_THEN_old_main_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE)) + val component = panels.main.instance + + nav.navigate { it.copy(main = 11) } + + assertEquals(DESTROYED, component.lifecycle.state) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_details_THEN_details_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE)) + + nav.navigate { it.copy(details = 22) } + + panels.assertPanels(main = 1, details = 22, extra = 3, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_details_THEN_old_details_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE)) + val component = panels.details?.instance + + nav.navigate { it.copy(details = 22) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_extra_THEN_extra_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = SINGLE)) + + nav.navigate { it.copy(extra = 33) } + + panels.assertPanels(main = 1, details = 2, extra = 33, mode = SINGLE) + } + + @Test + fun GIVEN_single_with_all_panels_WHEN_replace_extra_THEN_old_extra_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3)) + val component = panels.extra?.instance + + nav.navigate { it.copy(extra = 33) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } + + @Test + fun GIVEN_dual_with_main_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = DUAL)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = null, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_main_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = DUAL)) + + nav.navigate { it.copy(extra = 2) } + + panels.assertPanels(main = 1, details = null, extra = 2, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_main_WHEN_activate_details_and_extra_THEN_details_and_extra_panels_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = DUAL)) + + nav.navigate { it.copy(details = 2, extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_main_and_details_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = DUAL)) + + nav.navigate { it.copy(extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_main_and_extra_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = DUAL)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_main_THEN_main_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + + nav.navigate { it.copy(main = 11) } + + panels.assertPanels(main = 11, details = 2, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_main_THEN_old_main_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + val component = panels.main.instance + + nav.navigate { it.copy(main = 11) } + + assertEquals(DESTROYED, component.lifecycle.state) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_details_THEN_details_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + + nav.navigate { it.copy(details = 22) } + + panels.assertPanels(main = 1, details = 22, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_details_THEN_old_details_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + val component = panels.details?.instance + + nav.navigate { it.copy(details = 22) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_extra_THEN_extra_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + + nav.navigate { it.copy(extra = 33) } + + panels.assertPanels(main = 1, details = 2, extra = 33, mode = DUAL) + } + + @Test + fun GIVEN_dual_with_all_panels_WHEN_replace_extra_THEN_old_extra_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL)) + val component = panels.extra?.instance + + nav.navigate { it.copy(extra = 33) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } + + @Test + fun GIVEN_triple_with_main_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = TRIPLE)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = null, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_main_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = TRIPLE)) + + nav.navigate { it.copy(extra = 2) } + + panels.assertPanels(main = 1, details = null, extra = 2, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_main_WHEN_activate_details_and_extra_THEN_details_and_extra_panels_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, mode = TRIPLE)) + + nav.navigate { it.copy(details = 2, extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_main_and_details_WHEN_activate_extra_THEN_extra_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, mode = TRIPLE)) + + nav.navigate { it.copy(extra = 3) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_main_and_extra_WHEN_activate_details_THEN_details_panel_activated() { + val panels by context.childPanels(initialPanels = Panels(main = 1, extra = 3, mode = TRIPLE)) + + nav.navigate { it.copy(details = 2) } + + panels.assertPanels(main = 1, details = 2, extra = 3, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_main_THEN_main_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + + nav.navigate { it.copy(main = 11) } + + panels.assertPanels(main = 11, details = 2, extra = 3, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_main_THEN_old_main_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + val component = panels.main.instance + + nav.navigate { it.copy(main = 11) } + + assertEquals(DESTROYED, component.lifecycle.state) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_details_THEN_details_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + + nav.navigate { it.copy(details = 22) } + + panels.assertPanels(main = 1, details = 22, extra = 3, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_details_THEN_old_details_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + val component = panels.details?.instance + + nav.navigate { it.copy(details = 22) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_extra_THEN_extra_panel_replaced() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + + nav.navigate { it.copy(extra = 33) } + + panels.assertPanels(main = 1, details = 2, extra = 33, mode = TRIPLE) + } + + @Test + fun GIVEN_triple_with_all_panels_WHEN_replace_extra_THEN_old_extra_panel_destroyed() { + val panels by context.childPanels(initialPanels = Panels(main = 1, details = 2, extra = 3, mode = TRIPLE)) + val component = panels.extra?.instance + + nav.navigate { it.copy(extra = 33) } + + assertEquals(DESTROYED, component?.lifecycle?.state) + } +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsSavedStateTest.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsSavedStateTest.kt new file mode 100644 index 000000000..41716baf1 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/ChildPanelsSavedStateTest.kt @@ -0,0 +1,48 @@ +package com.arkivanov.decompose.router.panels + +import com.arkivanov.decompose.DefaultComponentContext +import com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL +import com.arkivanov.decompose.router.panels.ChildPanelsMode.SINGLE +import com.arkivanov.decompose.statekeeper.TestStateKeeperDispatcher +import com.arkivanov.decompose.value.getValue +import kotlin.test.Test + +@Suppress("TestFunctionName") +class ChildPanelsSavedStateTest : BaseChildPanelsTest() { + + private val context = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = stateKeeper) + + @Test + fun GIVEN_persistent_WHEN_recreated_THEN_state_restored() { + var stateKeeper = TestStateKeeperDispatcher() + var context = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = stateKeeper) + context.childPanels( + initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL), + persistent = true, + ) + + val savedState = stateKeeper.save() + stateKeeper = TestStateKeeperDispatcher(savedState = savedState) + context = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = stateKeeper) + val panels2 by context.childPanels() + + panels2.assertPanels(main = 1, details = 2, extra = 3, mode = DUAL) + } + + @Test + fun GIVEN_not_persistent_WHEN_recreated_THEN_initial_state_applied() { + var stateKeeper = TestStateKeeperDispatcher() + var context = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = stateKeeper) + context.childPanels( + initialPanels = Panels(main = 1, details = 2, extra = 3, mode = DUAL), + persistent = false, + ) + + val savedState = stateKeeper.save() + stateKeeper = TestStateKeeperDispatcher(savedState = savedState) + context = DefaultComponentContext(lifecycle = lifecycle, stateKeeper = stateKeeper) + val pages2 by context.childPanels(initialPanels = Panels(main = 11, details = 22, extra = 33, mode = SINGLE)) + + pages2.assertPanels(main = 11, details = 22, extra = 33, mode = SINGLE) + } +} diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/TestPanelsNavigator.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/TestPanelsNavigator.kt new file mode 100644 index 000000000..a4085b128 --- /dev/null +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/router/panels/TestPanelsNavigator.kt @@ -0,0 +1,22 @@ +package com.arkivanov.decompose.router.panels + +import kotlin.test.assertEquals + +class TestPanelsNavigator( + private var panels: Panels, +) : PanelsNavigator { + + fun assertPanels(panels: Panels) { + assertEquals(panels, this.panels) + } + + override fun navigate( + transformer: (Panels) -> Panels, + onComplete: (newState: Panels, oldState: Panels) -> Unit, + ) { + val oldPanels = panels + val newPanels = transformer(panels) + panels = newPanels + onComplete(newPanels, oldPanels) + } +} diff --git a/extensions-compose-experimental/api/android/extensions-compose-experimental.api b/extensions-compose-experimental/api/android/extensions-compose-experimental.api index 28971a08d..16f85094f 100644 --- a/extensions-compose-experimental/api/android/extensions-compose-experimental.api +++ b/extensions-compose-experimental/api/android/extensions-compose-experimental.api @@ -1,3 +1,41 @@ +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators { + public static final field $stable I + public fun ()V + public fun (Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator;Lkotlin/Pair;Lkotlin/Triple;)V + public synthetic fun (Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator;Lkotlin/Pair;Lkotlin/Triple;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getDual ()Lkotlin/Pair; + public final fun getSingle ()Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator; + public final fun getTriple ()Lkotlin/Triple; +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsKt { + public static final fun ChildPanels (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V +} + +public abstract interface class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout { + public abstract fun Layout (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ComposableSingletons$ChildPanelsKt { + public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ComposableSingletons$ChildPanelsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public static field lambda-2 Lkotlin/jvm/functions/Function3; + public fun ()V + public final fun getLambda-1$extensions_compose_experimental_release ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$extensions_compose_experimental_release ()Lkotlin/jvm/functions/Function3; +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout : com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout { + public static final field $stable I + public fun ()V + public fun (Lkotlin/Pair;Lkotlin/Triple;)V + public synthetic fun (Lkotlin/Pair;Lkotlin/Triple;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun Layout (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V +} + public final class com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackKt { public static final fun ChildStack (Lcom/arkivanov/decompose/router/stack/ChildStack;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ChildStack (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V diff --git a/extensions-compose-experimental/api/extensions-compose-experimental.klib.api b/extensions-compose-experimental/api/extensions-compose-experimental.klib.api index adae9972f..c7e04401b 100644 --- a/extensions-compose-experimental/api/extensions-compose-experimental.klib.api +++ b/extensions-compose-experimental/api/extensions-compose-experimental.klib.api @@ -14,10 +14,31 @@ abstract fun interface com.arkivanov.decompose.extensions.compose.experimental.s abstract fun (androidx.compose.animation/AnimatedVisibilityScope).animate(com.arkivanov.decompose.extensions.compose.stack.animation/Direction, androidx.compose.runtime/Composer?, kotlin/Int): androidx.compose.ui/Modifier // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator.animate|animate@androidx.compose.animation.AnimatedVisibilityScope(com.arkivanov.decompose.extensions.compose.stack.animation.Direction;androidx.compose.runtime.Composer?;kotlin.Int){}[0] } +abstract interface com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout { // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout|null[0] + abstract fun Layout(com.arkivanov.decompose.router.panels/ChildPanelsMode, kotlin/Function2, kotlin/Function2, kotlin/Function2, androidx.compose.runtime/Composer?, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout.Layout|Layout(com.arkivanov.decompose.router.panels.ChildPanelsMode;kotlin.Function2;kotlin.Function2;kotlin.Function2;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +} + abstract interface com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimationProvider { // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimationProvider|null[0] abstract fun <#A1: kotlin/Any, #B1: kotlin/Any> provide(): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimation<#A1, #B1>? // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimationProvider.provide|provide(){0§;1§}[0] } +final class com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators { // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators|null[0] + constructor (com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator? = ..., kotlin/Pair = ..., kotlin/Triple = ...) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.|(com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator?;kotlin.Pair;kotlin.Triple){}[0] + + final val dual // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.dual|{}dual[0] + final fun (): kotlin/Pair // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.dual.|(){}[0] + final val single // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.single|{}single[0] + final fun (): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator? // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.single.|(){}[0] + final val triple // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.triple|{}triple[0] + final fun (): kotlin/Triple // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators.triple.|(){}[0] +} + +final class com.arkivanov.decompose.extensions.compose.experimental.panels/HorizontalChildPanelsLayout : com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout { // com.arkivanov.decompose.extensions.compose.experimental.panels/HorizontalChildPanelsLayout|null[0] + constructor (kotlin/Pair = ..., kotlin/Triple = ...) // com.arkivanov.decompose.extensions.compose.experimental.panels/HorizontalChildPanelsLayout.|(kotlin.Pair;kotlin.Triple){}[0] + + final fun Layout(com.arkivanov.decompose.router.panels/ChildPanelsMode, kotlin/Function2, kotlin/Function2, kotlin/Function2, androidx.compose.runtime/Composer?, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/HorizontalChildPanelsLayout.Layout|Layout(com.arkivanov.decompose.router.panels.ChildPanelsMode;kotlin.Function2;kotlin.Function2;kotlin.Function2;androidx.compose.runtime.Composer?;kotlin.Int){}[0] +} + final class com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams { // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams|null[0] constructor (com.arkivanov.essenty.backhandler/BackHandler, kotlin/Function0, kotlin/Function1 = ...) // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams.|(com.arkivanov.essenty.backhandler.BackHandler;kotlin.Function0;kotlin.Function1){}[0] @@ -29,17 +50,30 @@ final class com.arkivanov.decompose.extensions.compose.experimental.stack.animat final fun (): kotlin/Function0 // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams.onBack.|(){}[0] } +final val com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop[0] +final val com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop[0] +final val com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop[0] +final val com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop[0] final val com.arkivanov.decompose.extensions.compose.experimental.stack.animation/LocalStackAnimationProvider // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/LocalStackAnimationProvider|{}LocalStackAnimationProvider[0] final fun (): androidx.compose.runtime/ProvidableCompositionLocal // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/LocalStackAnimationProvider.|(){}[0] final val com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop[0] final val com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop[0] final val com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop[0] +final val com.arkivanov.decompose.extensions.compose.experimental/com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop // com.arkivanov.decompose.extensions.compose.experimental/com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop|#static{}com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop[0] final fun (com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator).com.arkivanov.decompose.extensions.compose.experimental.stack.animation/plus(com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/plus|plus@com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator(com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator){}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels(com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, #E, #F>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators?, kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels|ChildPanels(com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,0:4,0:5>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsLayout?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators?;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§;2§;3§;4§;5§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any, #E: kotlin/Any, #F: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels(com.arkivanov.decompose.value/Value>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators?, kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels|ChildPanels(com.arkivanov.decompose.value.Value>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsLayout?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators?;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§;2§;3§;4§;5§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels(com.arkivanov.decompose.router.panels/ChildPanels<#A, #B, #C, #D, *, *>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators?, kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels|ChildPanels(com.arkivanov.decompose.router.panels.ChildPanels<0:0,0:1,0:2,0:3,*,*>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsLayout?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators?;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§;2§;3§}[0] +final fun <#A: kotlin/Any, #B: kotlin/Any, #C: kotlin/Any, #D: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels(com.arkivanov.decompose.value/Value>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, kotlin/Function3, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsLayout?, com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanelsAnimators?, kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?>?, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.panels/ChildPanels|ChildPanels(com.arkivanov.decompose.value.Value>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;kotlin.Function3,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsLayout?;com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators?;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>?;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§;2§;3§}[0] final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimation(com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator? = ..., kotlin/Boolean = ..., kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?> = ...): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimation<#A, #B> // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimation|stackAnimation(com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator?;kotlin.Boolean;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>){0§;1§}[0] final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimation(kotlin/Boolean = ..., kotlin/Function1, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/PredictiveBackParams?> = ..., kotlin/Function3, com.arkivanov.decompose/Child.Created<#A, #B>, com.arkivanov.decompose.extensions.compose.stack.animation/Direction, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator?>): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimation<#A, #B> // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimation|stackAnimation(kotlin.Boolean;kotlin.Function1,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams?>;kotlin.Function3,com.arkivanov.decompose.Child.Created<0:0,0:1>,com.arkivanov.decompose.extensions.compose.stack.animation.Direction,com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator?>){0§;1§}[0] final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.stack/ChildStack(com.arkivanov.decompose.router.stack/ChildStack<#A, #B>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimation<#A, #B>?, kotlin/Function4, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.stack/ChildStack|ChildStack(com.arkivanov.decompose.router.stack.ChildStack<0:0,0:1>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimation<0:0,0:1>?;kotlin.Function4,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§}[0] final fun <#A: kotlin/Any, #B: kotlin/Any> com.arkivanov.decompose.extensions.compose.experimental.stack/ChildStack(com.arkivanov.decompose.value/Value>, androidx.compose.ui/Modifier?, com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimation<#A, #B>?, kotlin/Function4, androidx.compose.runtime/Composer, kotlin/Int, kotlin/Unit>, androidx.compose.runtime/Composer?, kotlin/Int, kotlin/Int) // com.arkivanov.decompose.extensions.compose.experimental.stack/ChildStack|ChildStack(com.arkivanov.decompose.value.Value>;androidx.compose.ui.Modifier?;com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimation<0:0,0:1>?;kotlin.Function4,androidx.compose.runtime.Composer,kotlin.Int,kotlin.Unit>;androidx.compose.runtime.Composer?;kotlin.Int;kotlin.Int){0§;1§}[0] +final fun com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_panels_ChildPanelsAnimators$stableprop_getter(){}[0] +final fun com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_panels_HorizontalChildPanelsLayout$stableprop_getter(){}[0] +final fun com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Empty$stableprop_getter(){}[0] +final fun com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.panels/com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_panels_PanelChild_Panel$stableprop_getter(){}[0] final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimation$stableprop_getter(){}[0] final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_stack_animation_DefaultStackAnimator$stableprop_getter(){}[0] final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_stack_animation_PredictiveBackParams$stableprop_getter(){}[0] @@ -47,3 +81,4 @@ final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animatio final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/scale(androidx.compose.animation.core/FiniteAnimationSpec = ..., kotlin/Float = ..., kotlin/Float = ...): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/scale|scale(androidx.compose.animation.core.FiniteAnimationSpec;kotlin.Float;kotlin.Float){}[0] final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/slide(androidx.compose.animation.core/FiniteAnimationSpec = ..., androidx.compose.foundation.gestures/Orientation = ...): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/slide|slide(androidx.compose.animation.core.FiniteAnimationSpec;androidx.compose.foundation.gestures.Orientation){}[0] final fun com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimator(androidx.compose.animation.core/FiniteAnimationSpec = ..., kotlin/Function4): com.arkivanov.decompose.extensions.compose.experimental.stack.animation/StackAnimator // com.arkivanov.decompose.extensions.compose.experimental.stack.animation/stackAnimator|stackAnimator(androidx.compose.animation.core.FiniteAnimationSpec;kotlin.Function4){}[0] +final fun com.arkivanov.decompose.extensions.compose.experimental/com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop_getter(): kotlin/Int // com.arkivanov.decompose.extensions.compose.experimental/com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop_getter|com_arkivanov_decompose_extensions_compose_experimental_BroadcastBackHandler$stableprop_getter(){}[0] diff --git a/extensions-compose-experimental/api/jvm/extensions-compose-experimental.api b/extensions-compose-experimental/api/jvm/extensions-compose-experimental.api index 28971a08d..1c3ec111e 100644 --- a/extensions-compose-experimental/api/jvm/extensions-compose-experimental.api +++ b/extensions-compose-experimental/api/jvm/extensions-compose-experimental.api @@ -1,3 +1,41 @@ +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators { + public static final field $stable I + public fun ()V + public fun (Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator;Lkotlin/Pair;Lkotlin/Triple;)V + public synthetic fun (Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator;Lkotlin/Pair;Lkotlin/Triple;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getDual ()Lkotlin/Pair; + public final fun getSingle ()Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimator; + public final fun getTriple ()Lkotlin/Triple; +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsKt { + public static final fun ChildPanels (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/router/panels/ChildPanels;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V + public static final fun ChildPanels (Lcom/arkivanov/decompose/value/Value;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Lkotlin/jvm/functions/Function3;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout;Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators;Lkotlin/jvm/functions/Function1;Landroidx/compose/runtime/Composer;II)V +} + +public abstract interface class com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout { + public abstract fun Layout (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/ComposableSingletons$ChildPanelsKt { + public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/experimental/panels/ComposableSingletons$ChildPanelsKt; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public static field lambda-2 Lkotlin/jvm/functions/Function3; + public fun ()V + public final fun getLambda-1$extensions_compose_experimental ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$extensions_compose_experimental ()Lkotlin/jvm/functions/Function3; +} + +public final class com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout : com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout { + public static final field $stable I + public fun ()V + public fun (Lkotlin/Pair;Lkotlin/Triple;)V + public synthetic fun (Lkotlin/Pair;Lkotlin/Triple;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun Layout (Lcom/arkivanov/decompose/router/panels/ChildPanelsMode;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;I)V +} + public final class com/arkivanov/decompose/extensions/compose/experimental/stack/ChildStackKt { public static final fun ChildStack (Lcom/arkivanov/decompose/router/stack/ChildStack;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ChildStack (Lcom/arkivanov/decompose/value/Value;Landroidx/compose/ui/Modifier;Lcom/arkivanov/decompose/extensions/compose/experimental/stack/animation/StackAnimation;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V diff --git a/extensions-compose-experimental/build.gradle.kts b/extensions-compose-experimental/build.gradle.kts index 4828d6919..943818461 100644 --- a/extensions-compose-experimental/build.gradle.kts +++ b/extensions-compose-experimental/build.gradle.kts @@ -37,6 +37,7 @@ kotlin { all { languageSettings { optIn("com.arkivanov.decompose.InternalDecomposeApi") + optIn("com.arkivanov.decompose.ExperimentalDecomposeApi") } } diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/BroadcastBackHandler.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/BroadcastBackHandler.kt new file mode 100644 index 000000000..7f0c8a327 --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/BroadcastBackHandler.kt @@ -0,0 +1,50 @@ +package com.arkivanov.decompose.extensions.compose.experimental + +import com.arkivanov.essenty.backhandler.BackCallback +import com.arkivanov.essenty.backhandler.BackEvent +import com.arkivanov.essenty.backhandler.BackHandler + +internal class BroadcastBackHandler( + private val delegate: BackHandler, +) : BackCallback(), BackHandler { + private var callbacks = emptyList() + + override fun onBackStarted(backEvent: BackEvent) { + callbacks.forEach { it.onBackStarted(backEvent) } + } + + override fun onBackProgressed(backEvent: BackEvent) { + callbacks.forEach { it.onBackProgressed(backEvent) } + } + + override fun onBackCancelled() { + callbacks.forEach(BackCallback::onBackCancelled) + } + + override fun onBack() { + callbacks.forEach(BackCallback::onBack) + } + + override fun isRegistered(callback: BackCallback): Boolean = + callback in callbacks + + override fun register(callback: BackCallback) { + check(callback !in callbacks) + callbacks += callback + callbacks = callbacks.sortedByDescending(BackCallback::priority) + priority = callback.priority + + if (callbacks.size == 1) { + delegate.register(this) + } + } + + override fun unregister(callback: BackCallback) { + check(callback in callbacks) + callbacks -= callback + + if (callbacks.isEmpty()) { + delegate.unregister(this) + } + } +} diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt new file mode 100644 index 000000000..b4dc69e0d --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/Utils.kt @@ -0,0 +1,8 @@ +package com.arkivanov.decompose.extensions.compose.experimental + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember + +@Composable +internal fun rememberLazy(key: Any, provider: () -> T): Lazy = + remember(key) { lazy(provider) } diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt new file mode 100644 index 000000000..42bf75013 --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanels.kt @@ -0,0 +1,370 @@ +package com.arkivanov.decompose.extensions.compose.experimental.panels + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.experimental.BroadcastBackHandler +import com.arkivanov.decompose.extensions.compose.experimental.rememberLazy +import com.arkivanov.decompose.extensions.compose.experimental.stack.ChildStack +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.stackAnimation +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL +import com.arkivanov.decompose.router.panels.ChildPanelsMode.SINGLE +import com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE +import com.arkivanov.decompose.router.stack.ChildStack +import com.arkivanov.decompose.value.Value + +/** + * Displays the provided [ChildPanels], taking care of saving and restoring the UI state. This variant + * supports up to two child components: Main (required) and Details (optional). + * + * Child Panels layout is comparable with Compose + * [List-Details Layout](https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail). + * + * @param panels an observable [ChildPanels] to be displayed. + * @param mainChild a `Composable` function that displays the provided Main component. + * @param detailsChild a `Composable` function that displays the provided Details component. + * @param modifier a [Modifier] to applied to a wrapping container. + * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. + * @param animators a [ChildPanelsAnimators] containing panel animators for different + * kinds of layouts. + * @param predictiveBackParams a function that returns [PredictiveBackParams] for the specified [ChildPanels], + * or `null`. The predictive back gesture is enabled if the value returned for the specified [ChildStack] + * is not `null`, and disabled if the returned value is `null`. + * Only works if [ChildPanels.mode] is [ChildPanelsMode.SINGLE]. + */ +@ExperimentalDecomposeApi +@Composable +fun ChildPanels( + panels: Value>, + mainChild: @Composable (Child.Created) -> Unit, + detailsChild: @Composable (Child.Created) -> Unit, + modifier: Modifier = Modifier, + layout: ChildPanelsLayout = remember { HorizontalChildPanelsLayout() }, + animators: ChildPanelsAnimators = remember { ChildPanelsAnimators() }, + predictiveBackParams: (ChildPanels) -> PredictiveBackParams? = { null }, +) { + ChildPanels( + panels = panels, + mainChild = mainChild, + detailsChild = detailsChild, + extraChild = {}, + modifier = modifier, + layout = layout, + animators = animators, + predictiveBackParams = predictiveBackParams, + ) +} + +/** + * Displays the provided [ChildPanels], taking care of saving and restoring the UI state. This variant + * supports up to two child components: Main (required) and Details (optional). + * + * Child Panels layout is comparable with Compose + * [List-Details Layout](https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail). + * + * @param panels a [ChildPanels] to be displayed. + * @param mainChild a `Composable` function that displays the provided Main component. + * @param detailsChild a `Composable` function that displays the provided Details component. + * @param modifier a [Modifier] to applied to a wrapping container. + * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. + * @param animators a [ChildPanelsAnimators] containing panel animators for different + * kinds of layouts. + * @param predictiveBackParams a function that returns [PredictiveBackParams] for the specified [ChildPanels], + * or `null`. The predictive back gesture is enabled if the value returned for the specified [ChildStack] + * is not `null`, and disabled if the returned value is `null`. + * Only works if [ChildPanels.mode] is [ChildPanelsMode.SINGLE]. + */ +@ExperimentalDecomposeApi +@Composable +fun ChildPanels( + panels: ChildPanels, + mainChild: @Composable (Child.Created) -> Unit, + detailsChild: @Composable (Child.Created) -> Unit, + modifier: Modifier = Modifier, + layout: ChildPanelsLayout = remember { HorizontalChildPanelsLayout() }, + animators: ChildPanelsAnimators = remember { ChildPanelsAnimators() }, + predictiveBackParams: (ChildPanels) -> PredictiveBackParams? = { null }, +) { + ChildPanels( + panels = panels, + mainChild = mainChild, + detailsChild = detailsChild, + extraChild = {}, + modifier = modifier, + layout = layout, + animators = animators, + predictiveBackParams = predictiveBackParams, + ) +} + +/** + * Displays the provided [ChildPanels], taking care of saving and restoring the UI state. This variant + * supports up to three child components: Main (required), Details (optional) and Extra (optional). + * + * Child Panels layout is comparable with Compose + * [List-Details Layout](https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail). + * + * @param panels an observable [ChildPanels] to be displayed. + * @param mainChild a `Composable` function that displays the provided Main component. + * @param detailsChild a `Composable` function that displays the provided Details component. + * @param extraChild a `Composable` function that displays the provided Extra component. + * @param modifier a [Modifier] to applied to a wrapping container. + * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. + * @param animators a [ChildPanelsAnimators] containing panel animators for different + * kinds of layouts. + * @param predictiveBackParams a function that returns [PredictiveBackParams] for the specified [ChildPanels], + * or `null`. The predictive back gesture is enabled if the value returned for the specified [ChildStack] + * is not `null`, and disabled if the returned value is `null`. + * Only works if [ChildPanels.mode] is [ChildPanelsMode.SINGLE]. + */ +@ExperimentalDecomposeApi +@Composable +fun ChildPanels( + panels: Value>, + mainChild: @Composable (Child.Created) -> Unit, + detailsChild: @Composable (Child.Created) -> Unit, + extraChild: @Composable (Child.Created) -> Unit, + modifier: Modifier = Modifier, + layout: ChildPanelsLayout = remember { HorizontalChildPanelsLayout() }, + animators: ChildPanelsAnimators = remember { ChildPanelsAnimators() }, + predictiveBackParams: (ChildPanels) -> PredictiveBackParams? = { null }, +) { + val state = panels.subscribeAsState() + + ChildPanels( + panels = state.value, + mainChild = mainChild, + detailsChild = detailsChild, + extraChild = extraChild, + modifier = modifier, + layout = layout, + animators = animators, + predictiveBackParams = predictiveBackParams, + ) +} + +/** + * Displays the provided [ChildPanels], taking care of saving and restoring the UI state. This variant + * supports up to three child components: Main (required), Details (optional) and Extra (optional). + * + * Child Panels layout is comparable with Compose + * [List-Details Layout](https://developer.android.com/develop/ui/compose/layouts/adaptive/list-detail). + * + * @param panels a [ChildPanels] to be displayed. + * @param mainChild a `Composable` function that displays the provided Main component. + * @param detailsChild a `Composable` function that displays the provided Details component. + * @param extraChild a `Composable` function that displays the provided Extra component. + * @param modifier a [Modifier] to applied to a wrapping container. + * @param layout an implementation of [ChildPanelsLayout] responsible for laying out panels. + * @param animators a [ChildPanelsAnimators] containing panel animators for different + * kinds of layouts. + * @param predictiveBackParams a function that returns [PredictiveBackParams] for the specified [ChildPanels], + * or `null`. The predictive back gesture is enabled if the value returned for the specified [ChildStack] + * is not `null`, and disabled if the returned value is `null`. + * Only works if [ChildPanels.mode] is [ChildPanelsMode.SINGLE]. + */ +@ExperimentalDecomposeApi +@Composable +fun ChildPanels( + panels: ChildPanels, + mainChild: @Composable (Child.Created) -> Unit, + detailsChild: @Composable (Child.Created) -> Unit, + extraChild: @Composable (Child.Created) -> Unit, + modifier: Modifier = Modifier, + layout: ChildPanelsLayout = remember { HorizontalChildPanelsLayout() }, + animators: ChildPanelsAnimators = remember { ChildPanelsAnimators() }, + predictiveBackParams: (ChildPanels) -> PredictiveBackParams? = { null }, +) { + val main = remember(panels.main) { panels.main.asPanelChild() } + val details = remember(panels.details) { panels.details?.asPanelChild() } + val extra = remember(panels.extra) { panels.extra?.asPanelChild() } + val mode = panels.mode + val broadcastPredictiveBackParams = rememberBroadcastPredictiveBackParams(key = panels, count = 2) { predictiveBackParams(panels) } + + Box(modifier = modifier) { + layout.Layout( + mode = mode, + main = { + MainPanel( + main = main, + mode = mode, + hasDetails = details != null, + hasExtra = extra != null, + animators = animators, + predictiveBackParams = broadcastPredictiveBackParams, + content = mainChild, + ) + }, + details = { + DetailsPanel( + details = details, + mode = mode, + hasExtra = extra != null, + animators = animators, + predictiveBackParams = broadcastPredictiveBackParams, + content = detailsChild, + ) + }, + extra = { + ExtraPanel( + extra = extra, + mode = mode, + animators = animators, + predictiveBackParams = broadcastPredictiveBackParams, + content = extraChild, + ) + }, + ) + } +} + +@ExperimentalDecomposeApi +@Composable +private fun MainPanel( + main: Child.Created>, + mode: ChildPanelsMode, + hasDetails: Boolean, + hasExtra: Boolean, + animators: ChildPanelsAnimators, + predictiveBackParams: Lazy, + content: @Composable (Child.Created) -> Unit, +) { + ChildStack( + stack = when (mode) { + SINGLE -> stackOfNotNull(main, EmptyChild1.takeIf { hasDetails }, EmptyChild2.takeIf { hasExtra }) + DUAL, + TRIPLE -> stackOfNotNull(main) + }, + modifier = Modifier.fillMaxSize(), + animation = stackAnimation( + animator = when (mode) { + SINGLE -> animators.single + DUAL -> animators.dual.first + TRIPLE -> animators.triple.first + }, + predictiveBackParams = { if (it.active != main) predictiveBackParams.value else null }, + ), + ) { + when (val child = it.instance) { + is PanelChild.Panel -> content(child.child) + is PanelChild.Empty -> Unit // no-op + } + } +} + +@ExperimentalDecomposeApi +@Composable +private fun DetailsPanel( + details: Child.Created>?, + mode: ChildPanelsMode, + hasExtra: Boolean, + animators: ChildPanelsAnimators, + predictiveBackParams: Lazy, + content: @Composable (Child.Created) -> Unit, +) { + ChildStack( + stack = when (mode) { + SINGLE -> stackOfNotNull(EmptyChild1, details, EmptyChild2.takeIf { (details != null) && hasExtra }) + DUAL -> stackOfNotNull(EmptyChild3, details, EmptyChild4.takeIf { (details != null) && hasExtra }) + TRIPLE -> stackOfNotNull(EmptyChild3, details) + }, + modifier = Modifier.fillMaxSize(), + animation = stackAnimation( + animator = when (mode) { + SINGLE -> animators.single + DUAL -> animators.dual.second + TRIPLE -> animators.triple.second + }, + predictiveBackParams = { stack -> + when { + stack.active == EmptyChild2 -> predictiveBackParams.value + (stack.active == details) && (stack.items.first() == EmptyChild1) -> predictiveBackParams.value + else -> null + } + }, + ), + ) { + when (val child = it.instance) { + is PanelChild.Panel -> content(child.child) + is PanelChild.Empty -> Unit // no-op + } + } +} + +@ExperimentalDecomposeApi +@Composable +private fun ExtraPanel( + extra: Child.Created>?, + mode: ChildPanelsMode, + animators: ChildPanelsAnimators, + predictiveBackParams: Lazy, + content: @Composable (Child.Created) -> Unit, +) { + ChildStack( + stack = stackOfNotNull(if (mode == SINGLE) EmptyChild1 else EmptyChild2, extra), + modifier = Modifier.fillMaxSize(), + animation = stackAnimation( + animator = when (mode) { + SINGLE -> animators.single + DUAL -> animators.dual.second + TRIPLE -> animators.triple.third + }, + predictiveBackParams = { if (it.backStack.first() == EmptyChild1) predictiveBackParams.value else null }, + ), + ) { + when (val child = it.instance) { + is PanelChild.Panel -> content(child.child) + is PanelChild.Empty -> Unit // no-op + } + } +} + +@Composable +private fun rememberBroadcastPredictiveBackParams( + key: Any, + count: Int, + params: () -> PredictiveBackParams? +): Lazy = + rememberLazy(key) { + params()?.run { + var onBackCallCount = 0 + + copy( + backHandler = BroadcastBackHandler(backHandler), + onBack = { + if (++onBackCallCount == count) { + onBackCallCount = 0 + onBack() + } + }, + ) + } + } + +private fun stackOfNotNull(vararg stack: Child.Created?): ChildStack = + stack.filterNotNull().let { + ChildStack(active = it.last(), backStack = it.dropLast(1)) + } + +private fun Child.Created.asPanelChild(): Child.Created> = + Child.Created(configuration = configuration, PanelChild.Panel(child = this)) + +private val EmptyChild1 = Child.Created(configuration = EmptyConfig(value = 1), instance = PanelChild.Empty) +private val EmptyChild2 = Child.Created(configuration = EmptyConfig(value = 2), instance = PanelChild.Empty) +private val EmptyChild3 = Child.Created(configuration = EmptyConfig(value = 3), instance = PanelChild.Empty) +private val EmptyChild4 = Child.Created(configuration = EmptyConfig(value = 4), instance = PanelChild.Empty) + +private data class EmptyConfig(val value: Any) + +private sealed interface PanelChild { + class Panel(val child: Child.Created) : PanelChild + data object Empty : PanelChild +} diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators.kt new file mode 100644 index 000000000..bfa560429 --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsAnimators.kt @@ -0,0 +1,27 @@ +package com.arkivanov.decompose.extensions.compose.experimental.panels + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.StackAnimator +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.fade + +/** + * Contains panel animators for all layout kinds. + * + * @param single a [StackAnimator] to be used in + * [SINGLE][com.arkivanov.decompose.router.panels.ChildPanelsMode.SINGLE] mode. + * @param dual a [Pair] of [StackAnimator] to be used in + * [DUAL][com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL] mode. The [first][Pair.first] animator + * is used for the primary (first) panel, and the [second][Pair.second] animator is used for the secondary + * (second) panel. Default value is `Pair(single, single)`. + * @param triple a [Triple] of [StackAnimator] to be used in + * [TRIPLE][com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE] mode. The [first][Triple.first] animator + * is used for the primary (first) panel, the [second][Triple.second] animator is used for the secondary (second) + * panel, and the [third][Triple.third] animator is used for the tertiary (third) panel. + * Default values is `Triple(dual.first, dual.second, dual.second)`. + */ +@ExperimentalDecomposeApi +class ChildPanelsAnimators( + val single: StackAnimator? = fade(), + val dual: Pair = single to single, + val triple: Triple = Triple(dual.first, dual.second, dual.second), +) diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout.kt new file mode 100644 index 000000000..1003f39cc --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/ChildPanelsLayout.kt @@ -0,0 +1,28 @@ +package com.arkivanov.decompose.extensions.compose.experimental.panels + +import androidx.compose.runtime.Composable +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanelsMode + +/** + * A Child Panels layout used for laying out panels in single, dual and triple modes. + */ +@ExperimentalDecomposeApi +interface ChildPanelsLayout { + + /** + * Lays out the provided `Composable` panels according to the current [mode]. + * + * @param mode the current layout mode, see [ChildPanelsMode]. + * @param main the Main panel `Composable` function. + * @param details the Details panel `Composable` function. + * @param extra the Extra panel `Composable` function. + */ + @Composable + fun Layout( + mode: ChildPanelsMode, + main: @Composable () -> Unit, + details: @Composable () -> Unit, + extra: @Composable () -> Unit, + ) +} diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt new file mode 100644 index 000000000..475a66af4 --- /dev/null +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/panels/HorizontalChildPanelsLayout.kt @@ -0,0 +1,106 @@ +package com.arkivanov.decompose.extensions.compose.experimental.panels + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.Layout +import androidx.compose.ui.layout.Measurable +import androidx.compose.ui.layout.MeasurePolicy +import androidx.compose.ui.layout.MeasureResult +import androidx.compose.ui.layout.MeasureScope +import androidx.compose.ui.unit.Constraints +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanelsMode + +/** + * An implementation of [ChildPanelsLayout] for laying out panels horizontally. + * + * @param dualWeights a [Pair] of weights for two panels to be used in + * [DUAL][com.arkivanov.decompose.router.panels.ChildPanelsMode.DUAL] mode. + * Default values are `1F`, meaning that all panels have equal width. + * @param tripleWeights A [Triple] of weights for three panels to be used in + * [TRIPLE][com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE] mode. + * Default values are `1F`, meaning that all panel slots have equal width. + */ +@ExperimentalDecomposeApi +class HorizontalChildPanelsLayout( + private val dualWeights: Pair = Pair(1F, 1F), + private val tripleWeights: Triple = Triple(1F, 1F, 1F), +) : ChildPanelsLayout { + + private val singleMeasurePolicy = SingleMeasurePolicy() + private val dualMeasurePolicy = DualMeasurePolicy(weights = dualWeights) + private val tripleMeasurePolicy = TripleMeasurePolicy(weights = tripleWeights) + + @Composable + override fun Layout( + mode: ChildPanelsMode, + main: @Composable () -> Unit, + details: @Composable () -> Unit, + extra: @Composable () -> Unit, + ) { + Layout( + content = { + main() + details() + extra() + }, + modifier = Modifier.fillMaxSize(), + measurePolicy = when (mode) { + ChildPanelsMode.SINGLE -> singleMeasurePolicy + ChildPanelsMode.DUAL -> dualMeasurePolicy + ChildPanelsMode.TRIPLE -> tripleMeasurePolicy + }, + ) + } + + private class SingleMeasurePolicy : MeasurePolicy { + override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { + val placeables = measurables.map { it.measure(constraints) } + + return layout(constraints.maxWidth, constraints.maxHeight) { + placeables.forEach { + it.placeRelative(x = 0, y = 0) + } + } + } + } + + private class DualMeasurePolicy(weights: Pair) : MeasurePolicy { + private val primaryWeight = weights.first / (weights.first + weights.second) + + override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { + val w1 = (constraints.maxWidth.toFloat() * primaryWeight).toInt() + val w2 = constraints.maxWidth - w1 + val placeable1 = measurables[0].measure(constraints.copy(maxWidth = w1, minWidth = w1)) + val placeable2 = measurables[1].measure(constraints.copy(maxWidth = w2, minWidth = w2)) + val placeable3 = measurables[2].measure(constraints.copy(maxWidth = w2, minWidth = w2)) + + return layout(constraints.maxWidth, constraints.maxHeight) { + placeable1.placeRelative(x = 0, y = 0) + placeable2.placeRelative(x = w1, y = 0) + placeable3.placeRelative(x = w1, y = 0) + } + } + } + + private class TripleMeasurePolicy(weights: Triple) : MeasurePolicy { + private val primaryWeight = weights.first / (weights.first + weights.second + weights.third) + private val secondaryWeight = weights.second / (weights.first + weights.second + weights.third) + + override fun MeasureScope.measure(measurables: List, constraints: Constraints): MeasureResult { + val w1 = (constraints.maxWidth.toFloat() * primaryWeight).toInt() + val w2 = (constraints.maxWidth.toFloat() * secondaryWeight).toInt() + val w3 = (constraints.maxWidth - w1 - w2) + val placeable1 = measurables[0].measure(constraints.copy(maxWidth = w1, minWidth = w1)) + val placeable2 = measurables[1].measure(constraints.copy(maxWidth = w2, minWidth = w2)) + val placeable3 = measurables[2].measure(constraints.copy(maxWidth = w3, minWidth = w3)) + + return layout(constraints.maxWidth, constraints.maxHeight) { + placeable1.placeRelative(x = 0, y = 0) + placeable2.placeRelative(x = w1, y = 0) + placeable3.placeRelative(x = w1 + w2, y = 0) + } + } + } +} diff --git a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackParams.kt b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackParams.kt index fd9e2b268..2c6f4f51e 100644 --- a/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackParams.kt +++ b/extensions-compose-experimental/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/experimental/stack/animation/PredictiveBackParams.kt @@ -24,4 +24,16 @@ class PredictiveBackParams( val backHandler: BackHandler, val onBack: () -> Unit, val animatable: (initialBackEvent: BackEvent) -> PredictiveBackAnimatable? = { null }, -) +) { + + internal fun copy( + backHandler: BackHandler = this.backHandler, + onBack: () -> Unit = this.onBack, + animatable: (initialBackEvent: BackEvent) -> PredictiveBackAnimatable? = this.animatable, + ): PredictiveBackParams = + PredictiveBackParams( + backHandler = backHandler, + onBack = onBack, + animatable = animatable, + ) +} diff --git a/extensions-compose/build.gradle.kts b/extensions-compose/build.gradle.kts index a0ab7f411..774583deb 100644 --- a/extensions-compose/build.gradle.kts +++ b/extensions-compose/build.gradle.kts @@ -43,6 +43,7 @@ kotlin { all { languageSettings { optIn("com.arkivanov.decompose.InternalDecomposeApi") + optIn("com.arkivanov.decompose.ExperimentalDecomposeApi") } } diff --git a/sample/app-ios-compose/app-ios-compose.xcodeproj/project.xcworkspace/xcuserdata/arkivanov.xcuserdatad/UserInterfaceState.xcuserstate b/sample/app-ios-compose/app-ios-compose.xcodeproj/project.xcworkspace/xcuserdata/arkivanov.xcuserdatad/UserInterfaceState.xcuserstate index b40832aee..6f0bd201f 100644 Binary files a/sample/app-ios-compose/app-ios-compose.xcodeproj/project.xcworkspace/xcuserdata/arkivanov.xcuserdatad/UserInterfaceState.xcuserstate and b/sample/app-ios-compose/app-ios-compose.xcodeproj/project.xcworkspace/xcuserdata/arkivanov.xcuserdatad/UserInterfaceState.xcuserstate differ diff --git a/sample/app-ios/app-ios.xcodeproj/project.pbxproj b/sample/app-ios/app-ios.xcodeproj/project.pbxproj index 52066b885..1418b6906 100644 --- a/sample/app-ios/app-ios.xcodeproj/project.pbxproj +++ b/sample/app-ios/app-ios.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 79140A352BE56B7800D24D47 /* MenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79140A342BE56B7800D24D47 /* MenuView.swift */; }; 79140A372BE56DF300D24D47 /* TabsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79140A362BE56DF300D24D47 /* TabsView.swift */; }; + 79320FCB2CA987F800F8345B /* ArticleAuthorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 79320FCA2CA987F800F8345B /* ArticleAuthorView.swift */; }; 793B104A2A1AB70D006B2DEC /* StateValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 793B10492A1AB70D006B2DEC /* StateValue.swift */; }; 798DB83B282F05EE0072CCBD /* app_iosApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798DB83A282F05EE0072CCBD /* app_iosApp.swift */; }; 798DB83F282F05F20072CCBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 798DB83E282F05F20072CCBD /* Assets.xcassets */; }; @@ -30,6 +31,7 @@ /* Begin PBXFileReference section */ 79140A342BE56B7800D24D47 /* MenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuView.swift; sourceTree = ""; }; 79140A362BE56DF300D24D47 /* TabsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabsView.swift; sourceTree = ""; }; + 79320FCA2CA987F800F8345B /* ArticleAuthorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArticleAuthorView.swift; sourceTree = ""; }; 793B10492A1AB70D006B2DEC /* StateValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateValue.swift; sourceTree = ""; }; 798DB837282F05EE0072CCBD /* app-ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "app-ios.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 798DB83A282F05EE0072CCBD /* app_iosApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = app_iosApp.swift; sourceTree = ""; }; @@ -88,6 +90,7 @@ 798DB855282F0E6F0072CCBD /* MultiPaneView.swift */, 798DB857282F0EDF0072CCBD /* ArticleListView.swift */, 798DB859282F11800072CCBD /* ArticleDetailsView.swift */, + 79320FCA2CA987F800F8345B /* ArticleAuthorView.swift */, B308A848D511CF1748757FD1 /* CustomNavigationView.swift */, B308ADB617C2FED044531853 /* KittenView.swift */, 79140A342BE56B7800D24D47 /* MenuView.swift */, @@ -214,6 +217,7 @@ 798DB856282F0E6F0072CCBD /* MultiPaneView.swift in Sources */, 798DB852282F096C0072CCBD /* CountersView.swift in Sources */, 793B104A2A1AB70D006B2DEC /* StateValue.swift in Sources */, + 79320FCB2CA987F800F8345B /* ArticleAuthorView.swift in Sources */, 798DB85A282F11800072CCBD /* ArticleDetailsView.swift in Sources */, 798DB84A282F08670072CCBD /* CounterView.swift in Sources */, B308A68175BF2D307474DCCC /* CustomNavigationView.swift in Sources */, diff --git a/sample/app-ios/app-ios/ArticleAuthorView.swift b/sample/app-ios/app-ios/ArticleAuthorView.swift new file mode 100644 index 000000000..1aa08db0e --- /dev/null +++ b/sample/app-ios/app-ios/ArticleAuthorView.swift @@ -0,0 +1,82 @@ +// +// ArticleAuthorView.swift +// app-ios +// +// Created by Arkadii Ivanov on 29/09/2024. +// + +import SwiftUI +import Shared + +struct ArticleAuthorView: View { + private let component: ArticleAuthorComponent + + @StateValue + private var model: ArticleAuthorComponentModel + + init(_ component: ArticleAuthorComponent) { + self.component = component + _model = StateValue(component.models) + } + + var body: some View { + if model.isToolbarVisible { + NavigationView { + VStack { + AuthorView( + name: model.author.name, + bio: model.author.bio, + isCloseButtonVisible: model.isCloseButtonVisible, + onCloseClicked: component.onCloseClicked + ) + }.navigationBarTitle( + Text(model.author.name), + displayMode: .inline + ).navigationBarItems( + leading: Image(systemName: "arrow.backward") + .aspectRatio(contentMode: .fit) + .imageScale(.large) + .foregroundColor(.blue) + .onTapGesture(perform: component.onCloseClicked) + ) + } + } else { + AuthorView( + name: model.author.name, + bio: model.author.bio, + isCloseButtonVisible: model.isCloseButtonVisible, + onCloseClicked: component.onCloseClicked + ) + } + } +} + +private struct AuthorView: View { + let name: String + let bio: String + let isCloseButtonVisible: Bool + let onCloseClicked: () -> Void + + var body: some View { + ScrollView([.vertical]) { + VStack(alignment: .leading) { + HStack() { + Text(name) + .font(.title3) + .frame(maxWidth: .infinity, alignment: .leading) + + if (isCloseButtonVisible) { + Button(action: onCloseClicked) { + Image(systemName: "xmark") + } + } + } + .padding([.top, .leading, .trailing, .bottom]) + + Text(bio) + .padding([.leading, .trailing]) + .lineLimit(nil) + } + } + } +} diff --git a/sample/app-ios/app-ios/ArticleDetailsView.swift b/sample/app-ios/app-ios/ArticleDetailsView.swift index 111cb2e18..f343fba89 100644 --- a/sample/app-ios/app-ios/ArticleDetailsView.swift +++ b/sample/app-ios/app-ios/ArticleDetailsView.swift @@ -23,7 +23,11 @@ struct ArticleDetailsView: View { if model.isToolbarVisible { NavigationView { VStack { - DetailsTextView(text: model.article.text) + DetailsView( + authorName: model.article.authorName, + text: model.article.text, + onAuthorClicked: component.onAuthorClicked + ) }.navigationBarTitle( Text(model.article.title), displayMode: .inline @@ -36,38 +40,44 @@ struct ArticleDetailsView: View { ) } } else { - DetailsTextView(text: model.article.text) + DetailsView( + authorName: model.article.authorName, + text: model.article.text, + onAuthorClicked: component.onAuthorClicked + ) } } } -struct DetailsTextView: View { +private struct DetailsView: View { + let authorName: String let text: String + let onAuthorClicked: () -> Void var body: some View { ScrollView([.vertical]) { - Text(text) - .padding([.top, .leading, .trailing]) - .lineLimit(nil) + VStack(alignment: .leading) { + HStack { + Text("Author: ") + .font(.title3) + + Text(authorName) + .font(.title3) + .underline() + } + .padding([.top, .leading, .trailing, .bottom]) + .onTapGesture(perform: onAuthorClicked) + + Text(text) + .padding([.leading, .trailing]) + .lineLimit(nil) + } } } } struct DetailsView_Previews: PreviewProvider { static var previews: some View { - ArticleDetailsView(PreviewArticleDetailsComponent()) + ArticleDetailsView(PreviewArticleDetailsComponent(isToolbarVisible: true)) } } - -class PreviewArticleDetailsComponent: ArticleDetailsComponent { - func onCloseClicked() {} - - var models: Value = mutableValue( - ArticleDetailsComponentModel( - isToolbarVisible: false, - article: ArticleDetailsComponentArticle( - title: "You can use this approach to create loops of any type. For example, this code ", text: "You can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each color name and color value:, u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each , u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each u can use this approach to create loops of any type. For example, this code creates an array of three colors, loops over them all, and creates text views using each " - ) - ) - ) -} diff --git a/sample/app-ios/app-ios/ArticleListView.swift b/sample/app-ios/app-ios/ArticleListView.swift index fcd0f59f8..cb2af4154 100644 --- a/sample/app-ios/app-ios/ArticleListView.swift +++ b/sample/app-ios/app-ios/ArticleListView.swift @@ -24,7 +24,7 @@ struct ArticleListView: View { Text(article.title) .padding() .frame(maxWidth: .infinity, alignment: .leading) - .onTapGesture { component.onArticleClicked(id: article.id) } + .onTapGesture { component.onArticleClicked(article: article) } .listRowBackground( article.id == model.selectedArticleId?.int64Value ? Color.accentColor : Color.clear) .listRowInsets(EdgeInsets()) @@ -34,23 +34,6 @@ struct ArticleListView: View { struct ArticleListView_Previews: PreviewProvider { static var previews: some View { - ArticleListView(PreviewArticleListComponent()) + ArticleListView(PreviewArticleListComponent(isToolbarVisible: false)) } } - -class PreviewArticleListComponent: ArticleListComponent { - var models: Value = mutableValue( - ArticleListComponentModel( - articles: [ - ArticleListComponentArticle(id: 1, title: "Test Title 1"), - ArticleListComponentArticle(id: 2, title: "Test Title 2"), - ArticleListComponentArticle(id: 3, title: "Test Title 3") - ], - isToolbarVisible: true, - selectedArticleId: 1 - ) - ) - - func onArticleClicked(id: Int64) {} - func onCloseClicked() {} -} diff --git a/sample/app-ios/app-ios/MultiPaneView.swift b/sample/app-ios/app-ios/MultiPaneView.swift index f2edf9e3d..280f88a84 100644 --- a/sample/app-ios/app-ios/MultiPaneView.swift +++ b/sample/app-ios/app-ios/MultiPaneView.swift @@ -8,53 +8,64 @@ import SwiftUI import Shared -private let listPaneWeight = CGFloat(0.4) -private let detailsPaneWeight = CGFloat(0.6) +private let listPaneWeight = CGFloat(0.3) +private let detailsPaneWeight = CGFloat(0.4) +private let authorPaneWeight = CGFloat(0.3) struct MultiPaneView: View { private let component: MultiPaneComponent @StateValue - private var children: MultiPaneComponentChildren + private var panels: ChildPanels - private var isMultiPane: Bool { children.isMultiPane } - private var listComponent: ArticleListComponent { children.listChild.instance } - private var detailsComponent: ArticleDetailsComponent? { children.detailsChild?.instance } + private var mode: ChildPanelsMode { panels.mode } + private var listComponent: ArticleListComponent { panels.main.instance } + private var detailsComponent: ArticleDetailsComponent? { panels.details?.instance } + private var authorComponent: ArticleAuthorComponent? { panels.extra?.instance } init(_ component: MultiPaneComponent) { self.component = component - _children = StateValue(component.children) + _panels = StateValue(component.panels) } var body: some View { ZStack(alignment: .top) { ListPane( listComponent: listComponent, - isMultiPane: isMultiPane, - isVisible: isMultiPane || (detailsComponent == nil) + mode: mode, + isVisible: (mode != .single) || (detailsComponent == nil) ) DetailsPane( detailsComponent: detailsComponent, - isMultiPane: isMultiPane + mode: mode ) - }.onAppear { component.setMultiPane(isMultiPane: deviceRequiresMultiPane()) } + + AuthorPane( + authorComponent: authorComponent, + mode: mode + ) + } + .onAppear { component.setMode(mode: requiredMode()) } + .onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in + component.setMode(mode: requiredMode()) + } } } struct ListPane: View { let listComponent: ArticleListComponent - let isMultiPane: Bool + let mode: ChildPanelsMode let isVisible: Bool var body: some View { GeometryReader { metrics in HStack { ArticleListView(listComponent) - .frame(width: isMultiPane ? metrics.size.width * listPaneWeight : metrics.size.width) + .frame(width: mode == .single ? metrics.size.width : metrics.size.width * listPaneWeight) - if isMultiPane { - Spacer().frame(width: metrics.size.width * detailsPaneWeight) + if (mode != .single) { + Spacer().frame(width: metrics.size.width * (detailsPaneWeight + authorPaneWeight)) } } }.opacity(isVisible ? 1 : 0) @@ -63,18 +74,64 @@ struct ListPane: View { struct DetailsPane: View { let detailsComponent: ArticleDetailsComponent? - let isMultiPane: Bool + let mode: ChildPanelsMode var body: some View { if (detailsComponent != nil) { GeometryReader { metrics in HStack { - if isMultiPane { + if (mode != .single) { Spacer().frame(width: metrics.size.width * listPaneWeight) } + let width = switch mode { + case .single: metrics.size.width + case .dual: metrics.size.width * (detailsPaneWeight + authorPaneWeight) + case .triple: metrics.size.width * detailsPaneWeight + default: metrics.size.width + } + ArticleDetailsView(detailsComponent!) - .frame(width: isMultiPane ? metrics.size.width * detailsPaneWeight : metrics.size.width) + .frame(width: width) + .background(.background) + + if (mode == .triple) { + Spacer().frame(width: metrics.size.width * authorPaneWeight) + } + } + } + } else { + EmptyView() + } + } +} + +struct AuthorPane: View { + let authorComponent: ArticleAuthorComponent? + let mode: ChildPanelsMode + + var body: some View { + if (authorComponent != nil) { + GeometryReader { metrics in + HStack { + if (mode != .single) { + Spacer().frame(width: metrics.size.width * listPaneWeight) + } + + if (mode == .triple) { + Spacer().frame(width: metrics.size.width * detailsPaneWeight) + } + + let width = switch mode { + case .single: metrics.size.width + case .dual: metrics.size.width * (detailsPaneWeight + authorPaneWeight) + case .triple: metrics.size.width * authorPaneWeight + default: metrics.size.width + } + + ArticleAuthorView(authorComponent!) + .frame(width: width) + .background(.background) } } } else { @@ -83,27 +140,16 @@ struct DetailsPane: View { } } -private func deviceRequiresMultiPane() -> Bool { - return UIDevice.current.userInterfaceIdiom == .pad +private func requiredMode() -> ChildPanelsMode { + return if (UIDevice.current.userInterfaceIdiom == .pad) { + UIDevice.current.orientation.isLandscape ? .triple : .dual + } else { + UIDevice.current.orientation.isLandscape ? .dual : .single + } } struct MultiPaneView_Previews: PreviewProvider { static var previews: some View { - MultiPaneView(PreviewMultiPaneComponent()) + MultiPaneView(PreviewMultiPaneComponent(isMultiPane: true)) } } - -class PreviewMultiPaneComponent: MultiPaneComponent { - var listComponent: ArticleListComponent = PreviewArticleListComponent() - - var children: Value = mutableValue( - MultiPaneComponentChildren( - isMultiPane: false, - listChild: ChildCreated(configuration: "list" as AnyObject, instance: PreviewArticleListComponent()), - detailsChild: nil - ) - ) - - func setMultiPane(isMultiPane: Bool) {} - func onCloseClicked() {} -} diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt index 67b303bc3..8d2fadb4f 100644 --- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt +++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt @@ -1,115 +1,94 @@ package com.arkivanov.sample.shared.multipane -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Surface import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.movableContentOf -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.SaveableStateHolder -import androidx.compose.runtime.saveable.rememberSaveableStateHolder -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanels +import com.arkivanov.decompose.extensions.compose.experimental.panels.ChildPanelsAnimators +import com.arkivanov.decompose.extensions.compose.experimental.panels.HorizontalChildPanelsLayout +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.PredictiveBackParams +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.fade +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.plus +import com.arkivanov.decompose.extensions.compose.experimental.stack.animation.scale +import com.arkivanov.decompose.extensions.compose.stack.animation.predictiveback.materialPredictiveBackAnimatable import com.arkivanov.decompose.extensions.compose.subscribeAsState -import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.isSingle +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorContent import com.arkivanov.sample.shared.multipane.details.ArticleDetailsContent -import com.arkivanov.sample.shared.multipane.list.ArticleListComponent import com.arkivanov.sample.shared.multipane.list.ArticleListContent import com.arkivanov.sample.shared.utils.TopAppBar +@OptIn(ExperimentalDecomposeApi::class) @Composable internal fun MultiPaneContent(component: MultiPaneComponent, modifier: Modifier = Modifier) { - val children by component.children.subscribeAsState() - val listChild = children.listChild - val detailsChild = children.detailsChild - - val saveableStateHolder = rememberSaveableStateHolder() - - val listPane: @Composable (Child.Created<*, ArticleListComponent>) -> Unit = - remember { - movableContentOf { (config, component) -> - saveableStateHolder.SaveableStateProvider(key = config.hashCode()) { - ArticleListContent( - component = component, - modifier = Modifier.fillMaxSize(), - ) - } - } - } - - val detailsPane: @Composable (Child.Created<*, ArticleDetailsComponent>) -> Unit = - remember { - movableContentOf { (config, component) -> - saveableStateHolder.SaveableStateProvider(key = config.hashCode()) { - ArticleDetailsContent( - component = component, - modifier = Modifier.fillMaxSize(), - ) - } - } - } - - saveableStateHolder.OldDetailsKeyRemoved(selectedDetailsKey = children.detailsChild?.configuration?.hashCode()) + val panels by component.panels.subscribeAsState() Column(modifier = modifier) { - if (children.isMultiPane) { + if (!panels.mode.isSingle) { TopAppBar(title = "Multi-Pane Layout") } BoxWithConstraints(modifier = Modifier.fillMaxSize()) { - when { - children.isMultiPane -> - Row(modifier = Modifier.fillMaxSize()) { - Box(modifier = Modifier.fillMaxHeight().weight(0.4F)) { - listPane(children.listChild) - } - - Box(modifier = Modifier.fillMaxHeight().weight(0.6F)) { - children.detailsChild?.also { - detailsPane(it) - } - } + ChildPanels( + panels = panels, + mainChild = { + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + ArticleListContent( + component = it.instance, + modifier = Modifier.fillMaxSize(), + ) } + }, + detailsChild = { + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + ArticleDetailsContent( + component = it.instance, + modifier = Modifier.fillMaxSize(), + ) + } + }, + extraChild = { + Surface(modifier = Modifier.fillMaxSize(), color = MaterialTheme.colors.background) { + ArticleAuthorContent( + component = it.instance, + modifier = Modifier.fillMaxSize(), + ) + } + }, + layout = HorizontalChildPanelsLayout( + dualWeights = Pair(first = 0.3F, second = 0.7F), + tripleWeights = Triple(first = 0.3F, second = 0.4F, third = 0.3F), + ), + animators = ChildPanelsAnimators(single = fade() + scale(), dual = fade() to fade()), + predictiveBackParams = { + PredictiveBackParams( + backHandler = component.backHandler, + onBack = component::onBack, + animatable = ::materialPredictiveBackAnimatable, + ) + }, + ) + + val mode = + when { + maxWidth >= 1200.dp -> ChildPanelsMode.TRIPLE + maxWidth >= 800.dp -> ChildPanelsMode.DUAL + else -> ChildPanelsMode.SINGLE + } - detailsChild != null -> detailsPane(detailsChild) - else -> listPane(listChild) - } - - val isMultiPaneRequired = this@BoxWithConstraints.maxWidth >= 800.dp - - DisposableEffect(isMultiPaneRequired) { - component.setMultiPane(isMultiPaneRequired) + DisposableEffect(mode) { + component.setMode(mode) onDispose {} } } } } - -@Composable -private fun SaveableStateHolder.OldDetailsKeyRemoved(selectedDetailsKey: Any?) { - var lastDetailsKey by remember { mutableStateOf(selectedDetailsKey) } - - if (selectedDetailsKey == lastDetailsKey) { - return - } - - val keyToRemove = lastDetailsKey - lastDetailsKey = selectedDetailsKey - - if (keyToRemove == null) { - return - } - - DisposableEffect(keyToRemove) { - removeState(key = keyToRemove) - onDispose {} - } -} diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt new file mode 100644 index 000000000..4c72c0e1b --- /dev/null +++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt @@ -0,0 +1,64 @@ +package com.arkivanov.sample.shared.multipane.author + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.Icon +import androidx.compose.material.IconButton +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Close +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.sample.shared.utils.TopAppBar + +@Composable +internal fun ArticleAuthorContent(component: ArticleAuthorComponent, modifier: Modifier = Modifier) { + val model by component.models.subscribeAsState() + val author = model.author + + Column(modifier = modifier) { + if (model.isToolbarVisible) { + TopAppBar( + title = author.name, + onCloseClick = component::onCloseClicked, + ) + } + + Column(modifier = Modifier.fillMaxSize().verticalScroll(state = rememberScrollState())) { + Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { + Text( + text = author.name, + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.h6, + ) + + Spacer(modifier = Modifier.weight(1F)) + + if (model.isCloseButtonVisible) { + IconButton(onClick = component::onCloseClicked) { + Icon(imageVector = Icons.Default.Close, contentDescription = "Close button") + } + } + } + + Text( + text = author.bio, + modifier = Modifier.fillMaxSize().padding(16.dp), + textAlign = TextAlign.Justify, + ) + } + } +} diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt index daf9f79e8..a67b1f9e2 100644 --- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt +++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt @@ -1,16 +1,25 @@ package com.arkivanov.sample.shared.multipane.details +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material.MaterialTheme import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.unit.dp import com.arkivanov.decompose.extensions.compose.subscribeAsState +import com.arkivanov.sample.shared.utils.TopAppBar @Composable internal fun ArticleDetailsContent(component: ArticleDetailsComponent, modifier: Modifier = Modifier) { @@ -19,18 +28,39 @@ internal fun ArticleDetailsContent(component: ArticleDetailsComponent, modifier: Column(modifier = modifier) { if (model.isToolbarVisible) { - com.arkivanov.sample.shared.utils.TopAppBar( + TopAppBar( title = article.title, onCloseClick = component::onCloseClicked, ) } - Text( - text = article.text, - modifier = Modifier - .fillMaxHeight() - .verticalScroll(rememberScrollState()) - .padding(16.dp) - ) + Column(modifier = Modifier.fillMaxSize().verticalScroll(state = rememberScrollState())) { + Text( + text = article.title, + modifier = Modifier.padding(16.dp), + style = MaterialTheme.typography.h6, + ) + + Row(modifier = Modifier.fillMaxWidth().clickable(onClick = component::onAuthorClicked).padding(16.dp)) { + Text( + text = "Author:", + style = MaterialTheme.typography.subtitle2, + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = article.authorName, + textDecoration = TextDecoration.Underline, + style = MaterialTheme.typography.subtitle2, + ) + } + + Text( + text = article.text, + modifier = Modifier.fillMaxSize().padding(16.dp), + textAlign = TextAlign.Justify, + ) + } } } diff --git a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt index 9ce1b51d7..02106ca05 100644 --- a/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt +++ b/sample/shared/compose/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt @@ -40,7 +40,7 @@ internal fun ArticleListContent(component: ArticleListComponent, modifier: Modif .fillMaxWidth() .selectable( selected = isSelected, - onClick = { component.onArticleClicked(id = article.id) } + onClick = { component.onArticleClicked(article) } ) .run { if (isSelected) background(color = selectionColor()) else this } .padding(16.dp) diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/Utils.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/Utils.kt new file mode 100644 index 000000000..912381836 --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/Utils.kt @@ -0,0 +1,18 @@ +package com.arkivanov.sample.shared + +import com.arkivanov.decompose.value.Value +import com.badoo.reaktive.base.setCancellable +import com.badoo.reaktive.observable.Observable +import com.badoo.reaktive.observable.observable +import com.badoo.reaktive.subject.behavior.BehaviorObservable + +internal fun Value.asObservable(): BehaviorObservable = + object : BehaviorObservable, Observable by asObservableInternal() { + override val value: T get() = this@asObservable.value + } + +private fun Value.asObservableInternal(): Observable = + observable { emitter -> + val cancellation = subscribe(emitter::onNext) + emitter.setCancellable(cancellation::cancel) + } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt index 60a54b3eb..23d613ee0 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/DefaultMultiPaneComponent.kt @@ -1,16 +1,21 @@ package com.arkivanov.sample.shared.multipane -import com.arkivanov.decompose.Child import com.arkivanov.decompose.ComponentContext -import com.arkivanov.decompose.router.children.ChildNavState -import com.arkivanov.decompose.router.children.ChildNavState.Status -import com.arkivanov.decompose.router.children.NavState -import com.arkivanov.decompose.router.children.SimpleChildNavState -import com.arkivanov.decompose.router.children.SimpleNavigation -import com.arkivanov.decompose.router.children.children +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.ChildPanelsMode.TRIPLE +import com.arkivanov.decompose.router.panels.Panels +import com.arkivanov.decompose.router.panels.PanelsNavigation +import com.arkivanov.decompose.router.panels.activateExtra +import com.arkivanov.decompose.router.panels.childPanels +import com.arkivanov.decompose.router.panels.isDual +import com.arkivanov.decompose.router.panels.isSingle +import com.arkivanov.decompose.router.panels.navigate +import com.arkivanov.decompose.router.panels.pop import com.arkivanov.decompose.value.Value -import com.arkivanov.essenty.backhandler.BackCallback -import com.arkivanov.sample.shared.multipane.MultiPaneComponent.Children +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent +import com.arkivanov.sample.shared.multipane.author.DefaultArticleAuthorComponent import com.arkivanov.sample.shared.multipane.database.DefaultArticleDatabase import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent import com.arkivanov.sample.shared.multipane.details.DefaultArticleDetailsComponent @@ -22,99 +27,82 @@ import com.badoo.reaktive.observable.map import com.badoo.reaktive.observable.notNull import com.badoo.reaktive.subject.behavior.BehaviorSubject import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer +@OptIn(ExperimentalDecomposeApi::class) internal class DefaultMultiPaneComponent( componentContext: ComponentContext, ) : MultiPaneComponent, ComponentContext by componentContext, DisposableScope by componentContext.disposableScope() { private val database = DefaultArticleDatabase() - private val navigation = SimpleNavigation<(NavigationState) -> NavigationState>() - private val navState = BehaviorSubject(null) + private val navigation = PanelsNavigation() + private val _navState = BehaviorSubject?>(null) + private val navState = _navState.notNull() - override val children: Value = - children( + override val panels: Value> = + childPanels( source = navigation, - stateSerializer = NavigationState.serializer(), - key = "children", - initialState = ::NavigationState, - navTransformer = { navState, event -> event(navState) }, - stateMapper = { navState, children -> - @Suppress("UNCHECKED_CAST") - Children( - isMultiPane = navState.isMultiPane, - listChild = children.first { it.instance is ArticleListComponent } as Child.Created<*, ArticleListComponent>, - detailsChild = children.find { it.instance is ArticleDetailsComponent } as Child.Created<*, ArticleDetailsComponent>?, - ) - }, - onStateChanged = { newState, _ -> navState.onNext(newState) }, - childFactory = ::child, + initialPanels = { Panels(main = Unit) }, + serializers = Triple(Unit.serializer(), Details.serializer(), Extra.serializer()), + onStateChanged = { newState, _ -> _navState.onNext(newState) }, + handleBackButton = true, + mainFactory = { _, ctx -> listComponent(ctx) }, + detailsFactory = ::detailsComponent, + extraFactory = ::authorComponent, ) - private val backCallback = BackCallback(isEnabled = false, onBack = ::closeDetails) - - init { - backHandler.register(backCallback) - - navState.notNull().subscribeScoped { - backCallback.isEnabled = !it.isMultiPane && (it.articleId != null) - } - } - - private fun child(config: Config, componentContext: ComponentContext): Any = - when (config) { - is Config.List -> listComponent(componentContext) - is Config.Details -> detailsComponent(config, componentContext) - } - private fun listComponent(componentContext: ComponentContext): ArticleListComponent = DefaultArticleListComponent( componentContext = componentContext, database = database, - isToolbarVisible = navState.notNull().map { !it.isMultiPane }, - selectedArticleId = navState.notNull().map { if (it.isMultiPane) it.articleId else null }, - onArticleSelected = ::showDetails, + isToolbarVisible = navState.map { it.mode.isSingle }, + selectedArticleId = navState.map { it.takeUnless { it.mode.isSingle }?.details?.articleId }, + onArticleSelected = { articleId, authorId -> + navigation.navigate { state -> + state.copy( + details = Details(articleId = articleId, authorId = authorId), + extra = if (state.mode == TRIPLE) Extra(authorId = authorId) else null, + ) + } + }, ) - private fun detailsComponent(config: Config.Details, componentContext: ComponentContext): ArticleDetailsComponent = + private fun detailsComponent(config: Details, componentContext: ComponentContext): ArticleDetailsComponent = DefaultArticleDetailsComponent( componentContext = componentContext, database = database, articleId = config.articleId, - isToolbarVisible = navState.notNull().map { !it.isMultiPane }, - onFinished = ::closeDetails, + isToolbarVisible = navState.map { it.mode.isSingle }, + onAuthorRequested = { navigation.activateExtra(extra = Extra(authorId = config.authorId)) }, + onFinished = navigation::pop, ) - override fun setMultiPane(isMultiPane: Boolean) { - navigation.navigate { it.copy(isMultiPane = isMultiPane) } - } + private fun authorComponent(config: Extra, componentContext: ComponentContext): ArticleAuthorComponent = + DefaultArticleAuthorComponent( + componentContext = componentContext, + database = database, + authorId = config.authorId, + isToolbarVisible = navState.map { it.mode.isSingle }, + isCloseButtonVisible = navState.map { it.mode.isDual }, + onFinished = navigation::pop, + ) - private fun showDetails(articleId: Long) { - navigation.navigate { it.copy(articleId = articleId) } + override fun setMode(mode: ChildPanelsMode) { + navigation.navigate { state -> + state.copy( + extra = state.takeIf { mode == TRIPLE }?.details?.authorId?.let { Extra(authorId = it) } ?: state.extra, + mode = mode, + ) + } } - private fun closeDetails() { - navigation.navigate { it.copy(articleId = null) } + override fun onBack() { + navigation.pop() } @Serializable - private sealed interface Config { - @Serializable - data object List : Config - - @Serializable - data class Details(val articleId: Long) : Config - } + private data class Details(val articleId: Long, val authorId: Long) @Serializable - private data class NavigationState( - val isMultiPane: Boolean = false, - val articleId: Long? = null, - ) : NavState { - override val children: List> by lazy { - listOfNotNull( - SimpleChildNavState(Config.List, if (isMultiPane || (articleId == null)) Status.RESUMED else Status.CREATED), - if (articleId != null) SimpleChildNavState(Config.Details(articleId), Status.RESUMED) else null, - ) - } - } + private data class Extra(val authorId: Long) } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt index 67da404a5..8442e41b7 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneComponent.kt @@ -1,19 +1,19 @@ package com.arkivanov.sample.shared.multipane -import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode import com.arkivanov.decompose.value.Value +import com.arkivanov.essenty.backhandler.BackHandlerOwner +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent import com.arkivanov.sample.shared.multipane.list.ArticleListComponent -interface MultiPaneComponent { +@OptIn(ExperimentalDecomposeApi::class) +interface MultiPaneComponent : BackHandlerOwner { - val children: Value + val panels: Value> - fun setMultiPane(isMultiPane: Boolean) - - data class Children( - val isMultiPane: Boolean, - val listChild: Child.Created<*, ArticleListComponent>, - val detailsChild: Child.Created<*, ArticleDetailsComponent>?, - ) + fun setMode(mode: ChildPanelsMode) + fun onBack() } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/PreviewMultiPaneComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/PreviewMultiPaneComponent.kt new file mode 100644 index 000000000..049c47934 --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/PreviewMultiPaneComponent.kt @@ -0,0 +1,34 @@ +package com.arkivanov.sample.shared.multipane + +import com.arkivanov.decompose.Child +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanels +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.value.MutableValue +import com.arkivanov.decompose.value.Value +import com.arkivanov.sample.shared.PreviewComponentContext +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent +import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent +import com.arkivanov.sample.shared.multipane.details.PreviewArticleDetailsComponent +import com.arkivanov.sample.shared.multipane.list.ArticleListComponent +import com.arkivanov.sample.shared.multipane.list.PreviewArticleListComponent + +@OptIn(ExperimentalDecomposeApi::class) +class PreviewMultiPaneComponent( + isMultiPane: Boolean = false, +) : MultiPaneComponent, ComponentContext by PreviewComponentContext { + + @OptIn(ExperimentalDecomposeApi::class) + override val panels: Value> = + MutableValue( + ChildPanels( + main = Child.Created(configuration = Unit, instance = PreviewArticleListComponent()), + details = if (isMultiPane) Child.Created(configuration = Unit, instance = PreviewArticleDetailsComponent()) else null, + mode = if (isMultiPane) ChildPanelsMode.DUAL else ChildPanelsMode.SINGLE, + ) + ) + + override fun setMode(mode: ChildPanelsMode) {} + override fun onBack() {} +} diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorComponent.kt new file mode 100644 index 000000000..057a76a30 --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorComponent.kt @@ -0,0 +1,21 @@ +package com.arkivanov.sample.shared.multipane.author + +import com.arkivanov.decompose.value.Value + +interface ArticleAuthorComponent { + + val models: Value + + fun onCloseClicked() + + data class Model( + val isToolbarVisible: Boolean, + val isCloseButtonVisible: Boolean, + val author: Author, + ) + + data class Author( + val name: String, + val bio: String, + ) +} diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/DefaultArticleAuthorComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/DefaultArticleAuthorComponent.kt new file mode 100644 index 000000000..66075e34c --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/author/DefaultArticleAuthorComponent.kt @@ -0,0 +1,54 @@ +package com.arkivanov.sample.shared.multipane.author + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.decompose.value.MutableValue +import com.arkivanov.decompose.value.Value +import com.arkivanov.decompose.value.update +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent.Author +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent.Model +import com.arkivanov.sample.shared.multipane.database.ArticleDatabase +import com.arkivanov.sample.shared.multipane.database.AuthorEntity +import com.arkivanov.sample.shared.multipane.utils.disposableScope +import com.badoo.reaktive.disposable.scope.DisposableScope +import com.badoo.reaktive.observable.Observable + +internal class DefaultArticleAuthorComponent( + componentContext: ComponentContext, + database: ArticleDatabase, + authorId: Long, + isToolbarVisible: Observable, + isCloseButtonVisible: Observable, + private val onFinished: () -> Unit, +) : ArticleAuthorComponent, ComponentContext by componentContext, DisposableScope by componentContext.disposableScope() { + + private val _models = + MutableValue( + Model( + isToolbarVisible = false, + isCloseButtonVisible = false, + author = database.getAuthor(id = authorId).toAuthor(), + ) + ) + + override val models: Value = _models + + init { + isToolbarVisible.subscribeScoped { isVisible -> + _models.update { it.copy(isToolbarVisible = isVisible) } + } + + isCloseButtonVisible.subscribeScoped { isVisible -> + _models.update { it.copy(isCloseButtonVisible = isVisible) } + } + } + + private fun AuthorEntity.toAuthor(): Author = + Author( + name = name, + bio = bio, + ) + + override fun onCloseClicked() { + onFinished() + } +} diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleDatabase.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleDatabase.kt index 46badcfd6..9fccc6bc3 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleDatabase.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleDatabase.kt @@ -2,7 +2,9 @@ package com.arkivanov.sample.shared.multipane.database internal interface ArticleDatabase { - fun getAll(): List + fun getArticles(): List - fun getById(id: Long): ArticleEntity + fun getArticle(id: Long): ArticleEntity + + fun getAuthor(id: Long): AuthorEntity } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleEntity.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleEntity.kt index 0a3c50f33..3449cb1ea 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleEntity.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/ArticleEntity.kt @@ -2,6 +2,7 @@ package com.arkivanov.sample.shared.multipane.database internal data class ArticleEntity( val id: Long, + val author: AuthorEntity, val title: String, - val text: String + val text: String, ) diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/AuthorEntity.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/AuthorEntity.kt new file mode 100644 index 000000000..215c214be --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/AuthorEntity.kt @@ -0,0 +1,7 @@ +package com.arkivanov.sample.shared.multipane.database + +internal data class AuthorEntity( + val id: Long, + val name: String, + val bio: String, +) diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt index 1a2e4bc72..b895fb04c 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/DefaultArticleDatabase.kt @@ -2,24 +2,40 @@ package com.arkivanov.sample.shared.multipane.database import com.arkivanov.sample.shared.multipane.database.LorenIpsumGenerator.generate import com.arkivanov.sample.shared.multipane.database.LorenIpsumGenerator.generateSentence +import com.arkivanov.sample.shared.multipane.database.LorenIpsumGenerator.randomWord import kotlin.random.Random internal class DefaultArticleDatabase : ArticleDatabase { + private val authors = + List(10) { index -> + AuthorEntity( + id = index.toLong() + 1L, + name = "${randomWord(capitalize = true)} ${randomWord(capitalize = true)}", + bio = List(50) { generateSentence() }.joinToString(separator = " "), + ) + } + private val articles = List(50) { index -> ArticleEntity( id = index.toLong() + 1L, - title = generate(count = Random.nextInt(3, 7), minWordLength = 3) + author = authors[index % authors.size], + title = generate(count = Random.nextInt(3, 5), minWordLength = 3) .joinToString(separator = " ") { it.replaceFirstChar(Char::uppercase) }, text = List(50) { generateSentence() } .joinToString(separator = " ") ) } + private val authorMap = authors.associateBy(AuthorEntity::id) private val articleMap = articles.associateBy(ArticleEntity::id) - override fun getAll(): List = articles + override fun getArticles(): List = articles + + override fun getArticle(id: Long): ArticleEntity = + articleMap.getValue(id) - override fun getById(id: Long): ArticleEntity = articleMap.getValue(id) + override fun getAuthor(id: Long): AuthorEntity = + authorMap.getValue(id) } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt index 422b9ce0a..40df8b23f 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/database/LorenIpsumGenerator.kt @@ -1,6 +1,7 @@ package com.arkivanov.sample.shared.multipane.database import kotlin.random.Random +import kotlin.random.nextInt internal object LorenIpsumGenerator { @@ -8,7 +9,7 @@ internal object LorenIpsumGenerator { List(count) { var word: String do { - word = words.random() + word = randomWord() } while (word.length < minWordLength) word } @@ -21,6 +22,11 @@ internal object LorenIpsumGenerator { postfix = "." ) + fun randomWord(capitalize: Boolean = false): String = + words.random().let { word -> + word.takeIf { capitalize }?.replaceFirstChar(Char::uppercase) ?: word + } + private val words = listOf( "a", diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsComponent.kt index 6e679559f..1e527a5b7 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsComponent.kt @@ -6,6 +6,7 @@ interface ArticleDetailsComponent { val models: Value + fun onAuthorClicked() fun onCloseClicked() data class Model( @@ -15,6 +16,8 @@ interface ArticleDetailsComponent { data class Article( val title: String, + val authorId: Long, + val authorName: String, val text: String ) } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/DefaultArticleDetailsComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/DefaultArticleDetailsComponent.kt index 503f668e4..91c633aa9 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/DefaultArticleDetailsComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/DefaultArticleDetailsComponent.kt @@ -17,6 +17,7 @@ internal class DefaultArticleDetailsComponent( database: ArticleDatabase, articleId: Long, isToolbarVisible: Observable, + private val onAuthorRequested: () -> Unit, private val onFinished: () -> Unit, ) : ArticleDetailsComponent, ComponentContext by componentContext, DisposableScope by componentContext.disposableScope() { @@ -24,7 +25,7 @@ internal class DefaultArticleDetailsComponent( MutableValue( Model( isToolbarVisible = false, - article = database.getById(id = articleId).toArticle() + article = database.getArticle(id = articleId).toArticle() ) ) @@ -39,9 +40,15 @@ internal class DefaultArticleDetailsComponent( private fun ArticleEntity.toArticle(): Article = Article( title = title, + authorId = author.id, + authorName = author.name, text = text ) + override fun onAuthorClicked() { + onAuthorRequested() + } + override fun onCloseClicked() { onFinished() } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/PreviewArticleDetailsComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/PreviewArticleDetailsComponent.kt new file mode 100644 index 000000000..2e09014cb --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/details/PreviewArticleDetailsComponent.kt @@ -0,0 +1,27 @@ +package com.arkivanov.sample.shared.multipane.details + +import com.arkivanov.decompose.value.MutableValue +import com.arkivanov.decompose.value.Value +import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent.Article +import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent.Model + +class PreviewArticleDetailsComponent( + isToolbarVisible: Boolean = false, +) : ArticleDetailsComponent { + + override val models: Value = + MutableValue( + Model( + isToolbarVisible = isToolbarVisible, + article = Article( + title = "Article 1", + authorId = 1, + authorName = "John Smith", + text = "Article 1 ".repeat(50), + ), + ) + ) + + override fun onAuthorClicked() {} + override fun onCloseClicked() {} +} diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListComponent.kt index fc5d186b8..b2821e6f5 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListComponent.kt @@ -6,7 +6,7 @@ interface ArticleListComponent { val models: Value - fun onArticleClicked(id: Long) + fun onArticleClicked(article: Article) data class Model( val articles: List
, @@ -16,6 +16,7 @@ interface ArticleListComponent { data class Article( val id: Long, + val authorId: Long, val title: String ) } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/DefaultArticleListComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/DefaultArticleListComponent.kt index b99870483..73c5e6efe 100644 --- a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/DefaultArticleListComponent.kt +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/DefaultArticleListComponent.kt @@ -17,13 +17,13 @@ internal class DefaultArticleListComponent( database: ArticleDatabase, isToolbarVisible: Observable, selectedArticleId: Observable, - private val onArticleSelected: (id: Long) -> Unit, + private val onArticleSelected: (articleId: Long, authorId: Long) -> Unit, ) : ArticleListComponent, ComponentContext by componentContext, DisposableScope by componentContext.disposableScope() { private val _models = MutableValue( Model( - articles = database.getAll().map { it.toArticle() }, + articles = database.getArticles().map { it.toArticle() }, isToolbarVisible = false, selectedArticleId = null ) @@ -44,10 +44,11 @@ internal class DefaultArticleListComponent( private fun ArticleEntity.toArticle(): Article = Article( id = id, + authorId = author.id, title = title ) - override fun onArticleClicked(id: Long) { - onArticleSelected(id) + override fun onArticleClicked(article: Article) { + onArticleSelected(article.id, article.authorId) } } diff --git a/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/PreviewArticleListComponent.kt b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/PreviewArticleListComponent.kt new file mode 100644 index 000000000..a554ed7da --- /dev/null +++ b/sample/shared/shared/src/commonMain/kotlin/com/arkivanov/sample/shared/multipane/list/PreviewArticleListComponent.kt @@ -0,0 +1,24 @@ +package com.arkivanov.sample.shared.multipane.list + +import com.arkivanov.decompose.value.MutableValue +import com.arkivanov.decompose.value.Value +import com.arkivanov.sample.shared.multipane.list.ArticleListComponent.Article +import com.arkivanov.sample.shared.multipane.list.ArticleListComponent.Model + +class PreviewArticleListComponent( + isToolbarVisible: Boolean = false, +) : ArticleListComponent { + + override val models: Value = + MutableValue( + Model( + articles = List(10) { index -> + Article(id = index.toLong(), authorId = index.toLong(), title = "Article $index") + }, + isToolbarVisible = isToolbarVisible, + selectedArticleId = null, + ) + ) + + override fun onArticleClicked(article: Article) {} +} diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Scaffold.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Scaffold.kt index 640349032..afe9bf687 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Scaffold.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/Scaffold.kt @@ -2,24 +2,25 @@ package com.arkivanov.sample.shared import mui.material.AppBar import mui.material.AppBarPosition +import mui.material.Box import mui.material.Icon import mui.material.IconButton import mui.material.IconButtonColor import mui.material.IconButtonEdge import mui.material.Size -import mui.material.Stack import mui.material.Toolbar import mui.material.Typography import mui.material.styles.TypographyVariant -import mui.system.responsive +import mui.system.PropsWithSx import mui.system.sx import react.FC import react.PropsWithChildren -import web.cssom.AlignItems import web.cssom.BoxSizing +import web.cssom.Display +import web.cssom.FlexDirection import web.cssom.number -internal external interface ScaffoldProps : PropsWithChildren { +internal external interface ScaffoldProps : PropsWithChildren, PropsWithSx { var appBar: AppBar? } @@ -29,12 +30,12 @@ internal data class AppBar( ) internal val Scaffold: FC = FC { props -> - Stack { - spacing = responsive(2) - + Box { sx { - alignItems = AlignItems.center + display = Display.flex + flexDirection = FlexDirection.column boxSizing = BoxSizing.borderBox + +props.sx } props.appBar?.also { appBar -> diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/counters/counter/CounterContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/counters/counter/CounterContent.kt index bb3232d06..d25a698d1 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/counters/counter/CounterContent.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/counters/counter/CounterContent.kt @@ -4,27 +4,17 @@ import com.arkivanov.sample.shared.RProps import com.arkivanov.sample.shared.componentContent import com.arkivanov.sample.shared.dialog.DialogComponentContent import com.arkivanov.sample.shared.useAsState -import mui.material.AppBar -import mui.material.AppBarPosition import mui.material.Button import mui.material.ButtonColor import mui.material.ButtonVariant -import mui.material.Icon -import mui.material.IconButton -import mui.material.IconButtonColor -import mui.material.IconButtonEdge -import mui.material.Size import mui.material.Stack -import mui.material.Toolbar import mui.material.Typography import mui.material.styles.TypographyVariant import mui.system.responsive import mui.system.sx import react.FC -import react.PropsWithChildren import web.cssom.AlignItems import web.cssom.BoxSizing -import web.cssom.number internal val CounterContent: FC> = FC { props -> val model by props.component.model.useAsState() diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt index 873789f94..95b354e77 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/MultiPaneContent.kt @@ -1,9 +1,14 @@ package com.arkivanov.sample.shared.multipane +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.decompose.router.panels.ChildPanelsMode +import com.arkivanov.decompose.router.panels.isSingle import com.arkivanov.sample.shared.AppBar import com.arkivanov.sample.shared.RProps import com.arkivanov.sample.shared.Scaffold import com.arkivanov.sample.shared.componentContent +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorComponent +import com.arkivanov.sample.shared.multipane.author.ArticleAuthorContent import com.arkivanov.sample.shared.multipane.details.ArticleDetailsComponent import com.arkivanov.sample.shared.multipane.details.ArticleDetailsContent import com.arkivanov.sample.shared.multipane.list.ArticleListComponent @@ -16,21 +21,32 @@ import org.w3c.dom.events.Event import react.FC import react.Props import react.useEffectOnce +import web.cssom.BoxSizing import web.cssom.Display import web.cssom.Flex import web.cssom.number import web.cssom.pct import web.cssom.px -private const val MULTI_PANE_WIDTH_THRESHOLD = 960 +private const val DUAL_PANE_WIDTH_THRESHOLD = 640 +private const val TRIPLE_PANE_WIDTH_THRESHOLD = 1280 +@OptIn(ExperimentalDecomposeApi::class) val MultiPaneContent: FC> = FC { props -> - val children by props.component.children.useAsState() - val listComponent = children.listChild.instance - val detailsComponent = children.detailsChild?.instance + val panels by props.component.panels.useAsState() + val listComponent = panels.main.instance + val detailsComponent = panels.details?.instance + val authorComponent = panels.extra?.instance fun onWindowResized() { - props.component.setMultiPane(window.innerWidth >= MULTI_PANE_WIDTH_THRESHOLD) + val mode = + when { + window.innerWidth >= TRIPLE_PANE_WIDTH_THRESHOLD -> ChildPanelsMode.TRIPLE + window.innerWidth >= DUAL_PANE_WIDTH_THRESHOLD -> ChildPanelsMode.DUAL + else -> ChildPanelsMode.SINGLE + } + + props.component.setMode(mode) } useEffectOnce { @@ -42,20 +58,36 @@ val MultiPaneContent: FC> = FC { props -> } Scaffold { - if (children.isMultiPane) { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + boxSizing = BoxSizing.borderBox + } + + if (!panels.mode.isSingle) { appBar = AppBar(title = "Multi-Pane Layout") } - if (children.isMultiPane) { - DoublePane { - this.listComponent = listComponent - this.detailsComponent = detailsComponent - } - } else { - SinglePane { - this.listComponent = listComponent - this.detailsComponent = detailsComponent - } + when (panels.mode) { + ChildPanelsMode.SINGLE -> + SinglePane { + this.listComponent = listComponent + this.detailsComponent = detailsComponent + this.authorComponent = authorComponent + } + + ChildPanelsMode.DUAL -> + DoublePane { + this.listComponent = listComponent + this.detailsComponent = detailsComponent + this.authorComponent = authorComponent + } + + ChildPanelsMode.TRIPLE -> + TriplePane { + this.listComponent = listComponent + this.detailsComponent = detailsComponent + this.authorComponent = authorComponent + } } } } @@ -63,21 +95,25 @@ val MultiPaneContent: FC> = FC { props -> private external interface SinglePaneProps : Props { var listComponent: ArticleListComponent var detailsComponent: ArticleDetailsComponent? + var authorComponent: ArticleAuthorComponent? } private val SinglePane: FC = FC { props -> + val listComponent = props.listComponent val detailsComponent = props.detailsComponent + val authorComponent = props.authorComponent Box { sx { - width = 100.pct + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) height = 100.pct + boxSizing = BoxSizing.borderBox } - if (detailsComponent != null) { - componentContent(component = detailsComponent, content = ArticleDetailsContent) - } else { - componentContent(component = props.listComponent, content = ArticleListContent) + when { + authorComponent != null -> componentContent(component = authorComponent, content = ArticleAuthorContent) + detailsComponent != null -> componentContent(component = detailsComponent, content = ArticleDetailsContent) + else -> componentContent(component = listComponent, content = ArticleListContent) } } } @@ -85,20 +121,59 @@ private val SinglePane: FC = FC { props -> private external interface MultiPaneProps : Props { var listComponent: ArticleListComponent var detailsComponent: ArticleDetailsComponent? + var authorComponent: ArticleAuthorComponent? } private val DoublePane: FC = FC { props -> + val listComponent = props.listComponent + val detailsComponent = props.detailsComponent + val authorComponent = props.authorComponent + Box { sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) display = Display.flex width = 100.pct - height = 100.pct } Box { sx { + flex = Flex(grow = number(3.0), shrink = number(0.0), basis = 0.px) height = 100.pct - flex = Flex(grow = number(4.0), shrink = number(0.0), basis = 0.px) + boxSizing = BoxSizing.borderBox + } + + componentContent(component = listComponent, content = ArticleListContent) + } + + Box { + sx { + flex = Flex(grow = number(7.0), shrink = number(0.0), basis = 0.px) + height = 100.pct + boxSizing = BoxSizing.borderBox + } + + when { + authorComponent != null -> componentContent(component = authorComponent, content = ArticleAuthorContent) + detailsComponent != null -> componentContent(component = detailsComponent, content = ArticleDetailsContent) + } + } + } +} + +private val TriplePane: FC = FC { props -> + Box { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + boxSizing = BoxSizing.borderBox + } + + Box { + sx { + flex = Flex(grow = number(3.0), shrink = number(0.0), basis = 0.px) + height = 100.pct + boxSizing = BoxSizing.borderBox } componentContent(component = props.listComponent, content = ArticleListContent) @@ -106,13 +181,26 @@ private val DoublePane: FC = FC { props -> Box { sx { + flex = Flex(grow = number(4.0), shrink = number(0.0), basis = 0.px) height = 100.pct - flex = Flex(grow = number(6.0), shrink = number(0.0), basis = 0.px) + boxSizing = BoxSizing.borderBox } props.detailsComponent?.also { componentContent(component = it, content = ArticleDetailsContent) } } + + Box { + sx { + flex = Flex(grow = number(3.0), shrink = number(0.0), basis = 0.px) + height = 100.pct + boxSizing = BoxSizing.borderBox + } + + props.authorComponent?.also { + componentContent(component = it, content = ArticleAuthorContent) + } + } } } diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt new file mode 100644 index 000000000..7b997124e --- /dev/null +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/author/ArticleAuthorContent.kt @@ -0,0 +1,101 @@ +package com.arkivanov.sample.shared.multipane.author + +import com.arkivanov.sample.shared.AppBar +import com.arkivanov.sample.shared.RProps +import com.arkivanov.sample.shared.Scaffold +import com.arkivanov.sample.shared.useAsState +import mui.material.Box +import mui.material.Icon +import mui.material.IconButton +import mui.material.Typography +import mui.material.TypographyAlign +import mui.material.styles.TypographyVariant +import mui.system.sx +import react.FC +import web.cssom.AlignItems +import web.cssom.BoxSizing +import web.cssom.Color +import web.cssom.Display +import web.cssom.Flex +import web.cssom.FlexDirection +import web.cssom.Overflow +import web.cssom.Position +import web.cssom.number +import web.cssom.pct +import web.cssom.px + +val ArticleAuthorContent: FC> = FC { props -> + val model by props.component.models.useAsState() + + Scaffold { + sx { + width = 100.pct + height = 100.pct + } + + if (model.isToolbarVisible) { + appBar = AppBar(title = model.author.name, onCloseClick = props.component::onCloseClicked) + } + + Box { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + flexDirection = FlexDirection.column + width = 100.pct + boxSizing = BoxSizing.borderBox + overflowY = Overflow.scroll + } + + Box { + sx { + flex = Flex(grow = number(0.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + width = 100.pct + alignItems = AlignItems.center + boxSizing = BoxSizing.borderBox + paddingRight = 8.px + } + + Typography { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + padding = 16.px + } + + variant = TypographyVariant.h6 + +model.author.name + } + + if (model.isCloseButtonVisible) { + IconButton { + sx { + flex = Flex(grow = number(0.0), shrink = number(0.0), basis = 0.px) +// marginRight = 8.px + } + + onClick = { props.component.onCloseClicked() } + + Icon { + +"close" + } + } + } + } + + Typography { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + width = 100.pct + boxSizing = BoxSizing.borderBox + paddingLeft = 16.px + paddingRight = 16.px + paddingBottom = 16.px + } + + align = TypographyAlign.justify + +model.author.bio + } + } + } +} diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt index b34a62ac5..97a62d6bf 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/details/ArticleDetailsContent.kt @@ -4,77 +4,104 @@ import com.arkivanov.sample.shared.AppBar import com.arkivanov.sample.shared.RProps import com.arkivanov.sample.shared.Scaffold import com.arkivanov.sample.shared.useAsState +import mui.material.Box +import mui.material.ButtonBase import mui.material.Typography +import mui.material.TypographyAlign +import mui.material.styles.TypographyVariant import mui.system.sx import react.FC +import web.cssom.BoxSizing +import web.cssom.Display +import web.cssom.Flex +import web.cssom.FlexDirection import web.cssom.Overflow +import web.cssom.TextAlign +import web.cssom.TextDecoration +import web.cssom.number +import web.cssom.pct import web.cssom.px val ArticleDetailsContent: FC> = FC { props -> val model by props.component.models.useAsState() Scaffold { + sx { + width = 100.pct + height = 100.pct + } + if (model.isToolbarVisible) { appBar = AppBar(title = model.article.title, onCloseClick = props.component::onCloseClicked) } - Typography { + Box { sx { - padding = 16.px - overflowX = Overflow.clip + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + flexDirection = FlexDirection.column + width = 100.pct + boxSizing = BoxSizing.borderBox overflowY = Overflow.scroll } - +model.article.text + Typography { + sx { + flex = Flex(grow = number(0.0), shrink = number(0.0), basis = 0.px) + paddingTop = 16.px + paddingLeft = 16.px + paddingRight = 16.px + } + + variant = TypographyVariant.h6 + +model.article.title + } + + ButtonBase { + sx { + flex = Flex(grow = number(0.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + width = 100.pct + padding = 16.px + } + + onClick = { props.component.onAuthorClicked() } + + Typography { + sx { + flex = Flex(grow = number(0.0), shrink = number(0.0), basis = 0.px) + paddingRight = 8.px + } + + variant = TypographyVariant.subtitle2 + +"Author: " + } + + Typography { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + textDecoration = TextDecoration.underline + textAlign = TextAlign.start + } + + variant = TypographyVariant.subtitle2 + +model.article.authorName + } + } + + Typography { + sx { + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + width = 100.pct + boxSizing = BoxSizing.borderBox + paddingLeft = 16.px + paddingRight = 16.px + paddingBottom = 16.px + } + + align = TypographyAlign.justify + +model.article.text + } } } - -// Box { -// sx { -// display = Display.flex -// flexDirection = FlexDirection.column -// boxSizing = BoxSizing.borderBox -// width = 100.pct -// height = 100.pct -// } -// -// if (model.isToolbarVisible) { -// AppBar { -// position = AppBarPosition.static -// -// Toolbar { -// IconButton { -// size = Size.large -// edge = IconButtonEdge.start -// color = IconButtonColor.inherit -// onClick = { props.component.onCloseClicked() } -// -// Icon { -// +"arrow_back" -// } -// } -// -// Typography { -// variant = TypographyVariant.h6 -// -// sx { -// flexGrow = number(1.0) -// } -// -// +model.article.title -// } -// } -// } -// } -// -// Typography { -// sx { -// padding = 16.px -// overflowX = Overflow.clip -// overflowY = Overflow.scroll -// } -// -// +model.article.text -// } -// } } diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt index 72c66e151..7b3605b2b 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/multipane/list/ArticleListContent.kt @@ -9,22 +9,31 @@ import mui.material.Typography import mui.system.sx import react.FC import web.cssom.BoxSizing +import web.cssom.Color +import web.cssom.Flex import web.cssom.Overflow +import web.cssom.number import web.cssom.pct +import web.cssom.px val ArticleListContent: FC> = FC { props -> val model by props.component.models.useAsState() Scaffold { + sx { + width = 100.pct + height = 100.pct + } + if (model.isToolbarVisible) { appBar = AppBar(title = "Multi-Pane Layout") } mui.material.List { sx { - boxSizing = BoxSizing.borderBox + flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) width = 100.pct - height = 100.pct + boxSizing = BoxSizing.borderBox overflowX = Overflow.clip overflowY = Overflow.scroll } @@ -32,7 +41,7 @@ val ArticleListContent: FC> = FC { props -> model.articles.forEach { article -> ListItemButton { selected = article.id == model.selectedArticleId - onClick = { props.component.onArticleClicked(id = article.id) } + onClick = { props.component.onArticleClicked(article) } Typography { +article.title diff --git a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/tabs/TabsContent.kt b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/tabs/TabsContent.kt index fca3974c1..4a8530d76 100644 --- a/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/tabs/TabsContent.kt +++ b/sample/shared/shared/src/jsMain/kotlin/com/arkivanov/sample/shared/tabs/TabsContent.kt @@ -37,6 +37,7 @@ internal val TabsContent: FC> = FC { props -> sx { display = Display.flex flexDirection = FlexDirection.column + boxSizing = BoxSizing.borderBox position = Position.fixed padding = 0.px top = 0.px @@ -50,6 +51,8 @@ internal val TabsContent: FC> = FC { props -> width = 100.pct boxSizing = BoxSizing.borderBox flex = Flex(grow = number(1.0), shrink = number(0.0), basis = 0.px) + display = Display.flex + flexDirection = FlexDirection.column overflowY = Overflow.clip }