├── .github
└── workflows
│ └── tests.yml
├── .gitignore
├── .idea
└── copyright
│ ├── Apache.xml
│ └── profiles_settings.xml
├── LICENSE.txt
├── build-logic
├── convention
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── kotlin
│ │ ├── kotlin-jvm-convention.gradle.kts
│ │ ├── kotlin-jvm-convention.kt
│ │ └── kotlin-library-convention.gradle.kts
├── settings.gradle.kts
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── build.gradle.kts
├── contributing.md
├── core
├── .gitignore
├── build.gradle.kts
└── src
│ └── commonMain
│ └── kotlin
│ └── com
│ └── tunjid
│ └── mutator
│ └── Mutator.kt
├── coroutines
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── tunjid
│ │ └── mutator
│ │ └── coroutines
│ │ ├── ActionStateFlowMutator.kt
│ │ ├── FlowMutationExt.kt
│ │ ├── FlowMutationStream.kt
│ │ ├── StateFlowMutator.kt
│ │ └── StateMutation.kt
│ └── commonTest
│ └── kotlin
│ └── com
│ └── tunjid
│ └── mutator
│ └── coroutines
│ ├── ActionStateMutatorKtTest.kt
│ ├── FlowMutationStreamKtTest.kt
│ ├── State.kt
│ └── StateProductionKtTest.kt
├── demo
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── tunjid
│ │ └── mutator
│ │ └── demo
│ │ ├── App.kt
│ │ ├── Colors.kt
│ │ ├── Utilities.kt
│ │ ├── editor
│ │ ├── Layouts.kt
│ │ └── Text.kt
│ │ ├── sections
│ │ ├── 1_State_Production_With_Flows.kt
│ │ ├── 2_User_Actions.kt
│ │ ├── 3_Combining.kt
│ │ ├── 4_Merging.kt
│ │ ├── 5_Formalizing.kt
│ │ ├── 6_Conflicts.kt
│ │ ├── 7_Conflict_Resolution.kt
│ │ └── Template.kt
│ │ ├── snails
│ │ ├── Snail.kt
│ │ ├── Snail0.kt
│ │ ├── Snail1.kt
│ │ ├── Snail10.kt
│ │ ├── Snail11.kt
│ │ ├── Snail2.kt
│ │ ├── Snail3.kt
│ │ ├── Snail4.kt
│ │ ├── Snail5.kt
│ │ ├── Snail6.kt
│ │ ├── Snail7.kt
│ │ ├── Snail8.kt
│ │ └── Snail9.kt
│ │ └── udfvisualizer
│ │ └── UDFVisualizerStateHolder.kt
│ ├── desktopMain
│ ├── kotlin
│ │ ├── com
│ │ │ └── tunjid
│ │ │ │ └── mutator
│ │ │ │ └── demo
│ │ │ │ ├── Color.kt
│ │ │ │ ├── editor
│ │ │ │ ├── Editor.kt
│ │ │ │ ├── EditorView.kt
│ │ │ │ ├── Fonts.kt
│ │ │ │ ├── Layouts.kt
│ │ │ │ └── Text.kt
│ │ │ │ ├── platform
│ │ │ │ └── Resources.kt
│ │ │ │ ├── snails
│ │ │ │ └── Snail.kt
│ │ │ │ └── udfvisualizer
│ │ │ │ └── UDFVisualizer.kt
│ │ └── main.desktop.kt
│ └── resources
│ │ └── font
│ │ ├── jetbrainsmono_bold.ttf
│ │ ├── jetbrainsmono_bold_italic.ttf
│ │ ├── jetbrainsmono_extrabold.ttf
│ │ ├── jetbrainsmono_extrabold_italic.ttf
│ │ ├── jetbrainsmono_italic.ttf
│ │ ├── jetbrainsmono_medium.ttf
│ │ ├── jetbrainsmono_medium_italic.ttf
│ │ └── jetbrainsmono_regular.ttf
│ └── jsMain
│ ├── kotlin
│ ├── com
│ │ └── tunjid
│ │ │ └── mutator
│ │ │ └── demo
│ │ │ ├── Color.kt
│ │ │ ├── editor
│ │ │ ├── Layouts.kt
│ │ │ ├── Markdown.kt
│ │ │ └── Text.kt
│ │ │ ├── snails
│ │ │ └── Snail.kt
│ │ │ └── udfvisualizer
│ │ │ └── UDFVisualizer.kt
│ └── main.js.kt
│ └── resources
│ ├── android-chrome-192x192.png
│ ├── android-chrome-512x512.png
│ ├── apple-touch-icon.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon.ico
│ ├── hljs.css
│ ├── index.html
│ ├── manifest.json
│ ├── snail.svg
│ └── styles.css
├── docs
├── META-INF
│ └── MANIFEST.MF
├── android-chrome-192x192.png
├── android-chrome-512x512.png
├── apple-touch-icon.png
├── demo.js
├── demo.js.LICENSE.txt
├── demo.js.map
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── hljs.css
├── index.html
├── manifest.json
├── snail.svg
└── styles.css
├── gradle.properties
├── gradle
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── libraryVersion.properties
├── readme.md
└── settings.gradle.kts
/.github/workflows/tests.yml:
--------------------------------------------------------------------------------
1 | name: JVM Tests
2 |
3 | on:
4 | push:
5 | branches: [ develop ]
6 | pull_request:
7 | branches: [ develop ]
8 |
9 | jobs:
10 | test:
11 |
12 | runs-on: ubuntu-latest
13 |
14 | steps:
15 | - name: checkout
16 | uses: actions/checkout@v2
17 | - name: Set up JDK
18 | uses: actions/setup-java@v2
19 | with:
20 | java-version: '17'
21 | distribution: 'adopt'
22 | - name: Validate Gradle wrapper
23 | uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
24 | - name: JVM tests
25 | run: ./gradlew jvmTest
26 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 | .cxx
15 | local.properties
16 |
--------------------------------------------------------------------------------
/.idea/copyright/Apache.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/build-logic/convention/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | `kotlin-dsl`
19 | }
20 |
21 | group = "com.tunjid.tiler.buildlogic"
22 |
23 | java {
24 | sourceCompatibility = JavaVersion.VERSION_17
25 | targetCompatibility = JavaVersion.VERSION_17
26 | }
27 |
28 | dependencies {
29 | implementation(libs.jetbrains.compose.gradlePlugin)
30 | implementation(libs.kotlin.gradlePlugin)
31 | implementation(libs.dokka.gradlePlugin)
32 | }
33 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/kotlin-jvm-convention.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | configureKotlinJvm()
18 |
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/kotlin-jvm-convention.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import org.gradle.api.Action
18 | import org.gradle.api.JavaVersion
19 | import org.gradle.api.plugins.JavaPluginExtension
20 | import org.gradle.kotlin.dsl.configure
21 | import org.gradle.kotlin.dsl.withType
22 | import org.jetbrains.kotlin.gradle.dsl.KotlinJvmOptions
23 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
24 |
25 | /*
26 | * Copyright 2021 Google LLC
27 | *
28 | * Licensed under the Apache License, Version 2.0 (the "License");
29 | * you may not use this file except in compliance with the License.
30 | * You may obtain a copy of the License at
31 | *
32 | * https://www.apache.org/licenses/LICENSE-2.0
33 | *
34 | * Unless required by applicable law or agreed to in writing, software
35 | * distributed under the License is distributed on an "AS IS" BASIS,
36 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
37 | * See the License for the specific language governing permissions and
38 | * limitations under the License.
39 | */
40 |
41 | /**
42 | * Configure base Kotlin options for JVM (non-Android)
43 | */
44 | internal fun org.gradle.api.Project.configureKotlinJvm() {
45 | extensions.configure {
46 | // Up to Java 11 APIs are available through desugaring
47 | // https://developer.android.com/studio/write/java11-minimal-support-table
48 | sourceCompatibility = JavaVersion.VERSION_11
49 | targetCompatibility = JavaVersion.VERSION_11
50 | }
51 | configureKotlin()
52 | }
53 |
54 | /**
55 | * Configure base Kotlin options
56 | */
57 | private fun org.gradle.api.Project.configureKotlin() {
58 | // Use withType to workaround https://youtrack.jetbrains.com/issue/KT-55947
59 | tasks.withType().configureEach {
60 | kotlinOptions {
61 | // Set JVM target to 11
62 | jvmTarget = JavaVersion.VERSION_11.toString()
63 | freeCompilerArgs = freeCompilerArgs + listOf(
64 | "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
65 | "-Xuse-experimental=kotlinx.coroutines.FlowPreview"
66 | )
67 | }
68 | }
69 | val kotlinOptions = "kotlinOptions"
70 | if (extensions.findByName(kotlinOptions) != null) {
71 | extensions.configure(kotlinOptions, Action {
72 | jvmTarget = JavaVersion.VERSION_11.toString()
73 | freeCompilerArgs = freeCompilerArgs + listOf(
74 | "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
75 | "-Xuse-experimental=kotlinx.coroutines.FlowPreview"
76 | )
77 | })
78 | }
79 | }
--------------------------------------------------------------------------------
/build-logic/convention/src/main/kotlin/kotlin-library-convention.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
18 |
19 | /*
20 | * Copyright 2021 Google LLC
21 | *
22 | * Licensed under the Apache License, Version 2.0 (the "License");
23 | * you may not use this file except in compliance with the License.
24 | * You may obtain a copy of the License at
25 | *
26 | * https://www.apache.org/licenses/LICENSE-2.0
27 | *
28 | * Unless required by applicable law or agreed to in writing, software
29 | * distributed under the License is distributed on an "AS IS" BASIS,
30 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
31 | * See the License for the specific language governing permissions and
32 | * limitations under the License.
33 | */
34 |
35 | plugins {
36 | kotlin("multiplatform")
37 | `maven-publish`
38 | signing
39 | id("org.jetbrains.dokka")
40 | }
41 |
42 | kotlin {
43 | js(IR) {
44 | nodejs()
45 | browser()
46 | }
47 |
48 | jvm()
49 |
50 | ios()
51 | iosSimulatorArm64()
52 | linuxX64()
53 | macosX64()
54 | macosArm64()
55 | mingwX64()
56 | tvos()
57 | tvosSimulatorArm64()
58 | watchos()
59 | watchosSimulatorArm64()
60 |
61 | sourceSets {
62 | @Suppress("UnusedPrivateMember")
63 | val commonMain by getting
64 | val commonTest by getting {
65 | dependencies {
66 | implementation(kotlin("test"))
67 | }
68 | }
69 | all {
70 | languageSettings.apply {
71 | optIn("androidx.compose.animation.ExperimentalAnimationApi")
72 | optIn("androidx.compose.foundation.ExperimentalFoundationApi")
73 | optIn("androidx.compose.ui.ExperimentalComposeUiApi")
74 | optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
75 | optIn("kotlinx.coroutines.FlowPreview")
76 | }
77 | }
78 | }
79 | targets.withType(KotlinNativeTarget::class.java) {
80 | binaries.all {
81 | binaryOptions["memoryModel"] = "experimental"
82 | }
83 | }
84 | configureKotlinJvm()
85 | }
86 |
87 | allprojects {
88 | val versionKey = project.name + "_version"
89 | val libProps = parent?.ext?.get("libProps") as? java.util.Properties
90 | ?: return@allprojects
91 | group = libProps["groupId"] as String
92 | version = libProps[versionKey] as String
93 |
94 | task("printProjectVersion") {
95 | doLast {
96 | println(">> " + project.name + " version is " + version)
97 | }
98 | }
99 | }
100 |
101 | val dokkaHtml by tasks.getting(org.jetbrains.dokka.gradle.DokkaTask::class) {
102 | dokkaSourceSets {
103 | named("iosTest") {
104 | suppress.set(true)
105 | }
106 | }
107 | }
108 |
109 | val javadocJar: TaskProvider by tasks.registering(Jar::class) {
110 | dependsOn(dokkaHtml)
111 | archiveClassifier.set("javadoc")
112 | from(dokkaHtml.outputDirectory)
113 | }
114 |
115 | publishing {
116 | publications {
117 | withType {
118 | artifact(javadocJar)
119 | pom {
120 | name.set(project.name)
121 | description.set("A tiny library for representing mutable states and the types that drive said mutations")
122 | url.set("https://github.com/tunjid/Mutator")
123 | licenses {
124 | license {
125 | name.set("Apache License 2.0")
126 | url.set("https://github.com/tunjid/Mutator/blob/main/LICENSE")
127 | }
128 | }
129 | developers {
130 | developer {
131 | id.set("tunjid")
132 | name.set("Adetunji Dahunsi")
133 | email.set("tjdah100@gmail.com")
134 | }
135 | }
136 | scm {
137 | connection.set("scm:git:github.com/tunjid/Mutator.git")
138 | developerConnection.set("scm:git:ssh://github.com/tunjid/Mutator.git")
139 | url.set("https://github.com/tunjid/Mutator/tree/main")
140 | }
141 | }
142 |
143 | }
144 | }
145 | repositories {
146 | val localProperties = parent?.ext?.get("localProps") as? java.util.Properties
147 | ?: return@repositories
148 |
149 | val publishUrl = localProperties.getProperty("publishUrl")
150 | if (publishUrl != null) {
151 | maven {
152 | name = localProperties.getProperty("repoName")
153 | url = uri(localProperties.getProperty("publishUrl"))
154 | credentials {
155 | username = localProperties.getProperty("username")
156 | password = localProperties.getProperty("password")
157 | }
158 | }
159 | }
160 | }
161 | }
162 |
163 |
164 | signing {
165 | val localProperties = parent?.ext?.get("localProps") as? java.util.Properties
166 | ?: return@signing
167 |
168 | val signingKey = localProperties.getProperty("signingKey")
169 | val signingPassword = localProperties.getProperty("signingPassword")
170 |
171 | if (signingKey != null && signingPassword != null) {
172 | useInMemoryPgpKeys(signingKey, signingPassword)
173 | sign(publishing.publications)
174 | }
175 | }
176 |
177 | val signingTasks = tasks.withType()
178 | tasks.withType().configureEach {
179 | dependsOn(signingTasks)
180 | }
--------------------------------------------------------------------------------
/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS")
18 |
19 | dependencyResolutionManagement {
20 | repositories {
21 | gradlePluginPortal()
22 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
23 | mavenCentral()
24 | google()
25 | }
26 | versionCatalogs {
27 | create("libs") {
28 | from(files("../gradle/libs.versions.toml"))
29 | }
30 | }
31 | }
32 |
33 | rootProject.name = "build-logic"
34 | include(":convention")
35 |
--------------------------------------------------------------------------------
/build-logic/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/build-logic/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/build-logic/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2021 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # https://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 |
17 | #Mon Jul 05 07:23:39 EDT 2021
18 | distributionBase=GRADLE_USER_HOME
19 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
20 | distributionPath=wrapper/dists
21 | zipStorePath=wrapper/dists
22 | zipStoreBase=GRADLE_USER_HOME
23 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | buildscript {
18 | extra.apply {
19 | set("localProps", java.util.Properties().apply {
20 | file("local.properties").let { file ->
21 | if (file.exists()) load(java.io.FileInputStream(file))
22 | }
23 | })
24 | set("libProps", java.util.Properties().apply {
25 | file("libraryVersion.properties").let { file ->
26 | if (file.exists()) load(java.io.FileInputStream(file))
27 | }
28 | })
29 | }
30 | repositories {
31 | google()
32 | mavenCentral()
33 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
34 | maven("https://plugins.gradle.org/m2/")
35 | }
36 | }
37 |
38 | plugins {
39 | alias(libs.plugins.android.application) apply false
40 | alias(libs.plugins.android.library) apply false
41 | alias(libs.plugins.jetbrains.compose) apply false
42 | alias(libs.plugins.jetbrains.dokka) apply false
43 | alias(libs.plugins.kotlin.android) apply false
44 | alias(libs.plugins.kotlin.multiplatform) apply false
45 | }
46 |
--------------------------------------------------------------------------------
/contributing.md:
--------------------------------------------------------------------------------
1 | # How to Contribute
2 |
3 | We'd love to accept your patches and contributions to this project. There are
4 | just a few small guidelines you need to follow.
5 |
6 | ## Contributor License Agreement
7 |
8 | Contributions to this project must be accompanied by a Contributor License
9 | Agreement. You (or your employer) retain the copyright to your contribution,
10 | this simply gives us permission to use and redistribute your contributions as
11 | part of the project. Head over to to see
12 | your current agreements on file or to sign a new one.
13 |
14 | You generally only need to submit a CLA once, so if you've already submitted one
15 | (even if it was for a different project), you probably don't need to do it
16 | again.
17 |
--------------------------------------------------------------------------------
/core/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | id("kotlin-library-convention")
19 | }
20 |
--------------------------------------------------------------------------------
/core/src/commonMain/kotlin/com/tunjid/mutator/Mutator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator
18 |
19 | /**
20 | * Type definition for a unit of change for a type [State].
21 | */
22 | typealias Mutation = State.() -> State
23 |
24 | typealias StateHolder = StateMutator
25 |
26 | interface StateMutator {
27 | val state: State
28 | }
29 |
30 | interface ActionStateMutator : StateMutator {
31 | val accept: (Action) -> Unit
32 | }
33 |
34 | /**
35 | * Syntactic sugar for creating a [Mutation]
36 | */
37 | inline fun mutationOf(noinline mutation: State.() -> State): Mutation = mutation
38 |
39 | /**
40 | * Identity [Mutation] function; semantically a no op [Mutation]
41 | */
42 | fun identity(): Mutation = mutationOf { this }
43 |
44 | /**
45 | * Combines two [Mutation] instances into a single [Mutation]
46 | */
47 | operator fun Mutation.plus(other: Mutation) = mutationOf inner@{
48 | val result = this@plus(this@inner)
49 | other.invoke(result)
50 | }
51 |
--------------------------------------------------------------------------------
/coroutines/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/coroutines/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | id("kotlin-library-convention")
19 | }
20 |
21 | kotlin {
22 | sourceSets {
23 | val commonMain by getting {
24 | dependencies {
25 | implementation(project(":core"))
26 | implementation(libs.kotlinx.coroutines.core)
27 | }
28 | }
29 | val commonTest by getting {
30 | dependencies {
31 | implementation(libs.kotlinx.coroutines.test)
32 | implementation(libs.cashapp.turbine)
33 | }
34 | }
35 | all {
36 | languageSettings.apply {
37 | optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
38 | optIn("kotlinx.coroutines.FlowPreview")
39 | }
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/coroutines/src/commonMain/kotlin/com/tunjid/mutator/coroutines/ActionStateFlowMutator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import com.tunjid.mutator.ActionStateMutator
20 | import com.tunjid.mutator.Mutation
21 | import com.tunjid.mutator.StateHolder
22 | import kotlinx.coroutines.CoroutineScope
23 | import kotlinx.coroutines.channels.Channel
24 | import kotlinx.coroutines.flow.Flow
25 | import kotlinx.coroutines.flow.MutableStateFlow
26 | import kotlinx.coroutines.flow.SharingStarted
27 | import kotlinx.coroutines.flow.StateFlow
28 | import kotlinx.coroutines.flow.emptyFlow
29 | import kotlinx.coroutines.flow.receiveAsFlow
30 | import kotlinx.coroutines.flow.stateIn
31 | import kotlinx.coroutines.launch
32 |
33 | typealias SuspendingStateHolder = StateHolder State>
34 |
35 | /**
36 | * Defines a [ActionStateMutator] to convert a [Flow] of [Action] into a [StateFlow] of [State].
37 | *
38 | * [this@actionStateFlowMutator]: The [CoroutineScope] for the resulting [StateFlow]. Any [Action]s sent if there are no
39 | * subscribers to the output [StateFlow] will suspend until there is as least one subscriber.
40 | *
41 | * [initialState]: The seed state for the resulting [StateFlow].
42 | *
43 | * [started]: Semantics for the "hotness" of the output [StateFlow] @see [Flow.stateIn]
44 | *
45 | * [stateTransform]: Further transformations o be applied to the output [StateFlow]
46 | *
47 | * [actionTransform]: Defines the transformations to the [Action] [Flow] to create [Mutation]s
48 | * of state that will be reduced into the [initialState]. This is often achieved through the
49 | * [toMutationStream] [Flow] extension function.
50 | */
51 | fun CoroutineScope.actionStateFlowMutator(
52 | initialState: State,
53 | started: SharingStarted = SharingStarted.WhileSubscribed(DEFAULT_STOP_TIMEOUT_MILLIS),
54 | inputs: List>> = emptyList(),
55 | stateTransform: (Flow) -> Flow = { it },
56 | actionTransform: SuspendingStateHolder.(Flow) -> Flow> = { emptyFlow() }
57 | ): ActionStateMutator> = ActionStateFlowMutator(
58 | coroutineScope = this,
59 | initialState = initialState,
60 | started = started,
61 | inputs = inputs,
62 | stateTransform = stateTransform,
63 | actionTransform = actionTransform
64 | )
65 |
66 | private class ActionStateFlowMutator(
67 | coroutineScope: CoroutineScope,
68 | initialState: State,
69 | started: SharingStarted,
70 | inputs: List>>,
71 | stateTransform: (Flow) -> Flow = { it },
72 | actionTransform: SuspendingStateHolder.(Flow) -> Flow>
73 | ) : ActionStateMutator>,
74 | suspend () -> State {
75 |
76 | private val actions = Channel()
77 |
78 | // Allows for reading the current state in concurrent contexts.
79 | // Note that it suspends to prevent reading state before this class is fully constructed
80 | private val stateReader = object : SuspendingStateHolder {
81 | override val state: suspend () -> State = this@ActionStateFlowMutator
82 | }
83 |
84 | override val state: StateFlow =
85 | coroutineScope.mutateState(
86 | initialState = initialState,
87 | started = started,
88 | stateTransform = stateTransform,
89 | inputs = inputs + actionTransform(stateReader, actions.receiveAsFlow())
90 | )
91 |
92 | override val accept: (Action) -> Unit = { action ->
93 | coroutineScope.launch {
94 | actions.send(action)
95 | }
96 | }
97 |
98 | // There's no need to suspend when reading the value, however the caller must suspend to
99 | // invoke it
100 | override suspend fun invoke(): State = state.value
101 | }
102 |
103 | /**
104 | * Represents a type as a StateFlowMutator of itself with no op [Action]s.
105 | *
106 | * This is typically useful for testing or previews
107 | */
108 | fun State.asNoOpStateFlowMutator(): ActionStateMutator> =
109 | object : ActionStateMutator> {
110 | override val accept: (Action) -> Unit = {}
111 | override val state: StateFlow = MutableStateFlow(this@asNoOpStateFlowMutator)
112 | }
113 |
114 | internal const val DEFAULT_STOP_TIMEOUT_MILLIS = 5000L
115 |
--------------------------------------------------------------------------------
/coroutines/src/commonMain/kotlin/com/tunjid/mutator/coroutines/FlowMutationExt.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import com.tunjid.mutator.Mutation
20 | import com.tunjid.mutator.mutationOf
21 | import kotlinx.coroutines.flow.Flow
22 | import kotlinx.coroutines.flow.FlowCollector
23 | import kotlinx.coroutines.flow.flatMapConcat
24 | import kotlinx.coroutines.flow.flatMapLatest
25 | import kotlinx.coroutines.flow.flow
26 | import kotlinx.coroutines.flow.map
27 | import kotlinx.coroutines.flow.mapLatest
28 |
29 | /**
30 | * Maps each emission of [T] into a single mutation of [State]
31 | * @see [Flow.map]
32 | */
33 | inline fun Flow.mapToMutation(
34 | crossinline mapper: State.(T) -> State
35 | ): Flow> =
36 | map { mutationOf { mapper(it) } }
37 |
38 | /**
39 | * Maps the latest emission of [T] into a single mutation of [State]
40 | * @see [Flow.mapLatest]
41 | */
42 | inline fun Flow.mapLatestToMutation(
43 | crossinline mapper: State.(T) -> State
44 | ): Flow> =
45 | mapLatest { mutationOf { mapper(it) } }
46 |
47 | /**
48 | * Maps each emission of [T] into multiple mutations of [State]
49 | * @see [Flow.flatMapConcat]
50 | */
51 | inline fun Flow.mapToManyMutations(
52 | crossinline block: suspend FlowCollector>.(T) -> Unit
53 | ): Flow> =
54 | flatMapConcat { flow { block(it) } }
55 |
56 | /**
57 | * Maps the latest emission of [T] into multiple mutations of [State]
58 | * @see [Flow.flatMapLatest]
59 | */
60 | inline fun Flow.mapLatestToManyMutations(
61 | crossinline block: suspend FlowCollector>.(T) -> Unit
62 | ): Flow> =
63 | flatMapLatest { flow { block(it) } }
64 |
--------------------------------------------------------------------------------
/coroutines/src/commonMain/kotlin/com/tunjid/mutator/coroutines/StateFlowMutator.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import com.tunjid.mutator.Mutation
20 | import com.tunjid.mutator.StateMutator
21 | import kotlinx.coroutines.CoroutineScope
22 | import kotlinx.coroutines.Job
23 | import kotlinx.coroutines.channels.Channel
24 | import kotlinx.coroutines.flow.Flow
25 | import kotlinx.coroutines.flow.FlowCollector
26 | import kotlinx.coroutines.flow.SharingStarted
27 | import kotlinx.coroutines.flow.StateFlow
28 | import kotlinx.coroutines.flow.receiveAsFlow
29 | import kotlinx.coroutines.launch
30 |
31 | fun CoroutineScope.stateFlowMutator(
32 | initialState: State,
33 | started: SharingStarted = SharingStarted.WhileSubscribed(),
34 | inputs: List>>
35 | ) = StateFlowMutator(
36 | scope = this,
37 | initialState = initialState,
38 | started = started,
39 | inputs = inputs
40 | )
41 |
42 | /**
43 | * Manges state production for [State]
44 | */
45 | class StateFlowMutator internal constructor(
46 | private val scope: CoroutineScope,
47 | initialState: State,
48 | started: SharingStarted = SharingStarted.WhileSubscribed(DEFAULT_STOP_TIMEOUT_MILLIS),
49 | inputs: List>> = emptyList(),
50 | stateTransform: (Flow) -> Flow = { it },
51 | ) : StateMutator> {
52 | private val mutationChannel = Channel>()
53 | private val mutationSender = FlowCollector(mutationChannel::send)
54 |
55 | override val state = scope.mutateState(
56 | initialState = initialState,
57 | started = started,
58 | stateTransform = stateTransform,
59 | inputs = inputs + mutationChannel.receiveAsFlow()
60 | )
61 |
62 | /**
63 | * Runs [block] in parallel with any other tasks submitted to [launch]. [block] is only ever run if there is an
64 | * active collector of [state], and is managed under the [SharingStarted] policy passed to this [StateMutator].
65 | *
66 | * If there are no observers of [state] at the invocation of [launch], the Coroutine launched will suspend till
67 | * a collector begins to collect from [state].
68 | */
69 | fun launch(
70 | block: suspend FlowCollector>.() -> Unit
71 | ): Job = scope.launch {
72 | block(mutationSender)
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/coroutines/src/commonMain/kotlin/com/tunjid/mutator/coroutines/StateMutation.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import com.tunjid.mutator.Mutation
20 | import kotlinx.coroutines.CoroutineScope
21 | import kotlinx.coroutines.flow.Flow
22 | import kotlinx.coroutines.flow.SharingStarted
23 | import kotlinx.coroutines.flow.StateFlow
24 | import kotlinx.coroutines.flow.emitAll
25 | import kotlinx.coroutines.flow.flow
26 | import kotlinx.coroutines.flow.merge
27 | import kotlinx.coroutines.flow.onEach
28 | import kotlinx.coroutines.flow.scan
29 | import kotlinx.coroutines.flow.stateIn
30 |
31 | /**
32 | * Produces a [StateFlow] by merging [inputs] and reducing them into an
33 | * [initialState] state within [this] [CoroutineScope]
34 | */
35 | fun CoroutineScope.mutateState(
36 | initialState: State,
37 | started: SharingStarted,
38 | inputs: List>>,
39 | stateTransform: (Flow) -> Flow = { it }
40 | ): StateFlow {
41 | // Set the seed for the state
42 | var seed = initialState
43 |
44 | // Use the flow factory function to capture the seed variable
45 | return stateTransform(
46 | flow {
47 | emitAll(
48 | merge(*inputs.toTypedArray())
49 | // Reduce into the seed so if resubscribed, the last value of state is persisted
50 | // when the flow pipeline is started again
51 | .reduceInto(seed)
52 | // Set seed after each emission
53 | .onEach { seed = it }
54 | )
55 | }
56 | )
57 | .stateIn(
58 | scope = this,
59 | started = started,
60 | initialValue = seed
61 | )
62 | }
63 |
64 | fun Flow>.reduceInto(initialState: State): Flow =
65 | scan(initialState) { state, mutation -> mutation(state) }
66 |
--------------------------------------------------------------------------------
/coroutines/src/commonTest/kotlin/com/tunjid/mutator/coroutines/ActionStateMutatorKtTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import app.cash.turbine.test
20 | import com.tunjid.mutator.ActionStateMutator
21 | import kotlinx.coroutines.CoroutineScope
22 | import kotlinx.coroutines.Dispatchers
23 | import kotlinx.coroutines.SupervisorJob
24 | import kotlinx.coroutines.cancel
25 | import kotlinx.coroutines.delay
26 | import kotlinx.coroutines.flow.SharingStarted
27 | import kotlinx.coroutines.flow.StateFlow
28 | import kotlinx.coroutines.flow.map
29 | import kotlinx.coroutines.flow.onEach
30 | import kotlinx.coroutines.test.StandardTestDispatcher
31 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
32 | import kotlinx.coroutines.test.resetMain
33 | import kotlinx.coroutines.test.runTest
34 | import kotlinx.coroutines.test.setMain
35 | import kotlin.test.AfterTest
36 | import kotlin.test.BeforeTest
37 | import kotlin.test.Test
38 | import kotlin.test.assertEquals
39 |
40 | class ActionStateMutatorKtTest {
41 |
42 | private val testDispatcher = UnconfinedTestDispatcher()
43 |
44 | @BeforeTest
45 | fun setUp() {
46 | Dispatchers.setMain(testDispatcher)
47 | }
48 |
49 | @AfterTest
50 | fun tearDown() {
51 | Dispatchers.resetMain()
52 | }
53 |
54 | @Test
55 | fun actionStateFlowMutatorRemembersLastValue() = runTest {
56 | val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
57 |
58 | val mutator = scope.actionStateFlowMutator(
59 | initialState = State(),
60 | started = SharingStarted.WhileSubscribed(),
61 | actionTransform = { actions ->
62 | actions.toMutationStream {
63 | when (val action = type()) {
64 | is IntAction.Add -> action.flow
65 | .map { it.mutation }
66 |
67 | is IntAction.Subtract -> action.flow
68 | .map { it.mutation }
69 | }
70 | }
71 | }
72 | )
73 | mutator.state.test {
74 | // Read first emission, should be the initial value
75 | assertEquals(State(), awaitItem())
76 |
77 | // Add 2, then wait till its processed
78 | mutator.accept(IntAction.Add(value = 2))
79 | assertEquals(expected = State(count = 2.0), actual = awaitItem())
80 |
81 | cancelAndIgnoreRemainingEvents()
82 | }
83 |
84 | // At this point, there are no subscribers and the upstream is cancelled
85 | assertEquals(
86 | expected = State(count = 2.0),
87 | actual = mutator.state.value
88 | )
89 |
90 | // Subscribe again
91 | mutator.state.test {
92 | // Read first emission, should be the last value seen
93 | assertEquals(expected = State(2.0), actual = awaitItem())
94 |
95 | // Subtract 5, then wait till its processed
96 | mutator.accept(IntAction.Subtract(value = 5))
97 | assertEquals(State(count = -3.0), awaitItem())
98 |
99 | cancelAndIgnoreRemainingEvents()
100 | }
101 |
102 | scope.cancel()
103 | }
104 |
105 | @Test
106 | fun actionStateFlowMutatorSuspendsWithNoSubscribers() = runTest {
107 | val dispatcher = StandardTestDispatcher()
108 | val scope = CoroutineScope(SupervisorJob() + dispatcher)
109 |
110 | val mutator = scope.actionStateFlowMutator(
111 | initialState = State(),
112 | started = SharingStarted.WhileSubscribed(),
113 | actionTransform = { actions ->
114 | actions
115 | .onEach { delay(1000) }
116 | .toMutationStream {
117 | when (val action = type()) {
118 | is IntAction.Add -> action.flow
119 | .map { it.mutation }
120 |
121 | is IntAction.Subtract -> action.flow
122 | .map { it.mutation }
123 | }
124 | }
125 | }
126 | )
127 |
128 | mutator.accept(IntAction.Add(value = 1))
129 | mutator.accept(IntAction.Add(value = 1))
130 | mutator.accept(IntAction.Add(value = 1))
131 | mutator.accept(IntAction.Add(value = 1))
132 |
133 | mutator.state.test {
134 | // Read first emission, should be the initial value
135 | assertEquals(State(), awaitItem())
136 |
137 | // Queue should be drained
138 | dispatcher.scheduler.advanceTimeBy(1000)
139 | assertEquals(State(1.0), awaitItem())
140 |
141 | dispatcher.scheduler.advanceTimeBy(1000)
142 | assertEquals(State(2.0), awaitItem())
143 |
144 | dispatcher.scheduler.advanceTimeBy(1000)
145 | assertEquals(State(3.0), awaitItem())
146 |
147 | dispatcher.scheduler.advanceTimeBy(1000)
148 | assertEquals(State(4.0), awaitItem())
149 |
150 | cancelAndIgnoreRemainingEvents()
151 | }
152 |
153 | scope.cancel()
154 | }
155 |
156 | @Test
157 | fun noOpOperatorCompiles() {
158 | val noOpActionStateMutator: ActionStateMutator> = State().asNoOpStateFlowMutator()
159 | noOpActionStateMutator.accept(IntAction.Add(value = 1))
160 |
161 | assertEquals(
162 | expected = State(),
163 | actual = noOpActionStateMutator.state.value
164 | )
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/coroutines/src/commonTest/kotlin/com/tunjid/mutator/coroutines/State.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import com.tunjid.mutator.Mutation
20 | import com.tunjid.mutator.mutationOf
21 |
22 | data class State(
23 | val count: Double = 0.0
24 | )
25 |
26 | sealed class Action
27 |
28 | sealed class IntAction : Action() {
29 | abstract val value: Int
30 |
31 | data class Add(override val value: Int) : IntAction()
32 | data class Subtract(override val value: Int) : IntAction()
33 | }
34 |
35 | sealed class DoubleAction : Action() {
36 | abstract val value: Double
37 |
38 | data class Divide(override val value: Double) : DoubleAction()
39 | data class Multiply(override val value: Double) : DoubleAction()
40 | }
41 |
42 | val Action.mutation: Mutation
43 | get() = when (this) {
44 | is IntAction.Add -> mutationOf { copy(count = count + value) }
45 | is IntAction.Subtract -> mutationOf { copy(count = count - value) }
46 | is DoubleAction.Divide -> mutationOf { copy(count = count / value) }
47 | is DoubleAction.Multiply -> mutationOf { copy(count = count * value) }
48 | }
49 |
--------------------------------------------------------------------------------
/coroutines/src/commonTest/kotlin/com/tunjid/mutator/coroutines/StateProductionKtTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.coroutines
18 |
19 | import app.cash.turbine.test
20 | import com.tunjid.mutator.Mutation
21 | import com.tunjid.mutator.mutationOf
22 | import com.tunjid.mutator.plus
23 | import kotlinx.coroutines.CoroutineScope
24 | import kotlinx.coroutines.Dispatchers
25 | import kotlinx.coroutines.delay
26 | import kotlinx.coroutines.flow.MutableSharedFlow
27 | import kotlinx.coroutines.flow.SharingStarted
28 | import kotlinx.coroutines.flow.flow
29 | import kotlinx.coroutines.test.TestScope
30 | import kotlinx.coroutines.test.UnconfinedTestDispatcher
31 | import kotlinx.coroutines.test.advanceTimeBy
32 | import kotlinx.coroutines.test.resetMain
33 | import kotlinx.coroutines.test.runTest
34 | import kotlinx.coroutines.test.setMain
35 | import kotlin.test.AfterTest
36 | import kotlin.test.BeforeTest
37 | import kotlin.test.Test
38 | import kotlin.test.assertEquals
39 |
40 | class StateProductionKtTest {
41 |
42 | private val testDispatcher = UnconfinedTestDispatcher()
43 | private lateinit var scope: CoroutineScope
44 | private lateinit var eventMutations: MutableSharedFlow>
45 |
46 | @BeforeTest
47 | fun setUp() {
48 | Dispatchers.setMain(testDispatcher)
49 | scope = TestScope(testDispatcher)
50 | eventMutations = MutableSharedFlow()
51 | }
52 |
53 | @AfterTest
54 | fun tearDown() {
55 | Dispatchers.resetMain()
56 | }
57 |
58 | @Test
59 | fun test_simple_state_production() = runTest {
60 | val state = scope.mutateState(
61 | initialState = State(),
62 | started = SharingStarted.WhileSubscribed(),
63 | inputs = listOf(
64 | eventMutations
65 | )
66 | )
67 |
68 | state.test {
69 | assertEquals(State(0.0), awaitItem())
70 | eventMutations.emit { copy(count = count + 1) }
71 | assertEquals(State(1.0), awaitItem())
72 |
73 | cancelAndIgnoreRemainingEvents()
74 | }
75 | }
76 |
77 | @Test
78 | fun test_state_production_persists_after_unsubscribing() = runTest {
79 | val state = scope.mutateState(
80 | initialState = State(),
81 | started = SharingStarted.WhileSubscribed(),
82 | inputs = listOf(
83 | eventMutations
84 | )
85 | )
86 |
87 | // Subscribe the first time
88 | state.test {
89 | assertEquals(State(0.0), awaitItem())
90 | eventMutations.emit { copy(count = count + 1) }
91 | assertEquals(State(1.0), awaitItem())
92 |
93 | cancelAndIgnoreRemainingEvents()
94 | }
95 |
96 | // Subscribe again. The state flow count should not be reset by the pipeline restarting
97 | state.test {
98 | assertEquals(State(1.0), awaitItem())
99 | cancelAndIgnoreRemainingEvents()
100 | }
101 | }
102 |
103 | @Test
104 | fun test_state_production_with_merged_flows() = runTest {
105 | val state = scope.mutateState(
106 | initialState = State(),
107 | started = SharingStarted.WhileSubscribed(),
108 | inputs = listOf(
109 | eventMutations,
110 | flow {
111 | delay(1000)
112 | emit(mutationOf { copy(count = 3.0) })
113 |
114 | delay(1000)
115 | emit(mutationOf { copy(count = 7.0) })
116 | }
117 | )
118 | )
119 |
120 | state.test {
121 | assertEquals(State(0.0), awaitItem())
122 |
123 | advanceTimeBy(1200)
124 | assertEquals(State(3.0), awaitItem())
125 |
126 | advanceTimeBy(1200)
127 | assertEquals(State(7.0), awaitItem())
128 |
129 | eventMutations.emit { copy(count = 0.0) }
130 | assertEquals(State(0.0), awaitItem())
131 |
132 | cancelAndIgnoreRemainingEvents()
133 | }
134 | }
135 |
136 | @Test
137 | fun test_state_change_addition() {
138 | val additionMutation = mutationOf {
139 | copy(count = count + 1)
140 | } +
141 | mutationOf {
142 | copy(count = count + 1)
143 | } +
144 | mutationOf {
145 | copy(count = count + 1)
146 | }
147 |
148 | val state = additionMutation(State())
149 |
150 | assertEquals(State(3.0), state)
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/demo/build.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | plugins {
18 | kotlin("multiplatform")
19 | id("org.jetbrains.compose")
20 | id("kotlin-jvm-convention")
21 | }
22 |
23 | kotlin {
24 | jvm("desktop")
25 | js(IR) {
26 | browser()
27 | binaries.executable()
28 | }
29 | sourceSets {
30 | val commonMain by getting {
31 | dependencies {
32 | implementation(project(":core"))
33 | implementation(project(":coroutines"))
34 |
35 | implementation(libs.jetbrains.compose.runtime)
36 |
37 | implementation(libs.kotlinx.coroutines.core)
38 | }
39 | }
40 | val commonTest by getting {
41 | dependencies {
42 | implementation(libs.kotlinx.coroutines.test)
43 | implementation(libs.cashapp.turbine)
44 | }
45 | }
46 | val desktopMain by getting {
47 | dependencies {
48 | implementation(compose.desktop.currentOs)
49 |
50 | implementation(libs.jetbrains.compose.animation)
51 | implementation(libs.jetbrains.compose.material)
52 | implementation(libs.jetbrains.compose.foundation.layout)
53 |
54 | implementation(libs.richtext.commonmark)
55 | }
56 | }
57 | val jsMain by getting {
58 | dependencies {
59 | implementation(compose.html.core)
60 |
61 | implementation(libs.kotlin.wrappers.react)
62 | implementation(libs.kotlin.wrappers.reactDom)
63 |
64 | implementation(npm("react", "18.2.0"))
65 | implementation(npm("react-dom", "18.2.0"))
66 | implementation(npm("highlight.js", "10.7.2"))
67 | implementation(npm("react-markdown", "6.0.3"))
68 |
69 | }
70 | }
71 | all {
72 | languageSettings.apply {
73 | optIn("kotlinx.coroutines.ExperimentalCoroutinesApi")
74 | optIn("kotlinx.coroutines.FlowPreview")
75 | }
76 | }
77 | }
78 |
79 | }
80 |
81 | compose.desktop {
82 | application {
83 | mainClass = "Main_desktopKt"
84 |
85 | nativeDistributions {
86 | targetFormats(
87 | org.jetbrains.compose.desktop.application.dsl.TargetFormat.Dmg,
88 | org.jetbrains.compose.desktop.application.dsl.TargetFormat.Msi,
89 | org.jetbrains.compose.desktop.application.dsl.TargetFormat.Deb
90 | )
91 | packageName = "Falling Balls"
92 | packageVersion = "1.0.0"
93 |
94 | windows {
95 | menuGroup = "Compose Examples"
96 | // see https://wixtoolset.org/documentation/manual/v3/howtos/general/generate_guids.html
97 | upgradeUuid = "18159995-d967-4CD2-8885-77BFA97CFA9F"
98 | }
99 | }
100 | }
101 | }
102 |
103 | compose.experimental {
104 | web.application {}
105 | }
106 |
107 | tasks.withType {
108 | kotlinOptions.jvmTarget = "11"
109 | }
110 |
111 | kotlin {
112 | targets.withType {
113 | binaries.all {
114 | // TODO: the current compose binary surprises LLVM, so disable checks for now.
115 | freeCompilerArgs += "-Xdisable-phases=VerifyBitcode"
116 | }
117 | }
118 | }
119 |
120 | // a temporary workaround for a bug in jsRun invocation - see https://youtrack.jetbrains.com/issue/KT-48273
121 | afterEvaluate {
122 | rootProject.extensions.configure {
123 | versions.webpackDevServer.version = "4.0.0"
124 | versions.webpackCli.version = "4.10.0"
125 | nodeVersion = "16.0.0"
126 | }
127 | }
128 |
129 |
130 | // TODO: remove when https://youtrack.jetbrains.com/issue/KT-50778 fixed
131 | project.tasks.withType(org.jetbrains.kotlin.gradle.dsl.KotlinJsCompile::class.java).configureEach {
132 | kotlinOptions.freeCompilerArgs += listOf(
133 | "-Xir-dce-runtime-diagnostic=log"
134 | )
135 | }
136 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/App.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.ContainerLayout
21 | import com.tunjid.mutator.demo.sections.Section1
22 | import com.tunjid.mutator.demo.sections.Section2
23 | import com.tunjid.mutator.demo.sections.Section3
24 | import com.tunjid.mutator.demo.sections.Section4
25 | import com.tunjid.mutator.demo.sections.Section5
26 | import com.tunjid.mutator.demo.sections.Section6
27 | import com.tunjid.mutator.demo.sections.Section7
28 |
29 | @Composable
30 | fun App(
31 | ) {
32 | ContainerLayout {
33 | Section1()
34 | Section2()
35 | Section3()
36 | Section4()
37 | Section5()
38 | Section6()
39 | Section7()
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/Colors.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo
18 |
19 | import kotlin.math.pow
20 | import kotlin.math.roundToInt
21 |
22 | expect class Color(color: Long) {
23 | val argb: Int
24 | val r: Int
25 | val g: Int
26 | val b: Int
27 |
28 | fun isBright(): Boolean
29 |
30 | companion object {
31 | val Black: Color
32 | val LightGray: Color
33 | }
34 | }
35 |
36 |
37 | object MutedColors {
38 | private val mutedColors = listOf(
39 | Color(0xFF2980b9), // Belize Hole
40 | Color(0xFF2c3e50), // Midnight Blue
41 | Color(0xFFc0392b), // Pomegranate
42 | Color(0xFF16a085), // Green Sea
43 | Color(0xFF7f8c8d), // Concrete
44 | Color(0xFFFFFF),
45 | )
46 |
47 | private val darkerMutedColors = listOf(
48 | Color(0xFF304233),
49 | Color(0xFF353b45),
50 | Color(0xFF392e3a),
51 | Color(0xFF3e2a2a),
52 | Color(0xFF474747),
53 | Color(0x292929)
54 | )
55 |
56 | fun colors(isDark: Boolean) = if (isDark) darkerMutedColors else mutedColors
57 | }
58 |
59 | fun interpolateColors(fraction: Float, startValue: Int, endValue: Int): Int {
60 | val startA = (startValue shr 24 and 0xff) / 255.0f
61 | var startR = (startValue shr 16 and 0xff) / 255.0f
62 | var startG = (startValue shr 8 and 0xff) / 255.0f
63 | var startB = (startValue and 0xff) / 255.0f
64 | val endA = (endValue shr 24 and 0xff) / 255.0f
65 | var endR = (endValue shr 16 and 0xff) / 255.0f
66 | var endG = (endValue shr 8 and 0xff) / 255.0f
67 | var endB = (endValue and 0xff) / 255.0f
68 |
69 | // convert from sRGB to linear
70 | startR = startR.toDouble().pow(2.2).toFloat()
71 | startG = startG.toDouble().pow(2.2).toFloat()
72 | startB = startB.toDouble().pow(2.2).toFloat()
73 | endR = endR.toDouble().pow(2.2).toFloat()
74 | endG = endG.toDouble().pow(2.2).toFloat()
75 | endB = endB.toDouble().pow(2.2).toFloat()
76 |
77 | // compute the interpolated color in linear space
78 | var a = startA + fraction * (endA - startA)
79 | var r = startR + fraction * (endR - startR)
80 | var g = startG + fraction * (endG - startG)
81 | var b = startB + fraction * (endB - startB)
82 |
83 | // convert back to sRGB in the [0..255] range
84 | a *= 255.0f
85 | r = r.toDouble().pow(1.0 / 2.2).toFloat() * 255.0f
86 | g = g.toDouble().pow(1.0 / 2.2).toFloat() * 255.0f
87 | b = b.toDouble().pow(1.0 / 2.2).toFloat() * 255.0f
88 |
89 | return a.roundToInt() shl 24 or (r.roundToInt() shl 16) or (g.roundToInt() shl 8) or b.roundToInt()
90 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/Utilities.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo
18 |
19 | import kotlinx.coroutines.CoroutineScope
20 | import kotlinx.coroutines.delay
21 | import kotlinx.coroutines.flow.Flow
22 | import kotlinx.coroutines.flow.SharingStarted
23 | import kotlinx.coroutines.flow.asFlow
24 | import kotlinx.coroutines.flow.flatMapLatest
25 | import kotlinx.coroutines.flow.flow
26 | import kotlinx.coroutines.flow.map
27 | import kotlinx.coroutines.flow.onEach
28 | import kotlinx.coroutines.flow.scan
29 | import kotlinx.coroutines.flow.shareIn
30 |
31 | const val SPEED = 1600L
32 |
33 | enum class Speed(val multiplier: Int) {
34 | One(1), Two(2), Three(3), Four(4)
35 | }
36 |
37 | val Speed.text get() = when(this) {
38 | Speed.One -> "1x"
39 | Speed.Two -> "2x"
40 | Speed.Three -> "3x"
41 | Speed.Four -> "4x"
42 | }
43 |
44 | fun intervalFlow(intervalMillis: Long) = flow {
45 | var value = 0L
46 | while (true) {
47 | emit(++value)
48 | delay(intervalMillis)
49 | }
50 | }
51 |
52 | fun CoroutineScope.speedFlow() =
53 | intervalFlow(4000)
54 | .map { Speed.values().random() }
55 | .shareIn(this, SharingStarted.WhileSubscribed())
56 |
57 | fun Flow.toInterval(): Flow =
58 | flatMapLatest {
59 | intervalFlow(SPEED / it.multiplier)
60 | }
61 |
62 | fun Flow.toProgress(): Flow =
63 | scan(0f) { progress, _ -> (progress + 1) % 100 }
64 |
65 | data class ColorInterpolationProgress(
66 | val progress: Float,
67 | val colors: List
68 | )
69 |
70 | fun interpolateColors(
71 | startColors: IntArray,
72 | endColors: IntArray
73 | ): Flow = (0..100).asFlow()
74 | .onEach { delay(50) }
75 | .map { percentage ->
76 | val fraction = percentage.toFloat() / 100F
77 | val colors = startColors
78 | .zip(endColors)
79 | .map { (start, end) ->
80 | interpolateColors(
81 | fraction = fraction,
82 | startValue = start,
83 | endValue = end
84 | )
85 | }
86 | .map(Int::toLong)
87 | .map(::Color)
88 |
89 | ColorInterpolationProgress(
90 | progress = fraction,
91 | colors = colors,
92 | )
93 | }
94 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/editor/Layouts.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.runtime.Composable
20 |
21 | @Composable
22 | expect fun ContainerLayout(content: @Composable () -> Unit)
23 |
24 | @Composable
25 | expect fun SectionLayout(content: @Composable () -> Unit)
26 |
27 | @Composable
28 | expect fun VerticalLayout(content: @Composable () -> Unit)
29 |
30 | @Composable
31 | expect fun HorizontalLayout(
32 | centerOnMainAxis: Boolean = false,
33 | content: @Composable () -> Unit
34 | )
35 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/editor/Text.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.runtime.Composable
20 |
21 | @Composable
22 | expect fun Paragraph(text: String)
23 |
24 | @Composable
25 | expect fun Markdown(content: String)
26 |
27 | @Composable
28 | expect fun CallToAction(
29 | text: String,
30 | centerText: Boolean = false,
31 | )
32 |
33 | @Composable
34 | expect fun CodeBlock(content: String)
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/sections/2_User_Actions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.sections
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.CallToAction
21 | import com.tunjid.mutator.demo.editor.CodeBlock
22 | import com.tunjid.mutator.demo.editor.Markdown
23 | import com.tunjid.mutator.demo.editor.SectionLayout
24 | import com.tunjid.mutator.demo.snails.Snail4
25 |
26 | @Composable
27 | fun Section2() {
28 | SectionLayout {
29 | Markdown(oneMarkdown)
30 | CodeBlock(twoCode)
31 | Snail4()
32 | CallToAction(snail3Cta)
33 | Markdown(threeMarkdown)
34 | }
35 | }
36 |
37 | private val oneMarkdown = """
38 | # User actions as `Flows`
39 |
40 | These sources of change mentioned above can be from anything that can be modeled as a flow, be it reading data from a database or user actions. Imagine if we want to be able to change the snail's color on a whim. We can add a [`MutableSharedFlow`](https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-shared-flow/) for the color of the snail to our state holder class, and then `combine` it with the rest of the flows to produce the state.
41 | """.trimIndent()
42 |
43 | private val twoCode = """
44 | data class Snail4State(
45 | val progress: Float = 0f,
46 | val speed: Speed = Speed.One,
47 | val color: Color = Color.Blue,
48 | val colors: List = listOf()
49 | )
50 |
51 | class Snail4StateHolder(
52 | scope: CoroutineScope
53 | ) {
54 |
55 | private val speed: Flow = …
56 |
57 | private val progress: Flow = …
58 |
59 | private val color: MutableStateFlow = MutableStateFlow(Color.Cyan)
60 |
61 | val state: StateFlow = combine(
62 | progress,
63 | speed,
64 | color,
65 | ::Snail4State
66 | )
67 | .stateIn(...)
68 |
69 | fun setSnailColor(color: Color) {
70 | this.color.value = color
71 | }
72 | }
73 | """.trimIndent()
74 |
75 | private val snail3Cta = """
76 | Tap on a color to change the snail's color.
77 | """.trimIndent()
78 |
79 | private val threeMarkdown = """
80 | When the user wants to change the color, the `setColor` method is called, the `color` `MutableStateFlow` has its value updated, and it is emitted into the stream produced by the `combine` function to produce the new state.
81 | """.trimIndent()
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/sections/3_Combining.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.sections
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.CodeBlock
21 | import com.tunjid.mutator.demo.editor.Markdown
22 | import com.tunjid.mutator.demo.editor.SectionLayout
23 |
24 | @Composable
25 | fun Section3() {
26 | SectionLayout {
27 | Markdown(oneMarkdown)
28 | CodeBlock(twoCode)
29 | Markdown(threeMarkdown)
30 | }
31 | }
32 |
33 | private val oneMarkdown = """
34 | # Combining changes in state
35 |
36 | Combining sources of state as a general means of state production is rather robust, lends itself to a wide range of cases, and works well for simple to moderate state production pipelines. It also scales linearly, that is each source of state change will need to be added to the `combine` function. This poses a problem for states with more than 5 sources of change as the `combine` function allows for at most 5 flows. This is called the [arity](https://en.wikipedia.org/wiki/Arity) of the `combine` function.
37 |
38 | One way around this is to combine the sources of state change into intermediate states, before combining them again into the final state.
39 | """.trimIndent()
40 |
41 | private val twoCode = """
42 | data class LargeState(
43 | val property1: Int,
44 | val property2: Int,
45 | val property3: Int,
46 | val property4: Int,
47 | val property5: Int,
48 | val property6: Int,
49 | val property7: Int,
50 | val property8: Int,
51 | )
52 |
53 | data class IntermediateState1(
54 | val property1: Int,
55 | val property2: Int,
56 | val property3: Int,
57 | val property4: Int,
58 | )
59 |
60 | data class IntermediateState2(
61 | val property5: Int,
62 | val property6: Int,
63 | val property7: Int,
64 | val property8: Int,
65 | )
66 |
67 | fun intFlow() = flowOf(1)
68 |
69 | class LargeStateHolder {
70 | private val intermediateState1 = combine(
71 | intFlow(),
72 | intFlow(),
73 | intFlow(),
74 | intFlow(),
75 | ::IntermediateState1
76 | )
77 |
78 | private val intermediateState2 = combine(
79 | intFlow(),
80 | intFlow(),
81 | intFlow(),
82 | intFlow(),
83 | ::IntermediateState2
84 | )
85 |
86 | val state = combine(
87 | intermediateState1,
88 | intermediateState2
89 | ) { state1, state2 ->
90 | LargeState(
91 | state1.property1,
92 | state1.property2,
93 | state1.property3,
94 | state1.property4,
95 | state2.property5,
96 | state2.property5,
97 | state2.property6,
98 | state2.property7,
99 | )
100 | }
101 | }
102 | """.trimIndent()
103 |
104 | private val threeMarkdown = """
105 | The above works, but can be difficult to maintain. As new sources of state are added or removed over time, especially those from user events, the signatures of the `IntermediateState` instances will need to change to accommodate the arity of the `combine` function causing cascading changes. This brings us to another state production approach, merging sources of change.
106 | """.trimIndent()
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/sections/5_Formalizing.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.sections
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.CallToAction
21 | import com.tunjid.mutator.demo.editor.CodeBlock
22 | import com.tunjid.mutator.demo.editor.Markdown
23 | import com.tunjid.mutator.demo.editor.SectionLayout
24 | import com.tunjid.mutator.demo.snails.Snail7
25 |
26 | @Composable
27 | fun Section5() = SectionLayout {
28 | Markdown(oneMarkdown)
29 | CodeBlock(twoCode)
30 | Markdown(threeMarkdown)
31 | CodeBlock(fourCode)
32 | Snail7()
33 | CallToAction(Snail7Cta)
34 | }
35 |
36 | private val oneMarkdown = """
37 | # Formalizing state production
38 |
39 | The merge approach can be formalized into an extension function on the `CoroutineScope` the state is produced in:
40 | """.trimIndent()
41 |
42 | private val twoCode = """
43 | fun CoroutineScope.stateFlowMutator(
44 | initialState: State,
45 | started: SharingStarted = SharingStarted.WhileSubscribed(),
46 | inputs: List>>
47 | ) : StateFlowMutator
48 | """.trimIndent()
49 |
50 | private val threeMarkdown = """
51 | Where the use of it in the snail example becomes:
52 | """.trimIndent()
53 |
54 | private val fourCode = """
55 | class Snail7StateHolder(
56 | private val scope: CoroutineScope
57 | ) {
58 |
59 | private val speedChanges: Flow> = …
60 |
61 | private val progressChanges: Flow> = …
62 |
63 | private val stateMutator = scope.stateFlowMutator(
64 | initialState = Snail7State(),
65 | started = SharingStarted.WhileSubscribed(),
66 | inputs = listOf(
67 | speedChanges,
68 | progressChanges,
69 | )
70 | )
71 |
72 | val state: StateFlow = stateMutator.state
73 |
74 | fun setSnailColor(index: Int) = stateMutator.launch {
75 | emit { copy(color = colors[index]) }
76 | }
77 |
78 | fun setProgress(progress: Float) = stateMutator.launch {
79 | emit { copy(progress = progress) }
80 | }
81 | }
82 | """.trimIndent()
83 |
84 | private val Snail7Cta = """
85 | Snail7 is identical to Snail6; just with a formalized state production approach.
86 | """.trimIndent()
87 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/sections/6_Conflicts.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.sections
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.CallToAction
21 | import com.tunjid.mutator.demo.editor.CodeBlock
22 | import com.tunjid.mutator.demo.editor.Markdown
23 | import com.tunjid.mutator.demo.editor.SectionLayout
24 | import com.tunjid.mutator.demo.snails.Snail8
25 |
26 | @Composable
27 | fun Section6() = SectionLayout {
28 | Markdown(oneMarkdown)
29 | CodeBlock(twoCode)
30 | CallToAction(composeAnimationApiCta)
31 | Snail8()
32 | CallToAction(Snail8Cta)
33 | Markdown(threeMarkdown)
34 | }
35 |
36 | private val oneMarkdown = """
37 | # Conflicts in state production
38 |
39 | So far, all sources of state change have been relatively harmonious. That is, they don't conflict or compete with each other. Sometimes however, especially with asynchronous data sources, state changes can clash. This most often occurs when user events trigger a set of cascading state changes.
40 |
41 | This is best illustrated with an example. Say we wanted to expose our snail to the experience of day and night. Not only that, we want the experience to animate smoothly. We can do this by adding a new method:
42 | """.trimIndent()
43 |
44 | private val twoCode = """
45 | class Snail8StateHolder(
46 | private val scope: CoroutineScope
47 | ) {
48 |
49 | private val stateMutator = scope.stateFlowMutator(
50 | ...
51 | )
52 | ...
53 |
54 | fun setMode(isDark: Boolean) = stateMutator.launch {
55 | emit { copy(isDark = isDark) }
56 | /* Collect from a flow that animates color changes */
57 | interpolateColors(
58 | startColors = state.value.colors.map(Color::argb).toIntArray(),
59 | endColors = MutedColors.colors(isDark).map(Color::argb).toIntArray()
60 | ).collect { (progress, colors) ->
61 | emit {
62 | copy(
63 | colorInterpolationProgress = progress,
64 | colors = colors
65 | )
66 | }
67 | }
68 | }
69 | }
70 | """.trimIndent()
71 |
72 | private val composeAnimationApiCta = """
73 | In Jetpack Compose apps, animating color changes is best done with the [animateColorAsState](https://developer.android.com/reference/kotlin/androidx/compose/animation/package-summary#animateColorAsState(androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationSpec,kotlin.Function1)) APIs instead of manually as shown in the example above. The example is merely used to demonstrate long running operations that cause state changes, like uploading a file with a progress bar.
74 | """.trimIndent()
75 |
76 | private val Snail8Cta = """
77 | Tap the toggle button to switch between light and dark modes for the snail. Notice that tapping in quick succession will cause the UI to flicker as state changes conflict.
78 | """.trimIndent()
79 |
80 | private val threeMarkdown = """
81 | In the above, each time setMode is called, we first update the state to the new mode, and then crucially, begin to collect from a finite flow that updates the colors available to choose from.
82 |
83 | The source of conflict here is the `interpolateColors` `Flow`. If `setMode` is called twice in quick succession, there will be two instances of the `interpolateColors` `Flow` running which may cause the UI to flicker.
84 | """.trimIndent()
85 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/sections/Template.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.sections
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.editor.Markdown
21 | import com.tunjid.mutator.demo.editor.SectionLayout
22 |
23 | @Composable
24 | fun Section() = SectionLayout {
25 | Markdown(oneMarkdown)
26 | }
27 |
28 | private val oneMarkdown = """
29 |
30 | """.trimIndent()
31 |
32 | private val twoCode = """
33 |
34 | """.trimIndent()
35 |
36 | private val threeMarkdown = """
37 |
38 | """.trimIndent()
39 |
40 | private val fourCode = """
41 |
42 | """.trimIndent()
43 |
44 | private val fiveMarkdown = """
45 |
46 | """.trimIndent()
47 |
48 | private val sixCode = """
49 |
50 | """.trimIndent()
51 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.Color
21 | import com.tunjid.mutator.demo.MutedColors
22 | import com.tunjid.mutator.demo.Speed
23 |
24 |
25 | @Composable
26 | expect fun Illustration(
27 | content: @Composable () -> Unit
28 | )
29 |
30 | @Composable
31 | expect fun SnailCard(
32 | color: Color = Color(0xFFFFFF),
33 | content: @Composable () -> Unit
34 | )
35 |
36 | @Composable
37 | expect fun SnailText(
38 | color: Color,
39 | text: String
40 | )
41 |
42 | @Composable
43 | expect fun Snail(
44 | progress: Float,
45 | speed: Speed = Speed.One,
46 | color: Color = MutedColors.colors(isDark = false).first(),
47 | onValueChange: (Float) -> Unit = {}
48 | )
49 |
50 | @Composable
51 | expect fun ColorSwatch(
52 | colors: List = listOf(),
53 | onColorClicked: (Int) -> Unit = {}
54 | )
55 |
56 | @Composable
57 | expect fun ToggleButton(
58 | progress: Float,
59 | onClicked: () -> Unit = {}
60 | )
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail0.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.collectAsState
21 | import androidx.compose.runtime.getValue
22 | import androidx.compose.runtime.remember
23 | import androidx.compose.runtime.rememberCoroutineScope
24 | import com.tunjid.mutator.demo.intervalFlow
25 | import com.tunjid.mutator.demo.toProgress
26 | import kotlinx.coroutines.CoroutineScope
27 | import kotlinx.coroutines.flow.SharingStarted
28 | import kotlinx.coroutines.flow.StateFlow
29 | import kotlinx.coroutines.flow.stateIn
30 |
31 | class Snail0StateHolder(
32 | scope: CoroutineScope
33 | ) {
34 | val progress: StateFlow = intervalFlow(500)
35 | .toProgress()
36 | .stateIn(
37 | scope = scope,
38 | started = SharingStarted.WhileSubscribed(),
39 | initialValue = 0f
40 | )
41 | }
42 |
43 | @Composable
44 | fun Snail0() {
45 | val scope = rememberCoroutineScope()
46 | val stateHolder = remember { Snail0StateHolder(scope) }
47 | val state by stateHolder.progress.collectAsState()
48 |
49 | Snail(
50 | progress = state,
51 | )
52 | }
53 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail1.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.demo.Color
26 | import com.tunjid.mutator.demo.MutedColors
27 | import com.tunjid.mutator.demo.editor.Paragraph
28 | import com.tunjid.mutator.demo.editor.VerticalLayout
29 | import com.tunjid.mutator.demo.udfvisualizer.Event
30 | import com.tunjid.mutator.demo.udfvisualizer.Marble
31 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
32 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
33 | import kotlinx.coroutines.flow.MutableStateFlow
34 | import kotlinx.coroutines.flow.StateFlow
35 | import kotlinx.coroutines.flow.asStateFlow
36 | import kotlinx.coroutines.flow.update
37 |
38 | data class Snail1State(
39 | val progress: Float = 0f,
40 | val color: Color = MutedColors.colors(false).first(),
41 | val colors: List = MutedColors.colors(false)
42 | )
43 |
44 | class Snail1StateHolder {
45 |
46 | private val _state = MutableStateFlow(Snail1State())
47 | val state: StateFlow = _state.asStateFlow()
48 |
49 | fun setSnailColor(index: Int) {
50 | _state.update {
51 | it.copy(color = it.colors[index])
52 | }
53 | }
54 |
55 | fun setProgress(progress: Float) {
56 | _state.update {
57 | it.copy(progress = progress)
58 | }
59 | }
60 | }
61 |
62 | @Composable
63 | fun Snail1() {
64 | val scope = rememberCoroutineScope()
65 | val stateHolder = remember { Snail1StateHolder() }
66 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
67 | val state by stateHolder.state.collectAsState()
68 |
69 | LaunchedEffect(state) {
70 | udfStateHolder.accept(
71 | Event.StateChange(
72 | color = state.color,
73 | metadata = Marble.Metadata.Text(state.progress.toString())
74 | )
75 | )
76 | }
77 |
78 | Illustration {
79 | SnailCard {
80 | VerticalLayout {
81 | Paragraph(
82 | text = "Snail1"
83 | )
84 | Snail(
85 | progress = state.progress,
86 | color = state.color,
87 | onValueChange = {
88 | stateHolder.setProgress(it)
89 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Text(it.toString())))
90 | }
91 | )
92 | ColorSwatch(
93 | colors = state.colors,
94 | onColorClicked = {
95 | stateHolder.setSnailColor(it)
96 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
97 | }
98 | )
99 | Paragraph(
100 | text = "Progress: ${state.progress}"
101 | )
102 | }
103 | }
104 | UDFVisualizer(udfStateHolder)
105 | }
106 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail2.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.demo.editor.Paragraph
26 | import com.tunjid.mutator.demo.editor.VerticalLayout
27 | import com.tunjid.mutator.demo.intervalFlow
28 | import com.tunjid.mutator.demo.toProgress
29 | import com.tunjid.mutator.demo.udfvisualizer.Marble
30 | import com.tunjid.mutator.demo.udfvisualizer.Event
31 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
32 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
33 | import kotlinx.coroutines.CoroutineScope
34 | import kotlinx.coroutines.flow.SharingStarted
35 | import kotlinx.coroutines.flow.StateFlow
36 | import kotlinx.coroutines.flow.stateIn
37 |
38 | class Snail2StateHolder(
39 | scope: CoroutineScope
40 | ) {
41 | val progress: StateFlow = intervalFlow(500)
42 | .toProgress()
43 | .stateIn(
44 | scope = scope,
45 | started = SharingStarted.WhileSubscribed(),
46 | initialValue = 0f
47 | )
48 | }
49 |
50 | @Composable
51 | fun Snail2() {
52 | val scope = rememberCoroutineScope()
53 | val stateHolder = remember { Snail2StateHolder(scope) }
54 | val udf = remember { udfVisualizerStateHolder(scope) }
55 | val state by stateHolder.progress.collectAsState()
56 |
57 | LaunchedEffect(state) {
58 | udf.accept(Event.StateChange(metadata = Marble.Metadata.Text(state.toString())))
59 | }
60 |
61 | Illustration {
62 | SnailCard {
63 | VerticalLayout {
64 | Paragraph(
65 | text = "Snail2"
66 | )
67 | Snail(
68 | progress = state,
69 | )
70 | Paragraph(
71 | text = "Progress: $state"
72 | )
73 | }
74 | }
75 | UDFVisualizer(udf)
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail3.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.demo.Speed
26 | import com.tunjid.mutator.demo.editor.Paragraph
27 | import com.tunjid.mutator.demo.editor.VerticalLayout
28 | import com.tunjid.mutator.demo.speedFlow
29 | import com.tunjid.mutator.demo.text
30 | import com.tunjid.mutator.demo.toInterval
31 | import com.tunjid.mutator.demo.toProgress
32 | import com.tunjid.mutator.demo.udfvisualizer.Marble
33 | import com.tunjid.mutator.demo.udfvisualizer.Event
34 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
35 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
36 | import kotlinx.coroutines.CoroutineScope
37 | import kotlinx.coroutines.flow.Flow
38 | import kotlinx.coroutines.flow.SharingStarted
39 | import kotlinx.coroutines.flow.StateFlow
40 | import kotlinx.coroutines.flow.combine
41 | import kotlinx.coroutines.flow.stateIn
42 |
43 |
44 | data class Snail3State(
45 | val progress: Float = 0f,
46 | val speed: Speed = Speed.One,
47 | )
48 |
49 | class Snail3StateHolder(
50 | scope: CoroutineScope
51 | ) {
52 | private val speed: Flow = scope.speedFlow()
53 |
54 | private val progress: Flow = speed
55 | .toInterval()
56 | .toProgress()
57 |
58 | val state: StateFlow = combine(
59 | progress,
60 | speed,
61 | ::Snail3State
62 | )
63 | .stateIn(
64 | scope = scope,
65 | started = SharingStarted.WhileSubscribed(),
66 | initialValue = Snail3State()
67 | )
68 | }
69 |
70 | @Composable
71 | fun Snail3() {
72 | val scope = rememberCoroutineScope()
73 | val stateHolder = remember { Snail3StateHolder(scope) }
74 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
75 | val state by stateHolder.state.collectAsState()
76 |
77 | LaunchedEffect(state) {
78 | udfStateHolder.accept(
79 | Event.StateChange(
80 | metadata = Marble.Metadata.Text(state.progress.toString())
81 | )
82 | )
83 | }
84 |
85 | Illustration {
86 | SnailCard {
87 | VerticalLayout {
88 | Paragraph(
89 | text = "Snail3"
90 | )
91 | Snail(
92 | progress = state.progress,
93 | )
94 | Paragraph(
95 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
96 | )
97 | }
98 | }
99 | UDFVisualizer(udfStateHolder)
100 | }
101 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail4.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.demo.Color
26 | import com.tunjid.mutator.demo.MutedColors
27 | import com.tunjid.mutator.demo.Speed
28 | import com.tunjid.mutator.demo.editor.Paragraph
29 | import com.tunjid.mutator.demo.editor.VerticalLayout
30 | import com.tunjid.mutator.demo.speedFlow
31 | import com.tunjid.mutator.demo.text
32 | import com.tunjid.mutator.demo.toInterval
33 | import com.tunjid.mutator.demo.toProgress
34 | import com.tunjid.mutator.demo.udfvisualizer.Marble
35 | import com.tunjid.mutator.demo.udfvisualizer.Event
36 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
37 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
38 | import kotlinx.coroutines.CoroutineScope
39 | import kotlinx.coroutines.flow.Flow
40 | import kotlinx.coroutines.flow.MutableStateFlow
41 | import kotlinx.coroutines.flow.SharingStarted
42 | import kotlinx.coroutines.flow.StateFlow
43 | import kotlinx.coroutines.flow.combine
44 | import kotlinx.coroutines.flow.stateIn
45 |
46 |
47 | data class Snail4State(
48 | val progress: Float = 0f,
49 | val speed: Speed = Speed.One,
50 | val color: Color = MutedColors.colors(false).first(),
51 | val colors: List = MutedColors.colors(false)
52 | )
53 |
54 | class Snail4StateHolder(
55 | scope: CoroutineScope
56 | ) {
57 |
58 | private val speed: Flow = scope.speedFlow()
59 |
60 | private val progress: Flow = speed
61 | .toInterval()
62 | .toProgress()
63 |
64 | private val color: MutableStateFlow = MutableStateFlow(
65 | MutedColors.colors(isDark = false).first()
66 | )
67 |
68 | val state: StateFlow = combine(
69 | progress,
70 | speed,
71 | color,
72 | ::Snail4State
73 | )
74 | .stateIn(
75 | scope = scope,
76 | started = SharingStarted.WhileSubscribed(),
77 | initialValue = Snail4State()
78 | )
79 |
80 | fun setSnailColor(index: Int) {
81 | this.color.value = state.value.colors[index]
82 | }
83 | }
84 |
85 | @Composable
86 | fun Snail4() {
87 | val scope = rememberCoroutineScope()
88 | val stateHolder = remember { Snail4StateHolder(scope) }
89 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
90 | val state by stateHolder.state.collectAsState()
91 |
92 | LaunchedEffect(state) {
93 | udfStateHolder.accept(
94 | Event.StateChange(
95 | color = state.color,
96 | metadata = Marble.Metadata.Text(state.progress.toString())
97 | )
98 | )
99 | }
100 |
101 | Illustration {
102 | SnailCard {
103 | VerticalLayout {
104 | Paragraph(
105 | text = "Snail4"
106 | )
107 | Snail(
108 | progress = state.progress,
109 | color = state.color,
110 | )
111 | ColorSwatch(
112 | colors = state.colors,
113 | onColorClicked = {
114 | stateHolder.setSnailColor(it)
115 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
116 | }
117 | )
118 | Paragraph(
119 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
120 | )
121 | }
122 | }
123 | UDFVisualizer(udfStateHolder)
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail5.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.Mutation
26 | import com.tunjid.mutator.demo.Color
27 | import com.tunjid.mutator.demo.MutedColors
28 | import com.tunjid.mutator.demo.Speed
29 | import com.tunjid.mutator.demo.editor.Paragraph
30 | import com.tunjid.mutator.demo.editor.VerticalLayout
31 | import com.tunjid.mutator.demo.speedFlow
32 | import com.tunjid.mutator.demo.text
33 | import com.tunjid.mutator.demo.toInterval
34 | import com.tunjid.mutator.demo.udfvisualizer.Marble
35 | import com.tunjid.mutator.demo.udfvisualizer.Event
36 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
37 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
38 | import com.tunjid.mutator.mutationOf
39 | import kotlinx.coroutines.CoroutineScope
40 | import kotlinx.coroutines.flow.Flow
41 | import kotlinx.coroutines.flow.MutableSharedFlow
42 | import kotlinx.coroutines.flow.SharingStarted
43 | import kotlinx.coroutines.flow.StateFlow
44 | import kotlinx.coroutines.flow.map
45 | import kotlinx.coroutines.flow.merge
46 | import kotlinx.coroutines.flow.scan
47 | import kotlinx.coroutines.flow.stateIn
48 | import kotlinx.coroutines.launch
49 |
50 |
51 | data class Snail5State(
52 | val progress: Float = 0f,
53 | val speed: Speed = Speed.One,
54 | val color: Color = MutedColors.colors(false).first(),
55 | val colors: List = MutedColors.colors(false)
56 | )
57 |
58 | class Snail5StateHolder(
59 | private val scope: CoroutineScope
60 | ) {
61 |
62 | private val speed: Flow = scope.speedFlow()
63 |
64 | private val speedChanges: Flow> = speed
65 | .map { mutationOf { copy(speed = it) } }
66 |
67 | private val progressChanges: Flow> = speed
68 | .toInterval()
69 | .map { mutationOf { copy(progress = (progress + 1) % 100) } }
70 |
71 | private val changeEvents = MutableSharedFlow>()
72 |
73 | val state: StateFlow = merge(
74 | progressChanges,
75 | speedChanges,
76 | changeEvents,
77 | )
78 | .scan(Snail5State()) { state, mutation -> mutation(state) }
79 | .stateIn(
80 | scope = scope,
81 | started = SharingStarted.WhileSubscribed(),
82 | initialValue = Snail5State()
83 | )
84 |
85 | fun setSnailColor(index: Int) {
86 | scope.launch {
87 | changeEvents.emit { copy(color = colors[index]) }
88 | }
89 | }
90 | }
91 |
92 | @Composable
93 | fun Snail5() {
94 | val scope = rememberCoroutineScope()
95 | val stateHolder = remember { Snail5StateHolder(scope) }
96 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
97 | val state by stateHolder.state.collectAsState()
98 |
99 | LaunchedEffect(state) {
100 | udfStateHolder.accept(
101 | Event.StateChange(
102 | color = state.color,
103 | metadata = Marble.Metadata.Text(state.progress.toString())
104 | )
105 | )
106 | }
107 |
108 | Illustration {
109 | SnailCard {
110 | VerticalLayout {
111 | Paragraph(
112 | text = "Snail5"
113 | )
114 | Snail(
115 | progress = state.progress,
116 | color = state.color,
117 | )
118 | ColorSwatch(
119 | colors = state.colors,
120 | onColorClicked = {
121 | stateHolder.setSnailColor(it)
122 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
123 | }
124 | )
125 | Paragraph(
126 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
127 | )
128 | }
129 | }
130 | UDFVisualizer(udfStateHolder)
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail6.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.Mutation
26 | import com.tunjid.mutator.demo.Color
27 | import com.tunjid.mutator.demo.MutedColors
28 | import com.tunjid.mutator.demo.Speed
29 | import com.tunjid.mutator.demo.editor.Paragraph
30 | import com.tunjid.mutator.demo.editor.VerticalLayout
31 | import com.tunjid.mutator.demo.speedFlow
32 | import com.tunjid.mutator.demo.text
33 | import com.tunjid.mutator.demo.toInterval
34 | import com.tunjid.mutator.demo.udfvisualizer.Marble
35 | import com.tunjid.mutator.demo.udfvisualizer.Event
36 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
37 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
38 | import com.tunjid.mutator.mutationOf
39 | import kotlinx.coroutines.CoroutineScope
40 | import kotlinx.coroutines.flow.Flow
41 | import kotlinx.coroutines.flow.MutableSharedFlow
42 | import kotlinx.coroutines.flow.SharingStarted
43 | import kotlinx.coroutines.flow.StateFlow
44 | import kotlinx.coroutines.flow.map
45 | import kotlinx.coroutines.flow.merge
46 | import kotlinx.coroutines.flow.scan
47 | import kotlinx.coroutines.flow.stateIn
48 | import kotlinx.coroutines.launch
49 |
50 | data class Snail6State(
51 | val progress: Float = 0f,
52 | val speed: Speed = Speed.One,
53 | val color: Color = MutedColors.colors(false).first(),
54 | val colors: List = MutedColors.colors(false)
55 | )
56 |
57 | class Snail6StateHolder(
58 | private val scope: CoroutineScope
59 | ) {
60 |
61 | private val speed: Flow = scope.speedFlow()
62 |
63 | private val speedChanges: Flow> = speed
64 | .map { mutationOf { copy(speed = it) } }
65 |
66 | private val progressChanges: Flow> = speed
67 | .toInterval()
68 | .map { mutationOf { copy(progress = (progress + 1) % 100) } }
69 |
70 | private val changeEvents = MutableSharedFlow>()
71 |
72 | val state: StateFlow = merge(
73 | progressChanges,
74 | speedChanges,
75 | changeEvents,
76 | )
77 | .scan(Snail6State()) { state, mutation -> mutation(state) }
78 | .stateIn(
79 | scope = scope,
80 | started = SharingStarted.WhileSubscribed(),
81 | initialValue = Snail6State()
82 | )
83 |
84 | fun setSnailColor(index: Int) {
85 | scope.launch {
86 | changeEvents.emit { copy(color = colors[index]) }
87 | }
88 | }
89 |
90 | fun setProgress(progress: Float) {
91 | scope.launch {
92 | changeEvents.emit { copy(progress = progress) }
93 | }
94 | }
95 | }
96 |
97 | @Composable
98 | fun Snail6() {
99 | val scope = rememberCoroutineScope()
100 | val stateHolder = remember { Snail6StateHolder(scope) }
101 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
102 | val state by stateHolder.state.collectAsState()
103 |
104 | LaunchedEffect(state) {
105 | udfStateHolder.accept(
106 | Event.StateChange(
107 | color = state.color,
108 | metadata = Marble.Metadata.Text(state.progress.toString())
109 | )
110 | )
111 | }
112 |
113 | Illustration {
114 | SnailCard {
115 | VerticalLayout {
116 | Paragraph(
117 | text = "Snail6"
118 | )
119 | Snail(
120 | progress = state.progress,
121 | color = state.color,
122 | onValueChange = {
123 | stateHolder.setProgress(it)
124 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Text(it.toString())))
125 | }
126 | )
127 | ColorSwatch(
128 | colors = state.colors,
129 | onColorClicked = {
130 | stateHolder.setSnailColor(it)
131 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
132 | }
133 | )
134 | Paragraph(
135 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
136 | )
137 | }
138 | }
139 | UDFVisualizer(udfStateHolder)
140 | }
141 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail7.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.Mutation
26 | import com.tunjid.mutator.coroutines.stateFlowMutator
27 | import com.tunjid.mutator.demo.Color
28 | import com.tunjid.mutator.demo.MutedColors
29 | import com.tunjid.mutator.demo.Speed
30 | import com.tunjid.mutator.demo.editor.Paragraph
31 | import com.tunjid.mutator.demo.editor.VerticalLayout
32 | import com.tunjid.mutator.demo.speedFlow
33 | import com.tunjid.mutator.demo.text
34 | import com.tunjid.mutator.demo.toInterval
35 | import com.tunjid.mutator.demo.udfvisualizer.Event
36 | import com.tunjid.mutator.demo.udfvisualizer.Marble
37 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
38 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
39 | import com.tunjid.mutator.mutationOf
40 | import kotlinx.coroutines.CoroutineScope
41 | import kotlinx.coroutines.flow.Flow
42 | import kotlinx.coroutines.flow.SharingStarted
43 | import kotlinx.coroutines.flow.StateFlow
44 | import kotlinx.coroutines.flow.map
45 |
46 | data class Snail7State(
47 | val progress: Float = 0f,
48 | val speed: Speed = Speed.One,
49 | val color: Color = MutedColors.colors(false).first(),
50 | val colors: List = MutedColors.colors(false)
51 | )
52 |
53 | class Snail7StateHolder(
54 | scope: CoroutineScope
55 | ) {
56 |
57 | private val speed: Flow = scope.speedFlow()
58 |
59 | private val speedChanges: Flow> = speed
60 | .map { mutationOf { copy(speed = it) } }
61 |
62 | private val progressChanges: Flow> = speed
63 | .toInterval()
64 | .map { mutationOf { copy(progress = (progress + 1) % 100) } }
65 |
66 | private val stateMutator = scope.stateFlowMutator(
67 | initialState = Snail7State(),
68 | started = SharingStarted.WhileSubscribed(),
69 | inputs = listOf(
70 | speedChanges,
71 | progressChanges,
72 | )
73 | )
74 |
75 | val state: StateFlow = stateMutator.state
76 |
77 | fun setSnailColor(index: Int) = stateMutator.launch {
78 | emit { copy(color = colors[index]) }
79 | }
80 |
81 | fun setProgress(progress: Float) = stateMutator.launch {
82 | emit { copy(progress = progress) }
83 | }
84 | }
85 |
86 |
87 | @Composable
88 | fun Snail7() {
89 | val scope = rememberCoroutineScope()
90 | val stateHolder = remember { Snail7StateHolder(scope) }
91 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
92 | val state by stateHolder.state.collectAsState()
93 |
94 | LaunchedEffect(state) {
95 | udfStateHolder.accept(
96 | Event.StateChange(
97 | color = state.color,
98 | metadata = Marble.Metadata.Text(state.progress.toString())
99 | )
100 | )
101 | }
102 |
103 | Illustration {
104 | SnailCard {
105 | VerticalLayout {
106 | Paragraph(
107 | text = "Snail7"
108 | )
109 | Snail(
110 | progress = state.progress,
111 | color = state.color,
112 | onValueChange = {
113 | stateHolder.setProgress(it)
114 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Text(it.toString())))
115 | }
116 | )
117 | ColorSwatch(
118 | colors = state.colors,
119 | onColorClicked = {
120 | stateHolder.setSnailColor(it)
121 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
122 | }
123 | )
124 | Paragraph(
125 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
126 | )
127 | }
128 | }
129 | UDFVisualizer(udfStateHolder)
130 | }
131 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/snails/Snail8.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.LaunchedEffect
21 | import androidx.compose.runtime.collectAsState
22 | import androidx.compose.runtime.getValue
23 | import androidx.compose.runtime.remember
24 | import androidx.compose.runtime.rememberCoroutineScope
25 | import com.tunjid.mutator.Mutation
26 | import com.tunjid.mutator.coroutines.stateFlowMutator
27 | import com.tunjid.mutator.demo.Color
28 | import com.tunjid.mutator.demo.MutedColors
29 | import com.tunjid.mutator.demo.Speed
30 | import com.tunjid.mutator.demo.editor.VerticalLayout
31 | import com.tunjid.mutator.demo.interpolateColors
32 | import com.tunjid.mutator.demo.speedFlow
33 | import com.tunjid.mutator.demo.text
34 | import com.tunjid.mutator.demo.toInterval
35 | import com.tunjid.mutator.demo.udfvisualizer.Event
36 | import com.tunjid.mutator.demo.udfvisualizer.Marble
37 | import com.tunjid.mutator.demo.udfvisualizer.UDFVisualizer
38 | import com.tunjid.mutator.demo.udfvisualizer.udfVisualizerStateHolder
39 | import com.tunjid.mutator.mutationOf
40 | import kotlinx.coroutines.CoroutineScope
41 | import kotlinx.coroutines.flow.Flow
42 | import kotlinx.coroutines.flow.SharingStarted
43 | import kotlinx.coroutines.flow.StateFlow
44 | import kotlinx.coroutines.flow.map
45 |
46 |
47 | data class Snail8State(
48 | val progress: Float = 0f,
49 | val speed: Speed = Speed.One,
50 | val isDark: Boolean = false,
51 | val colorIndex: Int = 0,
52 | val colorInterpolationProgress: Float = 0F,
53 | val colors: List = MutedColors.colors(false)
54 | )
55 |
56 | val Snail8State.color get() = colors[colorIndex]
57 |
58 | val Snail8State.cardColor: Color get() = colors.last()
59 |
60 | val Snail8State.textColor: Color get() = if (cardColor.isBright()) Color.Black else Color.LightGray
61 |
62 | class Snail8StateHolder(
63 | scope: CoroutineScope
64 | ) {
65 |
66 | private val speed: Flow = scope.speedFlow()
67 |
68 | private val speedChanges: Flow> = speed
69 | .map { mutationOf { copy(speed = it) } }
70 |
71 | private val progressChanges: Flow> = speed
72 | .toInterval()
73 | .map { mutationOf { copy(progress = (progress + 1) % 100) } }
74 |
75 | private val stateMutator = scope.stateFlowMutator(
76 | initialState = Snail8State(),
77 | started = SharingStarted.WhileSubscribed(),
78 | inputs = listOf(
79 | speedChanges,
80 | progressChanges,
81 | )
82 | )
83 |
84 | val state: StateFlow = stateMutator.state
85 |
86 | fun setSnailColor(index: Int) = stateMutator.launch {
87 | emit { copy(colorIndex = index) }
88 | }
89 |
90 | fun setProgress(progress: Float) = stateMutator.launch {
91 | emit { copy(progress = progress) }
92 | }
93 |
94 | fun setMode(isDark: Boolean) = stateMutator.launch {
95 | emit { copy(isDark = isDark) }
96 | // Collect from a flow that animates color changes
97 | interpolateColors(
98 | startColors = state.value.colors.map(Color::argb).toIntArray(),
99 | endColors = MutedColors.colors(isDark).map(Color::argb).toIntArray()
100 | ).collect { (progress, colors) ->
101 | emit {
102 | copy(
103 | colorInterpolationProgress = progress,
104 | colors = colors
105 | )
106 | }
107 | }
108 | }
109 | }
110 |
111 | @Composable
112 | fun Snail8() {
113 | val scope = rememberCoroutineScope()
114 | val stateHolder = remember { Snail8StateHolder(scope) }
115 | val udfStateHolder = remember { udfVisualizerStateHolder(scope) }
116 | val state by stateHolder.state.collectAsState()
117 |
118 | LaunchedEffect(state) {
119 | udfStateHolder.accept(
120 | Event.StateChange(
121 | color = state.color,
122 | metadata = Marble.Metadata.Text(state.progress.toString())
123 | )
124 | )
125 | }
126 |
127 | Illustration {
128 | SnailCard(state.cardColor) {
129 | VerticalLayout {
130 | SnailText(
131 | color = state.textColor,
132 | text = "Snail8"
133 | )
134 | Snail(
135 | progress = state.progress,
136 | color = state.color,
137 | onValueChange = {
138 | stateHolder.setProgress(it)
139 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Text(it.toString())))
140 | }
141 | )
142 | ColorSwatch(
143 | colors = state.colors,
144 | onColorClicked = {
145 | stateHolder.setSnailColor(it)
146 | udfStateHolder.accept(Event.UserTriggered(metadata = Marble.Metadata.Tint(state.colors[it])))
147 | }
148 | )
149 | SnailText(
150 | color = state.textColor,
151 | text = "Progress: ${state.progress}; Speed: ${state.speed.text}"
152 | )
153 | ToggleButton(
154 | progress = state.colorInterpolationProgress,
155 | onClicked = {
156 | stateHolder.setMode(!state.isDark)
157 | udfStateHolder.accept(
158 | Event.UserTriggered(
159 | Marble.Metadata.Text(
160 | if (state.isDark) "☀️"
161 | else "\uD83C\uDF18"
162 | )
163 | )
164 | )
165 | }
166 | )
167 | }
168 | }
169 | UDFVisualizer(udfStateHolder)
170 | }
171 | }
--------------------------------------------------------------------------------
/demo/src/commonMain/kotlin/com/tunjid/mutator/demo/udfvisualizer/UDFVisualizerStateHolder.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.udfvisualizer
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.Mutation
21 | import com.tunjid.mutator.ActionStateMutator
22 | import com.tunjid.mutator.coroutines.actionStateFlowMutator
23 | import com.tunjid.mutator.demo.Color
24 | import com.tunjid.mutator.demo.intervalFlow
25 | import com.tunjid.mutator.mutationOf
26 | import kotlinx.coroutines.CoroutineScope
27 | import kotlinx.coroutines.flow.Flow
28 | import kotlinx.coroutines.flow.StateFlow
29 | import kotlinx.coroutines.flow.map
30 |
31 | val eventColor = Color(0xEF685C)
32 | val stateColor = Color(0x3080E9)
33 |
34 | typealias UDFVisualizerStateHolder = ActionStateMutator>
35 |
36 | sealed class Marble {
37 |
38 | sealed class Metadata {
39 | object Nothing : Metadata()
40 | data class Text(val text: String) : Metadata()
41 | data class Tint(val color: Color) : Metadata()
42 | }
43 |
44 | abstract val percentage: Int
45 | abstract val metadata: Metadata
46 |
47 | data class Event(
48 | override val percentage: Int = 0,
49 | override val metadata: Metadata = Metadata.Nothing,
50 | ) : Marble()
51 |
52 | data class State(
53 | val index: Int = -1,
54 | val color: Color = stateColor,
55 | override val percentage: Int = 0,
56 | override val metadata: Metadata = Metadata.Nothing,
57 | ) : Marble()
58 | }
59 |
60 | sealed class Event {
61 | data class StateChange(
62 | val color: Color = stateColor,
63 | val metadata: Marble.Metadata = Marble.Metadata.Nothing
64 | ) : Event()
65 |
66 | data class UserTriggered(
67 | val metadata: Marble.Metadata = Marble.Metadata.Nothing
68 | ) : Event()
69 | }
70 |
71 | data class State(
72 | val index: Int = -1,
73 | val marbles: List = listOf()
74 | )
75 |
76 | fun udfVisualizerStateHolder(
77 | scope: CoroutineScope
78 | ): ActionStateMutator> = scope.actionStateFlowMutator(
79 | initialState = State(),
80 | inputs = listOf(frames()),
81 | actionTransform = { it.stateMutations() },
82 | )
83 |
84 | private fun frames(): Flow> = intervalFlow(10)
85 | .map { mutationOf { advanceFrame() } }
86 |
87 | private fun Flow.stateMutations(): Flow> =
88 | map { mutationOf { this + it } }
89 |
90 | /**
91 | * Reduces the [action] into the [State]
92 | */
93 | private operator fun State.plus(action: Event) = copy(
94 | index = index + 1,
95 | marbles = marbles + when (action) {
96 | is Event.UserTriggered -> Marble.Event(metadata = action.metadata)
97 | is Event.StateChange -> Marble.State(
98 | index = index + 1,
99 | metadata = action.metadata,
100 | color = action.color
101 | )
102 | }
103 | )
104 |
105 | /**
106 | * Advances the frame for the UDF visualizer
107 | */
108 | private fun State.advanceFrame() = copy(marbles = marbles.mapNotNull {
109 | if (it.percentage > 100) null
110 | else when (it) {
111 | is Marble.Event -> it.copy(percentage = it.percentage + 1)
112 | is Marble.State -> it.copy(percentage = it.percentage + 1)
113 | }
114 | })
115 |
116 | @Composable
117 | expect fun UDFVisualizer(stateHolder: UDFVisualizerStateHolder)
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo
18 |
19 | import androidx.compose.ui.graphics.luminance
20 | import androidx.compose.ui.graphics.toArgb
21 | import androidx.compose.ui.graphics.Color as ComposeColor
22 |
23 | actual class Color actual constructor(color: Long) {
24 | internal val composeColor = ComposeColor(color)
25 |
26 | actual val argb: Int = composeColor.toArgb()
27 | actual val r: Int = composeColor.red.toRgbInt()
28 | actual val g: Int = composeColor.green.toRgbInt()
29 | actual val b: Int = composeColor.blue.toRgbInt()
30 |
31 | actual fun isBright(): Boolean = composeColor.luminance() >= 0.5
32 |
33 | private fun Float.toRgbInt() = (this * 255).toInt()
34 |
35 | actual companion object {
36 | actual val Black: Color = Color(ComposeColor.Black.toArgb().toLong())
37 | actual val LightGray: Color = Color(ComposeColor.LightGray.toArgb().toLong())
38 | }
39 | }
40 |
41 | val Color.toComposeColor get() = composeColor
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/editor/Editor.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.ui.unit.TextUnit
20 | import androidx.compose.ui.unit.sp
21 |
22 | data class Settings(
23 | val fontSize: TextUnit = 13.sp,
24 | val maxLineSymbols: Int = 120
25 | )
26 |
27 | data class Line(val number: Int, val content: Content)
28 |
29 | data class Lines(
30 | val data: List
31 | ) {
32 | val size: Int = data.size
33 | val lineNumberDigitCount: Int = size.toString().length
34 |
35 | operator fun get(index: Int): Line = Line(
36 | number = index,
37 | content = Content(data[index], true)
38 | )
39 | }
40 |
41 |
42 | data class Content(val value: String, val isCode: Boolean)
43 |
44 |
45 | fun String.toCodeLines() = Lines(split("\n"))
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/editor/Fonts.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.text.font.FontFamily
21 | import androidx.compose.ui.text.font.FontStyle
22 | import androidx.compose.ui.text.font.FontWeight
23 | import com.tunjid.mutator.demo.platform.Font
24 |
25 | object Fonts {
26 | @Composable
27 | fun jetbrainsMono() = FontFamily(
28 | Font(
29 | "JetBrains Mono",
30 | "jetbrainsmono_regular",
31 | FontWeight.Normal,
32 | FontStyle.Normal
33 | ),
34 | Font(
35 | "JetBrains Mono",
36 | "jetbrainsmono_italic",
37 | FontWeight.Normal,
38 | FontStyle.Italic
39 | ),
40 |
41 | Font(
42 | "JetBrains Mono",
43 | "jetbrainsmono_bold",
44 | FontWeight.Bold,
45 | FontStyle.Normal
46 | ),
47 | Font(
48 | "JetBrains Mono",
49 | "jetbrainsmono_bold_italic",
50 | FontWeight.Bold,
51 | FontStyle.Italic
52 | ),
53 |
54 | Font(
55 | "JetBrains Mono",
56 | "jetbrainsmono_extrabold",
57 | FontWeight.ExtraBold,
58 | FontStyle.Normal
59 | ),
60 | Font(
61 | "JetBrains Mono",
62 | "jetbrainsmono_extrabold_italic",
63 | FontWeight.ExtraBold,
64 | FontStyle.Italic
65 | ),
66 |
67 | Font(
68 | "JetBrains Mono",
69 | "jetbrainsmono_medium",
70 | FontWeight.Medium,
71 | FontStyle.Normal
72 | ),
73 | Font(
74 | "JetBrains Mono",
75 | "jetbrainsmono_medium_italic",
76 | FontWeight.Medium,
77 | FontStyle.Italic
78 | )
79 | )
80 | }
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/editor/Layouts.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.foundation.layout.Arrangement
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.Column
22 | import androidx.compose.foundation.layout.Row
23 | import androidx.compose.foundation.layout.fillMaxSize
24 | import androidx.compose.foundation.layout.padding
25 | import androidx.compose.foundation.layout.widthIn
26 | import androidx.compose.foundation.rememberScrollState
27 | import androidx.compose.foundation.verticalScroll
28 | import androidx.compose.runtime.Composable
29 | import androidx.compose.ui.Alignment
30 | import androidx.compose.ui.Modifier
31 | import androidx.compose.ui.unit.dp
32 |
33 | @Composable
34 | actual fun ContainerLayout(content: @Composable () -> Unit) {
35 | val scrollState = rememberScrollState()
36 | Box(
37 | modifier = Modifier
38 | .fillMaxSize()
39 | ) {
40 | Column(
41 | modifier = Modifier
42 | .fillMaxSize()
43 | .verticalScroll(scrollState)
44 | .widthIn(0.dp, 600.dp)
45 | .padding(horizontal = 16.dp)
46 | .align(Alignment.Center),
47 | horizontalAlignment = Alignment.CenterHorizontally
48 | ) {
49 | content()
50 | }
51 | }
52 | }
53 |
54 | @Composable
55 | actual fun SectionLayout(content: @Composable () -> Unit) {
56 | Column(
57 | modifier = Modifier.padding(horizontal = 16.dp),
58 | verticalArrangement = Arrangement.spacedBy(16.dp),
59 | horizontalAlignment = Alignment.CenterHorizontally,
60 | content = { content() }
61 | )
62 | }
63 |
64 | @Composable
65 | actual fun VerticalLayout(content: @Composable () -> Unit) {
66 | Column(
67 | modifier = Modifier.padding(horizontal = 16.dp),
68 | verticalArrangement = Arrangement.spacedBy(16.dp),
69 | content = { content() }
70 | )
71 | }
72 |
73 | @Composable
74 | actual fun HorizontalLayout(
75 | centerOnMainAxis: Boolean,
76 | content: @Composable () -> Unit
77 | ) {
78 | Row(
79 | modifier = Modifier.padding(horizontal = 8.dp),
80 | horizontalArrangement = if (centerOnMainAxis) Arrangement.Center else Arrangement.Start,
81 | content = { content() }
82 | )
83 | }
84 |
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/editor/Text.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.foundation.layout.padding
23 | import androidx.compose.foundation.shape.RoundedCornerShape
24 | import androidx.compose.material.MaterialTheme
25 | import androidx.compose.material.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Alignment
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.graphics.Color
30 | import androidx.compose.ui.text.TextStyle
31 | import androidx.compose.ui.unit.dp
32 | import com.halilibo.richtext.markdown.Markdown
33 | import com.halilibo.richtext.ui.RichText
34 |
35 | @Composable
36 | actual fun Paragraph(text: String) {
37 | StyledText(
38 | modifier = Modifier.padding(vertical = 8.dp),
39 | text = text,
40 | style = MaterialTheme.typography.body1.copy(
41 | MaterialTheme.colors.onSurface
42 | )
43 | )
44 | }
45 |
46 | @Composable
47 | actual fun Markdown(content: String) {
48 | RichText(
49 | modifier = Modifier.padding(horizontal = 16.dp)
50 | ) {
51 | Markdown(
52 | content = content
53 | )
54 | }
55 | }
56 |
57 | @Composable
58 | actual fun CallToAction(
59 | text: String,
60 | centerText: Boolean,
61 | ) {
62 | Box(
63 | modifier = Modifier.fillMaxWidth(0.8f)
64 | .background(
65 | color = Color(0xE1F4FE),
66 | shape = RoundedCornerShape(size = 16.dp)
67 | ),
68 | content = {
69 | Text(
70 | modifier = Modifier
71 | .fillMaxWidth()
72 | .padding(16.dp)
73 | .align(Alignment.Center)
74 | .let {
75 | if (centerText) it.align(Alignment.Center)
76 | else it
77 | },
78 | color = Color(0x00589B),
79 | text = text
80 | )
81 | },
82 | )
83 | }
84 |
85 | @Composable
86 | private fun StyledText(
87 | text: String,
88 | style: TextStyle,
89 | modifier: Modifier = Modifier
90 | ) {
91 | Text(
92 | modifier = modifier,
93 | style = style,
94 | text = text
95 | )
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/platform/Resources.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.platform
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.ui.text.font.Font
21 | import androidx.compose.ui.text.font.FontStyle
22 | import androidx.compose.ui.text.font.FontWeight
23 |
24 | @Composable
25 | internal fun Font(name: String, res: String, weight: FontWeight, style: FontStyle): Font =
26 | androidx.compose.ui.text.platform.Font("font/$res.ttf", weight, style)
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/snails/Snail.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Arrangement
21 | import androidx.compose.foundation.layout.Box
22 | import androidx.compose.foundation.layout.Column
23 | import androidx.compose.foundation.layout.Row
24 | import androidx.compose.foundation.layout.fillMaxWidth
25 | import androidx.compose.foundation.layout.padding
26 | import androidx.compose.material.Button
27 | import androidx.compose.material.ButtonDefaults
28 | import androidx.compose.material.MaterialTheme
29 | import androidx.compose.material.Slider
30 | import androidx.compose.material.SliderDefaults
31 | import androidx.compose.material.Surface
32 | import androidx.compose.material.Text
33 | import androidx.compose.runtime.Composable
34 | import androidx.compose.ui.Alignment
35 | import androidx.compose.ui.Modifier
36 | import androidx.compose.ui.unit.dp
37 | import com.tunjid.mutator.demo.Color
38 | import com.tunjid.mutator.demo.Speed
39 | import com.tunjid.mutator.demo.toComposeColor
40 |
41 | @Composable
42 | actual fun Illustration(
43 | content: @Composable () -> Unit
44 | ) {
45 | Column(
46 | modifier = Modifier.fillMaxWidth(0.8f),
47 | content = { content() },
48 | )
49 | }
50 |
51 | @Composable
52 | actual fun SnailCard(
53 | color: Color,
54 | content: @Composable () -> Unit
55 | ) {
56 | Surface(
57 | color = color.toComposeColor,
58 | content = { content() },
59 | )
60 | }
61 |
62 | @Composable
63 | actual fun SnailText(
64 | color: Color,
65 | text: String
66 | ) {
67 | Text(
68 | modifier = Modifier.padding(vertical = 8.dp),
69 | text = text,
70 | color = color.toComposeColor,
71 | style = MaterialTheme.typography.body1.copy(
72 | MaterialTheme.colors.onSurface
73 | )
74 | )
75 | }
76 |
77 | @Composable
78 | actual fun Snail(
79 | progress: Float,
80 | speed: Speed,
81 | color: Color,
82 | onValueChange: (Float) -> Unit
83 | ) {
84 | Slider(
85 | valueRange = 0f..100f,
86 | value = progress,
87 | colors = SliderDefaults.colors(
88 | thumbColor = color.toComposeColor,
89 | activeTrackColor = color.toComposeColor
90 | ),
91 | onValueChange = onValueChange
92 | )
93 | }
94 |
95 | @Composable
96 | actual fun ColorSwatch(
97 | colors: List,
98 | onColorClicked: (index: Int) -> Unit
99 | ) {
100 | Row(
101 | modifier = Modifier.fillMaxWidth(),
102 | horizontalArrangement = Arrangement.Center
103 | ) {
104 | colors.forEachIndexed { index, color ->
105 | Button(
106 | modifier = Modifier.padding(horizontal = 8.dp),
107 | colors = ButtonDefaults.buttonColors(backgroundColor = color.toComposeColor),
108 | onClick = {
109 | onColorClicked(index)
110 | },
111 | content = {
112 | },
113 | )
114 | }
115 | }
116 | }
117 |
118 | @Composable
119 | actual fun ToggleButton(
120 | progress: Float,
121 | onClicked: () -> Unit
122 | ) {
123 | Box(
124 | modifier = Modifier.fillMaxWidth()
125 | ) {
126 | Box(
127 | modifier = Modifier
128 | .fillMaxWidth(0.25F)
129 | .align(Alignment.Center)
130 | ) {
131 | Button(
132 | modifier = Modifier
133 | .fillMaxWidth()
134 | .align(Alignment.Center)
135 | .background(Color(0xA7C7E7).composeColor),
136 | onClick = {
137 | onClicked()
138 | },
139 | content = {
140 | Text(text = "Toggle mode")
141 | },
142 | )
143 | Button(
144 | modifier = Modifier
145 | .fillMaxWidth(progress)
146 | .align(Alignment.CenterStart)
147 | .background(Color(0xA7C7E7).toComposeColor),
148 | onClick = {
149 | onClicked()
150 | },
151 | content = {},
152 | )
153 | }
154 | }
155 | }
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/com/tunjid/mutator/demo/udfvisualizer/UDFVisualizer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.udfvisualizer
18 |
19 | import androidx.compose.runtime.Composable
20 |
21 | @Composable
22 | actual fun UDFVisualizer(stateHolder: UDFVisualizerStateHolder) {
23 | }
--------------------------------------------------------------------------------
/demo/src/desktopMain/kotlin/main.desktop.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import androidx.compose.ui.unit.DpSize
18 | import androidx.compose.ui.unit.dp
19 | import androidx.compose.ui.window.WindowState
20 | import androidx.compose.ui.window.singleWindowApplication
21 | import com.tunjid.mutator.demo.App
22 |
23 |
24 | fun main() {
25 | singleWindowApplication(
26 | title = "State",
27 | state = WindowState(size = DpSize(800.dp, 800.dp))
28 | ) {
29 | App()
30 | }
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_bold.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_bold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_bold_italic.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_extrabold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_extrabold.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_extrabold_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_extrabold_italic.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_italic.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_medium.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_medium_italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_medium_italic.ttf
--------------------------------------------------------------------------------
/demo/src/desktopMain/resources/font/jetbrainsmono_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/desktopMain/resources/font/jetbrainsmono_regular.ttf
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo
18 |
19 | import kotlin.math.sqrt
20 |
21 | actual class Color actual constructor(color: Long) {
22 |
23 | actual val argb: Int = color.toInt()
24 |
25 | actual val r: Int = argb and 0x00FF0000 shr 16
26 | actual val g: Int = argb and 0x0000FF00 shr 8
27 | actual val b: Int = argb and 0x000000FF
28 |
29 | actual companion object {
30 | actual val Black: Color = Color(0x000000)
31 | actual val LightGray: Color = Color(0xFFCCCCCC)
32 | }
33 |
34 | actual fun isBright(): Boolean {
35 | val hsp = sqrt(
36 | 0.299 * (r * r) +
37 | 0.587 * (g * g) +
38 | 0.114 * (b * b)
39 | )
40 | return hsp > 127.5
41 | }
42 | }
43 |
44 | fun Color.rgb() = org.jetbrains.compose.web.css.rgb(
45 | r = r,
46 | g = g,
47 | b = b,
48 | )
49 |
50 | fun Color.hex(): String {
51 | val r = r.toHexString()
52 | val g = g.toHexString()
53 | val b = b.toHexString()
54 | return "#$r$g$b"
55 | }
56 |
57 | fun Int.toHexString(): String {
58 | val hex = this.toString(16)
59 | return if (hex.length == 1) "0$hex" else hex
60 | }
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/editor/Layouts.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.runtime.Composable
20 | import org.jetbrains.compose.web.css.StyleScope
21 | import org.jetbrains.compose.web.dom.Div
22 |
23 | @Composable
24 | actual fun ContainerLayout(content: @Composable () -> Unit) {
25 | StyledDiv(content = content, classNames = listOf("container"))
26 | }
27 |
28 | @Composable
29 | actual fun SectionLayout(content: @Composable () -> Unit) {
30 | StyledDiv(content = content, classNames = listOf("sectionLayout"))
31 | }
32 |
33 | @Composable
34 | actual fun VerticalLayout(content: @Composable () -> Unit) {
35 | StyledDiv(content = content, classNames = listOf("verticalLayout"))
36 | }
37 |
38 | @Composable
39 | actual fun HorizontalLayout(
40 | centerOnMainAxis: Boolean,
41 | content: @Composable () -> Unit
42 | ) {
43 | StyledDiv(
44 | content = content,
45 | classNames = listOfNotNull(
46 | "horizontalLayout",
47 | "horizontallyCentered".takeIf { centerOnMainAxis }
48 | )
49 | )
50 | }
51 |
52 | @Composable
53 | fun StyledDiv(
54 | styles: StyleScope.() -> Unit = {},
55 | content: @Composable () -> Unit,
56 | classNames: List
57 | ) {
58 | Div(
59 | attrs = {
60 | style(styles)
61 | classes(*classNames.toTypedArray())
62 | },
63 | content = {
64 | content()
65 | }
66 | )
67 | }
68 |
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/editor/Markdown.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | //@file:JsModule("react-markdown")
18 | //@file:JsNonModule
19 | //
20 | //import react.FC
21 | //import react.Props
22 | //
23 | //@JsName("ReactMarkdown")
24 | //external val reactMarkdown: FC
25 | //
26 | //external interface ReactMarkdownProps : Props {
27 | // var children: String
28 | //}
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/editor/Text.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.editor
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.DisposableEffect
21 | import androidx.compose.runtime.key
22 | import org.jetbrains.compose.web.css.Color
23 | import org.jetbrains.compose.web.css.backgroundColor
24 | import org.jetbrains.compose.web.css.fontSize
25 | import org.jetbrains.compose.web.css.pt
26 | import org.jetbrains.compose.web.dom.Code
27 | import org.jetbrains.compose.web.dom.Div
28 | import org.jetbrains.compose.web.dom.ElementScope
29 | import org.jetbrains.compose.web.dom.P
30 | import org.jetbrains.compose.web.dom.Pre
31 | import org.jetbrains.compose.web.dom.Text
32 | import org.w3c.dom.HTMLElement
33 | import react.FC
34 | import react.Props
35 | import react.ReactElement
36 | import react.create
37 | import react.dom.client.createRoot
38 | import web.dom.Element
39 |
40 |
41 | @Composable
42 | actual fun Paragraph(text: String) = P(
43 | content = {
44 | Text(value = text)
45 | }
46 | )
47 |
48 | @Composable
49 | actual fun Markdown(content: String) = Div(
50 | attrs = {
51 | classes("markdown")
52 | },
53 | content = {
54 | UseReactEffect(
55 | content,
56 | reactMarkdown.create {
57 | children = content
58 | }
59 | )
60 | }
61 | )
62 |
63 | @Composable
64 | actual fun CallToAction(
65 | text: String,
66 | centerText: Boolean,
67 | ) = Div(
68 | attrs = {
69 | classes(
70 | listOfNotNull(
71 | "cta",
72 | "horizontallyCentered",
73 | "centerText".takeIf { centerText },
74 | )
75 | )
76 | }
77 | ) {
78 | UseReactEffect(
79 | text,
80 | reactMarkdown.create {
81 | children = text
82 | }
83 | )
84 | }
85 |
86 | @Composable
87 | actual fun CodeBlock(content: String) = key(content) {
88 | Pre {
89 | Code({
90 | classes("language-kotlin", "hljs")
91 | style {
92 | property("font-family", "'JetBrains Mono', monospace")
93 | property("tab-size", 4)
94 | fontSize(10.pt)
95 | backgroundColor(Color("transparent"))
96 | }
97 | }) {
98 | @Suppress("DEPRECATION")
99 | DomSideEffect(content) {
100 | it.setHighlightedCode(content)
101 | }
102 | }
103 | }
104 | }
105 |
106 | @Composable
107 | private fun ElementScope.UseReactEffect(
108 | key: Any?,
109 | content: ReactElement<*>
110 | ) {
111 | DisposableEffect(key1 = key) {
112 | val root = createRoot(scopeElement as Element)
113 | root.render(content)
114 | onDispose { root.unmount() }
115 | }
116 | }
117 |
118 | @JsName("ReactMarkdown")
119 | @JsModule("react-markdown")
120 | @JsNonModule
121 | external val reactMarkdown: FC
122 |
123 | external interface ReactMarkdownProps : Props {
124 | var children: String
125 | }
126 |
127 | @JsName("hljs")
128 | @JsModule("highlight.js")
129 | @JsNonModule
130 | external class HighlightJs {
131 | companion object {
132 | fun highlightElement(block: HTMLElement)
133 | }
134 | }
135 |
136 | private fun HTMLElement.setHighlightedCode(code: String) {
137 | innerText = code
138 | HighlightJs.highlightElement(this)
139 | }
140 |
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/snails/Snail.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.snails
18 |
19 | import androidx.compose.runtime.Composable
20 | import com.tunjid.mutator.demo.Color
21 | import com.tunjid.mutator.demo.Speed
22 | import com.tunjid.mutator.demo.editor.HorizontalLayout
23 | import com.tunjid.mutator.demo.editor.StyledDiv
24 | import com.tunjid.mutator.demo.hex
25 | import com.tunjid.mutator.demo.rgb
26 | import org.jetbrains.compose.web.attributes.InputType
27 | import org.jetbrains.compose.web.attributes.max
28 | import org.jetbrains.compose.web.attributes.min
29 | import org.jetbrains.compose.web.css.backgroundColor
30 | import org.jetbrains.compose.web.css.color
31 | import org.jetbrains.compose.web.css.percent
32 | import org.jetbrains.compose.web.css.width
33 | import org.jetbrains.compose.web.dom.Div
34 | import org.jetbrains.compose.web.dom.Input
35 | import org.jetbrains.compose.web.dom.P
36 | import org.jetbrains.compose.web.dom.Text
37 |
38 | @Composable
39 | actual fun Illustration(
40 | content: @Composable () -> Unit
41 | ) {
42 | StyledDiv(
43 | content = content,
44 | classNames = listOf("illustration", "sectionLayout"),
45 | )
46 | }
47 |
48 | @Composable
49 | actual fun SnailCard(
50 | color: Color,
51 | content: @Composable () -> Unit
52 | ) {
53 | StyledDiv(
54 | content = content,
55 | classNames = listOf("card"),
56 | styles = {
57 | backgroundColor(color.rgb())
58 | }
59 | )
60 | }
61 |
62 | @Composable
63 | actual fun SnailText(
64 | color: Color,
65 | text: String
66 | ) = P(
67 | attrs = {
68 | style { color(color.rgb()) }
69 | },
70 | content = {
71 | Text(value = text)
72 | }
73 | )
74 |
75 | @Composable
76 | actual fun Snail(
77 | progress: Float,
78 | speed: Speed,
79 | color: Color,
80 | onValueChange: (Float) -> Unit
81 | ) {
82 | Input(
83 | type = InputType.Range,
84 | attrs = {
85 | classes("snail", "horizontallyCentered")
86 | style {
87 | property("--snailColor", color.hex())
88 | }
89 | min("0")
90 | max("100")
91 | value(progress)
92 | onInput { onValueChange(it.value?.toFloat() ?: 0f) }
93 | }
94 | )
95 | }
96 |
97 | @Composable
98 | actual fun ColorSwatch(
99 | colors: List,
100 | onColorClicked: (Int) -> Unit
101 | ) {
102 | HorizontalLayout(centerOnMainAxis = true) {
103 | colors.forEachIndexed { index, color ->
104 | Div(
105 | attrs = {
106 | classes("colorSwatch")
107 | style {
108 | backgroundColor(
109 | color.rgb()
110 | )
111 | }
112 | onClick { onColorClicked(index) }
113 | },
114 | content = {
115 | }
116 | )
117 | }
118 | }
119 | }
120 |
121 | @Composable
122 | actual fun ToggleButton(
123 | progress: Float,
124 | onClicked: () -> Unit
125 | ) = Div(
126 | attrs = {
127 | classes("progress-btn", "horizontallyCentered")
128 | onClick { onClicked() }
129 | },
130 | content = {
131 | Div(
132 | attrs = {
133 | classes("btn")
134 | },
135 | content = {
136 | Text("Toggle mode")
137 | }
138 | )
139 | Div(
140 | attrs = {
141 | classes("progress")
142 | style { width(((progress * 100).toInt() % 100).percent) }
143 | }
144 | )
145 | }
146 | )
147 |
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/com/tunjid/mutator/demo/udfvisualizer/UDFVisualizer.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.tunjid.mutator.demo.udfvisualizer
18 |
19 | import androidx.compose.runtime.Composable
20 | import androidx.compose.runtime.collectAsState
21 | import androidx.compose.runtime.getValue
22 | import com.tunjid.mutator.demo.hex
23 | import org.jetbrains.compose.web.css.AlignItems
24 | import org.jetbrains.compose.web.css.DisplayStyle
25 | import org.jetbrains.compose.web.css.FlexDirection
26 | import org.jetbrains.compose.web.css.JustifyContent
27 | import org.jetbrains.compose.web.css.Position
28 | import org.jetbrains.compose.web.css.alignItems
29 | import org.jetbrains.compose.web.css.background
30 | import org.jetbrains.compose.web.css.borderRadius
31 | import org.jetbrains.compose.web.css.bottom
32 | import org.jetbrains.compose.web.css.color
33 | import org.jetbrains.compose.web.css.display
34 | import org.jetbrains.compose.web.css.flexDirection
35 | import org.jetbrains.compose.web.css.fontSize
36 | import org.jetbrains.compose.web.css.height
37 | import org.jetbrains.compose.web.css.justifyContent
38 | import org.jetbrains.compose.web.css.left
39 | import org.jetbrains.compose.web.css.margin
40 | import org.jetbrains.compose.web.css.minWidth
41 | import org.jetbrains.compose.web.css.padding
42 | import org.jetbrains.compose.web.css.paddingBottom
43 | import org.jetbrains.compose.web.css.paddingLeft
44 | import org.jetbrains.compose.web.css.paddingRight
45 | import org.jetbrains.compose.web.css.paddingTop
46 | import org.jetbrains.compose.web.css.percent
47 | import org.jetbrains.compose.web.css.position
48 | import org.jetbrains.compose.web.css.px
49 | import org.jetbrains.compose.web.css.textAlign
50 | import org.jetbrains.compose.web.css.top
51 | import org.jetbrains.compose.web.css.width
52 | import org.jetbrains.compose.web.dom.Div
53 | import org.jetbrains.compose.web.dom.P
54 | import org.jetbrains.compose.web.dom.Text
55 | import org.jetbrains.compose.web.css.Color as CSSColor
56 |
57 | private val marbleSize = 22.px
58 |
59 | @Composable
60 | actual fun UDFVisualizer(stateHolder: UDFVisualizerStateHolder) {
61 | val state by stateHolder.state.collectAsState()
62 |
63 | Div(
64 | {
65 | style {
66 | display(DisplayStyle.Flex)
67 | flexDirection(FlexDirection.Column)
68 | alignItems("center")
69 | width(100.percent)
70 | padding(0.px)
71 | margin(0.px)
72 | }
73 | },
74 | {
75 | Label("State Holder")
76 | Marbles(state.marbles)
77 | Label("UI")
78 | P { Text("UDF Visualizer") }
79 | }
80 | )
81 | }
82 |
83 | @Composable
84 | private fun Label(text: String) = P(
85 | attrs = {
86 | style {
87 | width(200.px)
88 | borderRadius(10.px)
89 | paddingLeft(16.px)
90 | paddingTop(8.px)
91 | paddingRight(16.px)
92 | paddingBottom(8.px)
93 | textAlign("center")
94 | background("#052F41")
95 | color(CSSColor("#FFFFFF"))
96 | margin(0.px)
97 | property("z-index", 1)
98 | }
99 | },
100 | content = {
101 | Text(value = text)
102 | }
103 | )
104 |
105 | @Composable
106 | private fun Marbles(marbles: List) = Div(
107 | attrs = {
108 | style {
109 | width(200.px)
110 | height(120.px)
111 | position(Position.Relative)
112 | padding(0.px)
113 | margin(0.px)
114 | }
115 | },
116 | content = {
117 | marbles.forEach { marble ->
118 | Marble(marble)
119 | }
120 | }
121 | )
122 |
123 | @Composable
124 | private fun Marble(marble: Marble) = Div(
125 | attrs = {
126 | style {
127 | property("z-index", 0)
128 | minWidth(marbleSize)
129 | height(marbleSize)
130 | borderRadius(marbleSize)
131 | position(Position.Absolute)
132 | color(CSSColor("#FFFFFF"))
133 | textAlign("center")
134 | fontSize(13.px)
135 | padding(0.px)
136 | margin(0.px)
137 | display(DisplayStyle.Flex)
138 | alignItems(AlignItems.Center)
139 | justifyContent(JustifyContent.Center)
140 | background(
141 | when (marble) {
142 | is Marble.Event -> when (val core = marble.metadata) {
143 | Marble.Metadata.Nothing -> marble.hexColor()
144 | is Marble.Metadata.Text -> marble.hexColor()
145 | is Marble.Metadata.Tint -> core.color.hex()
146 | }
147 |
148 | is Marble.State -> marble.color.hex()
149 | }
150 | )
151 | left(
152 | when (marble) {
153 | is Marble.Event -> 80.percent
154 | is Marble.State -> 20.percent
155 | }
156 | )
157 | when (marble) {
158 | is Marble.Event -> bottom(marble.percentage.percent)
159 | is Marble.State -> top(marble.percentage.percent)
160 | }
161 | }
162 | },
163 | content = {
164 | when (val core = marble.metadata) {
165 | Marble.Metadata.Nothing,
166 | is Marble.Metadata.Tint -> Unit
167 |
168 | is Marble.Metadata.Text -> Text(value = core.text)
169 | }
170 | }
171 | )
172 |
173 | private fun Marble.color() = when (this) {
174 | is Marble.Event -> eventColor
175 | is Marble.State -> stateColor
176 | }
177 |
178 | private fun Marble.hexColor() = color().hex()
179 |
--------------------------------------------------------------------------------
/demo/src/jsMain/kotlin/main.js.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | import com.tunjid.mutator.demo.App
18 | import org.jetbrains.compose.web.renderComposable
19 |
20 | fun main() {
21 | renderComposable(rootElementId = "container") {
22 | App()
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/android-chrome-192x192.png
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/android-chrome-512x512.png
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/apple-touch-icon.png
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/favicon-16x16.png
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/favicon-32x32.png
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/demo/src/jsMain/resources/favicon.ico
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/hljs.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*!
18 | * StackOverflow.com light style
19 | *
20 | * @stackoverflow/stacks v0.56.0
21 | * https://github.com/StackExchange/Stacks
22 | */
23 | .hljs {
24 | display: block;
25 | overflow-x: auto;
26 | padding: .5em;
27 | color: #2f3337;
28 | background: #f6f6f6
29 | }
30 |
31 | .hljs-comment {
32 | color: #656e77
33 | }
34 |
35 | .hljs-attr, .hljs-doctag, .hljs-keyword, .hljs-meta, .hljs-meta-keyword, .hljs-section, .hljs-selector-class, .hljs-selector-pseudo, .hljs-selector-tag {
36 | color: #015692;
37 | font-weight: bold;
38 | }
39 |
40 | .hljs-attribute {
41 | color: #803378
42 | }
43 |
44 | .hljs-built_in, .hljs-literal, .hljs-name, .hljs-number, .hljs-quote, .hljs-selector-id, .hljs-template-tag, .hljs-title, .hljs-type {
45 | color: #b75501
46 | }
47 |
48 | .hljs-title {
49 | font-style: italic;
50 | }
51 |
52 | .hljs-link, .hljs-meta-string, .hljs-regexp, .hljs-selector-attr, .hljs-string, .hljs-symbol, .hljs-template-variable, .hljs-variable {
53 | color: #54790d
54 | }
55 |
56 | .hljs-bullet, .hljs-code {
57 | color: #535a60
58 | }
59 |
60 | .hljs-deletion {
61 | color: #c02d2e
62 | }
63 |
64 | .hljs-addition {
65 | color: #2f6f44
66 | }
67 |
68 | .hljs-emphasis {
69 | font-style: italic
70 | }
71 |
72 | .hljs-strong {
73 | font-weight: 700
74 | }
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/index.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | State production with unidirectional data flow and Kotlin Flows
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "State production with flows": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/snail.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
98 |
--------------------------------------------------------------------------------
/demo/src/jsMain/resources/styles.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #root {
18 | height: 100%;
19 | width: 100%;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | }
24 |
25 | #container {
26 | max-width: 720px;
27 | }
28 |
29 | pre {
30 | width: 100%;
31 | overflow: auto;
32 | height: auto;
33 | white-space: pre-wrap;
34 | word-wrap: break-word;
35 | text-align: start;
36 | }
37 |
38 | .markdown {
39 | width: 100%;
40 | }
41 |
42 | .markdown h1 {
43 | font-family: "Muli", sans-serif;
44 | font-size: 48px;
45 | letter-spacing: 0px;
46 | font-weight: normal;
47 | line-height: 90%;
48 | }
49 |
50 | .markdown h2 {
51 | font-family: "Muli", sans-serif;
52 | font-size: 34px;
53 | letter-spacing: 0px;
54 | font-weight: lighter;
55 | line-height: 90%;
56 | }
57 |
58 | .markdown h3 {
59 | font-family: "Muli", sans-serif;
60 | font-size: 24px;
61 | letter-spacing: 0px;
62 | font-weight: lighter;
63 | line-height: 80%;
64 | }
65 |
66 | .markdown p, li, ul {
67 | font-family: "Muli", sans-serif;
68 | font-size: 18px;
69 | letter-spacing: 0px;
70 | font-weight: normal;
71 | line-height: 125%;
72 | }
73 |
74 |
75 | .sectionLayout {
76 | display: flex;
77 | flex-direction: column;
78 | align-items: center;
79 | }
80 |
81 | .verticalLayout {
82 | display: flex;
83 | flex-direction: column;
84 | align-items: start;
85 | }
86 |
87 | .horizontalLayout {
88 | display: flex;
89 | flex-direction: row;
90 | align-items: center;
91 | }
92 |
93 | .centerText {
94 | text-align: center;
95 | }
96 |
97 | .colorSwatch {
98 | width: 25px;
99 | height: 25px;
100 | border-radius: 50%;
101 | margin: 16px;
102 | }
103 |
104 | .cta {
105 | width: 80%;
106 | margin: 16px;
107 | padding: 8px;
108 | border-radius: 8px;
109 | color: #00579B;
110 | background-color: #E1F4FE;
111 | }
112 |
113 | .illustration {
114 | width: 80%;
115 | padding: 16px;
116 | margin: 20px;
117 | font-family: "RobotoDraft", "Roboto", sans-serif;
118 | }
119 |
120 | .card {
121 | box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2);
122 | transition: 0.3s;
123 | width: 100%;
124 | padding: 16px;
125 | margin: 20px;
126 | }
127 |
128 | .card:hover {
129 | box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
130 | }
131 |
132 | .snail {
133 | --snailColor: rgba(0, 0, 0, 0.2);
134 | -webkit-appearance: none;
135 | width: 80%;
136 | height: 15px;
137 | border-radius: 5px;
138 | background: #d3d3d3;
139 | outline: none;
140 | -webkit-transition: .2s;
141 | transition: opacity .2s;
142 | }
143 |
144 | .snail:hover {
145 | opacity: 1;
146 | }
147 |
148 | .snail::-webkit-slider-thumb {
149 | -webkit-appearance: none;
150 | appearance: none;
151 | width: 28px;
152 | height: 28px;
153 | border-radius: 50%;
154 | background: var(--snailColor);
155 | background-image: url('snail.svg');
156 | background-size: contain;
157 | background-position: center center;
158 | background-repeat: no-repeat;
159 | background-size: 20px;
160 | cursor: pointer;
161 | }
162 |
163 | .snail::-moz-range-thumb {
164 | width: 28px;
165 | height: 28px;
166 | border-radius: 50%;
167 | background: var(--snailColor);
168 | background-image: url('snail.svg');
169 | background-size: contain;
170 | background-position: center center;
171 | background-repeat: no-repeat;
172 | background-size: 20px;
173 | cursor: pointer;
174 | }
175 |
176 | .horizontallyCentered {
177 | margin: 0 auto;
178 | }
179 |
180 | .progress-btn {
181 | position: relative;
182 | width: 150px;
183 | height: 50px;
184 | display: inline-block;
185 | background: #A7C7E7;
186 | font-family: "RobotoDraft", "Roboto", sans-serif;
187 | color: #fff;
188 | font-weight: normal;
189 | font-size: 20px;
190 | }
191 |
192 | .progress-btn:not(.active) {
193 | cursor: pointer;
194 | }
195 |
196 | .progress-btn .btn {
197 | position: absolute;
198 | left: 0;
199 | top: 0;
200 | right: 0;
201 | bottom: 0;
202 | line-height: 50px;
203 | text-align: center;
204 | z-index: 5;
205 | }
206 |
207 | .progress-btn .progress {
208 | position: absolute;
209 | left: 0;
210 | top: 0;
211 | right: 0;
212 | bottom: 0;
213 | width: 50%;
214 | z-index: 10;
215 | background: #2a639ccc;
216 | }
217 |
218 |
219 |
--------------------------------------------------------------------------------
/docs/META-INF/MANIFEST.MF:
--------------------------------------------------------------------------------
1 | Manifest-Version: 1.0
2 |
3 |
--------------------------------------------------------------------------------
/docs/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/android-chrome-192x192.png
--------------------------------------------------------------------------------
/docs/android-chrome-512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/android-chrome-512x512.png
--------------------------------------------------------------------------------
/docs/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/apple-touch-icon.png
--------------------------------------------------------------------------------
/docs/demo.js.LICENSE.txt:
--------------------------------------------------------------------------------
1 | /*!
2 | * Determine if an object is a Buffer
3 | *
4 | * @author Feross Aboukhadijeh
5 | * @license MIT
6 | */
7 |
8 | /**
9 | * @license React
10 | * react-dom.production.min.js
11 | *
12 | * Copyright (c) Facebook, Inc. and its affiliates.
13 | *
14 | * This source code is licensed under the MIT license found in the
15 | * LICENSE file in the root directory of this source tree.
16 | */
17 |
18 | /**
19 | * @license React
20 | * react.production.min.js
21 | *
22 | * Copyright (c) Facebook, Inc. and its affiliates.
23 | *
24 | * This source code is licensed under the MIT license found in the
25 | * LICENSE file in the root directory of this source tree.
26 | */
27 |
28 | /**
29 | * @license React
30 | * scheduler.production.min.js
31 | *
32 | * Copyright (c) Facebook, Inc. and its affiliates.
33 | *
34 | * This source code is licensed under the MIT license found in the
35 | * LICENSE file in the root directory of this source tree.
36 | */
37 |
38 | /** @license React v17.0.2
39 | * react-is.production.min.js
40 | *
41 | * Copyright (c) Facebook, Inc. and its affiliates.
42 | *
43 | * This source code is licensed under the MIT license found in the
44 | * LICENSE file in the root directory of this source tree.
45 | */
46 |
--------------------------------------------------------------------------------
/docs/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/favicon-16x16.png
--------------------------------------------------------------------------------
/docs/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/favicon-32x32.png
--------------------------------------------------------------------------------
/docs/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/docs/favicon.ico
--------------------------------------------------------------------------------
/docs/hljs.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | /*!
18 | * StackOverflow.com light style
19 | *
20 | * @stackoverflow/stacks v0.56.0
21 | * https://github.com/StackExchange/Stacks
22 | */
23 | .hljs {
24 | display: block;
25 | overflow-x: auto;
26 | padding: .5em;
27 | color: #2f3337;
28 | background: #f6f6f6
29 | }
30 |
31 | .hljs-comment {
32 | color: #656e77
33 | }
34 |
35 | .hljs-attr, .hljs-doctag, .hljs-keyword, .hljs-meta, .hljs-meta-keyword, .hljs-section, .hljs-selector-class, .hljs-selector-pseudo, .hljs-selector-tag {
36 | color: #015692;
37 | font-weight: bold;
38 | }
39 |
40 | .hljs-attribute {
41 | color: #803378
42 | }
43 |
44 | .hljs-built_in, .hljs-literal, .hljs-name, .hljs-number, .hljs-quote, .hljs-selector-id, .hljs-template-tag, .hljs-title, .hljs-type {
45 | color: #b75501
46 | }
47 |
48 | .hljs-title {
49 | font-style: italic;
50 | }
51 |
52 | .hljs-link, .hljs-meta-string, .hljs-regexp, .hljs-selector-attr, .hljs-string, .hljs-symbol, .hljs-template-variable, .hljs-variable {
53 | color: #54790d
54 | }
55 |
56 | .hljs-bullet, .hljs-code {
57 | color: #535a60
58 | }
59 |
60 | .hljs-deletion {
61 | color: #c02d2e
62 | }
63 |
64 | .hljs-addition {
65 | color: #2f6f44
66 | }
67 |
68 | .hljs-emphasis {
69 | font-style: italic
70 | }
71 |
72 | .hljs-strong {
73 | font-weight: 700
74 | }
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 |
19 |
20 |
21 | State production with unidirectional data flow and Kotlin Flows
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
45 |
46 |
47 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/docs/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "",
3 | "State production with flows": "",
4 | "icons": [
5 | {
6 | "src": "/android-chrome-192x192.png",
7 | "sizes": "192x192",
8 | "type": "image/png"
9 | },
10 | {
11 | "src": "/android-chrome-512x512.png",
12 | "sizes": "512x512",
13 | "type": "image/png"
14 | }
15 | ],
16 | "theme_color": "#ffffff",
17 | "background_color": "#ffffff",
18 | "display": "standalone"
19 | }
--------------------------------------------------------------------------------
/docs/snail.svg:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
98 |
--------------------------------------------------------------------------------
/docs/styles.css:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | #root {
18 | height: 100%;
19 | width: 100%;
20 | display: flex;
21 | flex-direction: column;
22 | align-items: center;
23 | }
24 |
25 | #container {
26 | max-width: 720px;
27 | }
28 |
29 | pre {
30 | width: 100%;
31 | overflow: auto;
32 | height: auto;
33 | white-space: pre-wrap;
34 | word-wrap: break-word;
35 | text-align: start;
36 | }
37 |
38 | .markdown {
39 | width: 100%;
40 | }
41 |
42 | .markdown h1 {
43 | font-family: "Muli", sans-serif;
44 | font-size: 48px;
45 | letter-spacing: 0px;
46 | font-weight: normal;
47 | line-height: 90%;
48 | }
49 |
50 | .markdown h2 {
51 | font-family: "Muli", sans-serif;
52 | font-size: 34px;
53 | letter-spacing: 0px;
54 | font-weight: lighter;
55 | line-height: 90%;
56 | }
57 |
58 | .markdown h3 {
59 | font-family: "Muli", sans-serif;
60 | font-size: 24px;
61 | letter-spacing: 0px;
62 | font-weight: lighter;
63 | line-height: 80%;
64 | }
65 |
66 | .markdown p, li, ul {
67 | font-family: "Muli", sans-serif;
68 | font-size: 18px;
69 | letter-spacing: 0px;
70 | font-weight: normal;
71 | line-height: 125%;
72 | }
73 |
74 |
75 | .sectionLayout {
76 | display: flex;
77 | flex-direction: column;
78 | align-items: center;
79 | }
80 |
81 | .verticalLayout {
82 | display: flex;
83 | flex-direction: column;
84 | align-items: start;
85 | }
86 |
87 | .horizontalLayout {
88 | display: flex;
89 | flex-direction: row;
90 | align-items: center;
91 | }
92 |
93 | .centerText {
94 | text-align: center;
95 | }
96 |
97 | .colorSwatch {
98 | width: 25px;
99 | height: 25px;
100 | border-radius: 50%;
101 | margin: 16px;
102 | }
103 |
104 | .cta {
105 | width: 80%;
106 | margin: 16px;
107 | padding: 8px;
108 | border-radius: 8px;
109 | color: #00579B;
110 | background-color: #E1F4FE;
111 | }
112 |
113 | .illustration {
114 | width: 80%;
115 | padding: 16px;
116 | margin: 20px;
117 | font-family: "RobotoDraft", "Roboto", sans-serif;
118 | }
119 |
120 | .card {
121 | box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2);
122 | transition: 0.3s;
123 | width: 100%;
124 | padding: 16px;
125 | margin: 20px;
126 | }
127 |
128 | .card:hover {
129 | box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
130 | }
131 |
132 | .snail {
133 | --snailColor: rgba(0, 0, 0, 0.2);
134 | -webkit-appearance: none;
135 | width: 80%;
136 | height: 15px;
137 | border-radius: 5px;
138 | background: #d3d3d3;
139 | outline: none;
140 | -webkit-transition: .2s;
141 | transition: opacity .2s;
142 | }
143 |
144 | .snail:hover {
145 | opacity: 1;
146 | }
147 |
148 | .snail::-webkit-slider-thumb {
149 | -webkit-appearance: none;
150 | appearance: none;
151 | width: 28px;
152 | height: 28px;
153 | border-radius: 50%;
154 | background: var(--snailColor);
155 | background-image: url('snail.svg');
156 | background-size: contain;
157 | background-position: center center;
158 | background-repeat: no-repeat;
159 | background-size: 20px;
160 | cursor: pointer;
161 | }
162 |
163 | .snail::-moz-range-thumb {
164 | width: 28px;
165 | height: 28px;
166 | border-radius: 50%;
167 | background: var(--snailColor);
168 | background-image: url('snail.svg');
169 | background-size: contain;
170 | background-position: center center;
171 | background-repeat: no-repeat;
172 | background-size: 20px;
173 | cursor: pointer;
174 | }
175 |
176 | .horizontallyCentered {
177 | margin: 0 auto;
178 | }
179 |
180 | .progress-btn {
181 | position: relative;
182 | width: 150px;
183 | height: 50px;
184 | display: inline-block;
185 | background: #A7C7E7;
186 | font-family: "RobotoDraft", "Roboto", sans-serif;
187 | color: #fff;
188 | font-weight: normal;
189 | font-size: 20px;
190 | }
191 |
192 | .progress-btn:not(.active) {
193 | cursor: pointer;
194 | }
195 |
196 | .progress-btn .btn {
197 | position: absolute;
198 | left: 0;
199 | top: 0;
200 | right: 0;
201 | bottom: 0;
202 | line-height: 50px;
203 | text-align: center;
204 | z-index: 5;
205 | }
206 |
207 | .progress-btn .progress {
208 | position: absolute;
209 | left: 0;
210 | top: 0;
211 | right: 0;
212 | bottom: 0;
213 | width: 50%;
214 | z-index: 10;
215 | background: #2a639ccc;
216 | }
217 |
218 |
219 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2021 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # https://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
17 | # When configured, Gradle will run in incubating parallel mode.
18 | # This option should only be used with decoupled projects. More details, visit
19 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
20 | # org.gradle.parallel=true
21 | # AndroidX package structure to make it clearer which packages are bundled with the
22 | # Android operating system, and which are packaged with your app"s APK
23 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
24 | android.useAndroidX=true
25 | # Automatically convert third-party libraries to use AndroidX
26 | android.enableJetifier=true
27 | # Kotlin code style for this project: "official" or "obsolete":
28 | kotlin.code.style=official
--------------------------------------------------------------------------------
/gradle/libs.versions.toml:
--------------------------------------------------------------------------------
1 | [versions]
2 | androidGradlePlugin = "8.0.2"
3 | androidxCompose = "1.4.3"
4 | androidxComposeCompiler = "1.4.6"
5 | dokka = "1.8.10"
6 | jetbrainsCompose = "1.4.1"
7 | junit4 = "4.13.2"
8 | kotlin = "1.8.20"
9 | kotlinWrappers = "18.2.0-pre.584"
10 | kotlinxCoroutines = "1.7.3"
11 | richtext = "0.13.0"
12 | turbine = "0.7.0"
13 |
14 | [libraries]
15 | android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
16 | junit4 = { group = "junit", name = "junit", version.ref = "junit4" }
17 | androidx-compose-animation = { group = "androidx.compose.animation", name = "animation", version.ref = "androidxCompose" }
18 | androidx-compose-foundation = { group = "androidx.compose.foundation", name = "foundation", version.ref = "androidxCompose" }
19 | androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "androidxCompose" }
20 | androidx-compose-material = { group = "androidx.compose.material", name = "material", version.ref = "androidxCompose" }
21 | androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "androidxCompose" }
22 | androidx-compose-ui-test = { group = "androidx.compose.ui", name = "ui-test-junit4", version.ref = "androidxCompose" }
23 | androidx-compose-ui-testManifest = { group = "androidx.compose.ui", name = "ui-test-manifest", version.ref = "androidxCompose" }
24 | #androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling", version.ref = "androidxCompose" }
25 | androidx-compose-ui-tooling = { group = "androidx.compose.ui", name = "ui-tooling-preview-desktop", version.ref = "androidxCompose" }
26 | androidx-compose-ui-ui = { group = "androidx.compose.ui", name = "ui", version.ref = "androidxCompose" }
27 | androidx-compose-ui-util = { group = "androidx.compose.ui", name = "ui-util", version.ref = "androidxCompose" }
28 | cashapp-turbine = { group = "app.cash.turbine", name = "turbine", version.ref = "turbine" }
29 | jetbrains-compose-animation = { group = "org.jetbrains.compose.animation", name = "animation", version.ref = "jetbrainsCompose" }
30 | jetbrains-compose-foundation = { group = "org.jetbrains.compose.foundation", name = "foundation", version.ref = "jetbrainsCompose" }
31 | jetbrains-compose-foundation-layout = { group = "org.jetbrains.compose.foundation", name = "foundation-layout", version.ref = "jetbrainsCompose" }
32 | jetbrains-compose-gradlePlugin = { group = "org.jetbrains.compose", name = "compose-gradle-plugin", version.ref = "jetbrainsCompose" }
33 | jetbrains-compose-material = { group = "org.jetbrains.compose.material", name = "material", version.ref = "jetbrainsCompose" }
34 | jetbrains-compose-runtime = { group = "org.jetbrains.compose.runtime", name = "runtime", version.ref = "jetbrainsCompose" }
35 | jetbrains-compose-ui-test = { group = "org.jetbrains.compose.ui", name = "ui-test-junit4", version.ref = "jetbrainsCompose" }
36 | jetbrains-compose-ui-testManifest = { group = "org.jetbrains.compose.ui", name = "ui-test-manifest", version.ref = "jetbrainsCompose" }
37 | #jetbrains-compose-ui-tooling = { group = "org.jetbrains.compose.ui", name = "ui-tooling", version.ref = "jetbrainsCompose" }
38 | jetbrains-compose-ui-tooling = { group = "org.jetbrains.compose.ui", name = "ui-tooling-preview-desktop", version.ref = "jetbrainsCompose" }
39 | jetbrains-compose-ui-ui = { group = "org.jetbrains.compose.ui", name = "ui", version.ref = "jetbrainsCompose" }
40 | jetbrains-compose-ui-util = { group = "org.jetbrains.compose.ui", name = "ui-util", version.ref = "jetbrainsCompose" }
41 | dokka-gradlePlugin = { group = "org.jetbrains.dokka", name = "dokka-gradle-plugin", version.ref = "dokka" }
42 | kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
43 | kotlin-wrappers-react = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-react", version.ref = "kotlinWrappers" }
44 | kotlin-wrappers-reactDom = { group = "org.jetbrains.kotlin-wrappers", name = "kotlin-react-dom", version.ref = "kotlinWrappers" }
45 | kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
46 | kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "kotlinxCoroutines" }
47 | kotlinx-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
48 | richtext-commonmark= { group = "com.halilibo.compose-richtext", name = "richtext-commonmark", version.ref = "richtext" }
49 | richtext-material= { group = "com.halilibo.compose-richtext", name = "richtext-ui-material", version.ref = "richtext" }
50 |
51 | [plugins]
52 | android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" }
53 | android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" }
54 | android-test = { id = "com.android.test", version.ref = "androidGradlePlugin" }
55 | androidx-compose = { id = "com.android.test", version.ref = "androidGradlePlugin" }
56 | jetbrains-compose = { id = "org.jetbrains.compose", version.ref = "jetbrainsCompose" }
57 | jetbrains-dokka = { id = "org.jetbrains.dokka", version.ref = "dokka" }
58 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
59 | kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
60 | kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
61 | kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
62 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/tunjid/Mutator/0c94b699a3324f69488caa3847c59c26e314b421/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2021 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # https://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | distributionBase=GRADLE_USER_HOME
17 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.1-bin.zip
18 | distributionPath=wrapper/dists
19 | zipStorePath=wrapper/dists
20 | zipStoreBase=GRADLE_USER_HOME
21 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2021 Google LLC
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/libraryVersion.properties:
--------------------------------------------------------------------------------
1 | #
2 | # Copyright 2021 Google LLC
3 | #
4 | # Licensed under the Apache License, Version 2.0 (the "License");
5 | # you may not use this file except in compliance with the License.
6 | # You may obtain a copy of the License at
7 | #
8 | # https://www.apache.org/licenses/LICENSE-2.0
9 | #
10 | # Unless required by applicable law or agreed to in writing, software
11 | # distributed under the License is distributed on an "AS IS" BASIS,
12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | # See the License for the specific language governing permissions and
14 | # limitations under the License.
15 | #
16 | groupId=com.tunjid.mutator
17 | core_version=1.1.0
18 | coroutines_version=1.1.0
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2021 Google LLC
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * https://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | pluginManagement {
17 | includeBuild("build-logic")
18 | repositories {
19 | gradlePluginPortal()
20 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
21 | google()
22 | }
23 | }
24 | dependencyResolutionManagement {
25 | // Workaround for KT-51379
26 | repositoriesMode.set(RepositoriesMode.PREFER_PROJECT)
27 | repositories {
28 | google()
29 | mavenCentral()
30 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
31 | }
32 | }
33 | rootProject.name = "Mutator"
34 | include(
35 | ":core",
36 | ":coroutines",
37 | ":demo",
38 | )
39 |
40 |
--------------------------------------------------------------------------------