diff --git a/decompose/build.gradle.kts b/decompose/build.gradle.kts index 9b3838c50..5b92f687f 100644 --- a/decompose/build.gradle.kts +++ b/decompose/build.gradle.kts @@ -27,18 +27,18 @@ kotlin { setupSourceSets { val android by bundle() val nonAndroid by bundle() - val native by bundle() val nonNative by bundle() val darwin by bundle() + val itvos by bundle() val js by bundle() val nonJs by bundle() - (nonAndroid + native + nonNative + nonJs) dependsOn common + (nonAndroid + darwin + nonNative + nonJs) dependsOn common (allSet - android) dependsOn nonAndroid (allSet - nativeSet) dependsOn nonNative (allSet - js) dependsOn nonJs - (nativeSet + darwin) dependsOn native - darwinSet dependsOn darwin + (iosSet + tvosSet) dependsOn itvos + (darwinSet - iosSet - tvosSet + itvos) dependsOn darwin all { languageSettings { diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Relay.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Relay.kt index ec0f41fe7..ffd3e5404 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Relay.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Relay.kt @@ -6,10 +6,6 @@ internal class Relay( private val isMainThreadCheckEnabled: Boolean = false, ) { - init { - ensureNeverFrozen() - } - private val lock = Lock() private val queue = ArrayDeque() private var isDraining = false diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt index cec8ed846..c63206ebd 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/Utils.kt @@ -9,6 +9,4 @@ fun Any.hashString(): String = internal expect val KClass<*>.uniqueName: String? -internal expect fun Any.ensureNeverFrozen() - internal val Lifecycle.isDestroyed: Boolean get() = state == Lifecycle.State.DESTROYED diff --git a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/MutableValueBuilder.kt b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/MutableValueBuilder.kt index 7e2f0dad5..622db28b5 100644 --- a/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/MutableValueBuilder.kt +++ b/decompose/src/commonMain/kotlin/com/arkivanov/decompose/value/MutableValueBuilder.kt @@ -1,7 +1,6 @@ package com.arkivanov.decompose.value import com.arkivanov.decompose.Lock -import com.arkivanov.decompose.ensureNeverFrozen import com.arkivanov.decompose.synchronized /** @@ -12,10 +11,6 @@ fun MutableValue(initialValue: T): MutableValue = MutableValueImpl( private class MutableValueImpl(initialValue: T) : MutableValue() { - init { - ensureNeverFrozen() - } - private val lock = Lock() private var _value: T = initialValue private var isEmitting = false diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackDispatcher.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackDispatcher.kt index 1133cd042..1587dc9fb 100644 --- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackDispatcher.kt +++ b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestBackDispatcher.kt @@ -1,15 +1,10 @@ package com.arkivanov.decompose.backhandler -import com.arkivanov.decompose.ensureNeverFrozen import com.arkivanov.essenty.backhandler.BackCallback import com.arkivanov.essenty.backhandler.BackDispatcher internal class TestBackDispatcher : BackDispatcher { - init { - ensureNeverFrozen() - } - private var set = emptySet() val size: Int get() = set.size diff --git a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt b/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt deleted file mode 100644 index 3015bc12e..000000000 --- a/decompose/src/commonTest/kotlin/com/arkivanov/decompose/backhandler/TestChildBackHandler.kt +++ /dev/null @@ -1,32 +0,0 @@ -package com.arkivanov.decompose.backhandler - -import com.arkivanov.decompose.ensureNeverFrozen -import com.arkivanov.essenty.backhandler.BackCallback - -class TestChildBackHandler( - var isStarted: Boolean = false, - override var isEnabled: Boolean = false, -) : ChildBackHandler { - - init { - ensureNeverFrozen() - } - - override fun start() { - check(!isStarted) - isStarted = true - } - - override fun stop() { - check(isStarted) - isStarted = false - } - - override fun register(callback: BackCallback) { - TODO("Not yet implemented") - } - - override fun unregister(callback: BackCallback) { - TODO("Not yet implemented") - } -} diff --git a/decompose/src/itvosMain/kotlin/com/arkivanov/decompose/lifecycle/ApplicationLifecycle.kt b/decompose/src/itvosMain/kotlin/com/arkivanov/decompose/lifecycle/ApplicationLifecycle.kt new file mode 100644 index 000000000..0217e3c57 --- /dev/null +++ b/decompose/src/itvosMain/kotlin/com/arkivanov/decompose/lifecycle/ApplicationLifecycle.kt @@ -0,0 +1,84 @@ +package com.arkivanov.decompose.lifecycle + +import com.arkivanov.decompose.ExperimentalDecomposeApi +import com.arkivanov.essenty.lifecycle.Lifecycle +import com.arkivanov.essenty.lifecycle.LifecycleRegistry +import com.arkivanov.essenty.lifecycle.destroy +import com.arkivanov.essenty.lifecycle.pause +import com.arkivanov.essenty.lifecycle.resume +import com.arkivanov.essenty.lifecycle.start +import com.arkivanov.essenty.lifecycle.stop +import kotlinx.cinterop.BetaInteropApi +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.cinterop.ObjCAction +import platform.Foundation.NSNotificationCenter +import platform.Foundation.NSNotificationName +import platform.Foundation.NSSelectorFromString +import platform.UIKit.UIApplicationDidBecomeActiveNotification +import platform.UIKit.UIApplicationDidEnterBackgroundNotification +import platform.UIKit.UIApplicationWillEnterForegroundNotification +import platform.UIKit.UIApplicationWillResignActiveNotification +import platform.UIKit.UIApplicationWillTerminateNotification + +/** + * An implementation of [Lifecycle] that follows the [UIApplication][platform.UIKit.UIApplication] lifecycle notifications. + */ +@ExperimentalDecomposeApi +class ApplicationLifecycle private constructor( + private val lifecycle: LifecycleRegistry, +) : Lifecycle by lifecycle { + + constructor() : this(lifecycle = LifecycleRegistry()) + + init { + addObserver(name = UIApplicationWillEnterForegroundNotification, selectorName = "willEnterForeground") + addObserver(name = UIApplicationDidBecomeActiveNotification, selectorName = "didBecomeActive") + addObserver(name = UIApplicationWillResignActiveNotification, selectorName = "willResignActive") + addObserver(name = UIApplicationDidEnterBackgroundNotification, selectorName = "didEnterBackground") + addObserver(name = UIApplicationWillTerminateNotification, selectorName = "willTerminate") + } + + @OptIn(ExperimentalForeignApi::class) + private fun addObserver(name: NSNotificationName, selectorName: String) { + NSNotificationCenter.defaultCenter.addObserver( + name = name, + `object` = null, + observer = this, + selector = NSSelectorFromString(selectorName), + ) + } + + @Suppress("unused") + @OptIn(BetaInteropApi::class) + @ObjCAction + fun willEnterForeground() { + lifecycle.start() + } + + @Suppress("unused") + @OptIn(BetaInteropApi::class) + @ObjCAction + fun didBecomeActive() { + lifecycle.resume() + } + + @Suppress("unused") + @OptIn(BetaInteropApi::class) + @ObjCAction + fun willResignActive() { + lifecycle.pause() + } + + @Suppress("unused") + @OptIn(BetaInteropApi::class) + @ObjCAction + fun didEnterBackground() { + lifecycle.stop() + } + + @OptIn(BetaInteropApi::class) + @ObjCAction + fun willTerminate() { + lifecycle.destroy() + } +} diff --git a/decompose/src/nativeMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/nativeMain/kotlin/com/arkivanov/decompose/Utils.kt deleted file mode 100644 index e413ee3d2..000000000 --- a/decompose/src/nativeMain/kotlin/com/arkivanov/decompose/Utils.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.arkivanov.decompose - -import kotlin.native.concurrent.ensureNeverFrozen as ensureNeverFrozenNative - -internal actual fun Any.ensureNeverFrozen() { - ensureNeverFrozenNative() -} diff --git a/decompose/src/nonNativeMain/kotlin/com/arkivanov/decompose/Utils.kt b/decompose/src/nonNativeMain/kotlin/com/arkivanov/decompose/Utils.kt deleted file mode 100644 index b06b4b90b..000000000 --- a/decompose/src/nonNativeMain/kotlin/com/arkivanov/decompose/Utils.kt +++ /dev/null @@ -1,9 +0,0 @@ -@file:JvmName("UtilsJvm") - -package com.arkivanov.decompose - -import kotlin.jvm.JvmName - -internal actual fun Any.ensureNeverFrozen() { - // no-op -} diff --git a/deps.versions.toml b/deps.versions.toml index 843de56b3..a9e3e7c96 100644 --- a/deps.versions.toml +++ b/deps.versions.toml @@ -1,18 +1,18 @@ [versions] decompose = "2.2.0-compose-experimental-alpha04" -kotlin = "1.9.10" -essenty = "1.3.0-alpha03" -parcelizeDarwin = "0.2.2" +kotlin = "1.9.20" +essenty = "1.3.0-alpha04" +parcelizeDarwin = "0.2.3" reaktive = "1.2.3" junit = "4.13.2" -jetbrainsCompose = "1.5.1" +jetbrainsCompose = "1.5.10" jetbrainsKotlinWrappers = "1.0.0-pre.608" jetbrainsKotlinxCoroutines = "1.6.4" jetbrainsKotlinxSerialization = "1.6.0" jetbrainsBinaryCompatibilityValidator = "0.13.2" jetpackCompose = "1.5.0" -jetpackComposeCompiler = "1.5.3" +jetpackComposeCompiler = "1.5.4" androidGradle = "8.0.2" androidMaterial = "1.6.1" androidPlay = "1.10.3" diff --git a/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api b/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api index dee65b61c..26ca4566c 100644 --- a/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api +++ b/extensions-compose-jetbrains/api/android/extensions-compose-jetbrains.api @@ -30,11 +30,17 @@ public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/Pa public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Default : com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation { public static final field $stable I public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Default; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Disabled : com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation { public static final field $stable I public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Disabled; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenKt { diff --git a/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api b/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api index 140987c0b..a519761cf 100644 --- a/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api +++ b/extensions-compose-jetbrains/api/jvm/extensions-compose-jetbrains.api @@ -42,11 +42,17 @@ public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/Pa public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Default : com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation { public static final field $stable I public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Default; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Disabled : com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation { public static final field $stable I public static final field INSTANCE Lcom/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation$Disabled; + public fun equals (Ljava/lang/Object;)Z + public fun hashCode ()I + public fun toString ()Ljava/lang/String; } public final class com/arkivanov/decompose/extensions/compose/jetbrains/stack/ChildrenKt { diff --git a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation.kt b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation.kt index 1d20c1aa2..9aba9b04c 100644 --- a/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation.kt +++ b/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/pages/PagesScrollAnimation.kt @@ -8,7 +8,7 @@ import com.arkivanov.decompose.ExperimentalDecomposeApi @ExperimentalDecomposeApi sealed interface PagesScrollAnimation { - object Disabled : PagesScrollAnimation - object Default : PagesScrollAnimation + data object Disabled : PagesScrollAnimation + data object Default : PagesScrollAnimation class Custom(val spec: AnimationSpec) : PagesScrollAnimation } diff --git a/gradle.properties b/gradle.properties index 040c63887..cd97a4bb4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.parallel=true org.gradle.caching=true systemProp.org.gradle.internal.publish.checksums.insecure=true kotlin.mpp.androidSourceSetLayoutVersion=2 -org.jetbrains.compose.experimental.uikit.enabled=true +kotlin.mpp.applyDefaultHierarchyTemplate=false org.jetbrains.compose.experimental.macos.enabled=true org.jetbrains.compose.experimental.jscanvas.enabled=true diff --git a/sample/app-ios/app-ios/app_iosApp.swift b/sample/app-ios/app-ios/app_iosApp.swift index 5cb7def1a..bd20440a3 100644 --- a/sample/app-ios/app-ios/app_iosApp.swift +++ b/sample/app-ios/app-ios/app_iosApp.swift @@ -13,76 +13,40 @@ struct app_iosApp: App { @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate: AppDelegate - private var rootHolder: RootHolder { appDelegate.getRootHolder() } - var body: some Scene { WindowGroup { - RootView(rootHolder.root) - .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in - LifecycleRegistryExtKt.resume(rootHolder.lifecycle) - } - .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in - LifecycleRegistryExtKt.pause(rootHolder.lifecycle) - } - .onReceive(NotificationCenter.default.publisher(for: UIApplication.didEnterBackgroundNotification)) { _ in - LifecycleRegistryExtKt.stop(rootHolder.lifecycle) - } - .onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in - LifecycleRegistryExtKt.destroy(rootHolder.lifecycle) - } + RootView(appDelegate.root) } } } class AppDelegate: NSObject, UIApplicationDelegate { - private var rootHolder: RootHolder? - + private var stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: nil) + + lazy var root: RootComponent = DefaultRootComponent( + componentContext: DefaultComponentContext( + lifecycle: ApplicationLifecycle(), + stateKeeper: stateKeeper, + instanceKeeper: nil, + backHandler: nil + ), + featureInstaller: DefaultFeatureInstaller.shared, + deepLink: DefaultRootComponentDeepLinkNone.shared, + webHistoryController: nil + ) + func application(_ application: UIApplication, shouldSaveSecureApplicationState coder: NSCoder) -> Bool { - let savedState = rootHolder!.stateKeeper.save() - CodingKt.encodeParcelable(coder, value: savedState, key: "savedState") + CodingKt.encodeParcelable(coder, value: stateKeeper.save(), key: "savedState") return true } func application(_ application: UIApplication, shouldRestoreSecureApplicationState coder: NSCoder) -> Bool { do { let savedState = try CodingKt.decodeParcelable(coder, key: "savedState") as! ParcelableParcelableContainer - rootHolder = RootHolder(savedState: savedState) + stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: savedState) return true } catch { return false } } - - fileprivate func getRootHolder() -> RootHolder { - if (rootHolder == nil) { - rootHolder = RootHolder(savedState: nil) - } - - return rootHolder! - } -} - -private class RootHolder { - let lifecycle: LifecycleRegistry - let stateKeeper: StateKeeperDispatcher - let root: RootComponent - - init(savedState: ParcelableParcelableContainer?) { - lifecycle = LifecycleRegistryKt.LifecycleRegistry() - stateKeeper = StateKeeperDispatcherKt.StateKeeperDispatcher(savedState: savedState) - - root = DefaultRootComponent( - componentContext: DefaultComponentContext( - lifecycle: lifecycle, - stateKeeper: stateKeeper, - instanceKeeper: nil, - backHandler: nil - ), - featureInstaller: DefaultFeatureInstaller.shared, - deepLink: DefaultRootComponentDeepLinkNone.shared, - webHistoryController: nil - ) - - LifecycleRegistryExtKt.create(lifecycle) - } } diff --git a/sample/shared/dynamic-features/feature1Impl/build.gradle.kts b/sample/shared/dynamic-features/feature1Impl/build.gradle.kts index 64d7313f5..01aae0335 100644 --- a/sample/shared/dynamic-features/feature1Impl/build.gradle.kts +++ b/sample/shared/dynamic-features/feature1Impl/build.gradle.kts @@ -2,9 +2,6 @@ import com.arkivanov.gradle.bundle import com.arkivanov.gradle.iosCompat import com.arkivanov.gradle.setupMultiplatform import com.arkivanov.gradle.setupSourceSets -import org.jetbrains.compose.ComposeCompilerKotlinSupportPlugin -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType plugins { @@ -58,20 +55,6 @@ kotlin { } } -compose.web.targets() - - -plugins.removeAll { it is ComposeCompilerKotlinSupportPlugin } - -class ComposeNoNativePlugin : KotlinCompilerPluginSupportPlugin by ComposeCompilerKotlinSupportPlugin( - buildEventsListenerRegistry = {}, -) { - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { - return when (kotlinCompilation.target.platformType) { - KotlinPlatformType.native -> false - else -> ComposeCompilerKotlinSupportPlugin(buildEventsListenerRegistry = {}).isApplicable(kotlinCompilation) - } - } +compose { + platformTypes.set(platformTypes.get() - KotlinPlatformType.js - KotlinPlatformType.native) } - -apply() diff --git a/sample/shared/dynamic-features/feature2Impl/build.gradle.kts b/sample/shared/dynamic-features/feature2Impl/build.gradle.kts index 75a128d0c..6ec35b647 100644 --- a/sample/shared/dynamic-features/feature2Impl/build.gradle.kts +++ b/sample/shared/dynamic-features/feature2Impl/build.gradle.kts @@ -2,9 +2,6 @@ import com.arkivanov.gradle.bundle import com.arkivanov.gradle.iosCompat import com.arkivanov.gradle.setupMultiplatform import com.arkivanov.gradle.setupSourceSets -import org.jetbrains.compose.ComposeCompilerKotlinSupportPlugin -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilation -import org.jetbrains.kotlin.gradle.plugin.KotlinCompilerPluginSupportPlugin import org.jetbrains.kotlin.gradle.plugin.KotlinPlatformType plugins { @@ -58,19 +55,6 @@ kotlin { } } -compose.web.targets() - -plugins.removeAll { it is ComposeCompilerKotlinSupportPlugin } - -class ComposeNoNativePlugin : KotlinCompilerPluginSupportPlugin by ComposeCompilerKotlinSupportPlugin( - buildEventsListenerRegistry = {}, -) { - override fun isApplicable(kotlinCompilation: KotlinCompilation<*>): Boolean { - return when (kotlinCompilation.target.platformType) { - KotlinPlatformType.native -> false - else -> ComposeCompilerKotlinSupportPlugin(buildEventsListenerRegistry = {}).isApplicable(kotlinCompilation) - } - } +compose { + platformTypes.set(platformTypes.get() - KotlinPlatformType.js - KotlinPlatformType.native) } - -apply() diff --git a/settings.gradle.kts b/settings.gradle.kts index f4a99455c..7956392df 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -15,7 +15,7 @@ pluginManagement { resolutionStrategy { eachPlugin { if (requested.id.toString() == "com.arkivanov.gradle.setup") { - useModule("com.github.arkivanov:gradle-setup-plugin:2571f348ff") + useModule("com.github.arkivanov:gradle-setup-plugin:655aedff78") } } }