diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeWorkflow.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeWorkflow.kt index a48773a48..a66d4980e 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeWorkflow.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeWorkflow.kt @@ -1,14 +1,14 @@ package com.squareup.sample.compose.hellocompose -import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State -import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State.Goodbye +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import com.squareup.sample.compose.hellocompose.HelloComposeWorkflow.State.Hello -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.action -import com.squareup.workflow1.parse +import com.squareup.workflow1.StatelessWorkflow +import com.squareup.workflow1.WorkflowExperimentalApi -object HelloComposeWorkflow : StatefulWorkflow() { +object HelloComposeWorkflow : StatelessWorkflow() { enum class State { Hello, Goodbye; @@ -19,24 +19,15 @@ object HelloComposeWorkflow : StatefulWorkflow if (source.readInt() == 1) Hello else Goodbye } - ?: Hello - + @OptIn(WorkflowExperimentalApi::class) override fun render( renderProps: Unit, - renderState: State, context: RenderContext - ): HelloComposeScreen = HelloComposeScreen( - message = renderState.name, - onClick = { context.actionSink.send(helloAction) } - ) - - override fun snapshotState(state: State): Snapshot = Snapshot.of(if (state == Hello) 1 else 0) + ): HelloComposeScreen = context.renderComposable { + var state by remember { mutableStateOf(Hello) } + HelloComposeScreen( + message = state.name, + onClick = { state = state.theOtherState() } + ) + } } diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt index d7d04cd64..34c8599b9 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.graphics.Color import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.squareup.workflow1.SimpleLoggingWorkflowInterceptor import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.config.AndroidRuntimeConfigTools import com.squareup.workflow1.mapRendering @@ -59,7 +60,8 @@ class NestedRenderingsActivity : AppCompatActivity() { workflow = RecursiveWorkflow.mapRendering { it.withEnvironment(viewEnvironment) }, scope = viewModelScope, savedStateHandle = savedState, - runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig() + runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig(), + interceptors = listOf(SimpleLoggingWorkflowInterceptor()) ) } } diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveWorkflow.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveWorkflow.kt index 51c26325e..5dc6e1adf 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveWorkflow.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveWorkflow.kt @@ -1,13 +1,16 @@ package com.squareup.sample.compose.nestedrenderings +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import com.squareup.sample.compose.databinding.LegacyViewBinding import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.LegacyRendering import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.Rendering -import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.State -import com.squareup.workflow1.Snapshot -import com.squareup.workflow1.StatefulWorkflow -import com.squareup.workflow1.action -import com.squareup.workflow1.renderChild +import com.squareup.workflow1.StatelessWorkflow +import com.squareup.workflow1.WorkflowExperimentalApi +import com.squareup.workflow1.renderComposable import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory @@ -22,9 +25,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * through Composable renderings as well as adapting in both directions. */ @OptIn(WorkflowUiExperimentalApi::class) -object RecursiveWorkflow : StatefulWorkflow() { - - data class State(val children: Int = 0) +object RecursiveWorkflow : StatelessWorkflow() { /** * A rendering from a [RecursiveWorkflow]. @@ -51,33 +52,29 @@ object RecursiveWorkflow : StatefulWorkflow() { ) } - override fun initialState( - props: Unit, - snapshot: Snapshot? - ): State = State() - + @OptIn(WorkflowExperimentalApi::class) override fun render( renderProps: Unit, - renderState: State, context: RenderContext - ): Rendering { - return Rendering( - children = List(renderState.children) { i -> - val child = context.renderChild(RecursiveWorkflow, key = i.toString()) - if (i % 2 == 0) child else LegacyRendering(child) - }, - onAddChildClicked = { context.actionSink.send(addChild()) }, - onResetClicked = { context.actionSink.send(reset()) } - ) + ): Rendering = context.renderComposable { + produceRendering() } +} - override fun snapshotState(state: State): Snapshot? = null - - private fun addChild() = action("addChild") { - state = state.copy(children = state.children + 1) - } +@OptIn(WorkflowUiExperimentalApi::class) +@Composable +private fun produceRendering(): Rendering { + var children by rememberSaveable { mutableIntStateOf(0) } - private fun reset() = action("reset") { - state = State() - } + return Rendering( + children = List(children) { i -> + val child = produceRendering() + if ((i % 2) == 0) child else LegacyRendering(child) + }, + onAddChildClicked = { + println("OMG onAddChildClicked") + children++ + }, + onResetClicked = { children = 0 } + ) } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0cc8d2b26..394e1b285 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -7,6 +7,7 @@ pluginManagement { google() // For binary compatibility validator. maven { url = uri("https://kotlin.bintray.com/kotlinx") } + maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") } includeBuild("build-logic") } diff --git a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt index 3d72a2ba1..af674325e 100644 --- a/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-config/config-android/dependencies/releaseRuntimeClasspath.txt @@ -1,11 +1,27 @@ +androidx.annotation:annotation-experimental:1.4.1 +androidx.annotation:annotation-jvm:1.8.1 +androidx.annotation:annotation:1.8.1 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt index 3d72a2ba1..ea21d12ad 100644 --- a/workflow-config/config-jvm/dependencies/runtimeClasspath.txt +++ b/workflow-config/config-jvm/dependencies/runtimeClasspath.txt @@ -1,11 +1,23 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-core/build.gradle.kts b/workflow-core/build.gradle.kts index d0cbed541..774ea73e5 100644 --- a/workflow-core/build.gradle.kts +++ b/workflow-core/build.gradle.kts @@ -3,6 +3,7 @@ import com.squareup.workflow1.buildsrc.iosWithSimulatorArm64 plugins { id("kotlin-multiplatform") id("published") + id("org.jetbrains.compose") version "1.6.11" } kotlin { @@ -23,6 +24,8 @@ dependencies { commonMainApi(libs.kotlinx.coroutines.core) // For Snapshot. commonMainApi(libs.squareup.okio) + commonMainApi("org.jetbrains.compose.runtime:runtime:1.7.3") + commonMainApi("org.jetbrains.compose.runtime:runtime-saveable:1.7.3") commonTestImplementation(libs.kotlinx.atomicfu) commonTestImplementation(libs.kotlinx.coroutines.test.common) diff --git a/workflow-core/dependencies/jsRuntimeClasspath.txt b/workflow-core/dependencies/jsRuntimeClasspath.txt index a5e6dd8ce..c85d065fa 100644 --- a/workflow-core/dependencies/jsRuntimeClasspath.txt +++ b/workflow-core/dependencies/jsRuntimeClasspath.txt @@ -1,10 +1,19 @@ com.squareup.okio:okio-js:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation-js:1.7.3 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection-js:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-js:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-js:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-dom-api-compat:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-js:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlin:kotlinx-atomicfu-runtime:1.8.20 -org.jetbrains.kotlinx:atomicfu-js:0.21.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlin:kotlinx-atomicfu-runtime:1.9.24 +org.jetbrains.kotlinx:atomicfu-js:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 diff --git a/workflow-core/dependencies/jvmRuntimeClasspath.txt b/workflow-core/dependencies/jvmRuntimeClasspath.txt index 9bb8a4666..b9ffa5f1a 100644 --- a/workflow-core/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-core/dependencies/jvmRuntimeClasspath.txt @@ -1,10 +1,22 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 org.jetbrains:annotations:23.0.0 diff --git a/workflow-core/dependencies/runtimeClasspath.txt b/workflow-core/dependencies/runtimeClasspath.txt index 3d72a2ba1..0e42581a1 100644 --- a/workflow-core/dependencies/runtimeClasspath.txt +++ b/workflow-core/dependencies/runtimeClasspath.txt @@ -1,11 +1,23 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 org.jetbrains:annotations:23.0.0 diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt index e26614557..872ffc35a 100644 --- a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/BaseRenderContext.kt @@ -9,7 +9,9 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowAction.Companion.noAction +import com.squareup.workflow1.compose.WorkflowComposable import kotlinx.coroutines.CoroutineScope import kotlin.jvm.JvmMultifileClass import kotlin.jvm.JvmName @@ -85,6 +87,32 @@ public interface BaseRenderContext { handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT + /** + * Synchronously composes a [content] function and returns its rendering. Whenever [content] is + * invalidated (i.e. a compose snapshot state object is changed that was previously read by + * [content] or any functions it calls), this workflow will be re-rendered and the relevant + * composables will be recomposed. + * + * The `emitOutput` function passed to [content] should be used to trigger [WorkflowAction]s in + * this workflow via [handler]. Every invocation of `emitOutput` will result [handler]s action + * being sent to this context's [actionSink]. However, it's important for the composable never to + * send to [actionSink] directly because we need to ensure that any state writes the composable + * does invalidate their composables before sending into the [actionSink]. + * + * Any state saved using Compose's state restoration mechanism (e.g. [rememberSaveable]) will be + * saved and restored using the workflow snapshot mechanism. + */ + @WorkflowExperimentalApi + public fun renderComposable( + key: String = "", + handler: (ChildOutputT) -> WorkflowAction, + content: + @WorkflowComposable @Composable + ( + emitOutput: (ChildOutputT) -> Unit + ) -> ChildRenderingT + ): ChildRenderingT + /** * Ensures [sideEffect] is running with the given [key]. * @@ -375,6 +403,22 @@ public fun key: String = "" ): ChildRenderingT = renderChild(child, Unit, key) { noAction() } +/** + * TODO + */ +@WorkflowExperimentalApi +public fun + BaseRenderContext.renderComposable( + key: String = "", + content: + @WorkflowComposable @Composable + () -> ChildRenderingT + ): ChildRenderingT = renderComposable( + key = key, + handler = { noAction() }, + content = { content() } +) + /** * Ensures a [LifecycleWorker] is running. Since [worker] can't emit anything, * it can't trigger any [WorkflowAction]s. diff --git a/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposable.kt b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposable.kt new file mode 100644 index 000000000..77aef796e --- /dev/null +++ b/workflow-core/src/commonMain/kotlin/com/squareup/workflow1/compose/WorkflowComposable.kt @@ -0,0 +1,25 @@ +package com.squareup.workflow1.compose + +import androidx.compose.runtime.ComposableTargetMarker +import com.squareup.workflow1.WorkflowExperimentalApi +import kotlin.annotation.AnnotationRetention.BINARY +import kotlin.annotation.AnnotationTarget.FILE +import kotlin.annotation.AnnotationTarget.FUNCTION +import kotlin.annotation.AnnotationTarget.PROPERTY_GETTER +import kotlin.annotation.AnnotationTarget.TYPE +import kotlin.annotation.AnnotationTarget.TYPE_PARAMETER + +/** + * An annotation that can be used to mark a composable function as being expected to be use in a + * composable function that is also marked or inferred to be marked as a [WorkflowComposable], i.e. + * that can be called from [BaseRenderContext.renderComposable]. + * + * Using this annotation explicitly is rarely necessary as the Compose compiler plugin will infer + * the necessary equivalent annotations automatically. See + * [androidx.compose.runtime.ComposableTarget] for details. + */ +@WorkflowExperimentalApi +@ComposableTargetMarker(description = "Workflow Composable") +@Target(FILE, FUNCTION, PROPERTY_GETTER, TYPE, TYPE_PARAMETER) +@Retention(BINARY) +public annotation class WorkflowComposable diff --git a/workflow-runtime/build.gradle.kts b/workflow-runtime/build.gradle.kts index 0cea60e6e..faf87a1cf 100644 --- a/workflow-runtime/build.gradle.kts +++ b/workflow-runtime/build.gradle.kts @@ -3,6 +3,9 @@ import com.squareup.workflow1.buildsrc.iosWithSimulatorArm64 plugins { id("kotlin-multiplatform") id("published") + // TODO add android target so we can auto-save Parcelables from SaveableStateRegistry. + // id("com.android.library") + id("org.jetbrains.compose") version "1.6.11" } kotlin { @@ -16,6 +19,16 @@ kotlin { if (targets == "kmp" || targets == "js") { js(IR) { browser() } } + // if (targets == "kmp" || targets == "android") { + // androidTarget() + // } + sourceSets { + getByName("commonMain") { + dependencies { + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1") + } + } + } } dependencies { diff --git a/workflow-runtime/dependencies/jsRuntimeClasspath.txt b/workflow-runtime/dependencies/jsRuntimeClasspath.txt index a5e6dd8ce..145e31f4b 100644 --- a/workflow-runtime/dependencies/jsRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jsRuntimeClasspath.txt @@ -1,10 +1,19 @@ com.squareup.okio:okio-js:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation-js:1.7.3 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection-js:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-js:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-js:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-dom-api-compat:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-js:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlin:kotlinx-atomicfu-runtime:1.8.20 -org.jetbrains.kotlinx:atomicfu-js:0.21.0 -org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlin:kotlinx-atomicfu-runtime:1.9.24 +org.jetbrains.kotlinx:atomicfu-js:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 diff --git a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt index 9bb8a4666..63f1d9f81 100644 --- a/workflow-runtime/dependencies/jvmRuntimeClasspath.txt +++ b/workflow-runtime/dependencies/jvmRuntimeClasspath.txt @@ -1,10 +1,22 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt index 58bb7af70..5e4b74638 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/SimpleLoggingWorkflowInterceptor.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession import kotlinx.coroutines.CoroutineScope @@ -150,5 +151,15 @@ public open class SimpleLoggingWorkflowInterceptor : WorkflowInterceptor { } } } + + override fun onRenderComposable( + key: String, + content: @Composable () -> CR, + proceed: (key: String, content: @Composable () -> CR) -> CR + ): CR = proceed(key) { + logMethod("onRenderComposable", session, "key" to key, "content" to content) { + content() + } + } } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt index 0ee986a1a..663bedf8b 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/TreeSnapshot.kt @@ -21,7 +21,7 @@ import kotlin.LazyThreadSafetyMode.NONE */ public class TreeSnapshot internal constructor( workflowSnapshot: Snapshot?, - childTreeSnapshots: () -> Map + childTreeSnapshots: () -> Map ) { /** * The [Snapshot] for the root workflow, or null if that snapshot was empty or unspecified. @@ -35,7 +35,7 @@ public class TreeSnapshot internal constructor( * The map of child snapshots by child [WorkflowNodeId]. Computed lazily so the entire snapshot * tree isn't parsed upfront. */ - internal val childTreeSnapshots: Map + internal val childTreeSnapshots: Map by lazy(NONE, childTreeSnapshots) /** @@ -49,11 +49,10 @@ public class TreeSnapshot internal constructor( sink.writeByteStringWithLength(workflowSnapshot?.bytes ?: ByteString.EMPTY) val childBytes: List> = childTreeSnapshots.mapNotNull { (childId, childSnapshot) -> - val childIdBytes = childId.toByteStringOrNull() ?: return@mapNotNull null val childSnapshotBytes = childSnapshot.toByteString() .takeUnless { it.size == 0 } ?: return@mapNotNull null - return@mapNotNull Pair(childIdBytes, childSnapshotBytes) + return@mapNotNull Pair(childId, childSnapshotBytes) } sink.writeInt(childBytes.size) childBytes.forEach { (childIdBytes, childSnapshotBytes) -> @@ -104,9 +103,8 @@ public class TreeSnapshot internal constructor( buildMap(childSnapshotCount) { for (i in 0 until childSnapshotCount) { val idBytes = source.readByteStringWithLength() - val id = WorkflowNodeId.parse(idBytes) val childSnapshot = source.readByteStringWithLength() - this[id] = parse(childSnapshot) + this[idBytes] = parse(childSnapshot) } } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt index 637615650..9a30c78bf 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/WorkflowInterceptor.kt @@ -1,7 +1,9 @@ package com.squareup.workflow1 +import androidx.compose.runtime.Composable import com.squareup.workflow1.WorkflowInterceptor.RenderContextInterceptor import com.squareup.workflow1.WorkflowInterceptor.WorkflowSession +import com.squareup.workflow1.compose.WorkflowComposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Job import kotlin.coroutines.CoroutineContext @@ -259,6 +261,15 @@ public interface WorkflowInterceptor { handler: (CO) -> WorkflowAction ) -> CR ): CR = proceed(child, childProps, key, handler) + + public fun onRenderComposable( + key: String, + content: @Composable (CO) -> CR, + proceed: ( + key: String, + content: @Composable (CO) -> CR + ) -> CR + ): CR = proceed(key, content) } } @@ -384,6 +395,25 @@ private class InterceptedRenderContext( } } + @OptIn(WorkflowExperimentalApi::class) + override fun renderComposable( + key: String, + handler: (ChildOutputT) -> WorkflowAction, + content: + @WorkflowComposable @Composable + (emitOutput: (ChildOutputT) -> Unit) -> ChildRenderingT + ): ChildRenderingT = interceptor.onRenderComposable( + key = key, + content = content, + proceed = { iKey, iContent -> + baseRenderContext.renderComposable( + key = iKey, + handler = handler, + content = iContent + ) + } + ) + /** * In a block with a CoroutineScope receiver, calls to `coroutineContext` bind * to `CoroutineScope.coroutineContext` instead of `suspend val coroutineContext`. diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt index 9129bb638..ab15134ff 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RealRenderContext.kt @@ -1,10 +1,15 @@ +@file:OptIn(WorkflowExperimentalApi::class) + package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable import com.squareup.workflow1.BaseRenderContext import com.squareup.workflow1.Sink import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.WorkflowExperimentalApi import com.squareup.workflow1.WorkflowTracer +import com.squareup.workflow1.compose.WorkflowComposable import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.channels.SendChannel @@ -22,6 +27,12 @@ internal class RealRenderContext( key: String, handler: (ChildOutputT) -> WorkflowAction ): ChildRenderingT + + fun renderComposable( + key: String, + handler: (ChildOutputT) -> WorkflowAction, + content: @Composable (emitOutput: (ChildOutputT) -> Unit) -> ChildRenderingT + ): ChildRenderingT } interface SideEffectRunner { @@ -62,6 +73,17 @@ internal class RealRenderContext( return renderer.render(child, props, key, handler) } + override fun renderComposable( + key: String, + handler: (ChildOutputT) -> WorkflowAction, + content: + @WorkflowComposable @Composable + (emitOutput: (ChildOutputT) -> Unit) -> ChildRenderingT + ): ChildRenderingT { + checkNotFrozen() + return renderer.renderComposable(key, handler, content) + } + override fun runningSideEffect( key: String, sideEffect: suspend CoroutineScope.() -> Unit diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RecomposeAction.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RecomposeAction.kt new file mode 100644 index 000000000..228a19da4 --- /dev/null +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/RecomposeAction.kt @@ -0,0 +1,13 @@ +package com.squareup.workflow1.internal + +import com.squareup.workflow1.WorkflowAction + +/** + * This action doesn't actually update state, but it's special-cased inside WorkflowNode to always + * act like it updated state, to force a re-render and thus a recomposition. + */ +internal class RecomposeAction : WorkflowAction() { + override fun Updater.apply() { + // Noop + } +} diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt index 7ec3bd6ec..095b9f2ca 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/SubtreeManager.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1.internal +import androidx.compose.runtime.Composable import com.squareup.workflow1.ActionApplied import com.squareup.workflow1.ActionProcessingResult import com.squareup.workflow1.NoopWorkflowInterceptor @@ -13,6 +14,7 @@ import com.squareup.workflow1.WorkflowTracer import com.squareup.workflow1.identifier import com.squareup.workflow1.trace import kotlinx.coroutines.selects.SelectBuilder +import okio.ByteString import kotlin.coroutines.CoroutineContext /** @@ -86,19 +88,22 @@ import kotlin.coroutines.CoroutineContext * first time, they are also restored from their snapshots. */ internal class SubtreeManager( - private var snapshotCache: Map?, + private var snapshotCache: Map?, private val contextForChildren: CoroutineContext, private val emitActionToParent: ( action: WorkflowAction, - childResult: ActionApplied<*> + childResult: ActionApplied<*>? ) -> ActionProcessingResult, private val runtimeConfig: RuntimeConfig, private val workflowTracer: WorkflowTracer?, private val workflowSession: WorkflowSession? = null, private val interceptor: WorkflowInterceptor = NoopWorkflowInterceptor, - private val idCounter: IdCounter? = null + private val idCounter: IdCounter? = null, + private val requestRerender: () -> Unit = {}, + private val sendActionFromComposable: (WorkflowAction) -> Unit ) : RealRenderContext.Renderer { private var children = ActiveStagingList>() + private var composables = ActiveStagingList>() /** * Moves all the nodes that have been accumulated in the staging list to the active list, making @@ -112,6 +117,7 @@ internal class SubtreeManager( children.commitStaging { child -> child.workflowNode.cancel() } + composables.commitStaging(onRemove = WorkflowComposableNode<*, *, *, *, *>::dispose) // Get rid of any snapshots that weren't applied on the first render pass. // They belong to children that were saved but not restarted. snapshotCache = null @@ -144,6 +150,30 @@ internal class SubtreeManager( return stagedChild.render(child.asStatefulWorkflow(), props) } + override fun renderComposable( + key: String, + handler: (ChildOutputT) -> WorkflowAction, + content: @Composable (emitOutput: (ChildOutputT) -> Unit) -> ChildRenderingT + ): ChildRenderingT { + // Prevent duplicate workflows with the same key. + workflowTracer.trace("CheckingUniqueMatchesComposable") { + composables.forEachStaging { + require(key != it.workflowKey) { + "Expected keys to be unique for composable: key=\"$key\"" + } + } + } + + val stagedComposable = workflowTracer.trace("RetainingComposables") { + composables.retainOrCreate( + predicate = { it.workflowKey == key }, + create = { createComposableNode(key, handler) } + ) + } + stagedComposable.setHandler(handler) + return stagedComposable.render(content) + } + /** * Uses [selector] to invoke [WorkflowNode.onNextAction] for every running child workflow this instance * is managing. @@ -160,11 +190,18 @@ internal class SubtreeManager( return empty } - fun createChildSnapshots(): Map { - val snapshots = mutableMapOf() + fun createChildSnapshots(): Map { + val snapshots = mutableMapOf() children.forEachActive { child -> val childWorkflow = child.workflow.asStatefulWorkflow() - snapshots[child.id] = child.workflowNode.snapshot(childWorkflow) + // Skip children who aren't snapshottable. + val childIdBytes = child.id.toByteStringOrNull() ?: return@forEachActive + snapshots[childIdBytes] = child.workflowNode.snapshot(childWorkflow) + } + composables.forEachActive { composable -> + val childIdBytes = composable.getIdBytes() + val childSnapshot = composable.snapshot() + snapshots[childIdBytes] = TreeSnapshot(childSnapshot, childTreeSnapshots = ::emptyMap) } return snapshots } @@ -187,7 +224,7 @@ internal class SubtreeManager( return emitActionToParent(action, actionResult) } - val childTreeSnapshots = snapshotCache?.get(id) + val childTreeSnapshots = snapshotCache?.get(id.toByteStringOrNull()) val workflowNode = WorkflowNode( id = id, @@ -205,4 +242,23 @@ internal class SubtreeManager( return WorkflowChildNode(child, handler, workflowNode) .also { node = it } } + + private fun createComposableNode( + key: String, + handler: (ChildOutputT) -> WorkflowAction, + ): WorkflowComposableNode { + val composableId = WorkflowComposableNode.idFromKey(key) + val composableTreeSnapshot: TreeSnapshot? = snapshotCache?.get(composableId) + + return WorkflowComposableNode( + workflowKey = key, + handler = handler, + snapshot = composableTreeSnapshot?.workflowSnapshot, + coroutineContext = contextForChildren, + requestRerender = requestRerender, + sendAction = sendActionFromComposable, + ).also { + it.start() + } + } } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/UnitApplier.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/UnitApplier.kt new file mode 100644 index 000000000..d3ba559a6 --- /dev/null +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/UnitApplier.kt @@ -0,0 +1,42 @@ +package com.squareup.workflow1.internal + +import androidx.compose.runtime.Applier + +internal object UnitApplier : Applier { + override val current: Unit + get() = Unit + + override fun clear() { + } + + override fun down(node: Unit) { + } + + override fun insertBottomUp( + index: Int, + instance: Unit + ) { + } + + override fun insertTopDown( + index: Int, + instance: Unit + ) { + } + + override fun move( + from: Int, + to: Int, + count: Int + ) { + } + + override fun remove( + index: Int, + count: Int + ) { + } + + override fun up() { + } +} diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.kt new file mode 100644 index 000000000..d258ee7b3 --- /dev/null +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.kt @@ -0,0 +1,221 @@ +package com.squareup.workflow1.internal + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.Composition +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.MonotonicFrameClock +import androidx.compose.runtime.Recomposer +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.LocalSaveableStateRegistry +import androidx.compose.runtime.saveable.SaveableStateRegistry +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.WorkflowAction +import com.squareup.workflow1.internal.InlineLinkedList.InlineListNode +import kotlinx.coroutines.CancellableContinuation +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.launch +import kotlinx.coroutines.suspendCancellableCoroutine +import kotlinx.coroutines.yield +import okio.Buffer +import okio.ByteString +import kotlin.coroutines.ContinuationInterceptor +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext +import kotlin.coroutines.resume +import androidx.compose.runtime.snapshots.Snapshot.Companion as ComposeSnapshot + +internal class WorkflowComposableNode< + OutputT, + RenderingT, + ParentPropsT, + ParentStateT, + ParentOutputT + >( + val workflowKey: String, + private var handler: (OutputT) -> WorkflowAction, + snapshot: Snapshot?, + private val requestRerender: () -> Unit, + private val sendAction: (WorkflowAction) -> Unit, + coroutineContext: CoroutineContext = EmptyCoroutineContext, +) : InlineListNode>, MonotonicFrameClock { + + companion object { + fun idFromKey(key: String): ByteString = Buffer().also { + it.writeUtf8("composable:") + it.writeUtf8(key) + }.snapshot() + + private fun log(message: String) = message.lines().forEach { + println("WorkflowComposableNode $it") + } + } + + override var nextListNode: WorkflowComposableNode<*, *, *, *, *>? = null + + private val coroutineContext = coroutineContext + this + private val recomposer: Recomposer = Recomposer(coroutineContext) + private val composition: Composition = Composition(UnitApplier, recomposer) + private val rendering = mutableStateOf(null) + private val saveableStateRegistry = SaveableStateRegistry(snapshot) + private var frameRequest: FrameRequest<*>? = null + private var frameTimeCounter = 0L + private val emitOutput: (OutputT) -> Unit = { output -> + // TODO set flag in WFNode saying that it will re-render imminently. + + // Allow the function calling this one to finish doing any state updates before triggering + // a rerender. + post { + // Ensure any state updates performed by the caller get to invalidate any compositions that + // read them. If the dispatcher is Main.immediate, this will synchronously call + // withFrameNanos, so that needs to check the flag we set above. + ComposeSnapshot.sendApplyNotifications() + // If dispatcher is Main.immediate this will synchronously perform re-render. + sendAction(handler(output)) + } + } + + private fun post(action: () -> Unit) { + CoroutineScope(coroutineContext).launch { + val dispatcher = coroutineContext[ContinuationInterceptor] as? CoroutineDispatcher + if (dispatcher?.isDispatchNeeded(coroutineContext) != true) { + // TODO verify this actually posts to the main thread on Main.immediate + yield() + } + action() + } + } + + fun start() { + // TODO I think we need more than a simple UNDISPATCHED start to make this work – we have to + // pump the dispatcher until the composition is finished. + CoroutineScope(coroutineContext).launch(start = CoroutineStart.UNDISPATCHED) { + try { + log("runRecomposeAndApplyChanges") + recomposer.runRecomposeAndApplyChanges() + } finally { + composition.dispose() + } + } + } + + fun dispose() { + recomposer.cancel() + } + + /** + * Updates the handler function that will be invoked by [acceptChildOutput]. + */ + fun setHandler(newHandler: (CO) -> WorkflowAction) { + @Suppress("UNCHECKED_CAST") + handler = newHandler as (OutputT) -> WorkflowAction + } + + /** + * Has a separate type parameter to allow type erasure. + */ + fun render(content: @Composable (emitOutput: (O) -> Unit) -> R): R { + log("render setting content") + // log(RuntimeException().stackTraceToString()) + composition.setContent { + LocalsProvider(content) + } + + val frameRequest = this.frameRequest + if (frameRequest != null) { + this.frameRequest = null + val frameTime = frameTimeCounter++ + log("render executing frame with time $frameTime") + frameRequest.execute(frameTime) + log("render finished executing frame with time $frameTime") + } else { + log( + "render no frame request, skipping recomposition " + + "(hasInvalidations=${composition.hasInvalidations})" + ) + } + + log("render returning value: ${rendering.value}") + @Suppress("UNCHECKED_CAST") + return rendering.value as R + } + + /** + * Wrapper around [handler] that allows calling it with erased types. + */ + @Suppress("UNCHECKED_CAST") + fun acceptChildOutput(output: Any?): WorkflowAction = + handler(output as OutputT) + + fun getIdBytes(): ByteString = idFromKey(workflowKey) + + fun snapshot(): Snapshot = saveableStateRegistry.toSnapshot() + + override suspend fun withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { + check(frameRequest == null) { "Frame already requested" } + log("withFrameNanos") + // log(RuntimeException().stackTraceToString()) + return suspendCancellableCoroutine { continuation -> + frameRequest = FrameRequest( + onFrame = onFrame, + continuation = continuation + ) + requestRerender() + } + } + + /** + * [content] has return type `Any?` to allow type erasure. + */ + @Composable + private inline fun LocalsProvider( + crossinline content: @Composable (emitOutput: (Any?) -> Unit) -> Any? + ) { + CompositionLocalProvider( + LocalSaveableStateRegistry provides saveableStateRegistry, + ) { + @Suppress("UNCHECKED_CAST") + rendering.value = content(emitOutput as (Any?) -> Unit) as RenderingT + } + } +} + +private class FrameRequest( + private val onFrame: (frameTimeNanos: Long) -> R, + private val continuation: CancellableContinuation +) { + fun execute(frameTimeNanos: Long) { + val result = onFrame(frameTimeNanos) + continuation.resume(result) + } +} + +internal expect fun SaveableStateRegistry.toSnapshot(): Snapshot + +internal expect fun SaveableStateRegistry(snapshot: Snapshot?): SaveableStateRegistry + +// private fun SaveableStateRegistry.snapshot(): Snapshot = Snapshot.write { sink -> +// val values = performSave() +// sink.writeList(values.toList()) { (key, values) -> +// writeUtf8WithLength(key) +// writeList(values) { value -> +// println("OMG writing value: $value") +// } +// } +// } + +// private fun restoreValuesFromSnapshot(snapshot: Snapshot?): Map> { +// snapshot ?: return emptyMap() +// Buffer().apply { +// write(snapshot.bytes) +// val entries = readList { +// val key = readUtf8WithLength() +// val values = readList { +// // TODO +// } +// key to values +// } +// return entries.toMap() +// } +// } diff --git a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt index 5a2672dff..e7d963882 100644 --- a/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt +++ b/workflow-runtime/src/commonMain/kotlin/com/squareup/workflow1/internal/WorkflowNode.kt @@ -74,6 +74,8 @@ internal class WorkflowNode( private var cachedWorkflowInstance: StatefulWorkflow private var interceptedWorkflowInstance: StatefulWorkflow + private val eventActionsChannel = + Channel>(capacity = UNLIMITED) private val subtreeManager = SubtreeManager( snapshotCache = snapshot?.childTreeSnapshots, contextForChildren = coroutineContext, @@ -82,12 +84,11 @@ internal class WorkflowNode( workflowTracer = workflowTracer, workflowSession = this, interceptor = interceptor, - idCounter = idCounter + idCounter = idCounter, + requestRerender = { eventActionsChannel.trySend(RecomposeAction()) }, ) private val sideEffects = ActiveStagingList() private var lastProps: PropsT = initialProps - private val eventActionsChannel = - Channel>(capacity = UNLIMITED) private var state: StateT private val baseRenderContext = RealRenderContext( @@ -270,7 +271,9 @@ internal class WorkflowNode( // Aggregate the action with the child result, if any. val aggregateActionApplied = actionApplied.copy( // Changing state is sticky, we pass it up if it ever changed. - stateChanged = actionApplied.stateChanged || (childResult?.stateChanged ?: false) + stateChanged = action is RecomposeAction || + actionApplied.stateChanged || + (childResult?.stateChanged ?: false) ) return if (actionApplied.output != null) { emitAppliedActionToParent(aggregateActionApplied) diff --git a/workflow-runtime/src/jsMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.js.kt b/workflow-runtime/src/jsMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.js.kt new file mode 100644 index 000000000..129dee4cd --- /dev/null +++ b/workflow-runtime/src/jsMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.js.kt @@ -0,0 +1,12 @@ +package com.squareup.workflow1.internal + +import androidx.compose.runtime.saveable.SaveableStateRegistry +import com.squareup.workflow1.Snapshot + +internal actual fun SaveableStateRegistry.toSnapshot(): Snapshot { + TODO("Not yet implemented") +} + +internal actual fun SaveableStateRegistry(snapshot: Snapshot?): SaveableStateRegistry { + TODO("Not yet implemented") +} diff --git a/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.jvm.kt b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.jvm.kt new file mode 100644 index 000000000..ec9fa11d4 --- /dev/null +++ b/workflow-runtime/src/jvmMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.jvm.kt @@ -0,0 +1,47 @@ +package com.squareup.workflow1.internal + +import androidx.compose.runtime.MutableIntState +import androidx.compose.runtime.saveable.SaveableStateRegistry +import com.squareup.workflow1.Snapshot +import com.squareup.workflow1.readList +import com.squareup.workflow1.readUtf8WithLength +import com.squareup.workflow1.writeList +import com.squareup.workflow1.writeUtf8WithLength +import okio.Buffer + +internal actual fun SaveableStateRegistry.toSnapshot(): Snapshot { + performSave().let { + println("OMG values: $it") + } + return Snapshot.write { sink -> + val values = performSave() + sink.writeList(values.toList()) { (key, values) -> + writeUtf8WithLength(key) + writeList(values) { value -> + println("OMG writing value: $value") + } + } + } +} + +internal actual fun SaveableStateRegistry(snapshot: Snapshot?): SaveableStateRegistry { + val values = snapshot?.let { parseValues(snapshot) } + return SaveableStateRegistry( + restoredValues = values, + canBeSaved = { true } + ) +} + +private fun parseValues(snapshot: Snapshot): Map> { + Buffer().apply { + write(snapshot.bytes) + val entries = readList { + val key = readUtf8WithLength() + val values = readList { + // TODO + } + key to values + } + return entries.toMap() + } +} diff --git a/workflow-runtime/src/nativeMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.native.kt b/workflow-runtime/src/nativeMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.native.kt new file mode 100644 index 000000000..129dee4cd --- /dev/null +++ b/workflow-runtime/src/nativeMain/kotlin/com/squareup/workflow1/internal/WorkflowComposableNode.native.kt @@ -0,0 +1,12 @@ +package com.squareup.workflow1.internal + +import androidx.compose.runtime.saveable.SaveableStateRegistry +import com.squareup.workflow1.Snapshot + +internal actual fun SaveableStateRegistry.toSnapshot(): Snapshot { + TODO("Not yet implemented") +} + +internal actual fun SaveableStateRegistry(snapshot: Snapshot?): SaveableStateRegistry { + TODO("Not yet implemented") +} diff --git a/workflow-rx2/dependencies/runtimeClasspath.txt b/workflow-rx2/dependencies/runtimeClasspath.txt index 00f28d4a7..f8ef5908c 100644 --- a/workflow-rx2/dependencies/runtimeClasspath.txt +++ b/workflow-rx2/dependencies/runtimeClasspath.txt @@ -1,15 +1,27 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 io.reactivex.rxjava2:rxjava:2.2.21 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-reactive:1.8.0 +org.jetbrains.kotlinx:kotlinx-coroutines-rx2:1.8.0 org.jetbrains:annotations:23.0.0 org.reactivestreams:reactive-streams:1.0.4 diff --git a/workflow-testing/dependencies/runtimeClasspath.txt b/workflow-testing/dependencies/runtimeClasspath.txt index 85ebe34a6..563e655bd 100644 --- a/workflow-testing/dependencies/runtimeClasspath.txt +++ b/workflow-testing/dependencies/runtimeClasspath.txt @@ -1,16 +1,28 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 app.cash.turbine:turbine-jvm:1.0.0 app.cash.turbine:turbine:1.0.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-reflect:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-test-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-test:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-tracing/dependencies/runtimeClasspath.txt b/workflow-tracing/dependencies/runtimeClasspath.txt index 44200e3e2..78edead7a 100644 --- a/workflow-tracing/dependencies/runtimeClasspath.txt +++ b/workflow-tracing/dependencies/runtimeClasspath.txt @@ -1,13 +1,25 @@ +androidx.annotation:annotation-jvm:1.8.0 +androidx.annotation:annotation:1.8.0 +androidx.collection:collection-jvm:1.4.0 +androidx.collection:collection:1.4.0 com.squareup.moshi:moshi-adapters:1.15.0 com.squareup.moshi:moshi:1.15.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable-desktop:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt index a3591f233..31c3f83e9 100644 --- a/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose-tooling/dependencies/releaseRuntimeClasspath.txt @@ -18,10 +18,10 @@ androidx.compose.foundation:foundation-android:1.7.2 androidx.compose.foundation:foundation-layout-android:1.7.2 androidx.compose.foundation:foundation-layout:1.7.2 androidx.compose.foundation:foundation:1.7.2 -androidx.compose.runtime:runtime-android:1.7.2 -androidx.compose.runtime:runtime-saveable-android:1.7.2 -androidx.compose.runtime:runtime-saveable:1.7.2 -androidx.compose.runtime:runtime:1.7.2 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 androidx.compose.ui:ui-android:1.7.2 androidx.compose.ui:ui-geometry-android:1.7.2 androidx.compose.ui:ui-geometry:1.7.2 @@ -76,13 +76,19 @@ androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt index 36a24d66d..a966b812f 100644 --- a/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/compose/dependencies/releaseRuntimeClasspath.txt @@ -14,10 +14,10 @@ androidx.compose.animation:animation-core-android:1.7.2 androidx.compose.animation:animation-core:1.7.2 androidx.compose.foundation:foundation-layout-android:1.7.2 androidx.compose.foundation:foundation-layout:1.7.2 -androidx.compose.runtime:runtime-android:1.7.2 -androidx.compose.runtime:runtime-saveable-android:1.7.2 -androidx.compose.runtime:runtime-saveable:1.7.2 -androidx.compose.runtime:runtime:1.7.2 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 androidx.compose.ui:ui-android:1.7.2 androidx.compose.ui:ui-geometry-android:1.7.2 androidx.compose.ui:ui-geometry:1.7.2 @@ -70,13 +70,19 @@ androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt index c6b005f5a..44e88377b 100644 --- a/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/core-android/dependencies/releaseRuntimeClasspath.txt @@ -1,10 +1,15 @@ androidx.activity:activity:1.8.2 -androidx.annotation:annotation-experimental:1.4.0 +androidx.annotation:annotation-experimental:1.4.1 androidx.annotation:annotation-jvm:1.8.1 androidx.annotation:annotation:1.8.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 -androidx.collection:collection:1.1.0 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.13.1 androidx.core:core:1.13.1 @@ -36,13 +41,19 @@ androidx.versionedparcelable:versionedparcelable:1.1.1 com.google.guava:listenablefuture:1.0 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0 diff --git a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt index 4db9475ce..48caba375 100644 --- a/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt +++ b/workflow-ui/radiography/dependencies/releaseRuntimeClasspath.txt @@ -1,10 +1,15 @@ androidx.activity:activity:1.8.2 -androidx.annotation:annotation-experimental:1.4.0 +androidx.annotation:annotation-experimental:1.4.1 androidx.annotation:annotation-jvm:1.8.1 androidx.annotation:annotation:1.8.1 androidx.arch.core:core-common:2.2.0 androidx.arch.core:core-runtime:2.2.0 -androidx.collection:collection:1.1.0 +androidx.collection:collection-jvm:1.4.4 +androidx.collection:collection:1.4.4 +androidx.compose.runtime:runtime-android:1.7.6 +androidx.compose.runtime:runtime-saveable-android:1.7.6 +androidx.compose.runtime:runtime-saveable:1.7.6 +androidx.compose.runtime:runtime:1.7.6 androidx.concurrent:concurrent-futures:1.1.0 androidx.core:core-ktx:1.13.1 androidx.core:core:1.13.1 @@ -38,13 +43,19 @@ com.squareup.curtains:curtains:1.2.2 com.squareup.okio:okio-jvm:3.3.0 com.squareup.okio:okio:3.3.0 com.squareup.radiography:radiography:2.4.1 +org.jetbrains.compose.annotation-internal:annotation:1.7.3 +org.jetbrains.compose.collection-internal:collection:1.7.3 +org.jetbrains.compose.runtime:runtime-saveable:1.7.3 +org.jetbrains.compose.runtime:runtime:1.7.3 org.jetbrains.kotlin:kotlin-bom:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-common:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.24 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.24 org.jetbrains.kotlin:kotlin-stdlib:1.9.24 -org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3 -org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3 +org.jetbrains.kotlinx:atomicfu-jvm:0.23.2 +org.jetbrains.kotlinx:atomicfu:0.23.2 +org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.1 +org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1 org.jetbrains:annotations:23.0.0