diff --git a/core-mvi/build.gradle b/core-mvi/build.gradle index 0aa50cd2..3beb9d8a 100644 --- a/core-mvi/build.gradle +++ b/core-mvi/build.gradle @@ -7,7 +7,17 @@ java { targetCompatibility = JavaVersion.VERSION_1_8 } +test { + useJUnitPlatform() +} + dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1" + + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.4.1' + testImplementation 'io.kotest:kotest-runner-junit5:4.3.1' + testImplementation 'io.kotest:kotest-assertions-core:4.3.1' + testImplementation 'io.kotest:kotest-property:4.3.1' + testImplementation "io.mockk:mockk:1.10.2" } \ No newline at end of file diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlowContainer.kt b/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlowContainer.kt deleted file mode 100644 index 751bcd0e..00000000 --- a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlowContainer.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.vanced.manager.core.mvi - -import kotlinx.coroutines.CoroutineScope - -abstract class MviFlowContainer { - - protected abstract val handler: Handler - - protected abstract val reducer: Reducer - - fun create( - state: State, - scope: CoroutineScope - ): MviFlow = - MviFlow( - initialState = state, - reducer = reducer, - handler = handler, - scope = scope - ) -} \ No newline at end of file diff --git a/core-mvi/src/main/java/example/Example2Fragment.kt b/core-mvi/src/main/java/example/Example2Fragment.kt deleted file mode 100644 index b75c7f17..00000000 --- a/core-mvi/src/main/java/example/Example2Fragment.kt +++ /dev/null @@ -1,55 +0,0 @@ -package example - -import com.vanced.manager.core.mvi.MviRenderView -import example.ExampleContainer.Action -import example.ExampleContainer.SideEffect -import example.ExampleContainer.State -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.launch - - -class Example2Fragment : MviRenderView { - - val lifecycleScope = CoroutineScope(Job()) - - val mvi = ExampleContainer.create( - state = State.Default, - scope = lifecycleScope - ) // create "store" - - private fun onCreate() { - lifecycleScope.launch { // bind view for call render - mvi.bindView(view = this@Example2Fragment, scope = this) - } - lifecycleScope.launch { // bind side effects (single events) for catch on view - mvi.bindSideEffects(view = this@Example2Fragment, scope = this) - } - } - - override fun render(state: State) { - when (state) { - State.Default -> { - // render view - } - } - } - - @ExperimentalCoroutinesApi - override fun actionsFlow(): Flow = callbackFlow { - //generate actions click and other - awaitClose() - } - - override fun sideEffects(sideEffect: SideEffect) { // single events - when (sideEffect) { - is SideEffect.ShowToast -> { - // Toast.show - } - } - } -} \ No newline at end of file diff --git a/core-mvi/src/main/java/example/ExampleContainer.kt b/core-mvi/src/main/java/example/ExampleContainer.kt deleted file mode 100644 index 4ec44734..00000000 --- a/core-mvi/src/main/java/example/ExampleContainer.kt +++ /dev/null @@ -1,55 +0,0 @@ -package example - -import com.vanced.manager.core.mvi.Handler -import com.vanced.manager.core.mvi.MviFlowContainer -import com.vanced.manager.core.mvi.Reducer -import example.ExampleContainer.Action -import example.ExampleContainer.Modification -import example.ExampleContainer.SideEffect -import example.ExampleContainer.State -import kotlinx.coroutines.delay -import kotlinx.coroutines.flow.MutableSharedFlow - -object ExampleContainer : MviFlowContainer() { - - // "single events" - sealed class SideEffect { - data class ShowToast(val message: String) : SideEffect() - } - - // view state - sealed class State { - object Default : State() - } - - // view actions - sealed class Action { - object Click : Action() - } - - // Modification for view - sealed class Modification { - data class ChangeText(val text: String) : Modification() - } - - // handle actions, generate side effects (single events) and send changes (may be viewModel ane change) - override val handler: Handler = - { _: State, action: Action, sideEffect: MutableSharedFlow -> - when (action) { - Action.Click -> { - sideEffect.emit(SideEffect.ShowToast("")) - delay(10) - emit(Modification.ChangeText("")) - } - } - } - - // handle modifications and current state and generate new state for view - override val reducer: Reducer = { _: State, modification: Modification -> - when (modification) { - is Modification.ChangeText -> { - State.Default - } - } - } -} \ No newline at end of file diff --git a/core-mvi/src/main/java/example/ExampleViewModel.kt b/core-mvi/src/main/java/example/ExampleViewModel.kt deleted file mode 100644 index d5a93067..00000000 --- a/core-mvi/src/main/java/example/ExampleViewModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package example - -import example.ExampleContainer.State -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Job - -class ExampleViewModel { - - val viewModelScope = CoroutineScope(Job()) - - val mvi = ExampleContainer.create( - state = State.Default, - scope = viewModelScope - ) // create "store" -} \ No newline at end of file diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt similarity index 80% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt index 3c3efe0a..2d583ca0 100644 --- a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviFlow.kt +++ b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlow.kt @@ -6,7 +6,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock -interface MviFlow { +interface MviFlow { fun bindView( view: MviRenderView, @@ -25,7 +25,7 @@ private class MviFlowImpl( private val reducer: Reducer, private val handler: Handler, scope: CoroutineScope, -) : MviFlow, +) : MviFlow, CoroutineScope by scope, Mutex by Mutex() { @@ -70,19 +70,19 @@ private class MviFlowImpl( private suspend fun Flow.proceed() = collect { action -> handler.invoke( - MutableSharedFlow().setState(), + MutableSharedFlow().subscribeState(), state.value, action, sideEffect ) } - private fun MutableSharedFlow.setState(): MutableSharedFlow { - onEach { modification -> - withLock { - state.value = reducer.invoke(state.value, modification) - } - }.launchIn(this@MviFlowImpl) - return this - } + private fun MutableSharedFlow.subscribeState(): MutableSharedFlow = + also { + onEach { modification -> + withLock { + reducer.invoke(state, state.value, modification) + } + }.launchIn(this@MviFlowImpl) + } } fun MviFlow( @@ -90,7 +90,7 @@ fun MviFlow( reducer: Reducer, handler: Handler, scope: CoroutineScope -): MviFlow = MviFlowImpl( +): MviFlow = MviFlowImpl( initialState = initialState, reducer = reducer, handler = handler, diff --git a/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlowStore.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlowStore.kt new file mode 100644 index 00000000..0eaa3fd7 --- /dev/null +++ b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviFlowStore.kt @@ -0,0 +1,6 @@ +package com.vanced.manager.core.mvi + +interface MviFlowStore { + + val store: MviFlow +} \ No newline at end of file diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/MviRenderView.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviRenderView.kt similarity index 100% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/MviRenderView.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/MviRenderView.kt diff --git a/core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt similarity index 75% rename from core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt rename to core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt index 179e8ea3..b108ef2a 100644 --- a/core-mvi/src/main/java/com/vanced/manager/core/mvi/Typealias.kt +++ b/core-mvi/src/main/kotlin/com/vanced/manager/core/mvi/Typealias.kt @@ -6,11 +6,11 @@ typealias Handler = suspend MutableSharedFlow.( state: State, action: Action, - sideEffect: MutableSharedFlow + sideEffectsFlow: MutableSharedFlow, ) -> Unit typealias Reducer = - suspend ( + suspend MutableSharedFlow.( state: State, modification: Modification - ) -> State \ No newline at end of file + ) -> Unit \ No newline at end of file diff --git a/core-mvi/src/main/kotlin/example/ExampleManyStoreFragment.kt b/core-mvi/src/main/kotlin/example/ExampleManyStoreFragment.kt new file mode 100644 index 00000000..18ae925a --- /dev/null +++ b/core-mvi/src/main/kotlin/example/ExampleManyStoreFragment.kt @@ -0,0 +1,95 @@ +package example + +import com.vanced.manager.core.mvi.* +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch + +class ExampleManyStoreFragment { + + val lifecycleScope = CoroutineScope(Job()) + + private val view1 = getView() + private val container1 = getStore() + + private val view2 = getView() + private val container2 = getStore() + + private fun onCreate() { + lifecycleScope.launch { // bind view for call render + container1.store.bindView(view = view1, scope = this) + } + lifecycleScope.launch { // bind side effects (single events) for catch on view + container1.store.bindSideEffects(view = view1, scope = this) + } + + lifecycleScope.launch { // bind view for call render + container2.store.bindView(view = view2, scope = this) + } + lifecycleScope.launch { // bind side effects (single events) for catch on view + container2.store.bindSideEffects(view = view2, scope = this) + } + } +} + +// handle actions, generate side effects (single events) and send changes (may be viewModel ane change) +private fun createHandler(): Handler = + { _: State, action: Action, sideEffect: MutableSharedFlow -> + when (action) { + Action.Click -> { + sideEffect.emit(SideEffect.ShowToast("")) + delay(10) + emit(Modification.ChangeText("")) + } + } + } + +// handle modifications and current state and generate new state for view\ +private fun createReducer(): Reducer = { _: State, modification: Modification -> + when (modification) { + is Modification.ChangeText -> { + State.Default + } + } +} + +private fun ExampleManyStoreFragment.getStore() = object : MviFlowStore { + + override val store: MviFlow + get() = MviFlow( + initialState = State.Default, + reducer = createReducer(), + handler = createHandler(), + scope = lifecycleScope + )// create "store" +} + +private fun getView() = + object : MviRenderView { + override fun render(state: State) { + when (state) { + State.Default -> { + // render view + } + } + } + + override fun actionsFlow(): Flow = callbackFlow { + //generate actions click and other + awaitClose() + } + + override fun sideEffects(sideEffect: SideEffect) { + when (sideEffect) { + is SideEffect.ShowToast -> { + // Toast.show + } + } + } + + } \ No newline at end of file diff --git a/core-mvi/src/main/kotlin/example/ExampleStoreFragment.kt b/core-mvi/src/main/kotlin/example/ExampleStoreFragment.kt new file mode 100644 index 00000000..2f2b24ee --- /dev/null +++ b/core-mvi/src/main/kotlin/example/ExampleStoreFragment.kt @@ -0,0 +1,75 @@ +package example + +import com.vanced.manager.core.mvi.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.callbackFlow + +class ExampleStoreFragment : + MviRenderView, + MviFlowStore { + + private val lifecycleScope = CoroutineScope(Job()) + + // handle actions, generate side effects (single events) and send changes (may be viewModel ane change) + private val handler: Handler = + { _: State, action: Action, sideEffectsFlow: MutableSharedFlow -> + when (action) { + Action.Click -> { + sideEffectsFlow.emit(SideEffect.ShowToast("")) + delay(10) + emit(Modification.ChangeText("")) + } + } + } + + // handle modifications and current state and generate new state for view + private val reducer: Reducer = { _: State, modification: Modification -> + when (modification) { + is Modification.ChangeText -> { + State.Default + } + } + } + + override val store: MviFlow = + MviFlow( + initialState = State.Default, + reducer = reducer, + handler = handler, + scope = lifecycleScope + )// create "store" + + private fun onCreate() { + lifecycleScope.launch { // bind view for call render + store.bindView(view = this@ExampleStoreFragment, scope = this) + } + lifecycleScope.launch { // bind side effects (single events) for catch on view + store.bindSideEffects(view = this@ExampleStoreFragment, scope = this) + } + } + + override fun render(state: State) { + when (state) { + State.Default -> { + // render view + } + } + } + + @ExperimentalCoroutinesApi + override fun actionsFlow(): Flow = callbackFlow { + //generate actions click and other + awaitClose() + } + + override fun sideEffects(sideEffect: SideEffect) { // single events + when (sideEffect) { + is SideEffect.ShowToast -> { + // Toast.show + } + } + } +} \ No newline at end of file diff --git a/core-mvi/src/main/kotlin/example/ExampleViewModel.kt b/core-mvi/src/main/kotlin/example/ExampleViewModel.kt new file mode 100644 index 00000000..8666ba0d --- /dev/null +++ b/core-mvi/src/main/kotlin/example/ExampleViewModel.kt @@ -0,0 +1,43 @@ +package example + +import com.vanced.manager.core.mvi.Handler +import com.vanced.manager.core.mvi.MviFlow +import com.vanced.manager.core.mvi.MviFlowStore +import com.vanced.manager.core.mvi.Reducer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableSharedFlow + +class ExampleViewModel : MviFlowStore { + + private val viewModelScope = CoroutineScope(Job()) + + // handle actions, generate side effects (single events) and send changes (may be viewModel ane change) + private val handler: Handler = + { _: State, action: Action, sideEffectsFlow: MutableSharedFlow -> + when (action) { + Action.Click -> { + sideEffectsFlow.emit(SideEffect.ShowToast("")) + delay(10) + emit(Modification.ChangeText("")) + } + } + } + + // handle modifications and current state and generate new state for view + private val reducer: Reducer = { _: State, modification: Modification -> + when (modification) { + is Modification.ChangeText -> { + State.Default + } + } + } + + override val store: MviFlow = MviFlow( + initialState = State.Default, + reducer = reducer, + handler = handler, + scope = viewModelScope + ) // create "store" +} \ No newline at end of file diff --git a/core-mvi/src/main/java/example/ExampleFragment.kt b/core-mvi/src/main/kotlin/example/ExampleWithViewModelFragment.kt similarity index 69% rename from core-mvi/src/main/java/example/ExampleFragment.kt rename to core-mvi/src/main/kotlin/example/ExampleWithViewModelFragment.kt index a0835325..30a70d98 100644 --- a/core-mvi/src/main/java/example/ExampleFragment.kt +++ b/core-mvi/src/main/kotlin/example/ExampleWithViewModelFragment.kt @@ -1,9 +1,6 @@ package example import com.vanced.manager.core.mvi.MviRenderView -import example.ExampleContainer.Action -import example.ExampleContainer.SideEffect -import example.ExampleContainer.State import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job @@ -13,18 +10,18 @@ import kotlinx.coroutines.flow.callbackFlow import kotlinx.coroutines.launch -class ExampleFragment : MviRenderView { +class ExampleWithViewModelFragment : MviRenderView { - val lifecycleScope = CoroutineScope(Job()) + private val lifecycleScope = CoroutineScope(Job()) - val viewModel = ExampleViewModel() + private val viewModel = ExampleViewModel() private fun onCreate() { lifecycleScope.launch { - viewModel.mvi.bindView(view = this@ExampleFragment, scope = this) + viewModel.store.bindView(view = this@ExampleWithViewModelFragment, scope = this) } lifecycleScope.launch { - viewModel.mvi.bindSideEffects(view = this@ExampleFragment, scope = this) + viewModel.store.bindSideEffects(view = this@ExampleWithViewModelFragment, scope = this) } } diff --git a/core-mvi/src/main/kotlin/example/Mvi.kt b/core-mvi/src/main/kotlin/example/Mvi.kt new file mode 100644 index 00000000..81f23692 --- /dev/null +++ b/core-mvi/src/main/kotlin/example/Mvi.kt @@ -0,0 +1,25 @@ +package example + +// "single events" +sealed class SideEffect { + + data class ShowToast(val message: String) : SideEffect() +} + +// view state +sealed class State { + + object Default : State() +} + +// view actions +sealed class Action { + + object Click : Action() +} + +// Modification for view +sealed class Modification { + + data class ChangeText(val text: String) : Modification() +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt new file mode 100644 index 00000000..854888c2 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/MviFlowSpec.kt @@ -0,0 +1,169 @@ +package com.vanced.manager.core.mvi + +import com.vanced.manager.core.mvi.subject.* +import io.kotest.core.spec.style.ShouldSpec +import io.kotest.matchers.shouldBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.test.TestCoroutineScope + +@ExperimentalCoroutinesApi +class MviFlowSpec : ShouldSpec() { + + data class Test(val msg: String) + + init { + context("Various events") { + should("return Modifications") { + val testData = "testText" + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + store.modifications shouldBe listOf( + Modification.ChangeDescription(testData), + Modification.ChangeTitle(testData), + Modification.ChangeDescription(testData), + ) + } + should("return States") { + val testData = 4545454545 + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + view.states shouldBe listOf( + State.Default, + State.SetDescription(testData), + State.Default, + State.SetTitle(testData), + State.Default, + State.SetDescription(testData), + State.Default, + ) + } + should("return SideEffects") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Tap) + emit(Action.Click) + } + ) + delay(600) + view.sideEffects shouldBe listOf( + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + ) + } + } + + context("The same event") { + should("return Modifications") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + store.modifications shouldBe listOf( + Modification.ChangeDescription(testData), + Modification.ChangeDescription(testData), + Modification.ChangeDescription(testData), + ) + } + should("return States") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + view.states shouldBe listOf( + State.Default, + State.SetDescription(testData), + State.Default, + State.SetDescription(testData), + State.Default, + State.SetDescription(testData), + State.Default + ) + } + should("return SideEffects") { + val testData = Test("test") + val store = SubjectStore( + scope = TestCoroutineScope(), + defaultState = State.Default, + testData = testData + ) + val view = SubjectView( + scope = TestCoroutineScope(), + store = store, + actions = flow { + emit(Action.Click) + emit(Action.Click) + emit(Action.Click) + } + ) + delay(600) + view.sideEffects shouldBe listOf( + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + SideEffect.ShowToast(testData), + ) + } + } + } +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt new file mode 100644 index 00000000..aafab30c --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/MviSubject.kt @@ -0,0 +1,33 @@ +package com.vanced.manager.core.mvi.subject + +// "single events" +sealed class SideEffect { + + data class ShowToast(val message: testData) : SideEffect() +} + +// view state +sealed class State { + + object Default : State() + + data class SetTitle(val text: testData) : State() + + data class SetDescription(val text: testData) : State() +} + +// view actions +sealed class Action { + + object Click : Action() + + object Tap : Action() +} + +// Modification for view +sealed class Modification { + + data class ChangeTitle(val text: testData) : Modification() + + data class ChangeDescription(val text: testData) : Modification() +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt new file mode 100644 index 00000000..7c0596f6 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectStore.kt @@ -0,0 +1,54 @@ +package com.vanced.manager.core.mvi.subject + +import com.vanced.manager.core.mvi.Handler +import com.vanced.manager.core.mvi.MviFlow +import com.vanced.manager.core.mvi.MviFlowStore +import com.vanced.manager.core.mvi.Reducer +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow + +class SubjectStore( + scope: CoroutineScope, + defaultState: State, + private val testData: TD +) : MviFlowStore { + + val modifications = mutableListOf() + + private val handler: Handler = + { state: State, action: Action, sideEffectsFlow: MutableSharedFlow -> + when (action) { + Action.Click -> { + emit(Modification.ChangeDescription(testData)) + sideEffectsFlow.emit(SideEffect.ShowToast(testData)) + } + Action.Tap -> { + emit(Modification.ChangeTitle(testData)) + sideEffectsFlow.emit(SideEffect.ShowToast(testData)) + } + } + } + + private val reducer: Reducer = + { state: State, modification: Modification -> + modifications.add(modification) + when (modification) { + is Modification.ChangeDescription<*> -> { + emit(State.SetDescription(modification.text)) + emit(State.Default) + } + is Modification.ChangeTitle<*> -> { + emit(State.SetTitle(modification.text)) + emit(State.Default) + } + } + } + + override val store: MviFlow = + MviFlow( + scope = scope, + initialState = defaultState, + handler = handler, + reducer = reducer + ) +} \ No newline at end of file diff --git a/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt new file mode 100644 index 00000000..7a0dc1d7 --- /dev/null +++ b/core-mvi/src/test/kotlin/com/vanced/manager/core/mvi/subject/SubjectView.kt @@ -0,0 +1,43 @@ +package com.vanced.manager.core.mvi.subject + +import com.vanced.manager.core.mvi.MviRenderView +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +class SubjectView( + scope: CoroutineScope, + private val actions: Flow, + store: SubjectStore +) : MviRenderView { + + val states = mutableListOf() + val sideEffects = mutableListOf() + + init { + scope.launch { + store.store.bindSideEffects( + view = this@SubjectView, + scope = this + ) + } + scope.launch { + store.store.bindView( + view = this@SubjectView, + scope = this + ) + } + } + + override fun render(state: State) { + states.add(state) + } + + @ExperimentalCoroutinesApi + override fun actionsFlow(): Flow = actions + + override fun sideEffects(sideEffect: SideEffect) { + sideEffects.add(sideEffect) + } +} \ No newline at end of file