diff --git a/.idea/copyright/Apache.xml b/.idea/copyright/Apache.xml new file mode 100644 index 0000000..e7e1510 --- /dev/null +++ b/.idea/copyright/Apache.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 0000000..2102cda --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 49d38fc..569d995 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,7 @@ val commonMain by getting { - [Welcome](examples/welcome/welcome) - multi-module example of user on-boarding flow - [Parallel](examples/multi/parallel) - two machines running in parallel in one proxy state - [Navbar](examples/multi/navbar) - several machines running in proxy state, one of them active at a time +- [Mixed](examples/multi/mixed) - two machines of different gesture/UI system mixed in one state - [Lifecycle](examples/lifecycle) - track your Android app lifecycle to pause pending operations when the app is suspended ## The basic task - Load-Content-Error @@ -1258,12 +1259,12 @@ private sealed class MultiGesture { data class StringGesture(val data: String) : MultiGesture() } -private open class TestState : MultiMachineState() { +private open class TestState : MultiMachineState() { private data object IntKey : MachineKey(null) // Int for gesture and state private data object StringKey : MachineKey(null) // String for gesture and state - override val container: ProxyMachineContainer = AllTogetherMachineContainer( + override val container: ProxyMachineContainer = AllTogetherMachineContainer( listOf( object : MachineInit { override val key: MachineKey = IntKey @@ -1310,8 +1311,8 @@ private open class TestState : MultiMachineState() { private data object StringKey : MachineKey(null) // String for gesture and state // ... machine init omitted - - override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): String { + + override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, out Any>?): String { val i: Int = provider.getValue(IntKey) // Cast to Int val s: String = provider.getValue(StringKey) // Cast to String return "$i - $s" // Combined state of any kind you like @@ -1347,7 +1348,7 @@ private open class TestState : MultiMachineState() { // ... machine init omitted // Our parent gesture is - override fun mapGesture(parent: MultiGesture, processor: GestureProcessor) = when(parent) { + override fun mapGesture(parent: MultiGesture, processor: GestureProcessor) = when(parent) { is MultiGesture.IntGesture -> { processor.process(IntKey, parent.data) // Int expected } diff --git a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/CommonStateMachine.kt b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/CommonStateMachine.kt index 7ba27f3..b9b1a4b 100644 --- a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/CommonStateMachine.kt +++ b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/CommonStateMachine.kt @@ -17,7 +17,7 @@ package com.motorro.commonstatemachine * Common state machine input - from the outside world to the current state * @param G UI gesture */ -interface MachineInput { +interface MachineInput { /** * Updates state with UI gesture * @param gesture UI gesture to proceed @@ -51,7 +51,7 @@ interface MachineOutput { /** * Current public machine status */ -interface MachineStatus { +interface MachineStatus { /** * Checks if machine is started */ @@ -71,6 +71,11 @@ interface MachineStatus { */ interface CommonStateMachine : MachineInput, MachineOutput, MachineStatus { + /** + * Starts the machine + */ + fun start() + /** * Base state-machine implementation * @param G UI gesture @@ -104,9 +109,9 @@ interface CommonStateMachine : MachineInput, MachineOutput : MachineInput, MachineOutput( init: MachineInit, - onUiChanged: (MachineKey<*, *>, Any) -> Unit + onUiChanged: (MachineKey, U) -> Unit ) : CommonStateMachine, Activated { /** * Machine lifecycle @@ -41,7 +41,10 @@ internal class ActiveStateMachine( { onUiChanged(init.key, it.child) } ) - init { + /** + * Starts the machine + */ + override fun start() { machine.start() } diff --git a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineAccess.kt b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineAccess.kt index a2f6889..0cd36cc 100644 --- a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineAccess.kt +++ b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineAccess.kt @@ -13,24 +13,51 @@ package com.motorro.commonstatemachine.multi -import com.motorro.commonstatemachine.CommonStateMachine +/** + * Provides access to the state-machine + * @param CG Child gesture system + * @param CU Child UI-state system + */ +interface MachineAccess { + /** + * Keys collection + */ + val keys: Set> + + /** + * Retrieves UI state. + * [MachineInit] and [key] bind types securely. + * @param U Concrete UI state bound with the [key], subtype of CU + * @param key Machine key + */ + fun getState(key: MachineKey<*, U>): U? + + /** + * Processes machine gesture. + * [MachineInit] and [key] bind types securely. + * @param G Concrete gesture, subtype of th [CG] + * @param key Machine key + * @param gesture Gesture to process + */ + fun process(key: MachineKey, gesture: G) +} /** * Retrieves a ui-state given the [MachineKey] */ -interface UiStateProvider { +interface UiStateProvider { /** * Retrieves all running machine keys */ - fun getMachineKeys(): Set> + fun getMachineKeys(): Set> /** * Gets a concrete UI-state * @param key Machine key your state is bound to * @throws IllegalStateException if state is not found in common state */ - fun getValue(key: MachineKey<*, U>): U = checkNotNull(get(key)) { + fun getValue(key: MachineKey<*, out U>): U = checkNotNull(get(key)) { "Key $key not found in machine map" } @@ -38,30 +65,19 @@ interface UiStateProvider { * Gets a concrete UI-state * @param key Machine key your state is bound to */ - operator fun get(key: MachineKey<*, U>): U? + operator fun get(key: MachineKey<*, U>): U? } /** * Redirects your gesture to be processed with a child machine * identified by [MachineKey] */ -interface GestureProcessor { +interface GestureProcessor { /** * Redirects your gesture to be processed by child machine * if machine identified by [key] is found * @param key Machine key * @param gesture Gesture to process */ - fun process(key: MachineKey, gesture: G) -} - -/** - * Runs [block] with machine stored in [machineMap] under this key - */ -@Suppress("UNCHECKED_CAST") -internal inline fun withMachine( - key: MachineKey, - machineMap: MachineMap, - block: CommonStateMachine.() -> R -): R? = (machineMap[key] as? CommonStateMachine)?.block() - + fun process(key: MachineKey, gesture: G) +} \ No newline at end of file diff --git a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MultiMachineState.kt b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MultiMachineState.kt index 9baff18..7c2eba2 100644 --- a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MultiMachineState.kt +++ b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MultiMachineState.kt @@ -21,11 +21,11 @@ import com.motorro.commonstatemachine.CommonMachineState * - Override [mapUiState] to build a combined UI state af all the machines * - Override [container] with a [ProxyMachineContainer] of your choice */ -abstract class MultiMachineState : CommonMachineState() { +abstract class MultiMachineState : CommonMachineState() { /** * Proxy machines container */ - protected abstract val container: ProxyMachineContainer + protected abstract val container: ProxyMachineContainer /** * A part of [start] template to initialize state @@ -45,20 +45,18 @@ abstract class MultiMachineState : CommonMachineState( * Updates machine view-state */ @Suppress("UNUSED_PARAMETER") - private fun onUiStateChange(key: MachineKey<*, *>, uiState: Any) { + private fun onUiStateChange(key: MachineKey<*, out CU>, uiState: CU) { setUiState(buildUiState(key)) } /** * Builds common UI state */ - private fun buildUiState(changedKey: MachineKey<*, *>?): PU { - val machineMap = container.getMachines() - val uiStateProvider = object : UiStateProvider { - override fun getMachineKeys(): Set> = machineMap.keys - override fun get(key: MachineKey<*, U>): U? { - return withMachine(key, machineMap) { getUiState() } - } + private fun buildUiState(changedKey: MachineKey<*, out CU>?): PU { + val access = container.machineAccess + val uiStateProvider = object : UiStateProvider { + override fun getMachineKeys(): Set> = access.keys + override fun get(key: MachineKey<*, U>): U? = access.getState(key) } return mapUiState(uiStateProvider, changedKey) } @@ -74,10 +72,10 @@ abstract class MultiMachineState : CommonMachineState( * A part of [process] template to process UI gesture */ override fun doProcess(gesture: PG) { - val machineMap = container.getMachines() - val processor = object : GestureProcessor { - override fun process(key: MachineKey, gesture: G) { - withMachine(key, machineMap) { process(gesture) } + val access = container.machineAccess + val processor = object : GestureProcessor { + override fun process(key: MachineKey, gesture: G) { + access.process(key, gesture) } } mapGesture(gesture, processor) @@ -88,7 +86,7 @@ abstract class MultiMachineState : CommonMachineState( * @param parent Parent gesture * @param processor Use it to send child gesture to the relevant child machine */ - protected abstract fun mapGesture(parent: PG, processor: GestureProcessor) + protected abstract fun mapGesture(parent: PG, processor: GestureProcessor) /** * Maps combined child UI state to parent @@ -96,5 +94,5 @@ abstract class MultiMachineState : CommonMachineState( * @param changedKey Key of machine that changed the UI state. Null if called explicitly via [updateUi] * @see updateUi */ - protected abstract fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): PU + protected abstract fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, out CU>?): PU } \ No newline at end of file diff --git a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/ProxyMachineContainer.kt b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/ProxyMachineContainer.kt index f20feb7..dc55f0c 100644 --- a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/ProxyMachineContainer.kt +++ b/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/ProxyMachineContainer.kt @@ -13,25 +13,32 @@ package com.motorro.commonstatemachine.multi +import com.motorro.commonstatemachine.CommonStateMachine import com.motorro.commonstatemachine.ProxyStateMachine import com.motorro.commonstatemachine.lifecycle.MachineLifecycle /** - * Holds proxy-machines + * A machine container. Holds proxied machines. + * The [CG] and [CU] type binding are done via explicit cast to be able + * to host heterogeneous machines + * [MachineInit] binds the key with correct machine type + * @param CG Child gesture system + * @param CU Child UI-state system */ -interface ProxyMachineContainer { +interface ProxyMachineContainer { /** - * Starts machines + * Machine access */ - fun start(onUiChanged: (key: MachineKey<*, *>, uiState: Any) -> Unit) + val machineAccess: MachineAccess /** - * Returns a map of proxy machines + * Starts machines in the container + * @param onUiChanged UI change handler from hosting state */ - fun getMachines(): MachineMap + fun start(onUiChanged: (key: MachineKey<*, out CU>, uiState: CU) -> Unit) /** - * Clears machines + * Clears contained machines */ fun clear() @@ -41,7 +48,9 @@ interface ProxyMachineContainer { * always [MachineLifecycle.State.ACTIVE] * @param init Machine init */ - fun allTogether(init: Collection>): ProxyMachineContainer = AllTogetherMachineContainer(init) + fun allTogether( + init: Collection> + ): ProxyMachineContainer = AllTogetherMachineContainer(init) /** * Creates a container where machines may be activated and deactivated @@ -52,45 +61,130 @@ interface ProxyMachineContainer { * @param init Machine init * @param initiallyActive Machines that are initially active. Defaults to first machine in [init] */ - fun some( - init: Collection>, - initiallyActive: Set> = setOf(init.first().key) - ): ActiveMachineContainer = SomeActiveMachineContainer(init, initiallyActive) + fun some( + init: Collection>, + initiallyActive: Set> = setOf(init.first().key) + ): ActiveMachineContainer = SomeActiveMachineContainer(init, initiallyActive) + } + + /** + * Base machine container that binds gesture/ui type system with machines + * The [CG] and [CU] type binding are done via explicit cast to be able + * to host heterogeneous machines + * [MachineInit] binds the key with correct machine type + * @param CG Common gesture type + * @param CU Common UI type + * @param M Concrete machine type + * @param init Initialisation structures to create machines + */ + abstract class Base>( + private val init: Collection> + ) : ProxyMachineContainer { + /** + * Creates a specific machine for this container + * @param init Initialization structure + * @param onUiChanged UI change handler from hosting state + */ + abstract fun create( + init: MachineInit<*, out CU>, + onUiChanged: (key: MachineKey<*, out CU>, uiState: CU) -> Unit + ): M + + /** + * Machines bound with keys + */ + protected var machines: Map, M> = emptyMap() + + /** + * Machine access + */ + override val machineAccess: MachineAccess = object : MachineAccess { + /** + * Keys collection + */ + override val keys: Set> get() = machines.keys + + /** + * Processes machine gesture. + * [MachineInit] and [key] bind types securely. + * @param G Concrete gesture, subtype of th [CG] + * @param key Machine key + * @param gesture Gesture to process + */ + @Suppress("UNCHECKED_CAST") + override fun process(key: MachineKey, gesture: G) { + (machines[key] as? CommonStateMachine)?.process(gesture) + } + + /** + * Retrieves UI state. + * [MachineInit] and [key] bind types securely. + * @param U Concrete UI state bound with the [key], subtype of CU + * @param key Machine key + */ + @Suppress("UNCHECKED_CAST") + override fun getState(key: MachineKey<*, U>): U? { + return machines[key]?.getUiState() as? U + } + } + + /** + * Starts machines in the container + * @param onUiChanged UI change handler from hosting state + */ + override fun start(onUiChanged: (key: MachineKey<*, out CU>, uiState: CU) -> Unit) { + machines = init.associate { i -> i.key to create(i, onUiChanged) } + machines.forEach { (_, machine) -> machine.start() } + doStart() + } + + /** + * A part of [start] template called after initial startup + * Machines are created at this point + */ + open fun doStart() = Unit + + /** + * Clears contained machines + */ + override fun clear() { + machines.forEach { (_, machine) -> machine.clear() } + } } } /** * Container that activates machine */ -interface ActiveMachineContainer : ProxyMachineContainer { +interface ActiveMachineContainer : ProxyMachineContainer { /** * Retrieves currently active machine key */ - fun getActive(): Set> + fun getActive(): Set> /** * Sets active machine given the keys */ - fun setActive(keys: Set>) + fun setActive(keys: Set>) /** * Sets active machine given the key */ - fun setActive(vararg key: MachineKey<*, *>) = setActive(key.toSet()) + fun setActive(vararg key: MachineKey<*, out CU>) = setActive(key.toSet()) /** * Disposes machines. All machines by [keys] are deactivated disposed and dereferenced. * When activating again - a new machine is created using the same init. * Use to cleanup memory for example on low memory alert from system */ - fun dispose(keys: Set>) + fun dispose(keys: Set>) /** * Disposes machines. All machines by [key] are deactivated disposed and dereferenced. * When activating again - a new machine is created using the same init. * Use to cleanup memory for example on low memory alert from system */ - fun dispose(vararg key: MachineKey<*, *>) = dispose(key.toSet()) + fun dispose(vararg key: MachineKey<*, out CU>) = dispose(key.toSet()) /** * Disposes all inactive machines @@ -103,13 +197,9 @@ interface ActiveMachineContainer : ProxyMachineContainer { * All machines run in parallel without any lifecycle management * @param init Machine init */ -internal class AllTogetherMachineContainer(private val init: Collection>) : ProxyMachineContainer { - - /** - * Proxy machines - */ - private var machines: Map, ProxyStateMachine<*, *>> = emptyMap() - +internal class AllTogetherMachineContainer( + init: Collection> +) : ProxyMachineContainer.Base>(init) { /** * Machine lifecycle that is always started */ @@ -121,34 +211,23 @@ internal class AllTogetherMachineContainer(private val init: Collection MachineInit.machine(onUiChanged: (key: MachineKey<*, *>, uiState: Any) -> Unit) = ProxyStateMachine( + private fun MachineInit<*, U>.machine(onUiChanged: (MachineKey<*, U>, U) -> Unit) = ProxyStateMachine( initialUiState, { init(lifecycle) }, { onUiChanged(key, it) } ) /** - * Starts machines - */ - override fun start(onUiChanged: (key: MachineKey<*, *>, uiState: Any) -> Unit) { - machines = init.associate { i -> i.key to i.machine(onUiChanged) } - machines.forEach { it.value.start() } - } - - /** - * Returns a map of proxy machines - */ - override fun getMachines(): MachineMap = machines - - /** - * Clears machines + * Creates a specific machine for this container + * @param init Initialization structure + * @param onUiChanged UI change handler from hosting state */ - override fun clear() { - machines.forEach { it.value.clear() } - } + override fun create( + init: MachineInit<*, out CU>, + onUiChanged: (key: MachineKey<*, out CU>, uiState: CU) -> Unit + ): CommonStateMachine<*, out CU> = init.machine(onUiChanged) } /** @@ -157,47 +236,39 @@ internal class AllTogetherMachineContainer(private val init: Collection>, - private val initiallyActive: Set> -) : ActiveMachineContainer { - +internal class SomeActiveMachineContainer( + private val init: Collection>, + private val initiallyActive: Set> +) : ProxyMachineContainer.Base>(init), ActiveMachineContainer { /** - * Proxy machines + * Creates a specific machine for this container + * @param init Initialization structure + * @param onUiChanged UI change handler from hosting state */ - private var machines: Map, ActiveStateMachine<*, *>> = emptyMap() + override fun create( + init: MachineInit<*, out CU>, + onUiChanged: (key: MachineKey<*, out CU>, uiState: CU) -> Unit + ): ActiveStateMachine<*, out CU> = ActiveStateMachine(init, onUiChanged) /** - * Starts machines + * A part of [start] template called after initial startup + * Machines are created at this point */ - override fun start(onUiChanged: (key: MachineKey<*, *>, uiState: Any) -> Unit) { - machines = init.associate { i -> i.key to ActiveStateMachine(i, onUiChanged) } + override fun doStart() { setActive(initiallyActive) } - /** - * Returns a map of proxy machines - */ - override fun getMachines(): MachineMap = machines - - /** - * Clears machines - */ - override fun clear() { - machines.forEach { it.value.clear() } - } - /** * Retrieves currently active machine key */ - override fun getActive(): Set> { - return machines.entries.filter { (_, machine) -> machine.isActive() }.map { it.key }.toSet() - } + override fun getActive(): Set> = machines.entries + .filter { (_, machine) -> machine.isActive() } + .map { it.key }.toSet() /** * Sets active machine given the key */ - override fun setActive(keys: Set>) { + override fun setActive(keys: Set>) { val active = getActive() val toDeactivate = active.minus(keys) val toActivate = keys.minus(active) @@ -215,7 +286,7 @@ internal class SomeActiveMachineContainer( * When activating again - a new machine is created using the same init. * Use to cleanup memory for example on low memory alert from system */ - override fun dispose(keys: Set>) { + override fun dispose(keys: Set>) { machines.forEach { (key, machine) -> if (keys.contains(key)) { machine.dispose() diff --git a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/mocks.kt b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/mocks.kt index c8a9269..f31c535 100644 --- a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/mocks.kt +++ b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/mocks.kt @@ -40,8 +40,13 @@ class MachineMock(uiState: U) : CommonStateMachine { val processed = mutableListOf() val uiStates = mutableListOf(uiState) var cleared = false + var started = false + + override fun start() { + started = true + } - override fun isStarted(): Boolean = true + override fun isStarted(): Boolean = started override fun getUiState(): U = uiStates.last() diff --git a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/ActiveStateMachineTest.kt b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/ActiveStateMachineTest.kt index 88482e2..d8384d6 100644 --- a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/ActiveStateMachineTest.kt +++ b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/ActiveStateMachineTest.kt @@ -47,6 +47,7 @@ class ActiveStateMachineTest { } } machine = ActiveStateMachine(init) { _, u -> uiState.add(u) } + machine.start() } @Test diff --git a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/MultiMachineStateTest.kt b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/MultiMachineStateTest.kt index ab59e39..e97d70a 100644 --- a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/MultiMachineStateTest.kt +++ b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/MultiMachineStateTest.kt @@ -44,8 +44,8 @@ class MultiMachineStateTest { data class StringGesture(val data: String) : MultiGesture() } - private open class TestState : MultiMachineState() { - override val container: ProxyMachineContainer = AllTogetherMachineContainer( + private open class TestState : MultiMachineState() { + override val container: ProxyMachineContainer = AllTogetherMachineContainer( listOf( object : MachineInit { override val key: MachineKey = IntKey @@ -64,7 +64,7 @@ class MultiMachineStateTest { ) ) - override fun mapGesture(parent: MultiGesture, processor: GestureProcessor) = when(parent) { + override fun mapGesture(parent: MultiGesture, processor: GestureProcessor) = when(parent) { is MultiGesture.IntGesture -> { processor.process(IntKey, parent.data) } @@ -73,7 +73,7 @@ class MultiMachineStateTest { } } - override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): String { + override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, out Any>?): String { val i: Int = provider.getValue(IntKey) val s: String = provider.getValue(StringKey) return "$i - $s" @@ -110,7 +110,7 @@ class MultiMachineStateTest { fun providesActiveMachinesKeys() { var tested = false val state = object : TestState() { - override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, *>?): String { + override fun mapUiState(provider: UiStateProvider, changedKey: MachineKey<*, out Any>?): String { assertEquals( setOf(IntKey, StringKey), provider.getMachineKeys() diff --git a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/SomeActiveMachineContainerTest.kt b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/SomeActiveMachineContainerTest.kt index dd0218f..3f13f8b 100644 --- a/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/SomeActiveMachineContainerTest.kt +++ b/commonstatemachine/src/commonTest/kotlin/com/motorro/commonstatemachine/multi/SomeActiveMachineContainerTest.kt @@ -26,7 +26,7 @@ class SomeActiveMachineContainerTest { private lateinit var intState: StateMock private lateinit var stringState: StateMock - private lateinit var container: SomeActiveMachineContainer + private lateinit var container: SomeActiveMachineContainer private val intKey = object : MachineKey() { } private val stringKey = object : MachineKey() { } diff --git a/coroutines/src/commonTest/kotlin/com/motorro/commonstatemachine/coroutines/mocks.kt b/coroutines/src/commonTest/kotlin/com/motorro/commonstatemachine/coroutines/mocks.kt index 9211f40..70090fb 100644 --- a/coroutines/src/commonTest/kotlin/com/motorro/commonstatemachine/coroutines/mocks.kt +++ b/coroutines/src/commonTest/kotlin/com/motorro/commonstatemachine/coroutines/mocks.kt @@ -43,8 +43,13 @@ class MachineMock(uiState: U) : CommonStateMachine { val processed = mutableListOf() val uiStates = mutableListOf(uiState) var cleared = false + var started = false + + override fun start() { + started = true + } - override fun isStarted(): Boolean = true + override fun isStarted(): Boolean = started override fun getUiState(): U = uiStates.last() diff --git a/examples/multi/mixed/.gitignore b/examples/multi/mixed/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/examples/multi/mixed/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/examples/multi/mixed/build.gradle.kts b/examples/multi/mixed/build.gradle.kts new file mode 100644 index 0000000..862f4d5 --- /dev/null +++ b/examples/multi/mixed/build.gradle.kts @@ -0,0 +1,101 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@Suppress("DSL_SCOPE_VIOLATION") // TODO: Remove once KTIJ-19369 is fixed +plugins { + alias(libs.plugins.android.app) + alias(libs.plugins.kotlin.android) +} + +val versionCode: String by project.extra +val versionName: String by project.extra +val androidMinSdkVersion: Int by project.extra +val androidTargetSdkVersion: Int by project.extra +val androidCompileSdkVersion: Int by project.extra + +android { + namespace = "com.motorro.statemachine.multi.mixed" + compileSdk = androidCompileSdkVersion + + defaultConfig { + applicationId = "com.motorro.statemachine.multi.mixed" + minSdk = androidMinSdkVersion + targetSdk = androidTargetSdkVersion + versionCode = versionCode + versionName = versionName + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + isCoreLibraryDesugaringEnabled = true + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + kotlinOptions { + jvmTarget = "17" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = libs.versions.compose.compiler.get() + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + implementation(project(":commonstatemachine")) + implementation(project(":coroutines")) + implementation(project(":examples:commoncore")) + implementation(project(":examples:androidcore")) + implementation(project(":examples:timer")) + + coreLibraryDesugaring(libs.desugaring) + + implementation(libs.androidx.core) + implementation(libs.androidx.lifecycle.runtime) + implementation(libs.androidx.lifecycle.livedata) + implementation(libs.androidx.lifecycle.viewmodel) + + implementation(libs.kotlin.coroutines.core) + implementation(libs.kotlin.coroutines.android) + + implementation(libs.bundles.compose.core) + implementation(libs.compose.activity) + implementation(libs.compose.viewmodel) + implementation(libs.compose.foundation) + implementation(libs.compose.foundation.layouts) + + debugImplementation(libs.compose.tooling) + + testImplementation(libs.bundles.test.core) + testImplementation(libs.test.androidx.arch) + testImplementation(libs.test.kotlin.coroutines) +} \ No newline at end of file diff --git a/examples/multi/mixed/proguard-rules.pro b/examples/multi/mixed/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/examples/multi/mixed/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/examples/multi/mixed/src/main/AndroidManifest.xml b/examples/multi/mixed/src/main/AndroidManifest.xml new file mode 100644 index 0000000..dff16cc --- /dev/null +++ b/examples/multi/mixed/src/main/AndroidManifest.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/App.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/App.kt new file mode 100644 index 0000000..7809c87 --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/App.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed + +import android.app.Application +import timber.log.Timber + +class App : Application() { + override fun onCreate() { + super.onCreate() + setupLogger() + } + + private fun setupLogger() { + Timber.plant(Timber.DebugTree()) + } +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/MainActivity.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/MainActivity.kt new file mode 100644 index 0000000..080b13c --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/MainActivity.kt @@ -0,0 +1,105 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.motorro.statemachine.androidcore.ui.theme.CommonStateMachineTheme +import com.motorro.statemachine.mixed.model.MainViewModel +import com.motorro.statemachine.mixed.model.data.MixedGesture +import com.motorro.statemachine.mixed.model.data.SomeGesture +import com.motorro.statemachine.mixed.model.data.SomeUiState +import com.motorro.statemachine.timer.ui.TimerScreen + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContent { + val model: MainViewModel = viewModel() + val state by model.uiState.collectAsState() + + CommonStateMachineTheme { + Scaffold { padding -> + Column( + Modifier + .fillMaxSize() + .padding(padding), + verticalArrangement = Arrangement.SpaceEvenly + ) { + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(8.dp) + .border(1.dp, Color.Black), + contentAlignment = Alignment.Center + ) { + Button( + colors = ButtonDefaults.buttonColors( + containerColor = when(state.some) { + SomeUiState.Off -> Color.Red + SomeUiState.On -> Color.Green + } + ), + onClick = { model.update(MixedGesture.Some(SomeGesture.StateToggled)) } + ) { + Text( + text = when(state.some) { + SomeUiState.Off -> "Off" + SomeUiState.On -> "On" + } + ) + } + } + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(8.dp) + .border(1.dp, Color.Black) + ) { + TimerScreen( + modifier = Modifier.padding(padding), + title = "Bottom", + state = state.timer + ) { + model.update(MixedGesture.Timer(it)) + } + } + } + } + } + } + } +} + diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/MainViewModel.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/MainViewModel.kt new file mode 100644 index 0000000..978851f --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/MainViewModel.kt @@ -0,0 +1,38 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model + +import androidx.lifecycle.ViewModel +import com.motorro.commonstatemachine.coroutines.FlowStateMachine +import com.motorro.statemachine.mixed.model.data.MixedGesture +import com.motorro.statemachine.mixed.model.data.MixedUiState +import com.motorro.statemachine.mixed.model.data.SomeUiState +import com.motorro.statemachine.mixed.model.state.MixedState +import com.motorro.statemachine.timer.data.TimerUiState +import kotlinx.coroutines.flow.StateFlow +import kotlin.time.Duration + +class MainViewModel : ViewModel() { + private val machine = FlowStateMachine(MixedUiState(SomeUiState.Off, TimerUiState.Stopped(Duration.ZERO))) { + MixedState() + } + + val uiState: StateFlow get() = machine.uiState + + fun update(gesture: MixedGesture) = machine.process(gesture) + + override fun onCleared() { + machine.clear() + } +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedGesture.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedGesture.kt new file mode 100644 index 0000000..60cee14 --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedGesture.kt @@ -0,0 +1,31 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model.data + +import com.motorro.statemachine.timer.data.TimerGesture + +/** + * Application gestures + */ +sealed class MixedGesture { + /** + * Some gesture + */ + data class Some(val gesture: SomeGesture) : MixedGesture() + + /** + * Timer gesture + */ + data class Timer(val gesture: TimerGesture) : MixedGesture() +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedUiState.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedUiState.kt new file mode 100644 index 0000000..296ad07 --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/MixedUiState.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model.data + +import androidx.compose.runtime.Immutable +import com.motorro.statemachine.timer.data.TimerUiState + +/** + * Common state for both active machines + */ +@Immutable +data class MixedUiState( + val some: SomeUiState, + val timer: TimerUiState +) \ No newline at end of file diff --git a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineMap.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeGesture.kt similarity index 74% rename from commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineMap.kt rename to examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeGesture.kt index 9c26a7a..193d9ab 100644 --- a/commonstatemachine/src/commonMain/kotlin/com/motorro/commonstatemachine/multi/MachineMap.kt +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeGesture.kt @@ -11,11 +11,14 @@ * limitations under the License. */ -package com.motorro.commonstatemachine.multi - -import com.motorro.commonstatemachine.CommonStateMachine +package com.motorro.statemachine.mixed.model.data /** - * A map of state machines + * Some gesture */ -internal typealias MachineMap = Map, CommonStateMachine<*, *>> +sealed class SomeGesture { + /** + * State toggled + */ + data object StateToggled : SomeGesture() +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeUiState.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeUiState.kt new file mode 100644 index 0000000..22053fa --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/data/SomeUiState.kt @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model.data + +/** + * Some UI state + */ +sealed class SomeUiState { + /** + * Something is ON + */ + data object On : SomeUiState() + + /** + * Something is off + */ + data object Off : SomeUiState() +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/MixedState.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/MixedState.kt new file mode 100644 index 0000000..ae82198 --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/MixedState.kt @@ -0,0 +1,95 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model.state + +import com.motorro.commonstatemachine.CommonMachineState +import com.motorro.commonstatemachine.lifecycle.MachineLifecycle +import com.motorro.commonstatemachine.multi.GestureProcessor +import com.motorro.commonstatemachine.multi.MachineInit +import com.motorro.commonstatemachine.multi.MachineKey +import com.motorro.commonstatemachine.multi.MultiMachineState +import com.motorro.commonstatemachine.multi.ProxyMachineContainer +import com.motorro.commonstatemachine.multi.UiStateProvider +import com.motorro.statemachine.commoncore.log.Logger +import com.motorro.statemachine.mixed.model.data.MixedGesture +import com.motorro.statemachine.mixed.model.data.MixedUiState +import com.motorro.statemachine.mixed.model.data.SomeGesture +import com.motorro.statemachine.mixed.model.data.SomeUiState +import com.motorro.statemachine.timer.data.TimerGesture +import com.motorro.statemachine.timer.data.TimerKey +import com.motorro.statemachine.timer.data.TimerUiState +import com.motorro.statemachine.timer.state.TimerState +import kotlin.time.Duration + +/** + * Machines run in parallel. All machines are active + */ +internal class MixedState : MultiMachineState() { + private val someKey = object : MachineKey("some") { } + private val timerKey = TimerKey("bottom") + + /** + * Machines run in parallel and always active + */ + override val container: ProxyMachineContainer = ProxyMachineContainer.allTogether( + listOf( + object : MachineInit { + override val key: MachineKey = someKey + override val initialUiState: SomeUiState = SomeUiState.Off + override val init: (MachineLifecycle) -> CommonMachineState = { + SomeState() + } + }, + object : MachineInit { + override val key: TimerKey = timerKey + override val initialUiState: TimerUiState = TimerUiState.Stopped(Duration.ZERO) + override val init: (MachineLifecycle) -> CommonMachineState = { + val tag = requireNotNull(key.tag) + Logger.i("Creating machine for $tag") + TimerState.init(tag, it) + } + } + ) + ) + + /** + * Updates child machines with gestures if relevant + * @param parent Parent gesture + * @param processor Use it to send child gesture to the relevant child machine + */ + override fun mapGesture(parent: MixedGesture, processor: GestureProcessor) = when(parent) { + is MixedGesture.Some -> { + Logger.i("Top gesture: $parent") + processor.process(someKey, parent.gesture) + } + is MixedGesture.Timer -> { + Logger.i("Bottom gesture: $parent") + processor.process(timerKey, parent.gesture) + } + } + + /** + * Maps combined child UI state to parent + * @param provider Provides child UI states + * @param changedKey Key of machine that changed the UI state. Null if called explicitly via [updateUi] + * @see updateUi + */ + override fun mapUiState( + provider: UiStateProvider, + changedKey: MachineKey<*, out Any>? + ): MixedUiState = MixedUiState( + some = provider.getValue(someKey), + timer = provider.getValue(timerKey) + ) +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/SomeState.kt b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/SomeState.kt new file mode 100644 index 0000000..b0ed608 --- /dev/null +++ b/examples/multi/mixed/src/main/java/com/motorro/statemachine/mixed/model/state/SomeState.kt @@ -0,0 +1,39 @@ +/* + * Copyright 2023 Nikolai Kotchetkov. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.motorro.statemachine.mixed.model.state + +import com.motorro.commonstatemachine.CommonMachineState +import com.motorro.statemachine.mixed.model.data.SomeGesture +import com.motorro.statemachine.mixed.model.data.SomeUiState + +/** + * Some machine state with internal data + */ +class SomeState : CommonMachineState() { + + private var isOn = false + + override fun doStart() { + render() + } + + override fun doProcess(gesture: SomeGesture) { + isOn = isOn.not() + render() + } + + private fun render() { + setUiState(if (isOn) SomeUiState.On else SomeUiState.Off) + } +} \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/drawable/ic_launcher_background.xml b/examples/multi/mixed/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..7469e64 --- /dev/null +++ b/examples/multi/mixed/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,183 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/examples/multi/mixed/src/main/res/drawable/ic_launcher_foreground.xml b/examples/multi/mixed/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..c499cdf --- /dev/null +++ b/examples/multi/mixed/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..d18dad9 --- /dev/null +++ b/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..d18dad9 --- /dev/null +++ b/examples/multi/mixed/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher.webp b/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher.webp b/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher.webp b/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/examples/multi/mixed/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/examples/multi/mixed/src/main/res/values/colors.xml b/examples/multi/mixed/src/main/res/values/colors.xml new file mode 100644 index 0000000..4ccd21c --- /dev/null +++ b/examples/multi/mixed/src/main/res/values/colors.xml @@ -0,0 +1,23 @@ + + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/values/strings.xml b/examples/multi/mixed/src/main/res/values/strings.xml new file mode 100644 index 0000000..e81002c --- /dev/null +++ b/examples/multi/mixed/src/main/res/values/strings.xml @@ -0,0 +1,16 @@ + + + + Parallel + \ No newline at end of file diff --git a/examples/multi/mixed/src/main/res/values/themes.xml b/examples/multi/mixed/src/main/res/values/themes.xml new file mode 100644 index 0000000..fe0d9c1 --- /dev/null +++ b/examples/multi/mixed/src/main/res/values/themes.xml @@ -0,0 +1,18 @@ + + + + + +