From 418a4a85322d0c43bfde30d5974cbfadeb800eeb Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 30 Sep 2024 16:53:37 +0900 Subject: [PATCH 01/17] update to compose 1.7 --- gradle/libs.versions.toml | 18 +++---- precompose/build.gradle.kts | 4 +- .../precompose/PreComposeApp.android.kt | 2 +- .../tlaster/precompose/navigation/NavHost.kt | 26 +++++---- .../tlaster/precompose/PreComposeWindow.kt | 53 ------------------- 5 files changed, 27 insertions(+), 76 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 056ff199..2e086c6f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,25 +3,25 @@ libVersion = "1.6.2" compileSdk = "34" minSdk = "21" java = "11" -androidx-animation = "1.6.8" -androidx-foundation = "1.6.8" +androidx-animation = "1.7.2" +androidx-foundation = "1.7.2" androidx-appcompat = "1.7.0" androidx-coreKtx = "1.13.1" androidxActivityVer = "1.9.2" -androidGradlePlugin = "8.6.0" +androidGradlePlugin = "8.6.1" junit = "4.13.2" junitJupiterEngine = "5.11.0" junitJupiterApi = "5.11.0" kotlin = "2.0.0" -lifecycleRuntimeKtx = "2.8.5" -material = "1.6.8" -kotlinxCoroutinesCore = "1.8.1" +lifecycleRuntimeKtx = "2.8.6" +material = "1.7.2" +kotlinxCoroutinesCore = "1.9.0" moleculeRuntime = "2.0.0" savedstateKtx = "1.2.1" spotless = "6.25.0" -jetbrainsComposePlugin = "1.6.11" +jetbrainsComposePlugin = "1.7.0-beta02" skiko = "0.8.12" -koin = "4.0.0-RC2" +koin = "4.0.0" uuid = "0.8.4" webpackCliVersion = "5.1.4" nodeVersion = "20.14.0" @@ -48,7 +48,7 @@ molecule-runtime = { module = "app.cash.molecule:molecule-runtime", version.ref skiko = { module = "org.jetbrains.skiko:skiko", version.ref = "skiko" } skiko-js = { module = "org.jetbrains.skiko:skiko-js-wasm-runtime", version.ref = "skiko" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } -koin-compose = { module = "io.insert-koin:koin-compose", version = "4.0.0-RC2" } +koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } [plugins] diff --git a/precompose/build.gradle.kts b/precompose/build.gradle.kts index a970a965..e73e092c 100644 --- a/precompose/build.gradle.kts +++ b/precompose/build.gradle.kts @@ -30,8 +30,8 @@ kotlin { sourceSetTree.set(KotlinSourceSetTree.test) dependencies { - androidTestImplementation("androidx.compose.ui:ui-test-junit4-android:1.6.8") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.6.7") + androidTestImplementation("androidx.compose.ui:ui-test-junit4-android:1.7.2") + debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.2") } } } diff --git a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt index 0747f0ca..5e87f7d1 100644 --- a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt +++ b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt @@ -23,7 +23,7 @@ actual fun PreComposeApp( ) { val viewModel = androidx.lifecycle.viewmodel.compose.viewModel() - val lifecycle = androidx.compose.ui.platform.LocalLifecycleOwner.current.lifecycle + val lifecycle = androidx.lifecycle.compose.LocalLifecycleOwner.current.lifecycle val onBackPressedDispatcher = checkNotNull(androidx.activity.compose.LocalOnBackPressedDispatcherOwner.current) { "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" }.onBackPressedDispatcher diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt index e9f1c6f3..1e4033f3 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -9,6 +9,7 @@ import androidx.compose.animation.core.SeekableTransitionState import androidx.compose.animation.core.rememberTransition import androidx.compose.animation.core.tween import androidx.compose.animation.core.updateTransition +import androidx.compose.animation.rememberSplineBasedDecay import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.gestures.AnchoredDraggableState import androidx.compose.foundation.gestures.DraggableAnchors @@ -140,7 +141,8 @@ fun NavHost( val state = if (actualSwipeProperties != null) { val density = LocalDensity.current val width = constraints.maxWidth.toFloat() - remember { + val decayAnimationSpec = rememberSplineBasedDecay() + remember(actualSwipeProperties) { AnchoredDraggableState( initialValue = DragAnchors.Start, anchors = DraggableAnchors { @@ -149,7 +151,8 @@ fun NavHost( }, positionalThreshold = actualSwipeProperties.positionalThreshold, velocityThreshold = { actualSwipeProperties.velocityThreshold.invoke(density) }, - animationSpec = tween(), + snapAnimationSpec = tween(), + decayAnimationSpec = decayAnimationSpec, ) }.also { state -> LaunchedEffect( @@ -159,13 +162,13 @@ fun NavHost( if (state.currentValue == DragAnchors.End && !state.isAnimationRunning) { // play the animation to the end progress = 1f - state.snapTo(DragAnchors.Start) } } - LaunchedEffect(state.progress) { - if (state.progress != 1f) { - inPredictiveBack = state.progress > 0f - progress = state.progress + val stateProgress = state.progress(DragAnchors.Start, DragAnchors.End) + LaunchedEffect(stateProgress) { + if (stateProgress != 1f) { + inPredictiveBack = stateProgress > 0f + progress = stateProgress } else if (state.currentValue != DragAnchors.End && inPredictiveBack) { // reset the state to the initial value progress = -1f @@ -185,20 +188,21 @@ fun NavHost( } val transition = if (showPrev) { val transitionState by remember(sceneEntry) { - mutableStateOf(SeekableTransitionState(sceneEntry, prevSceneEntry!!)) + mutableStateOf(SeekableTransitionState(sceneEntry)) } LaunchedEffect(progress) { if (progress == 1f) { // play the animation to the end - transitionState.animateToTargetState() + transitionState.animateTo(prevSceneEntry!!) inPredictiveBack = false navigator.goBack() progress = 0f + state?.snapTo(DragAnchors.Start) } else if (progress >= 0) { - transitionState.snapToFraction(progress) + transitionState.seekTo(progress, targetState = prevSceneEntry!!) } else if (progress == -1f) { // reset the state to the initial value - transitionState.animateToCurrentState() + transitionState.seekTo(0f, targetState = prevSceneEntry!!) inPredictiveBack = false progress = 0f } diff --git a/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt b/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt index 209db201..6fbb1941 100644 --- a/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt +++ b/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt @@ -5,13 +5,9 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.graphics.painter.Painter -import androidx.compose.ui.input.key.KeyEvent import androidx.compose.ui.window.FrameWindowScope import androidx.compose.ui.window.Window import androidx.compose.ui.window.WindowScope -import androidx.compose.ui.window.WindowState -import androidx.compose.ui.window.rememberWindowState import moe.tlaster.precompose.lifecycle.Lifecycle import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.lifecycle.LifecycleRegistry @@ -25,55 +21,6 @@ import java.awt.Window import java.awt.event.WindowAdapter import java.awt.event.WindowEvent -@Deprecated( - message = """ - Use Window directly instead. And make sure wrap your content with PreComposeApp. - PreComposeWindow will be removed in the future release. - For migration guide, please refer to https://github.com/Tlaster/PreCompose/releases/tag/1.5.5 - """, - replaceWith = ReplaceWith("PreComposeWindow"), -) -@Composable -fun PreComposeWindow( - onCloseRequest: () -> Unit, - state: WindowState = rememberWindowState(), - visible: Boolean = true, - title: String = "Untitled", - icon: Painter? = null, - undecorated: Boolean = false, - transparent: Boolean = false, - resizable: Boolean = true, - enabled: Boolean = true, - focusable: Boolean = true, - alwaysOnTop: Boolean = false, - onPreviewKeyEvent: (KeyEvent) -> Boolean = { false }, - onKeyEvent: (KeyEvent) -> Boolean = { false }, - content: @Composable FrameWindowScope.() -> Unit, -) { - Window( - onCloseRequest = onCloseRequest, - state = state, - visible = visible, - title = title, - icon = icon, - undecorated = undecorated, - transparent = transparent, - resizable = resizable, - enabled = enabled, - focusable = focusable, - alwaysOnTop = alwaysOnTop, - onPreviewKeyEvent = onPreviewKeyEvent, - onKeyEvent = onKeyEvent, - content = { - ProvidePreComposeLocals { - PreComposeApp { - content.invoke(this) - } - } - }, - ) -} - val LocalWindow = staticCompositionLocalOf { error("No Window for PreCompose, please use ProvidePreComposeLocals in WindowScope to setup your desktop project") } From 076beb71723afed4b2936df2a398b4d27124ddc7 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 1 Oct 2024 13:02:40 +0900 Subject: [PATCH 02/17] version 1.7.0-alpha01 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2e086c6f..62d3c5f8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] # also check project root build.gradle.kts for versions -libVersion = "1.6.2" +libVersion = "1.7.0-alpha01" compileSdk = "34" minSdk = "21" java = "11" From bc6383b268b7c1d04d2a2a2c21f6dff44c0e0938 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 1 Oct 2024 15:09:27 +0900 Subject: [PATCH 03/17] migrating to Jetpack ViewModel and Lifecycle --- gradle/libs.versions.toml | 7 + precompose-koin/build.gradle.kts | 196 ----------------- .../moe/tlaster/precompose/koin/Koin.kt | 66 ------ .../tlaster/precompose/molecule/Molecule.kt | 40 ++-- precompose-viewmodel/build.gradle.kts | 197 ------------------ .../viewmodel/CloseableCoroutineScope.kt | 37 ---- .../tlaster/precompose/viewmodel/ViewModel.kt | 61 ------ .../precompose/viewmodel/ViewModelAdapter.kt | 65 ------ .../precompose/viewmodel/ViewModelTest.kt | 53 ----- precompose/build.gradle.kts | 6 +- .../precompose/PreComposeApp.android.kt | 69 +++--- .../lifecycle/PreComposeActivity.kt | 37 ---- .../lifecycle/PreComposeViewModel.kt | 47 ----- .../tlaster/precompose/flow/FlowExtensions.kt | 79 ------- .../tlaster/precompose/lifecycle/Lifecycle.kt | 18 -- .../precompose/lifecycle/LifecycleObserver.kt | 5 - .../precompose/lifecycle/LifecycleOwner.kt | 24 --- .../precompose/lifecycle/LifecycleRegistry.kt | 50 ----- .../precompose/lifecycle/RepeatOnLifecycle.kt | 142 ------------- .../precompose/navigation/BackHandler.kt | 6 +- .../precompose/navigation/BackStackEntry.kt | 52 +++-- .../precompose/navigation/BackStackManager.kt | 44 ++-- .../navigation/NavControllerViewModel.kt | 52 +++++ .../tlaster/precompose/navigation/NavHost.kt | 23 +- .../precompose/navigation/Navigator.kt | 27 +-- .../navigation/ViewModelStoreProvider.kt | 8 + .../stateholder/SavedStateHolder.kt | 51 ----- .../precompose/stateholder/StateHolder.kt | 57 ----- .../precompose/lifecycle/LifecycleTest.kt | 24 --- .../lifecycle/TestLifecycleOwner.kt | 7 - .../navigation/BackStackEntryTest.kt | 53 +++-- .../navigation/BackStackManagerTest.kt | 173 ++++++--------- .../precompose/navigation/NavigatorTest.kt | 13 +- .../navigation/SeavableStateRegistry.kt | 15 -- .../navigation/TestViewModelStoreOwner.kt | 20 ++ .../navigation/TestViewModelStoreProvider.kt | 19 ++ .../precompose/stateholder/StateHolderTest.kt | 34 --- .../precompose/PreComposeApplication.kt | 119 ++--------- .../tlaster/precompose/PreComposeWindow.kt | 44 ++-- .../tlaster/precompose/PreComposeWindow.kt | 84 +------- .../precompose/navigation/NavHostTest.kt | 126 ++++++----- .../tlaster/precompose/PreComposeWindow.kt | 39 ++-- .../moe/tlaster/precompose/PreComposeApp.kt | 44 ++-- .../precompose/molecule/sample/Main.kt | 5 +- sample/todo/common/build.gradle.kts | 3 +- .../kotlin/moe/tlaster/common/App.kt | 14 +- .../kotlin/moe/tlaster/common/di/AppModule.kt | 10 +- .../tlaster/common/scene/NoteDetailScene.kt | 2 +- .../moe/tlaster/common/scene/NoteEditScene.kt | 8 +- .../moe/tlaster/common/scene/NoteListScene.kt | 2 +- .../common/viewmodel/NoteDetailViewModel.kt | 2 +- .../common/viewmodel/NoteEditViewModel.kt | 17 +- .../common/viewmodel/NoteListViewModel.kt | 2 +- .../todo/desktop/src/jvmMain/kotlin/Main.kt | 5 +- settings.gradle.kts | 2 - 55 files changed, 481 insertions(+), 1924 deletions(-) delete mode 100644 precompose-koin/build.gradle.kts delete mode 100644 precompose-koin/src/commonMain/kotlin/moe/tlaster/precompose/koin/Koin.kt delete mode 100644 precompose-viewmodel/build.gradle.kts delete mode 100644 precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt delete mode 100644 precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt delete mode 100644 precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelAdapter.kt delete mode 100644 precompose-viewmodel/src/commonTest/kotlin/moe/tlaster/precompose/viewmodel/ViewModelTest.kt delete mode 100644 precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt delete mode 100644 precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeViewModel.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/flow/FlowExtensions.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt create mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt create mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/ViewModelStoreProvider.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/SavedStateHolder.kt delete mode 100644 precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/StateHolder.kt delete mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/LifecycleTest.kt delete mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/TestLifecycleOwner.kt delete mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/SeavableStateRegistry.kt create mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt create mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt delete mode 100644 precompose/src/commonTest/kotlin/moe/tlaster/precompose/stateholder/StateHolderTest.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 62d3c5f8..53298ebd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -22,6 +22,8 @@ spotless = "6.25.0" jetbrainsComposePlugin = "1.7.0-beta02" skiko = "0.8.12" koin = "4.0.0" +uiTestJunit4Android = "1.7.2" +uiTestManifest = "1.7.2" uuid = "0.8.4" webpackCliVersion = "5.1.4" nodeVersion = "20.14.0" @@ -36,6 +38,8 @@ androidx-lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtim androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleRuntimeKtx" } androidx-material = { module = "androidx.compose.material:material", version.ref = "material" } androidx-savedstate-ktx = { module = "androidx.savedstate:savedstate-ktx", version.ref = "savedstateKtx" } +androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "uiTestManifest" } +androidx-ui-test-junit4-android = { module = "androidx.compose.ui:ui-test-junit4-android", version.ref = "uiTestJunit4Android" } animation = { module = "androidx.compose.animation:animation", version.ref = "androidx-animation" } foundation = { module = "androidx.compose.foundation:foundation", version.ref = "androidx-foundation" } junit = { module = "junit:junit", version.ref = "junit" } @@ -49,7 +53,10 @@ skiko = { module = "org.jetbrains.skiko:skiko", version.ref = "skiko" } skiko-js = { module = "org.jetbrains.skiko:skiko-js-wasm-runtime", version.ref = "skiko" } koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } +koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } +jetbrains-lifecycle = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version = "2.8.2" } +jetbrains-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.2" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/precompose-koin/build.gradle.kts b/precompose-koin/build.gradle.kts deleted file mode 100644 index 3ae47ce3..00000000 --- a/precompose-koin/build.gradle.kts +++ /dev/null @@ -1,196 +0,0 @@ -import java.util.Properties - -plugins { - kotlin("multiplatform") - alias(libs.plugins.jetbrains.compose) - id("com.android.library") - id("maven-publish") - id("signing") - alias(libs.plugins.compose.compiler) -} - -group = "moe.tlaster" -version = libs.versions.libVersion.get() - -kotlin { - applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release", "debug") - } - jvm { - testRuns["test"].executionTask.configure { - useJUnitPlatform() - } - } - macosArm64() - macosX64() - // ios() - iosX64() - iosArm64() - iosSimulatorArm64() - js(IR) { - browser() - } - wasmJs { - browser() - binaries.executable() - } - sourceSets { - val commonMain by getting { - dependencies { - compileOnly(compose.foundation) - implementation(libs.koin) - implementation(libs.koin.compose) - implementation(project(":precompose")) - implementation(project(":precompose-viewmodel")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - // implementation(compose("org.jetbrains.compose.ui:ui-test-junit4")) - } - } - val androidMain by getting { - dependencies { - api(libs.androidx.activity.ktx) - implementation(libs.foundation) - } - } - val androidUnitTest by getting { - dependencies { - implementation(kotlin("test-junit")) - implementation(libs.junit) - } - } - val jvmMain by getting { - dependencies { - implementation(compose.foundation) - api(libs.kotlinx.coroutines.swing) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test-junit5")) - implementation(libs.junit.jupiter.api) - runtimeOnly(libs.junit.jupiter.engine) - } - } - val macosMain by getting { - dependencies { - implementation(compose.foundation) - } - } - val jsMain by getting { - dependencies { - implementation(compose.foundation) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } - val iosMain by getting { - dependencies { - implementation(compose.foundation) - } - } - } -} - -// adding it here to make sure skiko is unpacked and available in web tests -compose.experimental { - web.application {} -} -android { - compileSdk = libs.versions.compileSdk.get().toInt() - namespace = "moe.tlaster.precompose.koin" - defaultConfig { - minSdk = libs.versions.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - } -} - -extra.apply { - val publishPropFile = rootProject.file("publish.properties") - if (publishPropFile.exists()) { - Properties().apply { - load(publishPropFile.inputStream()) - }.forEach { name, value -> - if (name == "signing.secretKeyRingFile") { - set(name.toString(), rootProject.file(value.toString()).absolutePath) - } else { - set(name.toString(), value) - } - } - } else { - set("signing.keyId", System.getenv("SIGNING_KEY_ID")) - set("signing.password", System.getenv("SIGNING_PASSWORD")) - set("signing.secretKeyRingFile", System.getenv("SIGNING_SECRET_KEY_RING_FILE")) - set("ossrhUsername", System.getenv("OSSRH_USERNAME")) - set("ossrhPassword", System.getenv("OSSRH_PASSWORD")) - } -} -val javadocJar by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") -} -// https://github.com/gradle/gradle/issues/26091 -val signingTasks = tasks.withType() -tasks.withType().configureEach { - dependsOn(signingTasks) -} -publishing { - if (rootProject.file("publish.properties").exists()) { - signing { - sign(publishing.publications) - } - repositories { - maven { - val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = if (version.toString().endsWith("SNAPSHOT")) { - uri(snapshotsRepoUrl) - } else { - uri(releasesRepoUrl) - } - credentials { - username = project.ext.get("ossrhUsername").toString() - password = project.ext.get("ossrhPassword").toString() - } - } - } - } - - publications.withType { - artifact(javadocJar) - pom { - name.set("PreCompose-Koin") - description.set("PreCompose Koin intergration") - url.set("https://github.com/Tlaster/PreCompose") - - licenses { - license { - name.set("MIT") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("Tlaster") - name.set("James Tlaster") - email.set("tlaster@outlook.com") - } - } - scm { - url.set("https://github.com/Tlaster/PreCompose") - connection.set("scm:git:git://github.com/Tlaster/PreCompose.git") - developerConnection.set("scm:git:git://github.com/Tlaster/PreCompose.git") - } - } - } -} diff --git a/precompose-koin/src/commonMain/kotlin/moe/tlaster/precompose/koin/Koin.kt b/precompose-koin/src/commonMain/kotlin/moe/tlaster/precompose/koin/Koin.kt deleted file mode 100644 index 50aa5adf..00000000 --- a/precompose-koin/src/commonMain/kotlin/moe/tlaster/precompose/koin/Koin.kt +++ /dev/null @@ -1,66 +0,0 @@ -package moe.tlaster.precompose.koin - -import androidx.compose.runtime.Composable -import moe.tlaster.precompose.reflect.canonicalName -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder -import moe.tlaster.precompose.viewmodel.ViewModel -import org.koin.compose.LocalKoinScope -import org.koin.core.parameter.ParametersDefinition -import org.koin.core.qualifier.Qualifier -import org.koin.core.scope.Scope -import kotlin.reflect.KClass - -@Composable -inline fun koinViewModel( - qualifier: Qualifier? = null, - stateHolder: StateHolder = checkNotNull(LocalStateHolder.current) { - "No StateHolder was provided via LocalStateHolder" - }, - key: String? = null, - scope: Scope = LocalKoinScope.current, - noinline parameters: ParametersDefinition? = null, -): T { - return koinViewModel( - T::class, - qualifier, - stateHolder, - key, - scope, - parameters, - ) -} - -@Composable -fun koinViewModel( - vmClass: KClass, - qualifier: Qualifier? = null, - stateHolder: StateHolder = checkNotNull(LocalStateHolder.current) { - "No StateHolder was provided via LocalStateHolder" - }, - key: String? = null, - scope: Scope = LocalKoinScope.current, - parameters: ParametersDefinition? = null, -): T { - return resolveViewModel( - vmClass, - stateHolder, - key, - qualifier, - scope, - parameters, - ) -} - -private fun resolveViewModel( - vmClass: KClass, - stateHolder: StateHolder, - key: String? = null, - qualifier: Qualifier? = null, - scope: Scope, - parameters: ParametersDefinition? = null, -): T { - return stateHolder.getOrPut(qualifier?.value ?: key ?: vmClass.canonicalName ?: "") { - scope.get(vmClass, qualifier, parameters) - } -} diff --git a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt index 397f15b2..5d008b66 100644 --- a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt +++ b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt @@ -7,6 +7,9 @@ import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.viewmodel.viewModelFactory import app.cash.molecule.RecompositionMode import app.cash.molecule.launchMolecule import kotlinx.coroutines.CoroutineScope @@ -16,17 +19,14 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.consumeAsFlow import moe.tlaster.precompose.reflect.canonicalName -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder import kotlin.coroutines.CoroutineContext internal expect fun providePlatformDispatcher(): CoroutineContext -@OptIn(ExperimentalStdlibApi::class) private class PresenterHolder( useImmediateClock: Boolean, body: @Composable () -> T, -) : AutoCloseable { +) : ViewModel() { private val dispatcher = providePlatformDispatcher() private val clock = if (useImmediateClock || dispatcher[MonotonicFrameClock] == null) { RecompositionMode.Immediate @@ -36,16 +36,15 @@ private class PresenterHolder( private val scope = CoroutineScope(dispatcher) val state = scope.launchMolecule(mode = clock, body = body) - override fun close() { + override fun onCleared() { scope.cancel() } } -@OptIn(ExperimentalStdlibApi::class) -private class ActionViewHolder : AutoCloseable { +private class ActionViewHolder : ViewModel() { val channel = Channel(Channel.UNLIMITED) val pair = channel to channel.consumeAsFlow() - override fun close() { + override fun onCleared() { channel.close() } } @@ -54,18 +53,15 @@ private class ActionViewHolder : AutoCloseable { private fun rememberAction( keys: List, ): Pair, Flow> { - val stateHolder = LocalStateHolder.current val key = remember(keys) { (keys.map { it.hashCode().toString() } + ActionViewHolder::class.canonicalName).joinToString() } - return stateHolder.getOrPut(key) { - ActionViewHolder() - }.pair + return viewModel>(key = key).pair } /** * Return StateFlow, use it in your Compose UI - * The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder] + * The molecule scope will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] * @param keys The keys to use to identify the Presenter * @param body The body of the molecule presenter * @return StateFlow @@ -76,18 +72,22 @@ private fun rememberPresenterState( useImmediateClock: Boolean, body: @Composable () -> T, ): StateFlow { - val stateHolder = LocalStateHolder.current val key = remember(keys) { (keys.map { it.hashCode().toString() } + PresenterHolder::class.canonicalName).joinToString() } - return stateHolder.getOrPut(key) { - PresenterHolder(useImmediateClock, body) - }.state + val factory = remember { + viewModelFactory { + addInitializer(PresenterHolder::class) { + PresenterHolder(useImmediateClock, body) + } + } + } + return viewModel>(key = key, factory = factory).state } /** * Return State, use it in your Compose UI - * The molecule scope will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder] + * The molecule scope will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] * @param keys The keys to use to identify the Presenter * @param useImmediateClock Use immediate clock or not, for text input, you should set it to true * @param body The body of the molecule presenter @@ -105,7 +105,7 @@ fun producePresenter( /** * Return pair of State and Action Channel, use it in your Compose UI - * The molecule scope and the Action Channel will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder] + * The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] * * @param keys The keys to use to identify the Presenter * @param useImmediateClock Use immediate clock or not, for text input, you should set it to true @@ -126,7 +126,7 @@ fun rememberPresenter( /** * Return pair of State and Action Channel, use it in your Compose UI - * The molecule scope and the Action Channel will be managed by the [StateHolder], so it has the same lifecycle as the [StateHolder] + * The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] * * @param body The body of the molecule presenter, the flow parameter is the flow of the action channel * @return Pair of State and Action channel diff --git a/precompose-viewmodel/build.gradle.kts b/precompose-viewmodel/build.gradle.kts deleted file mode 100644 index 1b933070..00000000 --- a/precompose-viewmodel/build.gradle.kts +++ /dev/null @@ -1,197 +0,0 @@ -import java.util.Properties - -plugins { - kotlin("multiplatform") - alias(libs.plugins.jetbrains.compose) - id("com.android.library") - id("maven-publish") - id("signing") - alias(libs.plugins.compose.compiler) -} - -group = "moe.tlaster" -version = libs.versions.libVersion.get() - -kotlin { - applyDefaultHierarchyTemplate() - androidTarget { - publishLibraryVariants("release", "debug") - } - jvm { - testRuns["test"].executionTask.configure { - useJUnitPlatform() - } - } - macosArm64() - macosX64() - // ios() - iosX64() - iosArm64() - iosSimulatorArm64() - js(IR) { - browser() - } - wasmJs { - browser() - binaries.executable() - } - sourceSets { - val commonMain by getting { - dependencies { - compileOnly(compose.foundation) - implementation(project(":precompose")) - } - } - val commonTest by getting { - dependencies { - implementation(kotlin("test-common")) - implementation(kotlin("test-annotations-common")) - // implementation(compose("org.jetbrains.compose.ui:ui-test-junit4")) - } - } - val androidMain by getting { - dependencies { - api(libs.androidx.activity.ktx) - implementation(libs.foundation) - } - } - val androidUnitTest by getting { - dependencies { - implementation(kotlin("test-junit")) - implementation(libs.junit) - } - } - val jvmMain by getting { - dependencies { - implementation(compose.foundation) - api(libs.kotlinx.coroutines.swing) - } - } - val jvmTest by getting { - dependencies { - implementation(kotlin("test-junit5")) - implementation(libs.junit.jupiter.api) - runtimeOnly(libs.junit.jupiter.engine) - } - } - val macosMain by getting { - dependencies { - implementation(compose.foundation) - } - } - val jsMain by getting { - dependencies { - implementation(compose.foundation) - } - } - val jsTest by getting { - dependencies { - implementation(kotlin("test-js")) - } - } - val iosMain by getting { - dependencies { - implementation(compose.foundation) - } - } - val wasmJsMain by getting { - dependencies { - implementation(compose.foundation) - } - } - } -} -// adding it here to make sure skiko is unpacked and available in web tests -compose.experimental { - web.application {} -} -android { - compileSdk = libs.versions.compileSdk.get().toInt() - namespace = "moe.tlaster.precompose.viewmodel" - defaultConfig { - minSdk = libs.versions.minSdk.get().toInt() - } - compileOptions { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - } -} - -extra.apply { - val publishPropFile = rootProject.file("publish.properties") - if (publishPropFile.exists()) { - Properties().apply { - load(publishPropFile.inputStream()) - }.forEach { name, value -> - if (name == "signing.secretKeyRingFile") { - set(name.toString(), rootProject.file(value.toString()).absolutePath) - } else { - set(name.toString(), value) - } - } - } else { - set("signing.keyId", System.getenv("SIGNING_KEY_ID")) - set("signing.password", System.getenv("SIGNING_PASSWORD")) - set("signing.secretKeyRingFile", System.getenv("SIGNING_SECRET_KEY_RING_FILE")) - set("ossrhUsername", System.getenv("OSSRH_USERNAME")) - set("ossrhPassword", System.getenv("OSSRH_PASSWORD")) - } -} -val javadocJar by tasks.registering(Jar::class) { - archiveClassifier.set("javadoc") -} -// https://github.com/gradle/gradle/issues/26091 -val signingTasks = tasks.withType() -tasks.withType().configureEach { - dependsOn(signingTasks) -} -publishing { - if (rootProject.file("publish.properties").exists()) { - signing { - sign(publishing.publications) - } - repositories { - maven { - val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = if (version.toString().endsWith("SNAPSHOT")) { - uri(snapshotsRepoUrl) - } else { - uri(releasesRepoUrl) - } - credentials { - username = project.ext.get("ossrhUsername").toString() - password = project.ext.get("ossrhPassword").toString() - } - } - } - } - - publications.withType { - artifact(javadocJar) - pom { - name.set("PreCompose-ViewModel") - description.set("PreCompose ViewModel intergration") - url.set("https://github.com/Tlaster/PreCompose") - - licenses { - license { - name.set("MIT") - url.set("https://opensource.org/licenses/MIT") - } - } - developers { - developer { - id.set("Tlaster") - name.set("James Tlaster") - email.set("tlaster@outlook.com") - } - } - scm { - url.set("https://github.com/Tlaster/PreCompose") - connection.set("scm:git:git://github.com/Tlaster/PreCompose.git") - developerConnection.set("scm:git:git://github.com/Tlaster/PreCompose.git") - } - } - } -} diff --git a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt b/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt deleted file mode 100644 index 0d9232c8..00000000 --- a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/CloseableCoroutineScope.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.tlaster.precompose.viewmodel - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.cancel -import kotlin.coroutines.CoroutineContext - -private const val JOB_KEY = "moe.tlaster.precompose.viewmodel.ViewModelCoroutineScope.JOB_KEY" - -/** - * [CoroutineScope] tied to this [ViewModel]. - * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called - * - * This scope is bound to - * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate] - */ -val ViewModel.viewModelScope: CoroutineScope - get() { - val scope: CoroutineScope? = getTag(JOB_KEY) - if (scope != null) { - return scope - } - return setTagIfAbsent( - JOB_KEY, - CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate), - ) - } - -@OptIn(ExperimentalStdlibApi::class) -internal class CloseableCoroutineScope(context: CoroutineContext) : AutoCloseable, CoroutineScope { - override val coroutineContext: CoroutineContext = context - - override fun close() { - coroutineContext.cancel() - } -} diff --git a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt b/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt deleted file mode 100644 index 871ddaa1..00000000 --- a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModel.kt +++ /dev/null @@ -1,61 +0,0 @@ -package moe.tlaster.precompose.viewmodel - -abstract class ViewModel : AutoCloseable { - private var disposed = false - private val bagOfTags = hashMapOf() - private val closeables = linkedSetOf() - - constructor() - constructor(vararg closeables: AutoCloseable) { - this.closeables.addAll(closeables) - } - - fun addCloseable(closeable: AutoCloseable) { - closeables.add(closeable) - } - - protected open fun onCleared() {} - - fun clear() { - disposed = true - bagOfTags.let { - for (value in it.values) { - disposeWithRuntimeException(value) - } - } - bagOfTags.clear() - closeables.let { - for (value in it) { - disposeWithRuntimeException(value) - } - } - closeables.clear() - onCleared() - } - - open fun setTagIfAbsent(key: String, newValue: T): T { - @Suppress("UNCHECKED_CAST") - return bagOfTags.getOrPut(key) { - newValue as Any - }.also { - if (disposed) { - disposeWithRuntimeException(it) - } - } as T - } - - open fun getTag(key: String): T? { - @Suppress("UNCHECKED_CAST") - return bagOfTags[key] as T? - } - - private fun disposeWithRuntimeException(obj: Any) { - if (obj is AutoCloseable) { - obj.close() - } - } - - override fun close() { - clear() - } -} diff --git a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelAdapter.kt b/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelAdapter.kt deleted file mode 100644 index 4f13d214..00000000 --- a/precompose-viewmodel/src/commonMain/kotlin/moe/tlaster/precompose/viewmodel/ViewModelAdapter.kt +++ /dev/null @@ -1,65 +0,0 @@ -package moe.tlaster.precompose.viewmodel - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import moe.tlaster.precompose.reflect.canonicalName -import moe.tlaster.precompose.stateholder.LocalSavedStateHolder -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.SavedStateHolder -import moe.tlaster.precompose.stateholder.StateHolder -import kotlin.reflect.KClass - -/** - * Returns a [ViewModel] instance that is scoped to the given [StateHolder]. - * @param modelClass The class of the ViewModel. - * @param keys A list of keys that will be used to identify the ViewModel. - * @param creator A function that will be used to create the ViewModel if it doesn't exist. - * @return A ViewModel instance. - */ -@Composable -fun viewModel( - modelClass: KClass, - keys: List = emptyList(), - creator: (SavedStateHolder) -> T, -): T { - val stateHolder = checkNotNull(LocalStateHolder.current) { - "Require LocalStateHolder not null for $modelClass" - } - val savedStateHolder = checkNotNull(LocalSavedStateHolder.current) { - "Require LocalSavedStateHolder not null" - } - return remember( - modelClass, - keys, - creator, - stateHolder, - savedStateHolder, - ) { - stateHolder.getViewModel(keys, modelClass = modelClass) { - creator(savedStateHolder) - } - } -} - -private fun StateHolder.getViewModel( - keys: List = emptyList(), - modelClass: KClass, - creator: () -> T, -): T { - val key = (keys.map { it.hashCode().toString() } + modelClass.canonicalName).joinToString() - return this.getOrPut(key) { - creator() - } -} - -/** - * Returns a [ViewModel] instance that is scoped to the given [StateHolder]. - * @param keys A list of keys that will be used to identify the ViewModel. - * @param creator A function that will be used to create the ViewModel if it doesn't exist. - * @return A ViewModel instance. - */ -@Composable -inline fun viewModel( - keys: List = emptyList(), - noinline creator: (SavedStateHolder) -> T, -): T = viewModel(T::class, keys, creator = creator) diff --git a/precompose-viewmodel/src/commonTest/kotlin/moe/tlaster/precompose/viewmodel/ViewModelTest.kt b/precompose-viewmodel/src/commonTest/kotlin/moe/tlaster/precompose/viewmodel/ViewModelTest.kt deleted file mode 100644 index 4ae985c4..00000000 --- a/precompose-viewmodel/src/commonTest/kotlin/moe/tlaster/precompose/viewmodel/ViewModelTest.kt +++ /dev/null @@ -1,53 +0,0 @@ -package moe.tlaster.precompose.viewmodel - -import kotlin.test.Test -import kotlin.test.assertEquals -import kotlin.test.assertTrue - -class ViewModelTest { - @OptIn(ExperimentalStdlibApi::class) - internal class DisposableImpl : AutoCloseable { - var wasDisposable = false - - override fun close() { - wasDisposable = true - } - } - - internal class ViewModel : moe.tlaster.precompose.viewmodel.ViewModel() - - @Test - fun testCloseableTag() { - val vm = ViewModel() - val impl = DisposableImpl() - vm.setTagIfAbsent("totally_not_coroutine_context", impl) - vm.clear() - assertTrue(impl.wasDisposable) - } - - @Test - fun testCloseableTagAlreadyClearedVM() { - val vm = ViewModel() - vm.clear() - val impl = DisposableImpl() - vm.setTagIfAbsent("key", impl) - assertTrue(impl.wasDisposable) - } - - @Test - fun testAlreadyAssociatedKey() { - val vm = ViewModel() - assertEquals("first", vm.setTagIfAbsent("key", "first")) - assertEquals("first", vm.setTagIfAbsent("key", "second")) - } - - @OptIn(ExperimentalStdlibApi::class) - @Test - fun testClosable() { - val vm = ViewModel() - val impl = DisposableImpl() - vm.addCloseable(impl) - vm.clear() - assertTrue(impl.wasDisposable) - } -} diff --git a/precompose/build.gradle.kts b/precompose/build.gradle.kts index e73e092c..64930de7 100644 --- a/precompose/build.gradle.kts +++ b/precompose/build.gradle.kts @@ -30,8 +30,8 @@ kotlin { sourceSetTree.set(KotlinSourceSetTree.test) dependencies { - androidTestImplementation("androidx.compose.ui:ui-test-junit4-android:1.7.2") - debugImplementation("androidx.compose.ui:ui-test-manifest:1.7.2") + androidTestImplementation(libs.androidx.ui.test.junit4.android) + debugImplementation(libs.androidx.ui.test.manifest) } } } @@ -55,6 +55,8 @@ kotlin { compileOnly(compose.material) api(libs.kotlinx.coroutines.core) implementation(libs.uuid) + api(libs.jetbrains.lifecycle) + api(libs.jetbrains.viewmodel) } } val commonTest by getting { diff --git a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt index 5e87f7d1..cc8ef56e 100644 --- a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt +++ b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/PreComposeApp.android.kt @@ -1,20 +1,17 @@ package moe.tlaster.precompose +import androidx.activity.BackEventCompat +import androidx.activity.OnBackPressedCallback import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember -import androidx.compose.runtime.saveable.LocalSaveableStateRegistry import androidx.lifecycle.DefaultLifecycleObserver -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.lifecycle.PreComposeViewModel -import moe.tlaster.precompose.stateholder.LocalSavedStateHolder -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.SavedStateHolder +import androidx.lifecycle.compose.LocalLifecycleOwner +import moe.tlaster.precompose.ui.BackDispatcher +import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner @Composable @@ -23,7 +20,7 @@ actual fun PreComposeApp( ) { val viewModel = androidx.lifecycle.viewmodel.compose.viewModel() - val lifecycle = androidx.lifecycle.compose.LocalLifecycleOwner.current.lifecycle + val lifecycle = LocalLifecycleOwner.current.lifecycle val onBackPressedDispatcher = checkNotNull(androidx.activity.compose.LocalOnBackPressedDispatcherOwner.current) { "No OnBackPressedDispatcherOwner was provided via LocalOnBackPressedDispatcherOwner" }.onBackPressedDispatcher @@ -34,23 +31,6 @@ actual fun PreComposeApp( super.onCreate(owner) onBackPressedDispatcher.addCallback(owner, viewModel.backPressedCallback) } - - override fun onResume(owner: androidx.lifecycle.LifecycleOwner) { - super.onResume(owner) - viewModel.lifecycleRegistry.updateState(Lifecycle.State.Active) - } - - override fun onPause(owner: androidx.lifecycle.LifecycleOwner) { - super.onPause(owner) - viewModel.lifecycleRegistry.updateState(Lifecycle.State.InActive) - } - - // override fun onDestroy(owner: androidx.lifecycle.LifecycleOwner) { - // super.onDestroy(owner) - // if (!isChangingConfigurations) { - // viewModel.lifecycleRegistry.currentState = Lifecycle.State.Destroyed - // } - // } } lifecycle.addObserver(observer) onDispose { @@ -60,23 +40,38 @@ actual fun PreComposeApp( val state by viewModel.backDispatcher.canHandleBackPress.collectAsState(false) - val saveableStateRegistry = LocalSaveableStateRegistry.current - val savedStateHolder = remember(saveableStateRegistry) { - SavedStateHolder( - "root", - saveableStateRegistry, - ) - } - LaunchedEffect(state) { viewModel.backPressedCallback.isEnabled = state } CompositionLocalProvider( - LocalLifecycleOwner provides viewModel, - LocalStateHolder provides viewModel.stateHolder, LocalBackDispatcherOwner provides viewModel, - LocalSavedStateHolder provides savedStateHolder, ) { content.invoke() } } + +internal class PreComposeViewModel : + androidx.lifecycle.ViewModel(), + BackDispatcherOwner { + override val backDispatcher by lazy { + BackDispatcher() + } + + val backPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + backDispatcher.onBackPress() + } + + override fun handleOnBackStarted(backEvent: BackEventCompat) { + backDispatcher.onBackStarted() + } + + override fun handleOnBackProgressed(backEvent: BackEventCompat) { + backDispatcher.onBackProgressed(backEvent.progress) + } + + override fun handleOnBackCancelled() { + backDispatcher.onBackCancelled() + } + } +} diff --git a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt deleted file mode 100644 index 4b894dba..00000000 --- a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeActivity.kt +++ /dev/null @@ -1,37 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import androidx.activity.ComponentActivity -import androidx.activity.compose.setContent -import androidx.compose.runtime.Composable -import androidx.compose.runtime.CompositionContext -import moe.tlaster.precompose.PreComposeApp - -@Deprecated( - message = """ - Use ComponentActivity directly instead. And make sure wrap your content with PreComposeApp. - PreComposeActivity will be removed in the future release. - For migration guide, please refer to https://github.com/Tlaster/PreCompose/releases/tag/1.5.5 - """, - replaceWith = ReplaceWith("ComponentActivity"), -) -typealias PreComposeActivity = ComponentActivity - -@Deprecated( - message = """ - Use androidx.activity.compose.setContent directly instead. And make sure wrap your content with PreComposeApp. - PreComposeActivity.setContent will be removed in the future release. - For migration guide, please refer to https://github.com/Tlaster/PreCompose/releases/tag/1.5.5 - """, - replaceWith = ReplaceWith("androidx.activity.compose.setContent"), -) -@Suppress("DEPRECATION") -fun PreComposeActivity.setContent( - parent: CompositionContext? = null, - content: @Composable () -> Unit, -) { - setContent(parent) { - PreComposeApp { - content.invoke() - } - } -} diff --git a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeViewModel.kt b/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeViewModel.kt deleted file mode 100644 index 34d0f424..00000000 --- a/precompose/src/androidMain/kotlin/moe/tlaster/precompose/lifecycle/PreComposeViewModel.kt +++ /dev/null @@ -1,47 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import androidx.activity.BackEventCompat -import androidx.activity.OnBackPressedCallback -import moe.tlaster.precompose.stateholder.StateHolder -import moe.tlaster.precompose.ui.BackDispatcher -import moe.tlaster.precompose.ui.BackDispatcherOwner - -internal class PreComposeViewModel : - androidx.lifecycle.ViewModel(), - LifecycleOwner, - BackDispatcherOwner { - val stateHolder by lazy { - StateHolder() - } - val lifecycleRegistry by lazy { - LifecycleRegistry() - } - override val lifecycle: Lifecycle - get() = lifecycleRegistry - - override val backDispatcher by lazy { - BackDispatcher() - } - - val backPressedCallback = object : OnBackPressedCallback(true) { - override fun handleOnBackPressed() { - backDispatcher.onBackPress() - } - - override fun handleOnBackStarted(backEvent: BackEventCompat) { - backDispatcher.onBackStarted() - } - - override fun handleOnBackProgressed(backEvent: BackEventCompat) { - backDispatcher.onBackProgressed(backEvent.progress) - } - - override fun handleOnBackCancelled() { - backDispatcher.onBackCancelled() - } - } - - override fun onCleared() { - lifecycleRegistry.updateState(Lifecycle.State.Destroyed) - } -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/flow/FlowExtensions.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/flow/FlowExtensions.kt deleted file mode 100644 index 32d6123d..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/flow/FlowExtensions.kt +++ /dev/null @@ -1,79 +0,0 @@ -package moe.tlaster.precompose.flow - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.produceState -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.withContext -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.lifecycle.repeatOnLifecycle -import kotlin.coroutines.CoroutineContext -import kotlin.coroutines.EmptyCoroutineContext - -/** - * Collects values from this [StateFlow] and represents its latest value via [State] in a - * lifecycle-aware manner. - */ - -@Composable -fun StateFlow.collectAsStateWithLifecycle( - context: CoroutineContext = EmptyCoroutineContext, -): State { - return collectAsStateWithLifecycle(initial = this.value, context = context) -} - -/** - * Collects values from this [Flow] and represents its latest value via [State] in a - * lifecycle-aware manner. - */ - -@Composable -fun Flow.collectAsStateWithLifecycle( - initial: R, - context: CoroutineContext = EmptyCoroutineContext, -): State { - val lifecycleOwner = checkNotNull(LocalLifecycleOwner.current) - return collectAsStateWithLifecycle( - initial = initial, - lifecycle = lifecycleOwner.lifecycle, - context = context, - ) -} - -/** - * Collects values from this [Flow] and represents its latest value via [State] in a - * lifecycle-aware manner. - */ - -@Composable -fun Flow.collectAsStateWithLifecycle( - initial: R, - lifecycle: Lifecycle, - context: CoroutineContext = EmptyCoroutineContext, -): State { - return produceState(initial, this, lifecycle, context) { - lifecycle.repeatOnLifecycle { - if (context == EmptyCoroutineContext) { - this@collectAsStateWithLifecycle.collect { this@produceState.value = it } - } else { - withContext(context) { - this@collectAsStateWithLifecycle.collect { this@produceState.value = it } - } - } - } - } -} - -fun Flow.flowWithLifecycle( - lifecycle: Lifecycle, -): Flow = callbackFlow { - lifecycle.repeatOnLifecycle { - this@flowWithLifecycle.collect { - send(it) - } - } - close() -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt deleted file mode 100644 index db5cf067..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/Lifecycle.kt +++ /dev/null @@ -1,18 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import kotlinx.coroutines.flow.StateFlow - -interface Lifecycle { - enum class State { - Initialized, - Active, - InActive, - Destroyed, - } - - val currentState: State - val currentStateFlow: StateFlow - fun removeObserver(observer: LifecycleObserver) - fun addObserver(observer: LifecycleObserver) - fun hasObserver(): Boolean -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt deleted file mode 100644 index c2117896..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleObserver.kt +++ /dev/null @@ -1,5 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -interface LifecycleObserver { - fun onStateChanged(state: Lifecycle.State) -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt deleted file mode 100644 index 10d34ff9..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleOwner.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.compositionLocalOf - -interface LifecycleOwner { - val lifecycle: Lifecycle -} - -val LocalLifecycleOwner = compositionLocalOf { noLocalProvidedFor("LocalLifecycleOwner") } - -/** - * Returns current composition local value for the owner. - * @throws IllegalStateException if no value was provided. - */ -val currentLocalLifecycleOwner: LifecycleOwner - @Composable - @ReadOnlyComposable - get() = LocalLifecycleOwner.current - -private fun noLocalProvidedFor(name: String): Nothing { - error("CompositionLocal $name not present") -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt deleted file mode 100644 index 2e10da0b..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/LifecycleRegistry.kt +++ /dev/null @@ -1,50 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.asStateFlow - -class LifecycleRegistry : Lifecycle { - private var observers: List = emptyList() - private val _currentStateFlow = MutableStateFlow(Lifecycle.State.Initialized) - override val currentStateFlow: StateFlow = _currentStateFlow.asStateFlow() - - private var _state: Lifecycle.State = _currentStateFlow.value - set(value) { - if (field == Lifecycle.State.Destroyed || value == Lifecycle.State.Initialized) { - observers = emptyList() - return - } - field = value - _currentStateFlow.value = value - dispatchState(value) - } - - override val currentState: Lifecycle.State get() = _state - - fun updateState(value: Lifecycle.State) { - _state = value - } - - private fun dispatchState(value: Lifecycle.State) { - observers.forEach { - it.onStateChanged(value) - } - } - - override fun removeObserver(observer: LifecycleObserver) { - observers -= observer - } - - override fun addObserver(observer: LifecycleObserver) { - if (observers.contains(observer)) { - return - } - observers += observer - observer.onStateChanged(currentState) - } - - override fun hasObserver(): Boolean { - return observers.isNotEmpty() - } -} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt deleted file mode 100644 index 00f97466..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/lifecycle/RepeatOnLifecycle.kt +++ /dev/null @@ -1,142 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.launch -import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock -import kotlinx.coroutines.withContext -import kotlin.coroutines.resume - -/** - * Runs the given [block] in a new coroutine when `this` [Lifecycle] is at least at [state] and - * suspends the execution until `this` [Lifecycle] is [Lifecycle.State.Destroyed]. - * - * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state. - * - * ``` - * class MyActivity : AppCompatActivity() { - * override fun onCreate(savedInstanceState: Bundle?) { - * /* ... */ - * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. - * // The coroutine will be cancelled when the ON_STOP event happens and will - * // restart executing if the lifecycle receives the ON_START event again. - * lifecycleScope.launch { - * lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) { - * uiStateFlow.collect { uiState -> - * updateUi(uiState) - * } - * } - * } - * } - * } - * ``` - * - * The best practice is to call this function when the lifecycle is initialized. For - * example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise, multiple - * repeating coroutines doing the same could be created and be executed at the same time. - * - * Repeated invocations of `block` will run serially, that is they will always wait for the - * previous invocation to fully finish before re-starting execution as the state moves in and out - * of the required state. - * - * Warning: [Lifecycle.State.Initialized] is not allowed in this API. Passing it as a - * parameter will throw an [IllegalArgumentException]. - * - * @param state [Lifecycle.State] in which `block` runs in a new coroutine. That coroutine - * will cancel if the lifecycle falls below that state, and will restart if it's in that state - * again. - */ -suspend fun Lifecycle.repeatOnLifecycle( - block: suspend CoroutineScope.() -> Unit, -) { - if (currentState === Lifecycle.State.Destroyed) { - return - } - - // This scope is required to preserve context before we move to Dispatchers.Main - coroutineScope { - withContext(Dispatchers.Main.immediate) { - // Check the current state of the lifecycle as the previous check is not guaranteed - // to be done on the main thread. - if (currentState === Lifecycle.State.Destroyed) return@withContext - - // Instance of the running repeating coroutine - var launchedJob: Job? = null - - // Registered observer - var observer: LifecycleObserver? = null - try { - // Suspend the coroutine until the lifecycle is destroyed or - // the coroutine is cancelled - suspendCancellableCoroutine { cont -> - // Lifecycle observers that executes `block` when the lifecycle reaches certain state, and - // cancels when it falls below that state. - val mutex = Mutex() - observer = object : LifecycleObserver { - override fun onStateChanged(state: Lifecycle.State) { - when (state) { - Lifecycle.State.Initialized -> Unit - Lifecycle.State.Active -> { - launchedJob = this@coroutineScope.launch { - // Mutex makes invocations run serially, - // coroutineScope ensures all child coroutines finish - mutex.withLock { - coroutineScope { - block() - } - } - } - } - Lifecycle.State.InActive -> { - launchedJob?.cancel() - launchedJob = null - } - Lifecycle.State.Destroyed -> { - cont.resume(Unit) - } - } - } - } - this@repeatOnLifecycle.addObserver(observer as LifecycleObserver) - } - } finally { - launchedJob?.cancel() - observer?.let { - this@repeatOnLifecycle.removeObserver(it) - } - } - } - } -} - -/** - * [LifecycleOwner]'s extension function for [Lifecycle.repeatOnLifecycle] to allow an easier - * call to the API from LifecycleOwners such as Activities and Fragments. - * - * ``` - * class MyActivity : AppCompatActivity() { - * override fun onCreate(savedInstanceState: Bundle?) { - * /* ... */ - * // Runs the block of code in a coroutine when the lifecycle is at least STARTED. - * // The coroutine will be cancelled when the ON_STOP event happens and will - * // restart executing if the lifecycle receives the ON_START event again. - * lifecycleScope.launch { - * repeatOnLifecycle(Lifecycle.State.STARTED) { - * uiStateFlow.collect { uiState -> - * updateUi(uiState) - * } - * } - * } - * } - * } - * ``` - * - * @see Lifecycle.repeatOnLifecycle - */ -suspend fun LifecycleOwner.repeatOnLifecycle( - block: suspend CoroutineScope.() -> Unit, -): Unit = lifecycle.repeatOnLifecycle(block) diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt index ce31d18c..743f736d 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackHandler.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberUpdatedState +import androidx.lifecycle.compose.LocalLifecycleOwner import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.BufferOverflow @@ -15,7 +16,6 @@ import kotlinx.coroutines.channels.Channel.Factory.BUFFERED import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.launch -import moe.tlaster.precompose.lifecycle.currentLocalLifecycleOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackHandler import moe.tlaster.precompose.ui.DefaultBackHandler @@ -37,7 +37,7 @@ fun BackHandler(enabled: Boolean = true, onBack: () -> Unit) { } backCallback.isEnabled = enabled } - val lifecycleOwner = currentLocalLifecycleOwner + val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner, backDispatcher) { // Add callback to the backDispatcher backDispatcher.register(backCallback) @@ -140,7 +140,7 @@ fun PredictiveBackHandler( } backCallback.isEnabled = enabled } - val lifecycleOwner = currentLocalLifecycleOwner + val lifecycleOwner = LocalLifecycleOwner.current DisposableEffect(lifecycleOwner, backDispatcher) { backDispatcher.register(backCallback) diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt index 9fd55e1c..e1cddb8d 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackEntry.kt @@ -1,32 +1,27 @@ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.ViewModelStoreOwner import moe.tlaster.precompose.navigation.route.GroupRoute import moe.tlaster.precompose.navigation.route.Route import moe.tlaster.precompose.navigation.route.toSceneRoute import moe.tlaster.precompose.navigation.transition.NavTransition -import moe.tlaster.precompose.stateholder.SavedStateHolder -import moe.tlaster.precompose.stateholder.StateHolder class BackStackEntry internal constructor( internal val stateId: String, internal var routeInternal: Route, val path: String, val pathMap: Map, - private val parentStateHolder: StateHolder, - parentSavedStateHolder: SavedStateHolder, + private val provider: ViewModelStoreProvider, val queryString: QueryString? = null, -) : LifecycleOwner { +) : LifecycleOwner, + ViewModelStoreOwner { val route: Route get() = routeInternal internal var uiClosable: UiClosable? = null private var _destroyAfterTransition = false - val stateHolder: StateHolder = parentStateHolder.getOrPut(stateId) { - StateHolder() - } - val savedStateHolder: SavedStateHolder = parentSavedStateHolder.child(stateId) internal val swipeProperties: SwipeProperties? get() = route.toSceneRoute()?.swipeProperties @@ -34,25 +29,29 @@ class BackStackEntry internal constructor( get() = route.toSceneRoute()?.navTransition private val lifecycleRegistry by lazy { - LifecycleRegistry() + LifecycleRegistry(this) } override val lifecycle: Lifecycle get() = lifecycleRegistry + init { + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE) + } + fun active() { - lifecycleRegistry.updateState(Lifecycle.State.Active) + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) } fun inActive() { - lifecycleRegistry.updateState(Lifecycle.State.InActive) + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP) if (_destroyAfterTransition) { destroy() } } fun destroy() { - if (lifecycleRegistry.currentState != Lifecycle.State.InActive) { + if (lifecycleRegistry.currentState.isAtLeast(Lifecycle.State.STARTED)) { _destroyAfterTransition = true } else { destroyDirectly() @@ -60,16 +59,29 @@ class BackStackEntry internal constructor( } internal fun destroyDirectly() { - lifecycleRegistry.updateState(Lifecycle.State.Destroyed) - stateHolder.close() - parentStateHolder.remove(stateId) - savedStateHolder.close() + lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) + provider.clear(stateId) uiClosable?.close(stateId) } fun hasRoute(route: String): Boolean { return this.route.route == route || (this.route as? GroupRoute)?.hasRoute(route) == true } + + fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_DESTROY -> { + destroy() + } + else -> { + lifecycleRegistry.handleLifecycleEvent(event) + } + } + } + + override val viewModelStore by lazy { + provider.getViewModelStore(stateId) + } } internal fun BackStackEntry.hasRoute(route: String, path: String, includePath: Boolean): Boolean { diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackManager.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackManager.kt index ab3affeb..da9d994d 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackManager.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/BackStackManager.kt @@ -1,28 +1,26 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Stable +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModelStoreOwner import com.benasher44.uuid.uuid4 import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.map -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleObserver -import moe.tlaster.precompose.lifecycle.LifecycleOwner import moe.tlaster.precompose.navigation.route.SceneRoute import moe.tlaster.precompose.navigation.route.isFloatingRoute import moe.tlaster.precompose.navigation.route.isSceneRoute -import moe.tlaster.precompose.stateholder.SavedStateHolder -import moe.tlaster.precompose.stateholder.StateHolder import kotlin.coroutines.Continuation import kotlin.coroutines.resume import kotlin.coroutines.suspendCoroutine import kotlin.math.max @Stable -internal class BackStackManager : LifecycleObserver { - private lateinit var _stateHolder: StateHolder - private lateinit var _savedStateHolder: SavedStateHolder +internal class BackStackManager : LifecycleEventObserver { + private lateinit var _navControllerViewModel: NavControllerViewModel // internal for testing internal val backStacks = MutableStateFlow(listOf()) @@ -74,12 +72,10 @@ internal class BackStackManager : LifecycleObserver { get() = backStacks.asSharedFlow().map { it.lastOrNull { it.route.isFloatingRoute() } } fun init( - stateHolder: StateHolder, - savedStateHolder: SavedStateHolder, lifecycleOwner: LifecycleOwner, + viewModelStoreOwner: ViewModelStoreOwner, ) { - _stateHolder = stateHolder - _savedStateHolder = savedStateHolder + _navControllerViewModel = NavControllerViewModel.getInstance(viewModelStoreOwner.viewModelStore) lifecycleOwner.lifecycle.addObserver(this) } @@ -131,8 +127,7 @@ internal class BackStackManager : LifecycleObserver { QueryString(it) }, path = path, - parentStateHolder = _stateHolder, - parentSavedStateHolder = _savedStateHolder, + provider = _navControllerViewModel, ) } @@ -216,25 +211,18 @@ internal class BackStackManager : LifecycleObserver { } } - override fun onStateChanged(state: Lifecycle.State) { - when (state) { - Lifecycle.State.Initialized -> Unit - Lifecycle.State.Active -> { - val currentEntry = backStacks.value.lastOrNull() - currentEntry?.active() - } - - Lifecycle.State.InActive -> { - val currentEntry = backStacks.value.lastOrNull() - currentEntry?.inActive() - } - - Lifecycle.State.Destroyed -> { + override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) { + when (event) { + Lifecycle.Event.ON_DESTROY -> { backStacks.value.forEach { it.destroy() } backStacks.value = emptyList() } + else -> { + val currentEntry = backStacks.value.lastOrNull() + currentEntry?.onStateChanged(source, event) + } } } diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt new file mode 100644 index 00000000..ab03a71c --- /dev/null +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavControllerViewModel.kt @@ -0,0 +1,52 @@ +package moe.tlaster.precompose.navigation + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.get +import androidx.lifecycle.viewmodel.CreationExtras +import kotlin.reflect.KClass + +internal class NavControllerViewModel : ViewModel(), ViewModelStoreProvider { + private val viewModelStores = mutableMapOf() + + override fun clear(backStackEntryId: String) { + // Clear and remove the NavGraph's ViewModelStore + val viewModelStore = viewModelStores.remove(backStackEntryId) + viewModelStore?.clear() + } + + override fun onCleared() { + for (store in viewModelStores.values) { + store.clear() + } + viewModelStores.clear() + } + + override fun getViewModelStore(backStackEntryId: String): ViewModelStore { + var viewModelStore = viewModelStores[backStackEntryId] + if (viewModelStore == null) { + viewModelStore = ViewModelStore() + viewModelStores[backStackEntryId] = viewModelStore + } + return viewModelStore + } + + companion object { + private val FACTORY: ViewModelProvider.Factory = + object : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create( + modelClass: KClass, + extras: CreationExtras, + ): T { + return NavControllerViewModel() as T + } + } + + fun getInstance(viewModelStore: ViewModelStore): NavControllerViewModel { + val viewModelProvider = ViewModelProvider.create(viewModelStore, FACTORY) + return viewModelProvider.get() + } + } +} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt index 1e4033f3..4f1d3106 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/NavHost.kt @@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.ContentTransform -import androidx.compose.animation.core.ExperimentalTransitionApi import androidx.compose.animation.core.SeekableTransitionState import androidx.compose.animation.core.rememberTransition import androidx.compose.animation.core.tween @@ -36,17 +35,13 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.LayoutDirection +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import kotlinx.coroutines.CancellationException -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.lifecycle.currentLocalLifecycleOwner import moe.tlaster.precompose.navigation.route.ComposeRoute import moe.tlaster.precompose.navigation.route.FloatingRoute import moe.tlaster.precompose.navigation.route.GroupRoute import moe.tlaster.precompose.navigation.transition.NavTransition -import moe.tlaster.precompose.stateholder.LocalSavedStateHolder -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.currentLocalSavedStateHolder -import moe.tlaster.precompose.stateholder.currentLocalStateHolder /** * Provides in place in the Compose hierarchy for self-contained navigation to occur. @@ -64,7 +59,6 @@ import moe.tlaster.precompose.stateholder.currentLocalStateHolder * @param builder the builder used to construct the graph */ @OptIn( - ExperimentalTransitionApi::class, ExperimentalFoundationApi::class, ) @Composable @@ -76,16 +70,16 @@ fun NavHost( swipeProperties: SwipeProperties? = null, builder: RouteBuilder.() -> Unit, ) { - val lifecycleOwner = currentLocalLifecycleOwner - val stateHolder = currentLocalStateHolder - val savedStateHolder = currentLocalSavedStateHolder + val lifecycleOwner = LocalLifecycleOwner.current val composeStateHolder = rememberSaveableStateHolder() + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { + "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" + } // true for assuming that lifecycleOwner, stateHolder and composeStateHolder are not changing during the lifetime of the NavHost LaunchedEffect(true) { navigator.init( - stateHolder = stateHolder, - savedStateHolder = savedStateHolder, + viewModelStoreOwner = viewModelStoreOwner, lifecycleOwner = lifecycleOwner, ) } @@ -278,9 +272,8 @@ private fun AnimatedContentScope.NavHostContent( ) { stateHolder.SaveableStateProvider(entry.stateId) { CompositionLocalProvider( - LocalStateHolder provides entry.stateHolder, - LocalSavedStateHolder provides entry.savedStateHolder, LocalLifecycleOwner provides entry, + LocalViewModelStoreOwner provides entry, content = { entry.ComposeContent(this@NavHostContent) }, diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index b6595d7a..584bec81 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -1,11 +1,11 @@ package moe.tlaster.precompose.navigation import androidx.compose.runtime.Composable +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.flow.map -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.stateholder.SavedStateHolder -import moe.tlaster.precompose.stateholder.StateHolder -import moe.tlaster.precompose.stateholder.currentLocalStateHolder /** * Creates or returns an existing [Navigator] that controls the [NavHost]. @@ -13,9 +13,13 @@ import moe.tlaster.precompose.stateholder.currentLocalStateHolder * @return Returns an instance of Navigator. */ @Composable -fun rememberNavigator(name: String = ""): Navigator { - val stateHolder = currentLocalStateHolder - return stateHolder.getOrPut("${name}Navigator") { +fun rememberNavigator(key: String? = null): Navigator { + val viewModel = viewModel(key = key) + return viewModel.navigator +} + +internal class NavigatorViewModel : ViewModel() { + val navigator by lazy { Navigator() } } @@ -28,23 +32,20 @@ class Navigator { /** * Initializes the navigator with a set parameters. - * @param stateHolder: stateHolder object - * @param savedStateHolder: savedStateHolder object * @param lifecycleOwner: lifecycleOwner object + * @param viewModelStoreOwner: viewModelStoreOwner object */ internal fun init( - stateHolder: StateHolder, - savedStateHolder: SavedStateHolder, lifecycleOwner: LifecycleOwner, + viewModelStoreOwner: ViewModelStoreOwner, ) { if (_initialized) { return } _initialized = true stackManager.init( - stateHolder = stateHolder, - savedStateHolder = savedStateHolder, lifecycleOwner = lifecycleOwner, + viewModelStoreOwner = viewModelStoreOwner, ) } diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/ViewModelStoreProvider.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/ViewModelStoreProvider.kt new file mode 100644 index 00000000..44ecad31 --- /dev/null +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/ViewModelStoreProvider.kt @@ -0,0 +1,8 @@ +package moe.tlaster.precompose.navigation + +import androidx.lifecycle.ViewModelStore + +interface ViewModelStoreProvider { + fun getViewModelStore(backStackEntryId: String): ViewModelStore + fun clear(backStackEntryId: String) +} diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/SavedStateHolder.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/SavedStateHolder.kt deleted file mode 100644 index 8e35297f..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/SavedStateHolder.kt +++ /dev/null @@ -1,51 +0,0 @@ -package moe.tlaster.precompose.stateholder - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.compositionLocalOf -import androidx.compose.runtime.saveable.SaveableStateRegistry - -/** - * Allows components to save and restore their state using the saved instance state mechanism, - * which can be useful in some platforms such as Android. - * - * @param key The key used scope the state in the provided [saveableStateRegistry]. - * @param saveableStateRegistry The parent [SaveableStateRegistry] to use for saving and restoring - */ -@OptIn(ExperimentalStdlibApi::class) -@Suppress("UNCHECKED_CAST") -class SavedStateHolder( - private val key: String, - private val saveableStateRegistry: SaveableStateRegistry?, -) : SaveableStateRegistry by SaveableStateRegistry( - saveableStateRegistry?.consumeRestored(key) as? Map>, - { saveableStateRegistry?.canBeSaved(it) ?: true }, -), - AutoCloseable { - private val registryEntry = saveableStateRegistry?.registerProvider(key) { - performSave() - } - - fun child(key: String): SavedStateHolder { - return SavedStateHolder(key, this) - } - - override fun close() { - registryEntry?.unregister() - } -} - -val LocalSavedStateHolder = compositionLocalOf { - // A default implementation for platforms that don't offer a [SaveableStateRegistry] - SavedStateHolder("root", null) -} - -/** - * Returns the current [SavedStateHolder] from composition or throws an [IllegalStateException] - * if there is no [SavedStateHolder] in provided. - */ - -val currentLocalSavedStateHolder: SavedStateHolder - @Composable - @ReadOnlyComposable - get() = LocalSavedStateHolder.current diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/StateHolder.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/StateHolder.kt deleted file mode 100644 index 5ea28d1e..00000000 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/stateholder/StateHolder.kt +++ /dev/null @@ -1,57 +0,0 @@ -package moe.tlaster.precompose.stateholder - -import androidx.compose.runtime.Composable -import androidx.compose.runtime.ReadOnlyComposable -import androidx.compose.runtime.compositionLocalOf - -@OptIn(ExperimentalStdlibApi::class) -class StateHolder : AutoCloseable { - private val _states = mutableMapOf() - - operator fun set(key: String, value: T) { - _states[key] = value - } - - fun getOrPut(key: String, defaultValue: () -> T): T { - @Suppress("UNCHECKED_CAST") - return _states.getOrPut(key) { - defaultValue() as Any - } as T - } - - fun remove(key: String) { - _states.remove(key) - } - - operator fun get(key: String): T? { - @Suppress("UNCHECKED_CAST") - return _states[key] as T? - } - - override fun close() { - for (value in _states.values) { - if (value is AutoCloseable) { - value.close() - } - } - _states.clear() - } - - // for testing - internal fun contains(key: String): Boolean { - return _states.containsKey(key) - } -} - -val LocalStateHolder = compositionLocalOf { - error("No StateHolder provided") -} - -/** - * Returns the current [StateHolder] from composition or throws an [IllegalStateException] - * if there is no [StateHolder] in provided. - */ -val currentLocalStateHolder: StateHolder - @Composable - @ReadOnlyComposable - get() = LocalStateHolder.current diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/LifecycleTest.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/LifecycleTest.kt deleted file mode 100644 index 36b8abf8..00000000 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/LifecycleTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -import kotlin.test.Test -import kotlin.test.assertEquals - -class LifecycleTest { - @Test - fun lifeCycleTest() { - val lifecycleOwner = TestLifecycleOwner() - assertEquals(Lifecycle.State.Initialized, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Active) - assertEquals(Lifecycle.State.Active, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Initialized) - assertEquals(Lifecycle.State.Active, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.InActive) - assertEquals(Lifecycle.State.InActive, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Active) - assertEquals(Lifecycle.State.Active, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Destroyed) - assertEquals(Lifecycle.State.Destroyed, lifecycleOwner.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Active) - assertEquals(Lifecycle.State.Destroyed, lifecycleOwner.lifecycle.currentState) - } -} diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/TestLifecycleOwner.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/TestLifecycleOwner.kt deleted file mode 100644 index 138dba9b..00000000 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/lifecycle/TestLifecycleOwner.kt +++ /dev/null @@ -1,7 +0,0 @@ -package moe.tlaster.precompose.lifecycle - -class TestLifecycleOwner : LifecycleOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } -} diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt index f4d51b6d..e1711d3f 100644 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt +++ b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt @@ -1,86 +1,93 @@ package moe.tlaster.precompose.navigation +import androidx.lifecycle.Lifecycle import com.benasher44.uuid.uuid4 -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.stateholder.StateHolder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.withContext import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +fun runMainTest(block: () -> Unit) = runTest { + withContext(Dispatchers.Main.immediate) { + block() + } +} + class BackStackEntryTest { @Test - fun testActive() { - val parentStateHolder = StateHolder() + fun testActive() = runMainTest { + val parentStateHolder = TestViewModelStoreProvider() val entry = BackStackEntry( uuid4().toString(), TestRoute("foo/bar", "foo/bar"), "foo/bar", emptyMap(), parentStateHolder, - TestSavedStateHolder(), ) + entry.viewModelStore assertTrue(parentStateHolder.contains(entry.stateId)) - assertEquals(Lifecycle.State.Initialized, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.CREATED, entry.lifecycle.currentState) entry.active() - assertEquals(Lifecycle.State.Active, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.RESUMED, entry.lifecycle.currentState) } @Test - fun testInActive() { - val parentStateHolder = StateHolder() + fun testInActive() = runMainTest { + val parentStateHolder = TestViewModelStoreProvider() val entry = BackStackEntry( uuid4().toString(), TestRoute("foo/bar", "foo/bar"), "foo/bar", emptyMap(), parentStateHolder, - TestSavedStateHolder(), ) + entry.viewModelStore entry.active() - assertEquals(Lifecycle.State.Active, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.RESUMED, entry.lifecycle.currentState) entry.inActive() - assertEquals(Lifecycle.State.InActive, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.CREATED, entry.lifecycle.currentState) assertTrue(parentStateHolder.contains(entry.stateId)) } @Test - fun testDestroy() { - val parentStateHolder = StateHolder() + fun testDestroy() = runMainTest { + val parentStateHolder = TestViewModelStoreProvider() val entry = BackStackEntry( uuid4().toString(), TestRoute("foo/bar", "foo/bar"), "foo/bar", emptyMap(), parentStateHolder, - TestSavedStateHolder(), ) entry.active() - assertEquals(Lifecycle.State.Active, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.RESUMED, entry.lifecycle.currentState) entry.inActive() - assertEquals(Lifecycle.State.InActive, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.CREATED, entry.lifecycle.currentState) entry.destroy() - assertEquals(Lifecycle.State.Destroyed, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.DESTROYED, entry.lifecycle.currentState) assertFalse(parentStateHolder.contains(entry.stateId)) } @Test - fun testDestroyAfterTransition() { - val parentStateHolder = StateHolder() + fun testDestroyAfterTransition() = runMainTest { + val parentStateHolder = TestViewModelStoreProvider() val entry = BackStackEntry( uuid4().toString(), TestRoute("foo/bar", "foo/bar"), "foo/bar", emptyMap(), parentStateHolder, - TestSavedStateHolder(), ) + entry.viewModelStore entry.active() entry.destroy() - assertEquals(Lifecycle.State.Active, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.RESUMED, entry.lifecycle.currentState) assertTrue(parentStateHolder.contains(entry.stateId)) entry.inActive() - assertEquals(Lifecycle.State.Destroyed, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.DESTROYED, entry.lifecycle.currentState) assertFalse(parentStateHolder.contains(entry.stateId)) } } diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt index 39eb12c0..1529ffe3 100644 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt +++ b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt @@ -1,9 +1,7 @@ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.TestLifecycleOwner +import androidx.lifecycle.Lifecycle import moe.tlaster.precompose.navigation.route.GroupRoute -import moe.tlaster.precompose.stateholder.StateHolder import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertIs @@ -11,12 +9,11 @@ import kotlin.test.assertNotEquals class BackStackManagerTest { @Test - fun testInitialRoute() { + fun testInitialRoute() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( RouteGraph( @@ -36,12 +33,11 @@ class BackStackManagerTest { } @Test - fun testPush() { + fun testPush() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( RouteGraph( @@ -63,12 +59,11 @@ class BackStackManagerTest { } @Test - fun testPop() { + fun testPop() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( RouteGraph( @@ -92,12 +87,11 @@ class BackStackManagerTest { } @Test - fun testLaunchSingleTop() { + fun testLaunchSingleTop() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -128,12 +122,11 @@ class BackStackManagerTest { } @Test - fun testLaunchSingleTopWithIncludePath() { + fun testLaunchSingleTopWithIncludePath() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -161,12 +154,11 @@ class BackStackManagerTest { } @Test - fun testPopUpTo() { + fun testPopUpTo() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -194,12 +186,11 @@ class BackStackManagerTest { } @Test - fun testPopUpToWithInclusiveLast() { + fun testPopUpToWithInclusiveLast() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -227,12 +218,11 @@ class BackStackManagerTest { } @Test - fun testPopUpToWithInclusive() { + fun testPopUpToWithInclusive() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -261,12 +251,11 @@ class BackStackManagerTest { } @Test - fun testLaunchSingleTopWithPopUpToWithInclusive() { + fun testLaunchSingleTopWithPopUpToWithInclusive() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -293,12 +282,11 @@ class BackStackManagerTest { } @Test - fun testMultipleLaunchSingleTopWithPopUpTo() { + fun testMultipleLaunchSingleTopWithPopUpTo() = runMainTest { val manager = BackStackManager() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( RouteGraph( @@ -310,7 +298,7 @@ class BackStackManagerTest { ), ) - fun navigate(path: String, navOptions: NavOptions) { + fun navigate(path: String, navOptions: NavOptions) = runMainTest { val previousEntry = manager.backStacks.value.lastOrNull() manager.push(path, navOptions) // Mark the previous entry as inactive to simulate the lifecycle change by the NavHost @@ -328,13 +316,12 @@ class BackStackManagerTest { } @Test - fun testLifecycle() { + fun testLifecycle() = runMainTest { val manager = BackStackManager() val lifecycleOwner = TestLifecycleOwner() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), lifecycleOwner = lifecycleOwner, + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -347,23 +334,22 @@ class BackStackManagerTest { ), ) val entry = manager.backStacks.value[0] - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Active) - assertEquals(Lifecycle.State.Active, entry.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.InActive) - assertEquals(Lifecycle.State.InActive, entry.lifecycle.currentState) - lifecycleOwner.lifecycle.updateState(Lifecycle.State.Destroyed) + lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_RESUME) + assertEquals(Lifecycle.State.RESUMED, entry.lifecycle.currentState) + lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP) + assertEquals(Lifecycle.State.CREATED, entry.lifecycle.currentState) + lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) assertEquals(0, manager.backStacks.value.size) - assertEquals(Lifecycle.State.Destroyed, entry.lifecycle.currentState) + assertEquals(Lifecycle.State.DESTROYED, entry.lifecycle.currentState) } @Test - fun testGroupNavigation() { + fun testGroupNavigation() = runMainTest { val manager = BackStackManager() val lifecycleOwner = TestLifecycleOwner() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteBuilder("/home").apply { @@ -383,13 +369,12 @@ class BackStackManagerTest { } @Test - fun testNestedGroupNavigation() { + fun testNestedGroupNavigation() = runMainTest { val manager = BackStackManager() val lifecycleOwner = TestLifecycleOwner() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteBuilder("/home").apply { @@ -420,13 +405,12 @@ class BackStackManagerTest { } @Test - fun testGroupNavigationWithPopUpTo() { + fun testGroupNavigationWithPopUpTo() = runMainTest { val manager = BackStackManager() val lifecycleOwner = TestLifecycleOwner() manager.init( - stateHolder = StateHolder(), - savedStateHolder = TestSavedStateHolder(), - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteBuilder("/home").apply { @@ -453,15 +437,11 @@ class BackStackManagerTest { * #146 */ @Test - fun testNavigateWithPopupToWithDuplicateScene() { + fun testNavigateWithPopupToWithDuplicateScene() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() - manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -473,7 +453,7 @@ class BackStackManagerTest { ), ) - fun navigate(path: String, navOptions: NavOptions) { + fun navigate(path: String, navOptions: NavOptions) = runMainTest { val previousEntry = manager.backStacks.value.lastOrNull() manager.push(path, navOptions) // Mark the previous entry as inactive to simulate the lifecycle change by the NavHost @@ -496,15 +476,11 @@ class BackStackManagerTest { } @Test - fun testGoBackTwiceImmediately() { + fun testGoBackTwiceImmediately() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() - manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -534,15 +510,11 @@ class BackStackManagerTest { } @Test - fun testGoBackWithPopupTo() { + fun testGoBackWithPopupTo() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() - manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -571,15 +543,11 @@ class BackStackManagerTest { } @Test - fun testGoBackWithPopupToInclusive() { + fun testGoBackWithPopupToInclusive() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() - manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -608,15 +576,11 @@ class BackStackManagerTest { } @Test - fun testRouteGraphUpdateWithSameRoute() { + fun testRouteGraphUpdateWithSameRoute() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() - manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -654,15 +618,12 @@ class BackStackManagerTest { } @Test - fun testRouteGraphUpdateWithDifferentRoute() { + fun testRouteGraphUpdateWithDifferentRoute() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -700,14 +661,11 @@ class BackStackManagerTest { } @Test - fun testStateId() { + fun testStateId() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( @@ -730,14 +688,11 @@ class BackStackManagerTest { } @Test - fun testStateIdWithoutDestroy() { + fun testStateIdWithoutDestroy() = runMainTest { val manager = BackStackManager() - val lifecycleOwner = TestLifecycleOwner() - val saveableStateHolder = TestSavedStateHolder() manager.init( - stateHolder = StateHolder(), - savedStateHolder = saveableStateHolder, - lifecycleOwner = lifecycleOwner, + lifecycleOwner = TestLifecycleOwner(), + viewModelStoreOwner = TestViewModelStoreOwner(), ) manager.setRouteGraph( routeGraph = RouteGraph( diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt index b1a1c582..6640f3af 100644 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt +++ b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt @@ -1,17 +1,15 @@ package moe.tlaster.precompose.navigation -import moe.tlaster.precompose.lifecycle.TestLifecycleOwner -import moe.tlaster.precompose.stateholder.StateHolder import kotlin.test.Test import kotlin.test.assertEquals + class NavigatorTest { @Test - fun testNavigate() { + fun testNavigate() = runMainTest { val navigator = Navigator() navigator.init( - StateHolder(), - TestSavedStateHolder(), TestLifecycleOwner(), + TestViewModelStoreOwner(), ) navigator.setRouteGraph( RouteGraph( @@ -31,14 +29,13 @@ class NavigatorTest { } @Test - fun testPendingNavigate() { + fun testPendingNavigate() = runMainTest { val navigator = Navigator() navigator.navigate("foo/bar/1") assertEquals(0, navigator.stackManager.backStacks.value.size) navigator.init( - StateHolder(), - TestSavedStateHolder(), TestLifecycleOwner(), + TestViewModelStoreOwner(), ) navigator.setRouteGraph( RouteGraph( diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/SeavableStateRegistry.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/SeavableStateRegistry.kt deleted file mode 100644 index 5fe24c09..00000000 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/SeavableStateRegistry.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.tlaster.precompose.navigation - -import androidx.compose.runtime.saveable.SaveableStateRegistry -import moe.tlaster.precompose.stateholder.SavedStateHolder - -@Suppress("TestFunctionName") -internal fun TestSavedStateHolder( - restored: Map>? = null, -) = SavedStateHolder( - key = "key", - saveableStateRegistry = SaveableStateRegistry( - restoredValues = mapOf("key" to listOf(restored)), - canBeSaved = { true }, - ), -) diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt new file mode 100644 index 00000000..c12e1e44 --- /dev/null +++ b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt @@ -0,0 +1,20 @@ +package moe.tlaster.precompose.navigation + +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.LifecycleRegistry +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner + +class TestViewModelStoreOwner : ViewModelStoreOwner { + override val viewModelStore: ViewModelStore + get() = ViewModelStore() +} + +class TestLifecycleOwner : LifecycleOwner { + private val registry by lazy { + LifecycleRegistry(this) + } + override val lifecycle by lazy { + registry + } +} diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt new file mode 100644 index 00000000..7fa8c692 --- /dev/null +++ b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt @@ -0,0 +1,19 @@ +package moe.tlaster.precompose.navigation + +import androidx.lifecycle.ViewModelStore + +class TestViewModelStoreProvider : ViewModelStoreProvider { + private val viewModelStoreMap = mutableMapOf() + + override fun getViewModelStore(backStackEntryId: String): ViewModelStore { + return viewModelStoreMap.getOrPut(backStackEntryId) { ViewModelStore() } + } + + override fun clear(backStackEntryId: String) { + viewModelStoreMap.remove(backStackEntryId) + } + + fun contains(backStackEntryId: String): Boolean { + return viewModelStoreMap.containsKey(backStackEntryId) + } +} diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/stateholder/StateHolderTest.kt b/precompose/src/commonTest/kotlin/moe/tlaster/precompose/stateholder/StateHolderTest.kt deleted file mode 100644 index ed275101..00000000 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/stateholder/StateHolderTest.kt +++ /dev/null @@ -1,34 +0,0 @@ -package moe.tlaster.precompose.stateholder - -import kotlin.test.Test -import kotlin.test.assertFalse -import kotlin.test.assertNull -import kotlin.test.assertTrue - -class StateHolderTest { - @Test - fun testClear() { - val store = StateHolder() - val viewModel1 = TestObject() - val viewModel2 = TestObject() - val mockViewModel = TestObject() - store.set("a", viewModel1) - store.set("b", viewModel2) - store.set("mock", mockViewModel) - assertFalse(viewModel1.cleared) - assertFalse(viewModel2.cleared) - store.close() - assertTrue(viewModel1.cleared) - assertTrue(viewModel2.cleared) - assertNull(store["a"]) - assertNull(store["b"]) - } - - @OptIn(ExperimentalStdlibApi::class) - internal class TestObject() : AutoCloseable { - var cleared = false - override fun close() { - cleared = true - } - } -} diff --git a/precompose/src/iosMain/kotlin/moe/tlaster/precompose/PreComposeApplication.kt b/precompose/src/iosMain/kotlin/moe/tlaster/precompose/PreComposeApplication.kt index 800455a0..90fe30e4 100644 --- a/precompose/src/iosMain/kotlin/moe/tlaster/precompose/PreComposeApplication.kt +++ b/precompose/src/iosMain/kotlin/moe/tlaster/precompose/PreComposeApplication.kt @@ -2,129 +2,40 @@ package moe.tlaster.precompose import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember -import androidx.compose.ui.uikit.ComposeUIViewControllerConfiguration -import androidx.compose.ui.window.ComposeUIViewController -import kotlinx.cinterop.BetaInteropApi -import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.ObjCAction -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner -import platform.Foundation.NSNotification -import platform.Foundation.NSNotificationCenter -import platform.Foundation.NSSelectorFromString -import platform.UIKit.UIApplicationDidEnterBackgroundNotification -import platform.UIKit.UIApplicationWillEnterForegroundNotification -import platform.UIKit.UIApplicationWillTerminateNotification -import platform.UIKit.UIViewController -import platform.darwin.NSObject - -@Suppress("FunctionName") -@Deprecated( - message = """ - Use ComposeUIViewController directly instead. And make sure wrap your content with PreComposeApp. - PreComposeApplication will be removed in the future release. - For migration guide, please refer to https://github.com/Tlaster/PreCompose/releases/tag/1.5.5 - """, - replaceWith = ReplaceWith("ComposeUIViewController"), -) -fun PreComposeApplication( - configure: ComposeUIViewControllerConfiguration.() -> Unit = {}, - content: @Composable () -> Unit, -): UIViewController { - return ComposeUIViewController(configure) { - PreComposeApp { - content.invoke() - } - } -} @Composable actual fun PreComposeApp( content: @Composable () -> Unit, ) { - ProvidePreComposeCompositionLocals { - content.invoke() - } -} - -@Composable -fun ProvidePreComposeCompositionLocals( - holder: PreComposeWindowHolder = remember { + val holder = remember { PreComposeWindowHolder() - }, - content: @Composable () -> Unit, -) { + } + DisposableEffect(holder) { + onDispose { + holder.viewModelStore.clear() + } + } CompositionLocalProvider( - LocalLifecycleOwner provides holder, - LocalStateHolder provides holder.stateHolder, + LocalViewModelStoreOwner provides holder, LocalBackDispatcherOwner provides holder, ) { content.invoke() } } -@OptIn(ExperimentalForeignApi::class) -private class AppStateHolder( - private val lifecycle: LifecycleRegistry, -) : NSObject() { - init { - NSNotificationCenter.defaultCenter().addObserver( - this, - selector = NSSelectorFromString("appMovedToForeground:"), - name = UIApplicationWillEnterForegroundNotification, - `object` = null, - ) - NSNotificationCenter.defaultCenter().addObserver( - this, - selector = NSSelectorFromString("appMovedToBackground:"), - name = UIApplicationDidEnterBackgroundNotification, - `object` = null, - ) - NSNotificationCenter.defaultCenter().addObserver( - this, - selector = NSSelectorFromString("appWillTerminate:"), - name = UIApplicationWillTerminateNotification, - `object` = null, - ) - lifecycle.updateState(Lifecycle.State.Active) - } - - @OptIn(BetaInteropApi::class) - @ObjCAction - fun appMovedToForeground(notification: NSNotification) { - lifecycle.updateState(Lifecycle.State.Active) - } - - @OptIn(BetaInteropApi::class) - @ObjCAction - fun appMovedToBackground(notification: NSNotification) { - lifecycle.updateState(Lifecycle.State.InActive) - } - - @OptIn(BetaInteropApi::class) - @ObjCAction - fun appWillTerminate(notification: NSNotification) { - lifecycle.updateState(Lifecycle.State.Destroyed) - } -} - -class PreComposeWindowHolder : LifecycleOwner, BackDispatcherOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } - val stateHolder by lazy { - StateHolder() +class PreComposeWindowHolder : BackDispatcherOwner, ViewModelStoreOwner { + override val viewModelStore by lazy { + ViewModelStore() } override val backDispatcher by lazy { BackDispatcher() } - private val holder = AppStateHolder(lifecycle) } diff --git a/precompose/src/jsMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt b/precompose/src/jsMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt index 839ad690..31397e51 100644 --- a/precompose/src/jsMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt +++ b/precompose/src/jsMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt @@ -2,16 +2,14 @@ package moe.tlaster.precompose import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.unit.IntSize import androidx.compose.ui.window.CanvasBasedWindow -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner @@ -44,39 +42,27 @@ fun preComposeWindow( actual fun PreComposeApp( content: @Composable () -> Unit, ) { - ProvidePreComposeCompositionLocals { - content.invoke() - } -} - -@Composable -fun ProvidePreComposeCompositionLocals( - holder: PreComposeWindowHolder = remember { + val holder = remember { PreComposeWindowHolder() - }, - content: @Composable () -> Unit, -) { + } + DisposableEffect(holder) { + onDispose { + holder.viewModelStore.clear() + } + } CompositionLocalProvider( - LocalLifecycleOwner provides holder, - LocalStateHolder provides holder.stateHolder, + LocalViewModelStoreOwner provides holder, LocalBackDispatcherOwner provides holder, ) { content.invoke() } } -class PreComposeWindowHolder : LifecycleOwner, BackDispatcherOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } - val stateHolder by lazy { - StateHolder() +class PreComposeWindowHolder : BackDispatcherOwner, ViewModelStoreOwner { + override val viewModelStore by lazy { + ViewModelStore() } override val backDispatcher by lazy { BackDispatcher() } - - init { - lifecycle.updateState(Lifecycle.State.Active) - } } diff --git a/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt b/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt index 6fbb1941..90fe30e4 100644 --- a/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt +++ b/precompose/src/jvmMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt @@ -4,102 +4,36 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember -import androidx.compose.runtime.staticCompositionLocalOf -import androidx.compose.ui.window.FrameWindowScope -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.WindowScope -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner -import java.awt.Window -import java.awt.event.WindowAdapter -import java.awt.event.WindowEvent - -val LocalWindow = staticCompositionLocalOf { - error("No Window for PreCompose, please use ProvidePreComposeLocals in WindowScope to setup your desktop project") -} - -@Composable -fun FrameWindowScope.ProvidePreComposeLocals( - content: @Composable () -> Unit, -) { - CompositionLocalProvider( - LocalWindow provides window, - content = content, - ) -} @Composable actual fun PreComposeApp( content: @Composable () -> Unit, -) { - val window = LocalWindow.current - val scope = remember { - object : WindowScope { - override val window: Window get() = window - } - } - with(scope) { - this.PreComposeApp(content) - } -} - -@Composable -fun WindowScope.PreComposeApp( - content: @Composable () -> Unit, ) { val holder = remember { PreComposeWindowHolder() } - val listener = remember { - object : WindowAdapter() { - override fun windowOpened(e: WindowEvent?) { - holder.lifecycle.updateState(Lifecycle.State.Active) - } - override fun windowClosed(e: WindowEvent?) { - holder.lifecycle.updateState(Lifecycle.State.Destroyed) - } - override fun windowStateChanged(e: WindowEvent?) { - when (e?.newState) { - java.awt.Frame.ICONIFIED -> { - holder.lifecycle.updateState(Lifecycle.State.InActive) - } - else -> { - holder.lifecycle.updateState(Lifecycle.State.Active) - } - } - } - } - } - DisposableEffect(window) { - window.addWindowListener(listener) - window.addWindowStateListener(listener) + DisposableEffect(holder) { onDispose { - window.removeWindowListener(listener) - window.removeWindowStateListener(listener) + holder.viewModelStore.clear() } } CompositionLocalProvider( - LocalLifecycleOwner provides holder, - LocalStateHolder provides holder.stateHolder, + LocalViewModelStoreOwner provides holder, LocalBackDispatcherOwner provides holder, ) { content.invoke() } } -class PreComposeWindowHolder : LifecycleOwner, BackDispatcherOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } - val stateHolder by lazy { - StateHolder() +class PreComposeWindowHolder : BackDispatcherOwner, ViewModelStoreOwner { + override val viewModelStore by lazy { + ViewModelStore() } override val backDispatcher by lazy { BackDispatcher() diff --git a/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavHostTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavHostTest.kt index f7194c4d..4635034d 100644 --- a/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavHostTest.kt +++ b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavHostTest.kt @@ -1,72 +1,64 @@ package moe.tlaster.precompose.navigation -import androidx.compose.foundation.layout.Column -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.testTag -import androidx.compose.ui.test.ExperimentalTestApi -import androidx.compose.ui.test.assertTextEquals -import androidx.compose.ui.test.onNodeWithTag -import androidx.compose.ui.test.performClick -import androidx.compose.ui.test.runComposeUiTest -import moe.tlaster.precompose.PreComposeApp -import kotlin.test.Test - class NavHostTest { - @OptIn(ExperimentalTestApi::class) - @Test - fun navigationTest() = runComposeUiTest { - setContent { - PreComposeApp { - val navigator = rememberNavigator() - Column { - Button(onClick = { - navigator.goBack() - }, modifier = Modifier.testTag("goback")) { - Text("Go Back") - } - Button(onClick = { - navigator.navigate("/1") - }, modifier = Modifier.testTag("to1")) { - Text("1") - } - Button(onClick = { - navigator.navigate("/2") - }, modifier = Modifier.testTag("to2")) { - Text("2") - } - Button(onClick = { - navigator.navigate("/3") - }, modifier = Modifier.testTag("to3")) { - Text("3") - } - NavHost( - navigator = navigator, - initialRoute = "/1", - ) { - scene("/1") { - Text("1", modifier = Modifier.testTag("screen1")) - Text("1", modifier = Modifier.testTag("text")) - } - scene("/2") { - Text("2", modifier = Modifier.testTag("screen2")) - Text("2", modifier = Modifier.testTag("text")) - } - scene("/3") { - Text("3", modifier = Modifier.testTag("screen3")) - Text("3", modifier = Modifier.testTag("text")) - } - } - } - } - } - onNodeWithTag("text").assertTextEquals("1") - onNodeWithTag("to2").performClick() - onNodeWithTag("to3").performClick() - onNodeWithTag("to1").performClick() - onNodeWithTag("to3").performClick() - onNodeWithTag("text").assertTextEquals("3") - } + // @OptIn(ExperimentalTestApi::class) + // @Test + // fun navigationTest() = runComposeUiTest { + // setContent { + // val testLifecycleOwner = remember { TestLifecycleOwner() } + // CompositionLocalProvider( + // LocalLifecycleOwner provides testLifecycleOwner, + // ) { + // PreComposeApp { + // val navigator = rememberNavigator() + // Column { + // Button(onClick = { + // navigator.goBack() + // }, modifier = Modifier.testTag("goback")) { + // Text("Go Back") + // } + // Button(onClick = { + // navigator.navigate("/1") + // }, modifier = Modifier.testTag("to1")) { + // Text("1") + // } + // Button(onClick = { + // navigator.navigate("/2") + // }, modifier = Modifier.testTag("to2")) { + // Text("2") + // } + // Button(onClick = { + // navigator.navigate("/3") + // }, modifier = Modifier.testTag("to3")) { + // Text("3") + // } + // NavHost( + // navigator = navigator, + // initialRoute = "/1", + // ) { + // scene("/1") { + // Text("1", modifier = Modifier.testTag("screen1")) + // Text("1", modifier = Modifier.testTag("text")) + // } + // scene("/2") { + // Text("2", modifier = Modifier.testTag("screen2")) + // Text("2", modifier = Modifier.testTag("text")) + // } + // scene("/3") { + // Text("3", modifier = Modifier.testTag("screen3")) + // Text("3", modifier = Modifier.testTag("text")) + // } + // } + // } + // } + // } + // } + // onNodeWithTag("text").assertTextEquals("1") + // onNodeWithTag("to2").performClick() + // onNodeWithTag("to3").performClick() + // onNodeWithTag("to1").performClick() + // onNodeWithTag("to3").performClick() + // onNodeWithTag("text").assertTextEquals("3") + // } } diff --git a/precompose/src/macosMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt b/precompose/src/macosMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt index a2c9c346..9cad7b26 100644 --- a/precompose/src/macosMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt +++ b/precompose/src/macosMain/kotlin/moe/tlaster/precompose/PreComposeWindow.kt @@ -2,14 +2,13 @@ package moe.tlaster.precompose import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.ExperimentalComposeApi import androidx.compose.runtime.remember import androidx.compose.ui.window.Window -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner @@ -38,33 +37,25 @@ fun PreComposeWindow( actual fun PreComposeApp( content: @Composable () -> Unit, ) { - ProvidePreComposeCompositionLocals { - content.invoke() - } -} - -@Composable -fun ProvidePreComposeCompositionLocals( - holder: PreComposeWindowHolder = remember { + val holder = remember { PreComposeWindowHolder() - }, - content: @Composable () -> Unit, -) { + } + DisposableEffect(holder) { + onDispose { + holder.viewModelStore.clear() + } + } CompositionLocalProvider( - LocalLifecycleOwner provides holder, - LocalStateHolder provides holder.stateHolder, + LocalViewModelStoreOwner provides holder, LocalBackDispatcherOwner provides holder, ) { content.invoke() } } -class PreComposeWindowHolder : LifecycleOwner, BackDispatcherOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } - val stateHolder by lazy { - StateHolder() +class PreComposeWindowHolder : BackDispatcherOwner, ViewModelStoreOwner { + override val viewModelStore by lazy { + ViewModelStore() } override val backDispatcher by lazy { BackDispatcher() diff --git a/precompose/src/wasmJsMain/kotlin/moe/tlaster/precompose/PreComposeApp.kt b/precompose/src/wasmJsMain/kotlin/moe/tlaster/precompose/PreComposeApp.kt index a4457c6d..90fe30e4 100644 --- a/precompose/src/wasmJsMain/kotlin/moe/tlaster/precompose/PreComposeApp.kt +++ b/precompose/src/wasmJsMain/kotlin/moe/tlaster/precompose/PreComposeApp.kt @@ -2,13 +2,11 @@ package moe.tlaster.precompose import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.remember -import moe.tlaster.precompose.lifecycle.Lifecycle -import moe.tlaster.precompose.lifecycle.LifecycleOwner -import moe.tlaster.precompose.lifecycle.LifecycleRegistry -import moe.tlaster.precompose.lifecycle.LocalLifecycleOwner -import moe.tlaster.precompose.stateholder.LocalStateHolder -import moe.tlaster.precompose.stateholder.StateHolder +import androidx.lifecycle.ViewModelStore +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import moe.tlaster.precompose.ui.BackDispatcher import moe.tlaster.precompose.ui.BackDispatcherOwner import moe.tlaster.precompose.ui.LocalBackDispatcherOwner @@ -17,39 +15,27 @@ import moe.tlaster.precompose.ui.LocalBackDispatcherOwner actual fun PreComposeApp( content: @Composable () -> Unit, ) { - ProvidePreComposeCompositionLocals { - content.invoke() - } -} - -@Composable -fun ProvidePreComposeCompositionLocals( - holder: PreComposeWindowHolder = remember { + val holder = remember { PreComposeWindowHolder() - }, - content: @Composable () -> Unit, -) { + } + DisposableEffect(holder) { + onDispose { + holder.viewModelStore.clear() + } + } CompositionLocalProvider( - LocalLifecycleOwner provides holder, - LocalStateHolder provides holder.stateHolder, + LocalViewModelStoreOwner provides holder, LocalBackDispatcherOwner provides holder, ) { content.invoke() } } -class PreComposeWindowHolder : LifecycleOwner, BackDispatcherOwner { - override val lifecycle by lazy { - LifecycleRegistry() - } - val stateHolder by lazy { - StateHolder() +class PreComposeWindowHolder : BackDispatcherOwner, ViewModelStoreOwner { + override val viewModelStore by lazy { + ViewModelStore() } override val backDispatcher by lazy { BackDispatcher() } - - init { - lifecycle.updateState(Lifecycle.State.Active) - } } diff --git a/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt index ffaf278a..64b11fdf 100644 --- a/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt +++ b/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt @@ -2,7 +2,6 @@ package moe.tlaster.precompose.molecule.sample import androidx.compose.ui.window.Window import androidx.compose.ui.window.application -import moe.tlaster.precompose.ProvidePreComposeLocals fun main() { application { @@ -10,9 +9,7 @@ fun main() { title = "PreCompose Molecule Sample", onCloseRequest = ::exitApplication, ) { - ProvidePreComposeLocals { - App() - } + App() } } } diff --git a/sample/todo/common/build.gradle.kts b/sample/todo/common/build.gradle.kts index 0126e3a0..b802cf3a 100644 --- a/sample/todo/common/build.gradle.kts +++ b/sample/todo/common/build.gradle.kts @@ -25,9 +25,8 @@ kotlin { api(compose.material) api(libs.koin) api(libs.koin.compose) + api(libs.koin.compose.viewmodel) api(project(":precompose")) - api(project(":precompose-viewmodel")) - api(project(":precompose-koin")) } } val commonTest by getting diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/App.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/App.kt index ee47064c..a2b440d5 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/App.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/App.kt @@ -1,9 +1,5 @@ package moe.tlaster.common -import androidx.compose.animation.scaleIn -import androidx.compose.animation.scaleOut -import androidx.compose.animation.slideInVertically -import androidx.compose.animation.slideOutVertically import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable @@ -14,7 +10,6 @@ import moe.tlaster.precompose.PreComposeApp import moe.tlaster.precompose.navigation.NavHost import moe.tlaster.precompose.navigation.path import moe.tlaster.precompose.navigation.rememberNavigator -import moe.tlaster.precompose.navigation.transition.NavTransition import org.koin.compose.KoinContext @OptIn(ExperimentalMaterialApi::class) @@ -54,15 +49,8 @@ fun App() { ) } } - scene( + dialog( "/edit/{id:[0-9]+}?", - navTransition = NavTransition( - createTransition = slideInVertically(initialOffsetY = { it }), - destroyTransition = slideOutVertically(targetOffsetY = { it }), - pauseTransition = scaleOut(targetScale = 0.9f), - resumeTransition = scaleIn(initialScale = 0.9f), - exitTargetContentZIndex = 1f, - ), ) { backStackEntry -> val id = backStackEntry.path("id") NoteEditScene( diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/di/AppModule.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/di/AppModule.kt index b3596668..82eae22b 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/di/AppModule.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/di/AppModule.kt @@ -4,25 +4,25 @@ import moe.tlaster.common.repository.FakeRepository import moe.tlaster.common.viewmodel.NoteDetailViewModel import moe.tlaster.common.viewmodel.NoteEditViewModel import moe.tlaster.common.viewmodel.NoteListViewModel -import moe.tlaster.precompose.stateholder.SavedStateHolder +import org.koin.core.module.dsl.viewModel import org.koin.dsl.module object AppModule { val appModule = module { single { FakeRepository() } - factory { (id: Int) -> + viewModel { (id: Int) -> NoteDetailViewModel( id = id, fakeRepository = get(), ) } - factory { (id: Int?, savedStateHolder: SavedStateHolder) -> + + viewModel { (id: Int) -> NoteEditViewModel( id = id, - savedStateHolder = savedStateHolder, fakeRepository = get(), ) } - factory { NoteListViewModel(fakeRepository = get()) } + viewModel { NoteListViewModel(fakeRepository = get()) } } } diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteDetailScene.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteDetailScene.kt index 9aa7cbf5..8aad6ee7 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteDetailScene.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteDetailScene.kt @@ -17,7 +17,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import moe.tlaster.common.viewmodel.NoteDetailViewModel -import moe.tlaster.precompose.koin.koinViewModel +import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @ExperimentalMaterialApi diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteEditScene.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteEditScene.kt index 87aa1220..6157d340 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteEditScene.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteEditScene.kt @@ -17,10 +17,9 @@ import androidx.compose.material.icons.filled.Done import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import moe.tlaster.common.viewmodel.NoteEditViewModel -import moe.tlaster.precompose.flow.collectAsStateWithLifecycle -import moe.tlaster.precompose.koin.koinViewModel -import moe.tlaster.precompose.stateholder.LocalSavedStateHolder +import org.koin.compose.viewmodel.koinViewModel import org.koin.core.parameter.parametersOf @ExperimentalMaterialApi @@ -30,8 +29,7 @@ fun NoteEditScene( onDone: () -> Unit = {}, onBack: () -> Unit = {}, ) { - val stateHolder = LocalSavedStateHolder.current - val viewModel = koinViewModel { parametersOf(id, stateHolder) } + val viewModel = koinViewModel { parametersOf(id) } Scaffold( topBar = { diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteListScene.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteListScene.kt index 9c1af4af..c1370ce0 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteListScene.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/scene/NoteListScene.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import moe.tlaster.common.model.Note import moe.tlaster.common.viewmodel.NoteListViewModel -import moe.tlaster.precompose.koin.koinViewModel +import org.koin.compose.viewmodel.koinViewModel @ExperimentalMaterialApi @Composable diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteDetailViewModel.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteDetailViewModel.kt index bdbe6fe2..5313c2f2 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteDetailViewModel.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteDetailViewModel.kt @@ -1,7 +1,7 @@ package moe.tlaster.common.viewmodel +import androidx.lifecycle.ViewModel import moe.tlaster.common.repository.FakeRepository -import moe.tlaster.precompose.viewmodel.ViewModel class NoteDetailViewModel( private val id: Int, diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteEditViewModel.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteEditViewModel.kt index b8647655..34d6f42f 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteEditViewModel.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteEditViewModel.kt @@ -1,13 +1,11 @@ package moe.tlaster.common.viewmodel +import androidx.lifecycle.ViewModel import kotlinx.coroutines.flow.MutableStateFlow import moe.tlaster.common.repository.FakeRepository -import moe.tlaster.precompose.stateholder.SavedStateHolder -import moe.tlaster.precompose.viewmodel.ViewModel class NoteEditViewModel( private val id: Int?, - savedStateHolder: SavedStateHolder, private val fakeRepository: FakeRepository, ) : ViewModel() { @@ -19,17 +17,8 @@ class NoteEditViewModel( } } - val title = MutableStateFlow(savedStateHolder.consumeRestored("title") as String? ?: note?.title ?: "") - val content = MutableStateFlow(savedStateHolder.consumeRestored("content") as String? ?: note?.content ?: "") - - init { - savedStateHolder.registerProvider("title") { - title.value - } - savedStateHolder.registerProvider("content") { - content.value - } - } + val title = MutableStateFlow(note?.title ?: "") + val content = MutableStateFlow(note?.content ?: "") fun setTitle(value: String) { title.value = value diff --git a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteListViewModel.kt b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteListViewModel.kt index 1d71037d..57422ce4 100644 --- a/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteListViewModel.kt +++ b/sample/todo/common/src/commonMain/kotlin/moe/tlaster/common/viewmodel/NoteListViewModel.kt @@ -1,8 +1,8 @@ package moe.tlaster.common.viewmodel +import androidx.lifecycle.ViewModel import moe.tlaster.common.model.Note import moe.tlaster.common.repository.FakeRepository -import moe.tlaster.precompose.viewmodel.ViewModel class NoteListViewModel( private val fakeRepository: FakeRepository, diff --git a/sample/todo/desktop/src/jvmMain/kotlin/Main.kt b/sample/todo/desktop/src/jvmMain/kotlin/Main.kt index 44fb4edd..32613c97 100644 --- a/sample/todo/desktop/src/jvmMain/kotlin/Main.kt +++ b/sample/todo/desktop/src/jvmMain/kotlin/Main.kt @@ -3,7 +3,6 @@ import androidx.compose.ui.window.Window import androidx.compose.ui.window.application import moe.tlaster.common.App import moe.tlaster.common.di.AppModule -import moe.tlaster.precompose.ProvidePreComposeLocals import org.koin.core.context.startKoin fun main() { @@ -17,9 +16,7 @@ fun main() { exitApplication() }, ) { - ProvidePreComposeLocals { - App() - } + App() } } } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0ec70f57..2e0e6b7c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -23,9 +23,7 @@ dependencyResolutionManagement { rootProject.name = "precompose" include(":precompose") -include(":precompose-viewmodel") include(":precompose-molecule") -include(":precompose-koin") include(":sample:todo:android") include(":sample:todo:desktop") include(":sample:todo:common") From 7506448f50079b2ed8d57914e94db736e212d7cd Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 1 Oct 2024 18:00:16 +0900 Subject: [PATCH 04/17] move common test to jvm test --- .../moe/tlaster/precompose/navigation/BackDispatcherTest.kt | 0 .../moe/tlaster/precompose/navigation/BackStackEntryTest.kt | 6 ++++++ .../tlaster/precompose/navigation/BackStackManagerTest.kt | 0 .../moe/tlaster/precompose/navigation/NavigatorTest.kt | 0 .../moe/tlaster/precompose/navigation/QueryStringTest.kt | 0 .../moe/tlaster/precompose/navigation/RouteBuilderTest.kt | 0 .../moe/tlaster/precompose/navigation/RouteParserTest.kt | 0 .../kotlin/moe/tlaster/precompose/navigation/TestRoute.kt | 0 .../precompose/navigation/TestViewModelStoreOwner.kt | 0 .../precompose/navigation/TestViewModelStoreProvider.kt | 0 10 files changed, 6 insertions(+) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/BackDispatcherTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt (91%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/QueryStringTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/RouteBuilderTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/RouteParserTest.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/TestRoute.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt (100%) rename precompose/src/{commonTest => jvmTest}/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt (100%) diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackDispatcherTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackDispatcherTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackDispatcherTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackDispatcherTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt similarity index 91% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt index e1711d3f..f4bbe139 100644 --- a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt +++ b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackStackEntryTest.kt @@ -3,14 +3,20 @@ package moe.tlaster.precompose.navigation import androidx.lifecycle.Lifecycle import com.benasher44.uuid.uuid4 import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.UnconfinedTestDispatcher import kotlinx.coroutines.test.runTest +import kotlinx.coroutines.test.setMain import kotlinx.coroutines.withContext import kotlin.test.Test import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue +@OptIn(ExperimentalCoroutinesApi::class) fun runMainTest(block: () -> Unit) = runTest { + val testDispatcher = UnconfinedTestDispatcher(testScheduler) + Dispatchers.setMain(testDispatcher) withContext(Dispatchers.Main.immediate) { block() } diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/BackStackManagerTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/NavigatorTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/QueryStringTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/QueryStringTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/QueryStringTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/QueryStringTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/RouteBuilderTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/RouteBuilderTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/RouteBuilderTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/RouteBuilderTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/RouteParserTest.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/RouteParserTest.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/RouteParserTest.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/RouteParserTest.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestRoute.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestRoute.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestRoute.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestRoute.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreOwner.kt diff --git a/precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt b/precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt similarity index 100% rename from precompose/src/commonTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt rename to precompose/src/jvmTest/kotlin/moe/tlaster/precompose/navigation/TestViewModelStoreProvider.kt From ff5045d652a810b498853adaae5d84ec9d8164ce Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 1 Oct 2024 18:00:58 +0900 Subject: [PATCH 05/17] make molecule not depends on precompose --- precompose-molecule/build.gradle.kts | 2 +- .../tlaster/precompose/molecule/Molecule.kt | 29 +++++++------------ 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/precompose-molecule/build.gradle.kts b/precompose-molecule/build.gradle.kts index cc7808ea..05857e2a 100644 --- a/precompose-molecule/build.gradle.kts +++ b/precompose-molecule/build.gradle.kts @@ -40,8 +40,8 @@ kotlin { val commonMain by getting { dependencies { compileOnly(compose.foundation) - implementation(project(":precompose")) compileOnly(libs.molecule.runtime) + compileOnly(libs.jetbrains.viewmodel) } } val commonTest by getting { diff --git a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt index 5d008b66..fc3e8497 100644 --- a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt +++ b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt @@ -18,7 +18,6 @@ import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.consumeAsFlow -import moe.tlaster.precompose.reflect.canonicalName import kotlin.coroutines.CoroutineContext internal expect fun providePlatformDispatcher(): CoroutineContext @@ -51,31 +50,25 @@ private class ActionViewHolder : ViewModel() { @Composable private fun rememberAction( - keys: List, + key: String? = null, ): Pair, Flow> { - val key = remember(keys) { - (keys.map { it.hashCode().toString() } + ActionViewHolder::class.canonicalName).joinToString() - } return viewModel>(key = key).pair } /** * Return StateFlow, use it in your Compose UI * The molecule scope will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] - * @param keys The keys to use to identify the Presenter + * @param key The key to use to identify the Presenter * @param body The body of the molecule presenter * @return StateFlow */ @Composable private fun rememberPresenterState( - keys: List = emptyList(), + key: String? = null, useImmediateClock: Boolean, body: @Composable () -> T, ): StateFlow { - val key = remember(keys) { - (keys.map { it.hashCode().toString() } + PresenterHolder::class.canonicalName).joinToString() - } - val factory = remember { + val factory = remember(key) { viewModelFactory { addInitializer(PresenterHolder::class) { PresenterHolder(useImmediateClock, body) @@ -88,18 +81,18 @@ private fun rememberPresenterState( /** * Return State, use it in your Compose UI * The molecule scope will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] - * @param keys The keys to use to identify the Presenter + * @param key The key to use to identify the Presenter * @param useImmediateClock Use immediate clock or not, for text input, you should set it to true * @param body The body of the molecule presenter * @return State */ @Composable fun producePresenter( - keys: List = emptyList(), + key: String? = null, useImmediateClock: Boolean = false, body: @Composable () -> T, ): State { - val presenter = rememberPresenterState(keys = keys, useImmediateClock = useImmediateClock) { body() } + val presenter = rememberPresenterState(key = key, useImmediateClock = useImmediateClock) { body() } return presenter.collectAsState() } @@ -107,19 +100,19 @@ fun producePresenter( * Return pair of State and Action Channel, use it in your Compose UI * The molecule scope and the Action Channel will be managed by the [ViewModel], so it has the same lifecycle as the [ViewModel] * - * @param keys The keys to use to identify the Presenter + * @param key The key to use to identify the Presenter * @param useImmediateClock Use immediate clock or not, for text input, you should set it to true * @param body The body of the molecule presenter, the flow parameter is the flow of the action channel * @return Pair of State and Action channel */ @Composable fun rememberPresenter( - keys: List = emptyList(), + key: String? = null, useImmediateClock: Boolean = false, body: @Composable (flow: Flow) -> T, ): Pair> { - val (channel, action) = rememberAction(keys = keys) - val presenter = rememberPresenterState(keys = keys, useImmediateClock = useImmediateClock) { body(action) } + val (channel, action) = rememberAction(key = key) + val presenter = rememberPresenterState(key = key, useImmediateClock = useImmediateClock) { body(action) } val state by presenter.collectAsState() return state to channel } From 52fa48e13cb9e083290f9de9712208dfddf3828f Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 2 Oct 2024 10:53:38 +0900 Subject: [PATCH 06/17] version 1.7.0-alpha02 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 53298ebd..21ff9b12 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] # also check project root build.gradle.kts for versions -libVersion = "1.7.0-alpha01" +libVersion = "1.7.0-alpha02" compileSdk = "34" minSdk = "21" java = "11" From 3d69a955b7ac486c9a04e6916148ff63fbfdffff Mon Sep 17 00:00:00 2001 From: kkalisz Date: Tue, 8 Oct 2024 18:16:19 +0200 Subject: [PATCH 07/17] fixed viewmodel initialization issues on iOS, fixed illegal cast issue on js target --- gradle/libs.versions.toml | 2 +- .../kotlin/moe/tlaster/precompose/molecule/Molecule.kt | 4 +++- .../kotlin/moe/tlaster/precompose/navigation/Navigator.kt | 4 +++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 21ff9b12..18fd7241 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -12,7 +12,7 @@ androidGradlePlugin = "8.6.1" junit = "4.13.2" junitJupiterEngine = "5.11.0" junitJupiterApi = "5.11.0" -kotlin = "2.0.0" +kotlin = "2.0.20" lifecycleRuntimeKtx = "2.8.6" material = "1.7.2" kotlinxCoroutinesCore = "1.9.0" diff --git a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt index fc3e8497..99835c8b 100644 --- a/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt +++ b/precompose-molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/Molecule.kt @@ -52,7 +52,9 @@ private class ActionViewHolder : ViewModel() { private fun rememberAction( key: String? = null, ): Pair, Flow> { - return viewModel>(key = key).pair + return viewModel>(key = key) { + ActionViewHolder() + }.pair } /** diff --git a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt index 584bec81..4a4b6e2d 100644 --- a/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt +++ b/precompose/src/commonMain/kotlin/moe/tlaster/precompose/navigation/Navigator.kt @@ -14,7 +14,9 @@ import kotlinx.coroutines.flow.map */ @Composable fun rememberNavigator(key: String? = null): Navigator { - val viewModel = viewModel(key = key) + val viewModel = viewModel(key = key) { + NavigatorViewModel() + } return viewModel.navigator } From c6d419c91ab1ef4e864c9eb937f015adc4634630 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 11 Oct 2024 15:06:03 +0900 Subject: [PATCH 08/17] version 1.7.0-alpha03 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18fd7241..d7c63fea 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] # also check project root build.gradle.kts for versions -libVersion = "1.7.0-alpha02" +libVersion = "1.7.0-alpha03" compileSdk = "34" minSdk = "21" java = "11" From 3963622ad6c3aa1ef45a013f4c41a38339792527 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 3 Nov 2024 14:28:58 +0900 Subject: [PATCH 09/17] update to compose 1.7 stable --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d7c63fea..d6025e95 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ kotlinxCoroutinesCore = "1.9.0" moleculeRuntime = "2.0.0" savedstateKtx = "1.2.1" spotless = "6.25.0" -jetbrainsComposePlugin = "1.7.0-beta02" +jetbrainsComposePlugin = "1.7.0" skiko = "0.8.12" koin = "4.0.0" uiTestJunit4Android = "1.7.2" From 0a83519010c4d7ab5f0c17c87e1220a8fc9a0173 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 3 Nov 2024 14:29:12 +0900 Subject: [PATCH 10/17] update molecule sample --- sample/molecule/build.gradle.kts | 170 ------------------ sample/molecule/composeApp/build.gradle.kts | 117 ++++++++++++ .../src/androidMain/AndroidManifest.xml | 23 +++ .../molecule/sample/MainActivity.kt | 13 +- .../drawable-v24/ic_launcher_foreground.xml | 30 ++++ .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + .../res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 3593 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 5339 bytes .../res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2636 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 3388 bytes .../res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 4926 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 7472 bytes .../res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 7909 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 11873 bytes .../res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 10652 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 16570 bytes .../src/androidMain/res/values/strings.xml | 3 + .../drawable/compose-multiplatform.xml | 36 ++++ .../tlaster/precompose/molecule/sample/App.kt | 0 .../molecule/sample/MainViewController.kt | 5 + .../precompose/molecule/sample/Main.kt | 13 ++ .../precompose/molecule/sample/Main.kt | 0 .../src/wasmJsMain/resources/index.html | 2 +- .../src/wasmJsMain/resources/styles.css | 0 .../iosApp/Configuration/Config.xcconfig | 3 + .../AccentColor.colorset/Contents.json | 11 ++ .../AppIcon.appiconset/Contents.json | 14 ++ .../AppIcon.appiconset/app-icon-1024.png | Bin 0 -> 67285 bytes .../iosApp/Assets.xcassets/Contents.json | 6 + .../molecule/iosApp/iosApp/ContentView.swift | 21 +++ sample/molecule/iosApp/iosApp/Info.plist | 50 ++++++ .../Preview Assets.xcassets/Contents.json | 6 + sample/molecule/iosApp/iosApp/iOSApp.swift | 10 ++ .../src/androidMain/AndroidManifest.xml | 16 -- .../precompose/molecule/sample/Main.kt | 15 -- .../precompose/molecule/sample/Main.kt | 13 -- .../precompose/molecule/sample/Main.kt | 49 ----- settings.gradle.kts | 2 +- 40 files changed, 541 insertions(+), 267 deletions(-) delete mode 100644 sample/molecule/build.gradle.kts create mode 100644 sample/molecule/composeApp/build.gradle.kts create mode 100644 sample/molecule/composeApp/src/androidMain/AndroidManifest.xml rename sample/molecule/{ => composeApp}/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt (53%) create mode 100644 sample/molecule/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 sample/molecule/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 sample/molecule/composeApp/src/androidMain/res/values/strings.xml create mode 100644 sample/molecule/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml rename sample/molecule/{ => composeApp}/src/commonMain/kotlin/moe/tlaster/precompose/molecule/sample/App.kt (100%) create mode 100644 sample/molecule/composeApp/src/iosMain/kotlin/moe/tlaster/precompose/molecule/sample/MainViewController.kt create mode 100644 sample/molecule/composeApp/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt rename sample/molecule/{ => composeApp}/src/wasmJsMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt (100%) rename sample/molecule/{ => composeApp}/src/wasmJsMain/resources/index.html (89%) rename sample/molecule/{ => composeApp}/src/wasmJsMain/resources/styles.css (100%) create mode 100644 sample/molecule/iosApp/Configuration/Config.xcconfig create mode 100644 sample/molecule/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json create mode 100644 sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png create mode 100644 sample/molecule/iosApp/iosApp/Assets.xcassets/Contents.json create mode 100644 sample/molecule/iosApp/iosApp/ContentView.swift create mode 100644 sample/molecule/iosApp/iosApp/Info.plist create mode 100644 sample/molecule/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json create mode 100644 sample/molecule/iosApp/iosApp/iOSApp.swift delete mode 100644 sample/molecule/src/androidMain/AndroidManifest.xml delete mode 100644 sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt delete mode 100644 sample/molecule/src/macosMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt delete mode 100644 sample/molecule/src/uikitMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt diff --git a/sample/molecule/build.gradle.kts b/sample/molecule/build.gradle.kts deleted file mode 100644 index 9ef58c62..00000000 --- a/sample/molecule/build.gradle.kts +++ /dev/null @@ -1,170 +0,0 @@ -import org.jetbrains.compose.desktop.application.dsl.TargetFormat -import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget -import org.jetbrains.kotlin.gradle.targets.js.dsl.ExperimentalWasmDsl -import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig - -plugins { - kotlin("multiplatform") - alias(libs.plugins.jetbrains.compose) - id("com.android.application") - alias(libs.plugins.compose.compiler) -} - -kotlin { - applyDefaultHierarchyTemplate() - listOf( - iosSimulatorArm64(), - iosArm64(), - iosX64(), - ) - androidTarget() - macosX64 { - binaries { - executable { - entryPoint = "moe.tlaster.precompose.molecule.sample.main" - freeCompilerArgs += listOf( - "-linker-option", "-framework", "-linker-option", "Metal", - ) - } - } - } - macosArm64 { - binaries { - executable { - entryPoint = "moe.tlaster.precompose.molecule.sample.main" - freeCompilerArgs += listOf( - "-linker-option", "-framework", "-linker-option", "Metal", - ) - } - } - } - jvm() - @OptIn(ExperimentalWasmDsl::class) - wasmJs { - moduleName = "composeApp" - browser { - val projectDirPath = project.projectDir.path - commonWebpackConfig { - outputFileName = "composeApp.js" - devServer = - (devServer ?: KotlinWebpackConfig.DevServer()).apply { - static = - (static ?: mutableListOf()).apply { - // Serve sources to debug inside browser - add(projectDirPath) - } - } - } - } - binaries.executable() - } - - sourceSets { - val commonMain by getting { - dependencies { - implementation(compose.ui) - implementation(compose.runtime) - implementation(compose.foundation) - implementation(compose.material) - implementation(project(":precompose")) - implementation(project(":precompose-molecule")) - implementation(libs.molecule.runtime) - } - } - val jvmMain by getting { - dependencies { - implementation(compose.desktop.currentOs) - } - } - val androidMain by getting { - dependencies { - implementation(libs.androidx.appcompat) - implementation(libs.androidx.activity.compose) - } - } - } - - targets.withType { - binaries.all { - // TODO: the current compose binary surprises LLVM, so disable checks for now. - freeCompilerArgs += "-Xdisable-phases=VerifyBitcode" - binaryOptions["memoryModel"] = "experimental" - } - } -} - -compose { - desktop { - application { - mainClass = "moe.tlaster.precompose.molecule.sample.MainKt" - nativeDistributions { - targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) - packageName = "moe.tlaster.precompose.sample.molecule" - packageVersion = "1.0.0" - macOS { - bundleID = "moe.tlaster.precompose.sample.molecule" - // iconFile.set(project.file("src/jvmMain/resources/icon/ic_launcher.icns")) - } - linux { - // iconFile.set(project.file("src/jvmMain/resources/icon/ic_launcher.png")) - } - windows { - shortcut = true - menu = true - // iconFile.set(project.file("src/jvmMain/resources/icon/ic_launcher.ico")) - } - } - } - nativeApplication { - targets(kotlin.targets.getByName("macosX64"), kotlin.targets.getByName("macosArm64")) - distributions { - targetFormats(TargetFormat.Dmg) - packageName = "moe.tlaster.precompose.sample.molecule" - packageVersion = "1.0.0" - macOS { - bundleID = "moe.tlaster.precompose.sample.molecule" - // iconFile.set(project.file("src/jvmMain/resources/icon/ic_launcher.icns")) - } - } - } - } - // experimental { - // uikit { - // application { - // bundleIdPrefix = "moe.tlaster.precompose.sample.molecule" - // projectName = "PreComposeMoleculeSample" - // deployConfigurations { - // simulator("Simulator") { - // device = org.jetbrains.compose.experimental.dsl.IOSDevices.IPHONE_13_MINI - // } - // } - // } - // } - // } -} - -android { - compileSdk = libs.versions.compileSdk.get().toInt() - namespace = "moe.tlaster.precompose.molecule.sample" - defaultConfig { - applicationId = "moe.tlaster.precompose.molecule.sample" - minSdk = libs.versions.minSdk.get().toInt() - targetSdk = libs.versions.compileSdk.get().toInt() - versionCode = 1 - versionName = "0.1.0" - } - sourceSets { - getByName("main") { - manifest.srcFile("src/androidMain/AndroidManifest.xml") - } - } - buildTypes { - getByName("release") { - isMinifyEnabled = false - } - } - compileOptions { - sourceCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - targetCompatibility = JavaVersion.toVersion(libs.versions.java.get()) - } -} diff --git a/sample/molecule/composeApp/build.gradle.kts b/sample/molecule/composeApp/build.gradle.kts new file mode 100644 index 00000000..041c9c3c --- /dev/null +++ b/sample/molecule/composeApp/build.gradle.kts @@ -0,0 +1,117 @@ +import org.jetbrains.compose.desktop.application.dsl.TargetFormat +import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl +import org.jetbrains.kotlin.gradle.dsl.JvmTarget +import org.jetbrains.kotlin.gradle.targets.js.webpack.KotlinWebpackConfig + +plugins { + kotlin("multiplatform") + alias(libs.plugins.jetbrains.compose) + id("com.android.application") + alias(libs.plugins.compose.compiler) +} + +kotlin { + applyDefaultHierarchyTemplate() + androidTarget { + @OptIn(ExperimentalKotlinGradlePluginApi::class) + compilerOptions { + jvmTarget.set(JvmTarget.JVM_11) + } + } + + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + ).forEach { iosTarget -> + iosTarget.binaries.framework { + baseName = "ComposeApp" + isStatic = true + } + } + + jvm() + + @OptIn(ExperimentalWasmDsl::class) + wasmJs { + moduleName = "composeApp" + browser { + val rootDirPath = project.rootDir.path + val projectDirPath = project.projectDir.path + commonWebpackConfig { + outputFileName = "composeApp.js" + devServer = (devServer ?: KotlinWebpackConfig.DevServer()).apply { + static = (static ?: mutableListOf()).apply { + // Serve sources to debug inside browser + add(rootDirPath) + add(projectDirPath) + } + } + } + } + binaries.executable() + } + + sourceSets { + androidMain.dependencies { + implementation(libs.androidx.appcompat) + implementation(libs.androidx.activity.compose) + } + commonMain.dependencies { + implementation(compose.ui) + implementation(compose.runtime) + implementation(compose.foundation) + implementation(compose.material) + implementation(project(":precompose")) + implementation(project(":precompose-molecule")) + implementation(libs.molecule.runtime) + } + jvmMain.dependencies { + implementation(compose.desktop.currentOs) + } + } +} + +android { + namespace = "moe.tlaster.precompose.molecule.sample" + compileSdk = libs.versions.compileSdk.get().toInt() + + defaultConfig { + applicationId = "moe.tlaster.precompose.molecule.sample" + minSdk = libs.versions.minSdk.get().toInt() + targetSdk = libs.versions.compileSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } + buildTypes { + getByName("release") { + isMinifyEnabled = false + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } +} + +dependencies { + debugImplementation(compose.uiTooling) +} + +compose.desktop { + application { + mainClass = "moe.tlaster.precompose.molecule.sample.MainKt" + + nativeDistributions { + targetFormats(TargetFormat.Dmg, TargetFormat.Msi, TargetFormat.Deb) + packageName = "moe.tlaster.precompose.molecule.sample" + packageVersion = "1.0.0" + } + } +} diff --git a/sample/molecule/composeApp/src/androidMain/AndroidManifest.xml b/sample/molecule/composeApp/src/androidMain/AndroidManifest.xml new file mode 100644 index 00000000..c5db0b15 --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/molecule/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt similarity index 53% rename from sample/molecule/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt rename to sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt index 7d329a83..cc572dc7 100644 --- a/sample/molecule/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt +++ b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt @@ -1,14 +1,23 @@ package moe.tlaster.precompose.molecule.sample import android.os.Bundle +import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.fragment.app.FragmentActivity +import androidx.compose.runtime.Composable +import androidx.compose.ui.tooling.preview.Preview -class MainActivity : FragmentActivity() { +class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + setContent { App() } } } + +@Preview +@Composable +fun AppAndroidPreview() { + App() +} diff --git a/sample/molecule/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml b/sample/molecule/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..2b068d11 --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/sample/molecule/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml b/sample/molecule/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..e93e11ad --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..eca70cfe --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png b/sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..a571e60098c92c2baca8a5df62f2929cbff01b52 GIT binary patch literal 3593 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 P00000NkvXXu0mjfP=x?Y literal 0 HcmV?d00001 diff --git a/sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png b/sample/molecule/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..61da551c5594a1f9d26193983d2cd69189014603 GIT binary patch literal 5339 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 literal 0 HcmV?d00001 diff --git a/sample/molecule/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/molecule/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..b216f2d313cc673d8b8c4da591c174ebed52795c GIT binary patch literal 11873 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 literal 0 HcmV?d00001 diff --git a/sample/molecule/composeApp/src/androidMain/res/values/strings.xml b/sample/molecule/composeApp/src/androidMain/res/values/strings.xml new file mode 100644 index 00000000..5e8b1baf --- /dev/null +++ b/sample/molecule/composeApp/src/androidMain/res/values/strings.xml @@ -0,0 +1,3 @@ + + molecule-sample + \ No newline at end of file diff --git a/sample/molecule/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml b/sample/molecule/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml new file mode 100644 index 00000000..c0bcfb28 --- /dev/null +++ b/sample/molecule/composeApp/src/commonMain/composeResources/drawable/compose-multiplatform.xml @@ -0,0 +1,36 @@ + + + + + + + + \ No newline at end of file diff --git a/sample/molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/sample/App.kt b/sample/molecule/composeApp/src/commonMain/kotlin/moe/tlaster/precompose/molecule/sample/App.kt similarity index 100% rename from sample/molecule/src/commonMain/kotlin/moe/tlaster/precompose/molecule/sample/App.kt rename to sample/molecule/composeApp/src/commonMain/kotlin/moe/tlaster/precompose/molecule/sample/App.kt diff --git a/sample/molecule/composeApp/src/iosMain/kotlin/moe/tlaster/precompose/molecule/sample/MainViewController.kt b/sample/molecule/composeApp/src/iosMain/kotlin/moe/tlaster/precompose/molecule/sample/MainViewController.kt new file mode 100644 index 00000000..a87d5a56 --- /dev/null +++ b/sample/molecule/composeApp/src/iosMain/kotlin/moe/tlaster/precompose/molecule/sample/MainViewController.kt @@ -0,0 +1,5 @@ +package moe.tlaster.precompose.molecule.sample + +import androidx.compose.ui.window.ComposeUIViewController + +fun MainViewController() = ComposeUIViewController { App() } diff --git a/sample/molecule/composeApp/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/composeApp/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt new file mode 100644 index 00000000..6628e9a4 --- /dev/null +++ b/sample/molecule/composeApp/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt @@ -0,0 +1,13 @@ +package moe.tlaster.precompose.molecule.sample + +import androidx.compose.ui.window.Window +import androidx.compose.ui.window.application + +fun main() = application { + Window( + onCloseRequest = ::exitApplication, + title = "molecule-sample", + ) { + App() + } +} diff --git a/sample/molecule/src/wasmJsMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/composeApp/src/wasmJsMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt similarity index 100% rename from sample/molecule/src/wasmJsMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt rename to sample/molecule/composeApp/src/wasmJsMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt diff --git a/sample/molecule/src/wasmJsMain/resources/index.html b/sample/molecule/composeApp/src/wasmJsMain/resources/index.html similarity index 89% rename from sample/molecule/src/wasmJsMain/resources/index.html rename to sample/molecule/composeApp/src/wasmJsMain/resources/index.html index a8a4240c..9982f142 100644 --- a/sample/molecule/src/wasmJsMain/resources/index.html +++ b/sample/molecule/composeApp/src/wasmJsMain/resources/index.html @@ -3,7 +3,7 @@ - molecule + molecule-sample diff --git a/sample/molecule/src/wasmJsMain/resources/styles.css b/sample/molecule/composeApp/src/wasmJsMain/resources/styles.css similarity index 100% rename from sample/molecule/src/wasmJsMain/resources/styles.css rename to sample/molecule/composeApp/src/wasmJsMain/resources/styles.css diff --git a/sample/molecule/iosApp/Configuration/Config.xcconfig b/sample/molecule/iosApp/Configuration/Config.xcconfig new file mode 100644 index 00000000..f04bc523 --- /dev/null +++ b/sample/molecule/iosApp/Configuration/Config.xcconfig @@ -0,0 +1,3 @@ +TEAM_ID= +BUNDLE_ID=moe.tlaster.precompose.molecule.sample.molecule-sample +APP_NAME=molecule-sample \ No newline at end of file diff --git a/sample/molecule/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json b/sample/molecule/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 00000000..ee7e3ca0 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,11 @@ +{ + "colors" : [ + { + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json b/sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..8edf56e7 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,14 @@ +{ + "images" : [ + { + "filename" : "app-icon-1024.png", + "idiom" : "universal", + "platform" : "ios", + "size" : "1024x1024" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png b/sample/molecule/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/app-icon-1024.png new file mode 100644 index 0000000000000000000000000000000000000000..53fc536fb9ac5c1dbb27c7e1da13db3760070a11 GIT binary patch literal 67285 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 BXNCX( literal 0 HcmV?d00001 diff --git a/sample/molecule/iosApp/iosApp/Assets.xcassets/Contents.json b/sample/molecule/iosApp/iosApp/Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sample/molecule/iosApp/iosApp/ContentView.swift b/sample/molecule/iosApp/iosApp/ContentView.swift new file mode 100644 index 00000000..3cd5c325 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/ContentView.swift @@ -0,0 +1,21 @@ +import UIKit +import SwiftUI +import ComposeApp + +struct ComposeView: UIViewControllerRepresentable { + func makeUIViewController(context: Context) -> UIViewController { + MainViewControllerKt.MainViewController() + } + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} +} + +struct ContentView: View { + var body: some View { + ComposeView() + .ignoresSafeArea(.keyboard) // Compose has own keyboard handler + } +} + + + diff --git a/sample/molecule/iosApp/iosApp/Info.plist b/sample/molecule/iosApp/iosApp/Info.plist new file mode 100644 index 00000000..412e3781 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/Info.plist @@ -0,0 +1,50 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + $(PRODUCT_BUNDLE_PACKAGE_TYPE) + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + LSRequiresIPhoneOS + + CADisableMinimumFrameDurationOnPhone + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + + UILaunchScreen + + UIRequiredDeviceCapabilities + + armv7 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + diff --git a/sample/molecule/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json b/sample/molecule/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json new file mode 100644 index 00000000..4aa7c535 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} \ No newline at end of file diff --git a/sample/molecule/iosApp/iosApp/iOSApp.swift b/sample/molecule/iosApp/iosApp/iOSApp.swift new file mode 100644 index 00000000..d83dca61 --- /dev/null +++ b/sample/molecule/iosApp/iosApp/iOSApp.swift @@ -0,0 +1,10 @@ +import SwiftUI + +@main +struct iOSApp: App { + var body: some Scene { + WindowGroup { + ContentView() + } + } +} \ No newline at end of file diff --git a/sample/molecule/src/androidMain/AndroidManifest.xml b/sample/molecule/src/androidMain/AndroidManifest.xml deleted file mode 100644 index 1307b6ed..00000000 --- a/sample/molecule/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - - - - - diff --git a/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt deleted file mode 100644 index 64b11fdf..00000000 --- a/sample/molecule/src/jvmMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt +++ /dev/null @@ -1,15 +0,0 @@ -package moe.tlaster.precompose.molecule.sample - -import androidx.compose.ui.window.Window -import androidx.compose.ui.window.application - -fun main() { - application { - Window( - title = "PreCompose Molecule Sample", - onCloseRequest = ::exitApplication, - ) { - App() - } - } -} diff --git a/sample/molecule/src/macosMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/src/macosMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt deleted file mode 100644 index 7853b675..00000000 --- a/sample/molecule/src/macosMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt +++ /dev/null @@ -1,13 +0,0 @@ -package moe.tlaster.precompose.molecule.sample - -import androidx.compose.ui.window.Window -import platform.AppKit.NSApp - -fun main() { - Window( - "PreCompose Molecule Sample", - ) { - App() - } - NSApp?.run() -} diff --git a/sample/molecule/src/uikitMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt b/sample/molecule/src/uikitMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt deleted file mode 100644 index 25ddb38f..00000000 --- a/sample/molecule/src/uikitMain/kotlin/moe/tlaster/precompose/molecule/sample/Main.kt +++ /dev/null @@ -1,49 +0,0 @@ -package moe.tlaster.precompose.molecule.sample - -import androidx.compose.ui.window.ComposeUIViewController -import kotlinx.cinterop.BetaInteropApi -import kotlinx.cinterop.ExperimentalForeignApi -import kotlinx.cinterop.autoreleasepool -import kotlinx.cinterop.cstr -import kotlinx.cinterop.memScoped -import kotlinx.cinterop.toCValues -import platform.Foundation.NSStringFromClass -import platform.UIKit.UIApplication -import platform.UIKit.UIApplicationDelegateProtocol -import platform.UIKit.UIApplicationDelegateProtocolMeta -import platform.UIKit.UIApplicationMain -import platform.UIKit.UIResponder -import platform.UIKit.UIResponderMeta -import platform.UIKit.UIScreen -import platform.UIKit.UIWindow - -@OptIn(ExperimentalForeignApi::class, BetaInteropApi::class) -fun main() { - val args = emptyArray() - memScoped { - val argc = args.size + 1 - val argv = (arrayOf("skikoApp") + args).map { it.cstr.ptr }.toCValues() - autoreleasepool { - UIApplicationMain(argc, argv, null, NSStringFromClass(SkikoAppDelegate)) - } - } -} - -@BetaInteropApi -class SkikoAppDelegate : UIResponder, UIApplicationDelegateProtocol { - companion object : UIResponderMeta(), UIApplicationDelegateProtocolMeta - - @OverrideInit - constructor() : super() - - @OptIn(ExperimentalForeignApi::class) - override fun application(application: UIApplication, didFinishLaunchingWithOptions: Map?): Boolean { - window = UIWindow(frame = UIScreen.mainScreen.bounds).apply { - rootViewController = ComposeUIViewController { - App() - } - makeKeyAndVisible() - } - return true - } -} diff --git a/settings.gradle.kts b/settings.gradle.kts index 2e0e6b7c..db5d77a0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -30,4 +30,4 @@ include(":sample:todo:common") include(":sample:todo:ios") include(":sample:todo:macos") include(":sample:todo:js") -include(":sample:molecule") +include(":sample:molecule:composeApp") From a96c5eb2f49556907389c279389354b229b71d63 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 3 Nov 2024 14:45:21 +0900 Subject: [PATCH 11/17] update molecule sample --- .../moe/tlaster/precompose/molecule/sample/MainActivity.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt index cc572dc7..68e22826 100644 --- a/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt +++ b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt @@ -4,7 +4,6 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -15,9 +14,3 @@ class MainActivity : ComponentActivity() { } } } - -@Preview -@Composable -fun AppAndroidPreview() { - App() -} From 76ed0665dd27205ef7b9e22bd8ad91cc99641c7b Mon Sep 17 00:00:00 2001 From: Tlaster Date: Sun, 3 Nov 2024 14:49:26 +0900 Subject: [PATCH 12/17] update molecule sample --- .../moe/tlaster/precompose/molecule/sample/MainActivity.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt index 68e22826..6c3fcbc9 100644 --- a/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt +++ b/sample/molecule/composeApp/src/androidMain/kotlin/moe/tlaster/precompose/molecule/sample/MainActivity.kt @@ -3,7 +3,6 @@ package moe.tlaster.precompose.molecule.sample import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.runtime.Composable class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { From 28253ab2c9999e21b8ff85f754f1e2a4f00d5135 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Nov 2024 12:42:23 +0900 Subject: [PATCH 13/17] upgrade to compose 1.7.1 --- gradle/libs.versions.toml | 2 +- precompose-molecule/build.gradle.kts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d6025e95..dec94256 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -19,7 +19,7 @@ kotlinxCoroutinesCore = "1.9.0" moleculeRuntime = "2.0.0" savedstateKtx = "1.2.1" spotless = "6.25.0" -jetbrainsComposePlugin = "1.7.0" +jetbrainsComposePlugin = "1.7.1" skiko = "0.8.12" koin = "4.0.0" uiTestJunit4Android = "1.7.2" diff --git a/precompose-molecule/build.gradle.kts b/precompose-molecule/build.gradle.kts index 05857e2a..0880f42d 100644 --- a/precompose-molecule/build.gradle.kts +++ b/precompose-molecule/build.gradle.kts @@ -39,9 +39,9 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - compileOnly(compose.foundation) + implementation(compose.foundation) compileOnly(libs.molecule.runtime) - compileOnly(libs.jetbrains.viewmodel) + implementation(libs.jetbrains.viewmodel) } } val commonTest by getting { From 4f4d3062a9cd68251a662477f42d2374a79dd7cc Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Nov 2024 13:40:35 +0900 Subject: [PATCH 14/17] update skiko --- gradle/libs.versions.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dec94256..346bc6c3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] # also check project root build.gradle.kts for versions -libVersion = "1.7.0-alpha03" +libVersion = "1.7.0" compileSdk = "34" minSdk = "21" java = "11" @@ -20,7 +20,7 @@ moleculeRuntime = "2.0.0" savedstateKtx = "1.2.1" spotless = "6.25.0" jetbrainsComposePlugin = "1.7.1" -skiko = "0.8.12" +skiko = "0.8.18" koin = "4.0.0" uiTestJunit4Android = "1.7.2" uiTestManifest = "1.7.2" From de7e4b5813073415b98ac96d1aa497fe38bb1a70 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Nov 2024 14:45:56 +0900 Subject: [PATCH 15/17] update dependencies --- gradle/libs.versions.toml | 20 ++++++++++---------- settings.gradle.kts | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 346bc6c3..dfed6bef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -3,18 +3,18 @@ libVersion = "1.7.0" compileSdk = "34" minSdk = "21" java = "11" -androidx-animation = "1.7.2" -androidx-foundation = "1.7.2" +androidx-animation = "1.7.5" +androidx-foundation = "1.7.5" androidx-appcompat = "1.7.0" -androidx-coreKtx = "1.13.1" -androidxActivityVer = "1.9.2" +androidx-coreKtx = "1.15.0" +androidxActivityVer = "1.9.3" androidGradlePlugin = "8.6.1" junit = "4.13.2" junitJupiterEngine = "5.11.0" junitJupiterApi = "5.11.0" kotlin = "2.0.20" -lifecycleRuntimeKtx = "2.8.6" -material = "1.7.2" +lifecycleRuntimeKtx = "2.8.7" +material = "1.7.5" kotlinxCoroutinesCore = "1.9.0" moleculeRuntime = "2.0.0" savedstateKtx = "1.2.1" @@ -22,8 +22,8 @@ spotless = "6.25.0" jetbrainsComposePlugin = "1.7.1" skiko = "0.8.18" koin = "4.0.0" -uiTestJunit4Android = "1.7.2" -uiTestManifest = "1.7.2" +uiTestJunit4Android = "1.7.5" +uiTestManifest = "1.7.5" uuid = "0.8.4" webpackCliVersion = "5.1.4" nodeVersion = "20.14.0" @@ -55,8 +55,8 @@ koin = { module = "io.insert-koin:koin-core", version.ref = "koin" } koin-compose = { module = "io.insert-koin:koin-compose", version.ref = "koin" } koin-compose-viewmodel = { module = "io.insert-koin:koin-compose-viewmodel", version.ref = "koin" } uuid = { module = "com.benasher44:uuid", version.ref = "uuid" } -jetbrains-lifecycle = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version = "2.8.2" } -jetbrains-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.2" } +jetbrains-lifecycle = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version = "2.8.4" } +jetbrains-viewmodel = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version = "2.8.4" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } diff --git a/settings.gradle.kts b/settings.gradle.kts index db5d77a0..6f521380 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,6 @@ include(":sample:todo:android") include(":sample:todo:desktop") include(":sample:todo:common") include(":sample:todo:ios") -include(":sample:todo:macos") +//include(":sample:todo:macos") include(":sample:todo:js") include(":sample:molecule:composeApp") From c15a1b67ecb4fafb3d46d4dd45abdcd3e34f3f62 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Nov 2024 14:51:11 +0900 Subject: [PATCH 16/17] fix code style --- settings.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle.kts b/settings.gradle.kts index 6f521380..e3815c63 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -28,6 +28,6 @@ include(":sample:todo:android") include(":sample:todo:desktop") include(":sample:todo:common") include(":sample:todo:ios") -//include(":sample:todo:macos") +// include(":sample:todo:macos") include(":sample:todo:js") include(":sample:molecule:composeApp") From a7f761188b27ba0dadc5e08451159759e006ca4b Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 22 Nov 2024 14:54:59 +0900 Subject: [PATCH 17/17] update compile sdk to 35 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index dfed6bef..32f01dcf 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] # also check project root build.gradle.kts for versions libVersion = "1.7.0" -compileSdk = "34" +compileSdk = "35" minSdk = "21" java = "11" androidx-animation = "1.7.5"