-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #51 from stslex/dev
Refactor mvi arch
- Loading branch information
Showing
22 changed files
with
494 additions
and
188 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
36 changes: 36 additions & 0 deletions
36
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/store_di/StoreBeanV2.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
package com.stslex.wizard.core.ui.mvi.store_di | ||
|
||
import com.stslex.wizard.core.ui.mvi.v2.BaseStore | ||
import org.koin.core.definition.BeanDefinition | ||
import org.koin.core.definition.KoinDefinition | ||
import org.koin.core.module.Module | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>> Module.storeOf( | ||
crossinline constructor: () -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>, reified T1> Module.storeOf( | ||
crossinline constructor: (T1) -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>, reified T1, reified T2> Module.storeOf( | ||
crossinline constructor: (T1, T2) -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>, reified T1, reified T2, reified T3> Module.storeOf( | ||
crossinline constructor: (T1, T2, T3) -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>, reified T1, reified T2, reified T3, reified T4> Module.storeOf( | ||
crossinline constructor: (T1, T2, T3, T4) -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) | ||
|
||
inline fun <reified R : BaseStore<*, *, *, *>, reified T1, reified T2, reified T3, reified T4, reified T5> Module.storeOf( | ||
crossinline constructor: (T1, T2, T3, T4, T5) -> R, | ||
noinline options: (BeanDefinition<R>.() -> Unit)? = null, | ||
): KoinDefinition<R> = viewModelOf(constructor, options) |
114 changes: 114 additions & 0 deletions
114
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/BaseStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import androidx.lifecycle.ViewModel | ||
import androidx.lifecycle.viewModelScope | ||
import com.stslex.wizard.core.core.AppDispatcher | ||
import com.stslex.wizard.core.core.coroutine.AppCoroutineScope | ||
import com.stslex.wizard.core.core.coroutine.AppCoroutineScopeImpl | ||
import com.stslex.wizard.core.ui.mvi.Store | ||
import com.stslex.wizard.core.ui.mvi.Store.Action | ||
import com.stslex.wizard.core.ui.mvi.Store.Event | ||
import com.stslex.wizard.core.ui.mvi.Store.State | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.MutableSharedFlow | ||
import kotlinx.coroutines.flow.MutableStateFlow | ||
import kotlinx.coroutines.flow.SharedFlow | ||
import kotlinx.coroutines.flow.StateFlow | ||
import kotlinx.coroutines.flow.asSharedFlow | ||
import kotlinx.coroutines.flow.asStateFlow | ||
import kotlinx.coroutines.flow.update | ||
import kotlinx.coroutines.launch | ||
|
||
open class BaseStore<S : State, A : Action, E : Event, HStore : HandlerStore<S, A, E>>( | ||
initialState: S, | ||
private val handlerCreator: HandlerCreator<S, A, E, HStore>, | ||
) : ViewModel(), Store<S, A, E>, HandlerStore<S, A, E> { | ||
|
||
private val _event: MutableSharedFlow<E> = MutableSharedFlow() | ||
override val event: SharedFlow<E> = _event.asSharedFlow() | ||
|
||
private val _state: MutableStateFlow<S> = MutableStateFlow(initialState) | ||
override val state: StateFlow<S> = _state.asStateFlow() | ||
|
||
protected val scope: AppCoroutineScope = AppCoroutineScopeImpl(viewModelScope) | ||
|
||
private var _lastAction: A? = null | ||
override val lastAction: A? | ||
get() = _lastAction | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
override fun sendAction(action: A) { | ||
if (lastAction != action && action !is Action.RepeatLast) { | ||
_lastAction = action | ||
} | ||
val handler = handlerCreator(action) as Handler<A, HStore> | ||
handler.invoke(this as HStore, action) | ||
} | ||
|
||
/** | ||
* Updates the state of the screen. | ||
* @param update - function that updates the state | ||
* */ | ||
override fun updateState(update: (S) -> S) { | ||
_state.update(update) | ||
} | ||
|
||
/** | ||
* Sends an event to the screen. The event is sent on the default dispatcher of the AppDispatcher. | ||
* @param event - event to be sent | ||
* @see AppDispatcher | ||
* */ | ||
override fun sendEvent(event: E) { | ||
viewModelScope.launch { _event.emit(event) } | ||
} | ||
|
||
/** | ||
* Launches a coroutine and catches exceptions. The coroutine is launched on the default dispatcher of the AppDispatcher. | ||
* @param onError - error handler | ||
* @param onSuccess - success handler | ||
* @param action - action to be executed | ||
* @return Job | ||
* @see Job | ||
* @see AppDispatcher | ||
* */ | ||
override fun <T> launch( | ||
onError: suspend (Throwable) -> Unit, | ||
onSuccess: suspend CoroutineScope.(T) -> Unit, | ||
workDispatcher: CoroutineDispatcher, | ||
eachDispatcher: CoroutineDispatcher, | ||
action: suspend CoroutineScope.() -> T, | ||
) = scope.launch( | ||
onError = onError, | ||
workDispatcher = workDispatcher, | ||
eachDispatcher = eachDispatcher, | ||
onSuccess = onSuccess, | ||
action = action | ||
) | ||
|
||
/** | ||
* Launches a flow and collects it in the screenModelScope. The flow is collected on the default dispatcher. of the AppDispatcher. | ||
* @param onError - error handler | ||
* @param each - action for each element of the flow | ||
* @return Job | ||
* @see Flow | ||
* @see Job | ||
* @see AppDispatcher | ||
* */ | ||
override fun <T> Flow<T>.launch( | ||
onError: suspend (cause: Throwable) -> Unit, | ||
workDispatcher: CoroutineDispatcher, | ||
eachDispatcher: CoroutineDispatcher, | ||
each: suspend (T) -> Unit | ||
): Job = scope.launch( | ||
flow = this, | ||
workDispatcher = workDispatcher, | ||
eachDispatcher = eachDispatcher, | ||
onError = onError, | ||
each = each, | ||
) | ||
|
||
|
||
} |
13 changes: 13 additions & 0 deletions
13
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/BaseStoreImpl.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import com.stslex.wizard.core.ui.mvi.Store.Action | ||
import com.stslex.wizard.core.ui.mvi.Store.Event | ||
import com.stslex.wizard.core.ui.mvi.Store.State | ||
|
||
internal class BaseStoreImpl<S : State, A : Action, E : Event, HStore : HandlerStore<S, A, E>>( | ||
initialState: S, | ||
handlerCreator: HandlerCreator<S, A, E, HStore>, | ||
) : BaseStore<S, A, E, HStore>( | ||
initialState = initialState, | ||
handlerCreator = handlerCreator | ||
) |
9 changes: 9 additions & 0 deletions
9
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/Handler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import com.stslex.wizard.core.ui.mvi.Store.Action | ||
|
||
fun interface Handler<A : Action, TStore : HandlerStore<*, *, *>> { | ||
|
||
fun TStore.invoke(action: A) | ||
|
||
} |
10 changes: 10 additions & 0 deletions
10
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/HandlerCreator.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import com.stslex.wizard.core.ui.mvi.Store.Action | ||
import com.stslex.wizard.core.ui.mvi.Store.Event | ||
import com.stslex.wizard.core.ui.mvi.Store.State | ||
|
||
fun interface HandlerCreator<S : State, A : Action, E : Event, HStore : HandlerStore<S, A, E>> { | ||
|
||
operator fun invoke(action: A): Handler<*, HStore> | ||
} |
58 changes: 58 additions & 0 deletions
58
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/HandlerStore.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import com.stslex.wizard.core.core.AppDispatcher | ||
import com.stslex.wizard.core.core.AppDispatcherImpl | ||
import com.stslex.wizard.core.ui.mvi.Store | ||
import com.stslex.wizard.core.ui.mvi.Store.Event | ||
import com.stslex.wizard.core.ui.mvi.Store.State | ||
import kotlinx.coroutines.CoroutineDispatcher | ||
import kotlinx.coroutines.CoroutineScope | ||
import kotlinx.coroutines.Job | ||
import kotlinx.coroutines.flow.Flow | ||
import kotlinx.coroutines.flow.StateFlow | ||
|
||
interface HandlerStore<S : State, A : Store.Action, E : Event> { | ||
|
||
val state: StateFlow<S> | ||
|
||
val lastAction: A? | ||
|
||
fun sendEvent(event: E) | ||
|
||
fun sendAction(action: A) | ||
|
||
fun updateState(update: (S) -> S) | ||
|
||
/** | ||
* Launches a coroutine and catches exceptions. The coroutine is launched on the default dispatcher of the AppDispatcher. | ||
* @param onError - error handler | ||
* @param onSuccess - success handler | ||
* @param action - action to be executed | ||
* @return Job | ||
* @see Job | ||
* @see AppDispatcher | ||
* */ | ||
fun <T> launch( | ||
onError: suspend (Throwable) -> Unit = {}, | ||
onSuccess: suspend CoroutineScope.(T) -> Unit = {}, | ||
workDispatcher: CoroutineDispatcher = AppDispatcherImpl.default, | ||
eachDispatcher: CoroutineDispatcher = AppDispatcherImpl.main.immediate, | ||
action: suspend CoroutineScope.() -> T, | ||
): Job | ||
|
||
/** | ||
* Launches a flow and collects it in the screenModelScope. The flow is collected on the default dispatcher. of the AppDispatcher. | ||
* @param onError - error handler | ||
* @param each - action for each element of the flow | ||
* @return Job | ||
* @see Flow | ||
* @see Job | ||
* @see AppDispatcher | ||
* */ | ||
fun <T> Flow<T>.launch( | ||
onError: suspend (cause: Throwable) -> Unit = {}, | ||
workDispatcher: CoroutineDispatcher = AppDispatcherImpl.default, | ||
eachDispatcher: CoroutineDispatcher = AppDispatcherImpl.main.immediate, | ||
each: suspend (T) -> Unit | ||
): Job | ||
} |
25 changes: 25 additions & 0 deletions
25
core/ui/mvi/src/commonMain/kotlin/com/stslex/wizard/core/ui/mvi/v2/StoreExt.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package com.stslex.wizard.core.ui.mvi.v2 | ||
|
||
import com.stslex.wizard.core.ui.mvi.Store | ||
import com.stslex.wizard.core.ui.mvi.Store.Action | ||
import com.stslex.wizard.core.ui.mvi.Store.Event | ||
import com.stslex.wizard.core.ui.mvi.Store.State | ||
|
||
@Suppress("UNCHECKED_CAST") | ||
fun <S : State, A : Action, E : Event, TStore : Store<S, A, E>, HStore : HandlerStore<S, A, E>> store( | ||
initialState: S, | ||
handlerCreator: HandlerCreator<S, A, E, HStore> | ||
): TStore = BaseStoreImpl(initialState, handlerCreator) as TStore | ||
|
||
fun <S : State, A : Action, E : Event, HStore : HandlerStore<S, A, E>> Handler<A, HStore>.invoke( | ||
store: HStore, | ||
action: A | ||
) { | ||
with(store) { | ||
this.invoke(action) | ||
} | ||
} | ||
|
||
inline fun <S : State, A : Action, E : Event, HStore : HandlerStore<S, A, E>> handler( | ||
crossinline block: HandlerStore<S, A, E>.(action: A) -> Unit | ||
) = Handler<A, HStore> { block(it) } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
18 changes: 18 additions & 0 deletions
18
...re/profile/src/commonMain/kotlin/com/stslex/wizard/feature/profile/mvi/ClickersHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package com.stslex.wizard.feature.profile.mvi | ||
|
||
import com.stslex.wizard.core.ui.mvi.v2.Handler | ||
import com.stslex.wizard.feature.profile.ui.store.ProfileHandlerStore | ||
import com.stslex.wizard.feature.profile.ui.store.ProfileStore.Action | ||
|
||
class ClickersHandler : Handler<Action.Click, ProfileHandlerStore> { | ||
|
||
override fun ProfileHandlerStore.invoke(action: Action.Click) { | ||
when (action) { | ||
Action.Click.BackButtonClick -> sendAction(Action.Navigation.Back) | ||
Action.Click.FavouriteClick -> sendAction(Action.Navigation.Favourite(state.value.uuid)) | ||
Action.Click.FollowersClick -> sendAction(Action.Navigation.Followers(state.value.uuid)) | ||
Action.Click.FollowingClick -> sendAction(Action.Navigation.Following(state.value.uuid)) | ||
Action.Click.SettingsClick -> sendAction(Action.Navigation.Settings) | ||
} | ||
} | ||
} |
57 changes: 57 additions & 0 deletions
57
...profile/src/commonMain/kotlin/com/stslex/wizard/feature/profile/mvi/InitStorageHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
package com.stslex.wizard.feature.profile.mvi | ||
|
||
import androidx.compose.ui.graphics.Color | ||
import com.stslex.wizard.core.database.store.UserStore | ||
import com.stslex.wizard.core.navigation.Screen | ||
import com.stslex.wizard.core.ui.mvi.v2.Handler | ||
import com.stslex.wizard.feature.profile.domain.interactor.ProfileInteractor | ||
import com.stslex.wizard.feature.profile.ui.model.ProfileAvatarModel | ||
import com.stslex.wizard.feature.profile.ui.model.toUi | ||
import com.stslex.wizard.feature.profile.ui.store.ProfileHandlerStore | ||
import com.stslex.wizard.feature.profile.ui.store.ProfileScreenState | ||
import com.stslex.wizard.feature.profile.ui.store.ProfileStore.Action | ||
|
||
class InitStorageHandler( | ||
private val interactor: ProfileInteractor, | ||
private val userStore: UserStore, | ||
) : Handler<Action.Init, ProfileHandlerStore> { | ||
|
||
override fun ProfileHandlerStore.invoke(action: Action.Init) { | ||
val uuid = action.uuid.ifBlank { userStore.uuid } | ||
|
||
updateState { currentState -> | ||
currentState.copy( | ||
isSelf = action.type == Screen.Profile.Type.SELF, | ||
uuid = uuid, | ||
) | ||
} | ||
|
||
interactor.getProfile(uuid) | ||
.launch( | ||
onError = { error -> | ||
updateState { currentState -> | ||
currentState.copy( | ||
screen = ProfileScreenState.Error(error) | ||
) | ||
} | ||
} | ||
) { profile -> | ||
val avatar = if (profile.avatarUrl.isBlank()) { | ||
ProfileAvatarModel.Empty( | ||
color = Color.Gray, // TODO replace with random color | ||
symbol = profile.username.firstOrNull()?.lowercase().orEmpty() | ||
) | ||
} else { | ||
ProfileAvatarModel.Content(profile.avatarUrl) | ||
} | ||
val profileUi = profile.toUi( | ||
avatarModel = avatar | ||
) | ||
updateState { currentState -> | ||
currentState.copy( | ||
screen = ProfileScreenState.Content.NotLoading(profileUi) | ||
) | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.