diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/AfterStartModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/AfterStartModule.kt index be63e26..16583a4 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/AfterStartModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/AfterStartModule.kt @@ -1,19 +1,22 @@ package org.timemates.app.authorization.dependencies.screens -import org.timemates.app.authorization.dependencies.AuthorizationDataModule -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartReducer -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine +import com.arkivanov.decompose.ComponentContext import io.timemates.sdk.authorization.email.types.value.VerificationHash import org.koin.core.annotation.Factory import org.koin.core.annotation.Module +import org.timemates.app.authorization.dependencies.AuthorizationDataModule +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartReducer +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent @Module(includes = [AuthorizationDataModule::class]) class AfterStartModule { @Factory - fun stateMachine( + fun mviComponent( + componentContext: ComponentContext, verificationHash: VerificationHash, - ): AfterStartStateMachine { - return AfterStartStateMachine( + ): AfterStartScreenComponent { + return AfterStartScreenComponent( + componentContext, reducer = AfterStartReducer( verificationHash = verificationHash ), diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfigureAccountModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfigureAccountModule.kt index dbcc171..f85b301 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfigureAccountModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfigureAccountModule.kt @@ -1,19 +1,18 @@ package org.timemates.app.authorization.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.authorization.dependencies.AuthorizationDataModule import org.timemates.app.authorization.repositories.AuthorizationsRepository import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountMiddleware import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountReducer -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent import org.timemates.app.authorization.usecases.CreateNewAccountUseCase import org.timemates.app.authorization.validation.UserDescriptionValidator import org.timemates.app.authorization.validation.UserNameValidator -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [AuthorizationDataModule::class]) class ConfigureAccountModule { @@ -33,21 +32,23 @@ class ConfigureAccountModule { fun userDescriptionValidator(): UserDescriptionValidator = UserDescriptionValidator() @Factory - fun stateMachine( + fun mviComponent( + componentContext: ComponentContext, verificationHash: VerificationHash, configureAccountMiddleware: ConfigureAccountMiddleware, createNewAccountUseCase: CreateNewAccountUseCase, userNameValidator: UserNameValidator, userDescriptionValidator: UserDescriptionValidator, - ): ConfigureAccountStateMachine { - return ConfigureAccountStateMachine( + ): ConfigureAccountScreenComponent { + return ConfigureAccountScreenComponent( + componentContext = componentContext, reducer = ConfigureAccountReducer( verificationHash = verificationHash, createNewAccountUseCase = createNewAccountUseCase, userNameValidator = userNameValidator, userDescriptionValidator = userDescriptionValidator, ), - configureAccountMiddleware, + middleware = configureAccountMiddleware, ) } } \ No newline at end of file diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfirmAuthorizationModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfirmAuthorizationModule.kt index a6b2000..857bb3d 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfirmAuthorizationModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/ConfirmAuthorizationModule.kt @@ -1,18 +1,17 @@ package org.timemates.app.authorization.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.authorization.dependencies.AuthorizationDataModule import org.timemates.app.authorization.repositories.AuthorizationsRepository import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationMiddleware -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationsReducer import org.timemates.app.authorization.usecases.ConfirmEmailAuthorizationUseCase import org.timemates.app.authorization.validation.ConfirmationCodeValidator -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [AuthorizationDataModule::class]) class ConfirmAuthorizationModule { @@ -29,13 +28,15 @@ class ConfirmAuthorizationModule { ): ConfirmEmailAuthorizationUseCase = ConfirmEmailAuthorizationUseCase(authorizationsRepository) @Factory - fun stateMachine( + fun mviComponent( + componentContext: ComponentContext, verificationHash: VerificationHash, middleware: ConfirmAuthorizationMiddleware, confirmEmailAuthorizationUseCase: ConfirmEmailAuthorizationUseCase, confirmationCodeValidator: ConfirmationCodeValidator, - ): ConfirmAuthorizationStateMachine { - return ConfirmAuthorizationStateMachine( + ): ConfirmAuthorizationScreenComponent { + return ConfirmAuthorizationScreenComponent( + componentContext = componentContext, reducer = ConfirmAuthorizationsReducer( verificationHash, confirmEmailAuthorizationUseCase, diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/InitialAuthorizationModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/InitialAuthorizationModule.kt index f16936e..ea1b834 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/InitialAuthorizationModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/InitialAuthorizationModule.kt @@ -1,16 +1,17 @@ package org.timemates.app.authorization.dependencies.screens -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationReducer -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine import org.koin.core.annotation.Factory import org.koin.core.annotation.Module +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationReducer +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent @Module class InitialAuthorizationModule { @Factory - fun stateMachine(): InitialAuthorizationStateMachine { - return InitialAuthorizationStateMachine( - reducer = InitialAuthorizationReducer() + fun mviComponent(componentComponent: InitialAuthorizationScreenComponent): InitialAuthorizationScreenComponent { + return InitialAuthorizationScreenComponent( + componentContext = componentComponent, + reducer = InitialAuthorizationReducer(), ) } } \ No newline at end of file diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/NewAccountInfoModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/NewAccountInfoModule.kt index 32af5d7..2007b56 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/NewAccountInfoModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/NewAccountInfoModule.kt @@ -1,19 +1,22 @@ package org.timemates.app.authorization.dependencies.screens -import org.timemates.app.authorization.dependencies.AuthorizationDataModule -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoReducer -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine +import com.arkivanov.decompose.ComponentContext import io.timemates.sdk.authorization.email.types.value.VerificationHash import org.koin.core.annotation.Factory import org.koin.core.annotation.Module +import org.timemates.app.authorization.dependencies.AuthorizationDataModule +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoReducer +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent @Module(includes = [AuthorizationDataModule::class]) class NewAccountInfoModule { @Factory - fun stateMachine( + fun mviComponent( + componentContext: ComponentContext, verificationHash: VerificationHash, - ): NewAccountInfoStateMachine { - return NewAccountInfoStateMachine( + ): NewAccountInfoScreenComponent { + return NewAccountInfoScreenComponent( + componentContext = componentContext, reducer = NewAccountInfoReducer( verificationHash = verificationHash ), diff --git a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/StartAuthorizationModule.kt b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/StartAuthorizationModule.kt index c30446c..c6f35cf 100644 --- a/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/StartAuthorizationModule.kt +++ b/feature/authorization/dependencies/src/commonMain/kotlin/org/timemates/app/authorization/dependencies/screens/StartAuthorizationModule.kt @@ -1,17 +1,16 @@ package org.timemates.app.authorization.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.authorization.dependencies.AuthorizationDataModule import org.timemates.app.authorization.repositories.AuthorizationsRepository +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationMiddleware import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationReducer -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine import org.timemates.app.authorization.usecases.AuthorizeByEmailUseCase import org.timemates.app.authorization.validation.EmailAddressValidator -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [AuthorizationDataModule::class]) class StartAuthorizationModule { @@ -37,11 +36,13 @@ class StartAuthorizationModule { fun middleware(): StartAuthorizationMiddleware = StartAuthorizationMiddleware() @Factory - fun stateMachine( + fun mviComponent( + componentContext: ComponentContext, reducer: StartAuthorizationReducer, middleware: StartAuthorizationMiddleware, - ): StartAuthorizationStateMachine { - return StartAuthorizationStateMachine( + ): StartAuthorizationComponent { + return StartAuthorizationComponent( + componentContext = componentContext, reducer = reducer, middleware = middleware, ) diff --git a/feature/authorization/domain/src/commonMain/kotlin/org/timemates/app/authorization/validation/ConfirmationCodeValidator.kt b/feature/authorization/domain/src/commonMain/kotlin/org/timemates/app/authorization/validation/ConfirmationCodeValidator.kt index 42ba2d1..33d8520 100644 --- a/feature/authorization/domain/src/commonMain/kotlin/org/timemates/app/authorization/validation/ConfirmationCodeValidator.kt +++ b/feature/authorization/domain/src/commonMain/kotlin/org/timemates/app/authorization/validation/ConfirmationCodeValidator.kt @@ -1,9 +1,9 @@ package org.timemates.app.authorization.validation -import org.timemates.app.foundation.validation.Validator -import org.timemates.app.foundation.validation.unknownValidationFailure import io.timemates.sdk.authorization.sessions.types.value.ConfirmationCode import io.timemates.sdk.common.constructor.CreationFailure +import org.timemates.app.foundation.validation.Validator +import org.timemates.app.foundation.validation.unknownValidationFailure /** * A validator for confirmation codes. @@ -32,10 +32,10 @@ class ConfirmationCodeValidator : Validator, + mvi: MVI, navigateToConfirmation: (String) -> Unit, navigateToStart: () -> Unit, ) { val painter: Painter = Resources.image.confirm_authorization_info_image.painterResource() LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { - is AfterStartStateMachine.Effect.NavigateToConfirmation -> + is Effect.NavigateToConfirmation -> navigateToConfirmation(effect.verificationHash.string) - AfterStartStateMachine.Effect.OnChangeEmailClicked -> + Effect.OnChangeEmailClicked -> navigateToStart() } } @@ -58,7 +58,7 @@ fun AfterStartScreen( AppBar( navigationIcon = { IconButton( - onClick = { stateMachine.dispatchEvent(Event.OnChangeEmailClicked) }, + onClick = { mvi.dispatchEvent(Event.OnChangeEmailClicked) }, ) { Icon(Icons.Rounded.ArrowBack, contentDescription = null) } @@ -106,7 +106,7 @@ fun AfterStartScreen( Button( modifier = Modifier.fillMaxWidth(), primary = false, - onClick = { stateMachine.dispatchEvent(Event.OnChangeEmailClicked) }, + onClick = { mvi.dispatchEvent(Event.OnChangeEmailClicked) }, ) { Text(LocalStrings.current.changeEmail) } @@ -114,7 +114,7 @@ fun AfterStartScreen( Button( modifier = Modifier.fillMaxWidth(), primary = true, - onClick = { stateMachine.dispatchEvent(Event.NextClicked) }, + onClick = { mvi.dispatchEvent(Event.NextClicked) }, ) { Text(LocalStrings.current.nextStep) } diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartReducer.kt index ecb3da2..aae9912 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartReducer.kt @@ -1,14 +1,14 @@ package org.timemates.app.authorization.ui.afterstart.mvi -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine.Effect -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.Effect +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.Event +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.State import org.timemates.app.foundation.mvi.Reducer import org.timemates.app.foundation.mvi.ReducerScope -import io.timemates.sdk.authorization.email.types.value.VerificationHash -class AfterStartReducer(private val verificationHash: VerificationHash) : Reducer { - override fun ReducerScope.reduce(state: EmptyState, event: Event): EmptyState { +class AfterStartReducer(private val verificationHash: VerificationHash) : Reducer { + override fun ReducerScope.reduce(state: State, event: Event): State { return when (event) { is Event.NextClicked -> { sendEffect(Effect.NavigateToConfirmation(verificationHash)) diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartScreenComponent.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartScreenComponent.kt new file mode 100644 index 0000000..0ec693e --- /dev/null +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartScreenComponent.kt @@ -0,0 +1,42 @@ +package org.timemates.app.authorization.ui.afterstart.mvi + +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.Effect +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.Event +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.State +import org.timemates.app.foundation.mvi.MVIComponent +import org.timemates.app.foundation.mvi.UiEffect +import org.timemates.app.foundation.mvi.UiEvent +import org.timemates.app.foundation.mvi.UiState +import org.timemates.app.foundation.mvi.mviComponent + +class AfterStartScreenComponent( + componentContext: ComponentContext, + reducer: AfterStartReducer, +) : MVIComponent by mviComponent( + componentName = "AfterStartComponent", + componentContext = componentContext, + initState = State, + reducer = reducer, + middlewares = emptyList(), +) { + @Serializable + data object State : UiState { + private fun readResolve(): Any = State + + } + + sealed class Effect : UiEffect { + data class NavigateToConfirmation(val verificationHash: VerificationHash) : Effect() + + data object OnChangeEmailClicked : Effect() + } + + sealed class Event : UiEvent { + data object NextClicked : Event() + + data object OnChangeEmailClicked : Event() + } +} \ No newline at end of file diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartStateMachine.kt deleted file mode 100644 index b8cc69a..0000000 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/afterstart/mvi/AfterStartStateMachine.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.timemates.app.authorization.ui.afterstart.mvi - -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine.Effect -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine -import org.timemates.app.foundation.mvi.UiEffect -import org.timemates.app.foundation.mvi.UiEvent -import io.timemates.sdk.authorization.email.types.value.VerificationHash - -class AfterStartStateMachine( - reducer: AfterStartReducer, -) : StateMachine( - initState = EmptyState, - reducer = reducer, - middlewares = emptyList(), -) { - sealed class Effect : UiEffect { - data class NavigateToConfirmation(val verificationHash: VerificationHash) : Effect() - - object OnChangeEmailClicked : Effect() - } - - sealed class Event : UiEvent { - object NextClicked : Event() - - object OnChangeEmailClicked : Event() - } -} \ No newline at end of file diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountScreen.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountScreen.kt index d09abdf..1f1c454 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountScreen.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountScreen.kt @@ -27,25 +27,25 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Effect -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Event -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State +import io.timemates.sdk.users.profile.types.value.UserDescription +import io.timemates.sdk.users.profile.types.value.UserName +import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Effect +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Event +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.ButtonWithProgress -import io.timemates.sdk.users.profile.types.value.UserDescription -import io.timemates.sdk.users.profile.types.value.UserName -import kotlinx.coroutines.channels.consumeEach @Composable fun ConfigureAccountScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToHome: () -> Unit, onBack: () -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val nameSize = remember(state.name) { state.name.length } @@ -54,7 +54,7 @@ fun ConfigureAccountScreen( val strings = LocalStrings.current LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> snackbarData.showSnackbar(message = effect.throwable.getDefaultDisplayMessage(strings)) @@ -96,7 +96,7 @@ fun ConfigureAccountScreen( modifier = Modifier.fillMaxWidth(), leadingIcon = { Icon(Icons.Outlined.PersonOutline, contentDescription = null) }, value = state.name, - onValueChange = { stateMachine.dispatchEvent(Event.NameIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.NameIsChanged(it)) }, label = { Text(LocalStrings.current.yourName) }, isError = state.isNameSizeInvalid || nameSize > UserName.SIZE_RANGE.last, singleLine = true, @@ -118,7 +118,7 @@ fun ConfigureAccountScreen( OutlinedTextField( modifier = Modifier.fillMaxWidth(), value = state.aboutYou, - onValueChange = { stateMachine.dispatchEvent(Event.DescriptionIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.DescriptionIsChanged(it)) }, label = { Text(LocalStrings.current.aboutYou) }, isError = state.isAboutYouSizeInvalid || aboutYouSize > UserDescription.SIZE_RANGE.last, maxLines = 5, @@ -150,7 +150,7 @@ fun ConfigureAccountScreen( ButtonWithProgress( primary = true, modifier = Modifier.fillMaxWidth(), - onClick = { stateMachine.dispatchEvent(Event.OnDoneClicked) }, + onClick = { mvi.dispatchEvent(Event.OnDoneClicked) }, enabled = !state.isLoading, isLoading = state.isLoading ) { diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountMiddleware.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountMiddleware.kt index e2fa1df..4810629 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountMiddleware.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.authorization.ui.configure_account.mvi -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Effect -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Effect +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore class ConfigureAccountMiddleware : Middleware { override fun onEffect(effect: Effect, state: State): State { diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountReducer.kt index bc6f156..89a70b9 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountReducer.kt @@ -1,19 +1,19 @@ package org.timemates.app.authorization.ui.configure_account.mvi -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Effect -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Event -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State -import org.timemates.app.authorization.usecases.CreateNewAccountUseCase -import org.timemates.app.authorization.validation.UserDescriptionValidator -import org.timemates.app.authorization.validation.UserNameValidator -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope import io.timemates.sdk.authorization.email.types.value.VerificationHash import io.timemates.sdk.common.constructor.createOrThrow import io.timemates.sdk.users.profile.types.value.UserDescription import io.timemates.sdk.users.profile.types.value.UserName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Effect +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Event +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State +import org.timemates.app.authorization.usecases.CreateNewAccountUseCase +import org.timemates.app.authorization.validation.UserDescriptionValidator +import org.timemates.app.authorization.validation.UserNameValidator +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope class ConfigureAccountReducer( private val verificationHash: VerificationHash, diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountScreenComponent.kt similarity index 71% rename from feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountStateMachine.kt rename to feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountScreenComponent.kt index 01f9b95..a9c92dc 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountStateMachine.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/configure_account/mvi/ConfigureAccountScreenComponent.kt @@ -1,23 +1,30 @@ package org.timemates.app.authorization.ui.configure_account.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Effect -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Event -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.sessions.types.Authorization +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Effect +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Event +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import io.timemates.sdk.authorization.sessions.types.Authorization +import org.timemates.app.foundation.mvi.mviComponent -class ConfigureAccountStateMachine( +class ConfigureAccountScreenComponent( + componentContext: ComponentContext, reducer: ConfigureAccountReducer, middleware: ConfigureAccountMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "ConfigureAccountComponent", initState = State(), reducer = reducer, middlewares = listOf(middleware), ) { + @Serializable @Immutable data class State( val name: String = "", diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationScreen.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationScreen.kt index edac29f..a9e5dab 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationScreen.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationScreen.kt @@ -24,30 +24,30 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State +import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.ButtonWithProgress -import kotlinx.coroutines.channels.consumeEach @Composable fun ConfirmAuthorizationScreen( - stateMachine: StateMachine, + mvi: MVI, onBack: () -> Unit, navigateToConfiguring: (String) -> Unit, navigateToHome: () -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val strings = LocalStrings.current LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> snackbarData.showSnackbar(message = effect.throwable.getDefaultDisplayMessage(strings)) @@ -92,7 +92,7 @@ fun ConfirmAuthorizationScreen( modifier = Modifier.fillMaxWidth(), leadingIcon = { Icon(Icons.Outlined.Lock, contentDescription = null) }, value = state.code, - onValueChange = { stateMachine.dispatchEvent(Event.CodeChange(it)) }, + onValueChange = { mvi.dispatchEvent(Event.CodeChange(it)) }, label = { Text(LocalStrings.current.confirmation) }, isError = state.isCodeInvalid || state.isCodeSizeInvalid, supportingText = { if (supportText != null) Text(supportText) }, @@ -114,7 +114,7 @@ fun ConfirmAuthorizationScreen( ButtonWithProgress( primary = true, modifier = Modifier.fillMaxWidth(), - onClick = { stateMachine.dispatchEvent(Event.OnConfirmClicked) }, + onClick = { mvi.dispatchEvent(Event.OnConfirmClicked) }, enabled = !state.isLoading, isLoading = state.isLoading ) { diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationMiddleware.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationMiddleware.kt index f3d68f2..737f2e3 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationMiddleware.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.authorization.ui.confirmation.mvi -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore /** * This middleware works with async operation effects and updates the state when necessary. diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationScreenComponent.kt similarity index 73% rename from feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationStateMachine.kt rename to feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationScreenComponent.kt index 4fb42e4..0689fe9 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationStateMachine.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationScreenComponent.kt @@ -1,24 +1,31 @@ package org.timemates.app.authorization.ui.confirmation.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import io.timemates.sdk.authorization.sessions.types.Authorization +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import io.timemates.sdk.authorization.sessions.types.Authorization +import org.timemates.app.foundation.mvi.mviComponent -class ConfirmAuthorizationStateMachine( +class ConfirmAuthorizationScreenComponent( + componentContext: ComponentContext, reducer: ConfirmAuthorizationsReducer, middleware: ConfirmAuthorizationMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "ConfirmAuthorizationComponent", initState = State(), reducer = reducer, middlewares = listOf(middleware), ) { + @Serializable @Immutable data class State( val code: String = "", diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationsReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationsReducer.kt index 9f7f765..f462ba5 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationsReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/confirmation/mvi/ConfirmAuthorizationsReducer.kt @@ -1,17 +1,17 @@ package org.timemates.app.authorization.ui.confirmation.mvi -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State -import org.timemates.app.authorization.usecases.ConfirmEmailAuthorizationUseCase -import org.timemates.app.authorization.validation.ConfirmationCodeValidator -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope import io.timemates.sdk.authorization.email.types.value.VerificationHash import io.timemates.sdk.authorization.sessions.types.value.ConfirmationCode import io.timemates.sdk.common.constructor.createOrThrow import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State +import org.timemates.app.authorization.usecases.ConfirmEmailAuthorizationUseCase +import org.timemates.app.authorization.validation.ConfirmationCodeValidator +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope class ConfirmAuthorizationsReducer( private val verificationHash: VerificationHash, diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationScreen.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationScreen.kt index 14b92a8..f109ce2 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationScreen.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationScreen.kt @@ -19,25 +19,25 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.github.skeptick.libres.compose.painterResource -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine +import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.State +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.Resources import org.timemates.app.style.system.button.Button import org.timemates.app.style.system.theme.AppTheme -import kotlinx.coroutines.channels.consumeEach @Composable fun InitialAuthorizationScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToStartAuthorization: () -> Unit, ) { val painter: Painter = Resources.image.initial_screen_image.painterResource() LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.NavigateToStart -> navigateToStartAuthorization() @@ -85,7 +85,7 @@ fun InitialAuthorizationScreen( Button( modifier = Modifier.fillMaxWidth(), primary = true, - onClick = { stateMachine.dispatchEvent(Event.OnStartClicked) }, + onClick = { mvi.dispatchEvent(Event.OnStartClicked) }, ) { Text(LocalStrings.current.letsStart) } diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationReducer.kt index 8abcd22..b4b16c2 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationReducer.kt @@ -1,13 +1,13 @@ package org.timemates.app.authorization.ui.initial_authorization.mvi -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.State import org.timemates.app.foundation.mvi.Reducer import org.timemates.app.foundation.mvi.ReducerScope -class InitialAuthorizationReducer : Reducer { - override fun ReducerScope.reduce(state: EmptyState, event: Event): EmptyState { +class InitialAuthorizationReducer : Reducer { + override fun ReducerScope.reduce(state: State, event: Event): State { return when (event) { is Event.OnStartClicked -> { sendEffect(Effect.NavigateToStart) diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationScreenComponent.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationScreenComponent.kt new file mode 100644 index 0000000..b3b1fc0 --- /dev/null +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationScreenComponent.kt @@ -0,0 +1,35 @@ +package org.timemates.app.authorization.ui.initial_authorization.mvi + +import com.arkivanov.decompose.ComponentContext +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.State +import org.timemates.app.foundation.mvi.MVIComponent +import org.timemates.app.foundation.mvi.UiEffect +import org.timemates.app.foundation.mvi.UiEvent +import org.timemates.app.foundation.mvi.UiState +import org.timemates.app.foundation.mvi.mviComponent + +class InitialAuthorizationScreenComponent( + componentContext: ComponentContext, + reducer: InitialAuthorizationReducer, +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "InitialAuthorizationScreen", + reducer = reducer, + initState = State, +) { + @Serializable + data object State : UiState { + private fun readResolve(): Any = State + } + + sealed class Effect : UiEffect { + data object NavigateToStart : Effect() + } + + sealed class Event : UiEvent { + data object OnStartClicked : Event() + } +} diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationStateMachine.kt deleted file mode 100644 index 20d611e..0000000 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/initial_authorization/mvi/InitialAuthorizationStateMachine.kt +++ /dev/null @@ -1,24 +0,0 @@ -package org.timemates.app.authorization.ui.initial_authorization.mvi - -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine -import org.timemates.app.foundation.mvi.UiEffect -import org.timemates.app.foundation.mvi.UiEvent - -class InitialAuthorizationStateMachine( - reducer: InitialAuthorizationReducer, -) : StateMachine( - initState = EmptyState, - reducer = reducer, - middlewares = emptyList(), -) { - sealed class Effect : UiEffect { - object NavigateToStart : Effect() - } - - sealed class Event : UiEvent { - object OnStartClicked : Event() - } -} diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/NewAccountInfoScreen.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/NewAccountInfoScreen.kt index 0c83f85..3ebcf43 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/NewAccountInfoScreen.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/NewAccountInfoScreen.kt @@ -22,27 +22,27 @@ import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import io.github.skeptick.libres.compose.painterResource -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Effect -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine +import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Effect +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Event +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.State +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.Resources import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.Button import org.timemates.app.style.system.theme.AppTheme -import kotlinx.coroutines.channels.consumeEach @Composable fun NewAccountInfoScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToConfigure: (String) -> Unit, navigateToStart: () -> Unit, ) { val painter: Painter = Resources.image.new_account_info_image.painterResource() LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.NavigateToAccountConfiguring -> navigateToConfigure(effect.verificationHash.string) @@ -58,7 +58,7 @@ fun NewAccountInfoScreen( AppBar( navigationIcon = { IconButton( - onClick = { stateMachine.dispatchEvent(Event.OnBackClicked) }, + onClick = { mvi.dispatchEvent(Event.OnBackClicked) }, ) { Icon(Icons.Rounded.ArrowBack, contentDescription = null) } @@ -106,7 +106,7 @@ fun NewAccountInfoScreen( Button( modifier = Modifier.fillMaxWidth(), primary = true, - onClick = { stateMachine.dispatchEvent(Event.NextClicked) }, + onClick = { mvi.dispatchEvent(Event.NextClicked) }, ) { Text(LocalStrings.current.nextStep) } diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducer.kt index c2e8f60..e01ab22 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducer.kt @@ -1,14 +1,14 @@ package org.timemates.app.authorization.ui.new_account_info.mvi -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Effect -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Effect +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Event +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.State import org.timemates.app.foundation.mvi.Reducer import org.timemates.app.foundation.mvi.ReducerScope -import io.timemates.sdk.authorization.email.types.value.VerificationHash -class NewAccountInfoReducer(private val verificationHash: VerificationHash) : Reducer { - override fun ReducerScope.reduce(state: EmptyState, event: Event): EmptyState { +class NewAccountInfoReducer(private val verificationHash: VerificationHash) : Reducer { + override fun ReducerScope.reduce(state: State, event: Event): State { return when (event) { is Event.NextClicked -> { sendEffect(Effect.NavigateToAccountConfiguring(verificationHash)) diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoScreenComponent.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoScreenComponent.kt new file mode 100644 index 0000000..867be33 --- /dev/null +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoScreenComponent.kt @@ -0,0 +1,40 @@ +package org.timemates.app.authorization.ui.new_account_info.mvi + +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Effect +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Event +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.State +import org.timemates.app.foundation.mvi.MVIComponent +import org.timemates.app.foundation.mvi.UiEffect +import org.timemates.app.foundation.mvi.UiEvent +import org.timemates.app.foundation.mvi.UiState +import org.timemates.app.foundation.mvi.mviComponent + +class NewAccountInfoScreenComponent( + componentContext: ComponentContext, + reducer: NewAccountInfoReducer, +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "NewAccountInfoScreen", + reducer = reducer, + initState = State, +) { + @Serializable + data object State : UiState { + private fun readResolve(): Any = State + } + + sealed class Effect : UiEffect { + data class NavigateToAccountConfiguring(val verificationHash: VerificationHash) : Effect() + + data object NavigateToStart : Effect() + } + + sealed class Event : UiEvent { + data object NextClicked : Event() + + data object OnBackClicked : Event() + } +} \ No newline at end of file diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoStateMachine.kt deleted file mode 100644 index daf4bf4..0000000 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoStateMachine.kt +++ /dev/null @@ -1,29 +0,0 @@ -package org.timemates.app.authorization.ui.new_account_info.mvi - -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Effect -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine -import org.timemates.app.foundation.mvi.UiEffect -import org.timemates.app.foundation.mvi.UiEvent -import io.timemates.sdk.authorization.email.types.value.VerificationHash - -class NewAccountInfoStateMachine( - reducer: NewAccountInfoReducer, -) : StateMachine( - initState = EmptyState, - reducer = reducer, - middlewares = emptyList(), -) { - sealed class Effect : UiEffect { - data class NavigateToAccountConfiguring(val verificationHash: VerificationHash) : Effect() - - data object NavigateToStart : Effect() - } - - sealed class Event : UiEvent { - data object NextClicked : Event() - - data object OnBackClicked : Event() - } -} \ No newline at end of file diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationScreen.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationScreen.kt index cade014..8f0df4b 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationScreen.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationScreen.kt @@ -25,29 +25,29 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Effect +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Event +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.ButtonWithProgress -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import kotlinx.coroutines.channels.consumeEach @Composable fun StartAuthorizationScreen( - stateMachine: StateMachine, + mvi: MVI, onNavigateToConfirmation: (VerificationHash) -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val strings = LocalStrings.current LaunchedEffect(true) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> { effect.throwable.printStackTrace() @@ -93,7 +93,7 @@ fun StartAuthorizationScreen( modifier = Modifier.fillMaxWidth(), leadingIcon = { Icon(Icons.Outlined.Email, contentDescription = null) }, value = state.email, - onValueChange = { stateMachine.dispatchEvent(Event.EmailChange(it)) }, + onValueChange = { mvi.dispatchEvent(Event.EmailChange(it)) }, label = { Text(LocalStrings.current.email) }, isError = state.isEmailInvalid || state.isEmailLengthSizeInvalid, supportingText = { if (supportText != null) Text(supportText) }, @@ -117,7 +117,7 @@ fun StartAuthorizationScreen( enabled = !state.isLoading, primary = true, modifier = Modifier.fillMaxWidth(), - onClick = { stateMachine.dispatchEvent(Event.OnStartClick) }, + onClick = { mvi.dispatchEvent(Event.OnStartClick) }, isLoading = state.isLoading ) { Text(text = LocalStrings.current.start) diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationStateMachine.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationComponent.kt similarity index 70% rename from feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationStateMachine.kt rename to feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationComponent.kt index 50c256c..a85befb 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationStateMachine.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationComponent.kt @@ -1,23 +1,30 @@ package org.timemates.app.authorization.ui.start.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import kotlinx.serialization.Serializable +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Effect +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Event +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import io.timemates.sdk.authorization.email.types.value.VerificationHash +import org.timemates.app.foundation.mvi.mviComponent -class StartAuthorizationStateMachine( +class StartAuthorizationComponent( + componentContext: ComponentContext, reducer: StartAuthorizationReducer, middleware: StartAuthorizationMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "StartAuthorizationComponent", initState = State(), reducer = reducer, - middlewares = listOf(middleware) + middlewares = listOf(middleware), ) { + @Serializable @Immutable data class State( val email: String = "", diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationMiddleware.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationMiddleware.kt index 9fd64ef..036e620 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationMiddleware.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.authorization.ui.start.mvi -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Effect +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore /** * A middleware responsible for handling effects in the Start Authorization screen. @@ -20,7 +19,6 @@ class StartAuthorizationMiddleware : Middleware { * it sets the loading state in the UI to false by updating the state. * * @param effect The effect to be handled. - * @param store The state store containing the current state. */ override fun onEffect(effect: Effect, state: State): State { return when (effect) { diff --git a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationReducer.kt b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationReducer.kt index fca0ef4..0cd1878 100644 --- a/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationReducer.kt +++ b/feature/authorization/presentation/src/commonMain/kotlin/org/timemates/app/authorization/ui/start/mvi/StartAuthorizationReducer.kt @@ -1,16 +1,16 @@ package org.timemates.app.authorization.ui.start.mvi -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State -import org.timemates.app.authorization.usecases.AuthorizeByEmailUseCase -import org.timemates.app.authorization.validation.EmailAddressValidator -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope import io.timemates.sdk.common.constructor.createOrThrow import io.timemates.sdk.users.profile.types.value.EmailAddress import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Effect +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Event +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State +import org.timemates.app.authorization.usecases.AuthorizeByEmailUseCase +import org.timemates.app.authorization.validation.EmailAddressValidator +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope class StartAuthorizationReducer( private val validateEmail: EmailAddressValidator, diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/afterstart/AfterStartReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/afterstart/AfterStartReducerTest.kt index e9f2454..77a935e 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/afterstart/AfterStartReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/afterstart/AfterStartReducerTest.kt @@ -2,15 +2,14 @@ package org.timemates.app.authorization.ui.afterstart import io.mockk.mockk import io.mockk.verify -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartReducer -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.reduce -import org.timemates.app.foundation.random.nextString import io.timemates.sdk.authorization.email.types.value.VerificationHash import io.timemates.sdk.common.constructor.createOrThrow -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartReducer +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.State +import org.timemates.app.foundation.mvi.reduce +import org.timemates.app.foundation.random.nextString import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals @@ -18,35 +17,35 @@ import kotlin.test.assertEquals class AfterStartReducerTest { private val verificationHash: VerificationHash = VerificationHash.createOrThrow(Random.nextString(VerificationHash.SIZE)) - private val sendEffect: (AfterStartStateMachine.Effect) -> Unit = mockk(relaxed = true) + private val sendEffect: (AfterStartScreenComponent.Effect) -> Unit = mockk(relaxed = true) private val reducer: AfterStartReducer = AfterStartReducer(verificationHash) private val coroutineScope = TestScope() @Test fun `reducing event NextClicked event should not update the state`() { // GIVEN - val state = EmptyState - val event = AfterStartStateMachine.Event.NextClicked + val state = State + val event = AfterStartScreenComponent.Event.NextClicked // WHEN val resultState = reducer.reduce(state, event, coroutineScope, sendEffect) // THEN - verify { sendEffect(AfterStartStateMachine.Effect.NavigateToConfirmation(verificationHash)) } + verify { sendEffect(AfterStartScreenComponent.Effect.NavigateToConfirmation(verificationHash)) } assertEquals(state, resultState) } @Test fun `reducing event OnChangeEmailClicked event should not update the state`() { // GIVEN - val state = EmptyState - val event = AfterStartStateMachine.Event.OnChangeEmailClicked + val state = State + val event = AfterStartScreenComponent.Event.OnChangeEmailClicked // WHEN val resultState = reducer.reduce(state, event, coroutineScope, sendEffect) // THEN - verify { sendEffect(AfterStartStateMachine.Effect.OnChangeEmailClicked) } + verify { sendEffect(AfterStartScreenComponent.Effect.OnChangeEmailClicked) } assertEquals(state, resultState) } } diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountMiddlewareTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountMiddlewareTest.kt index 580b96c..3049483 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountMiddlewareTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountMiddlewareTest.kt @@ -1,27 +1,23 @@ package org.timemates.app.authorization.ui.configure_account -import io.mockk.every import io.mockk.mockk -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountMiddleware -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine -import org.timemates.app.foundation.mvi.StateStore import io.timemates.sdk.authorization.sessions.types.Authorization -import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Test +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountMiddleware +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State class ConfigureAccountMiddlewareTest { - private val stateStore: StateStore = mockk() private val middleware: ConfigureAccountMiddleware = ConfigureAccountMiddleware() private val authorization: Authorization = mockk() @Test fun `effects produced by network operations should remove loading status`() { // GIVEN - val effects = listOf(ConfigureAccountStateMachine.Effect.Failure(Exception())) - every { stateStore.state } returns MutableStateFlow(ConfigureAccountStateMachine.State(isLoading = true)) + val effects = listOf(ConfigureAccountScreenComponent.Effect.Failure(Exception())) // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } // THEN .forEach { (effect, state) -> assert(!state.isLoading) { @@ -34,13 +30,12 @@ class ConfigureAccountMiddlewareTest { fun `effects not produced by network operations should not remove loading status`() { // GIVEN val effects = listOf( - ConfigureAccountStateMachine.Effect.NavigateToStart, - ConfigureAccountStateMachine.Effect.NavigateToHomePage(authorization) + ConfigureAccountScreenComponent.Effect.NavigateToStart, + ConfigureAccountScreenComponent.Effect.NavigateToHomePage(authorization) ) - every { stateStore.state } returns MutableStateFlow(ConfigureAccountStateMachine.State(isLoading = true)) // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } // THEN .forEach { (effect, state) -> assert(state.isLoading) { diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountReducerTest.kt index 12af23e..1b20ebd 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/configure_account/ConfigureAccountReducerTest.kt @@ -2,19 +2,17 @@ package org.timemates.app.authorization.ui.configure_account import io.mockk.every import io.mockk.mockk -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import kotlinx.coroutines.test.TestScope +import org.junit.jupiter.api.Test import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountReducer -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.Event -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.Event +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State import org.timemates.app.authorization.usecases.CreateNewAccountUseCase import org.timemates.app.authorization.validation.UserDescriptionValidator import org.timemates.app.authorization.validation.UserNameValidator import org.timemates.app.foundation.mvi.reduce -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestScope -import org.junit.jupiter.api.Test import kotlin.test.assertEquals class ConfigureAccountReducerTest { @@ -28,7 +26,7 @@ class ConfigureAccountReducerTest { private val invalidName = "ko" private val invalidDescription = "" private val coroutineScope = TestScope() - private val sendEffect: (ConfigureAccountStateMachine.Effect) -> Unit = mockk(relaxed = true) + private val sendEffect: (ConfigureAccountScreenComponent.Effect) -> Unit = mockk(relaxed = true) private val reducer = ConfigureAccountReducer( verificationHash, diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationMiddlewareTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationMiddlewareTest.kt index b860f75..505e3f8 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationMiddlewareTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationMiddlewareTest.kt @@ -1,19 +1,13 @@ package org.timemates.app.authorization.ui.confirmation -import io.mockk.every import io.mockk.mockk -import io.mockk.verify import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationMiddleware -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State -import org.timemates.app.foundation.mvi.StateStore -import kotlinx.coroutines.flow.MutableStateFlow +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State import kotlin.test.Test import kotlin.test.assertEquals class ConfirmAuthorizationMiddlewareTest { - - private val store: StateStore = mockk(relaxed = true) private val middleware = ConfirmAuthorizationMiddleware() @Test @@ -22,17 +16,14 @@ class ConfirmAuthorizationMiddlewareTest { val initialState = State(isLoading = true) val effect = Effect.Failure(RuntimeException("Authorization failed")) - every { store.state } returns MutableStateFlow(initialState) - // WHEN - val newState = middleware.onEffect(effect, store) + val newState = middleware.onEffect(effect, initialState) // THEN assertEquals( expected = initialState.copy(isLoading = false), actual = newState ) - verify { store.state } } @Test @@ -41,17 +32,14 @@ class ConfirmAuthorizationMiddlewareTest { val initialState = State(isLoading = true) val effect = Effect.TooManyAttempts - every { store.state } returns MutableStateFlow(initialState) - // WHEN - val newState = middleware.onEffect(effect, store) + val newState = middleware.onEffect(effect, initialState) // THEN assertEquals( expected = initialState.copy(isLoading = false), actual = newState ) - verify { store.state } } @Test @@ -63,10 +51,8 @@ class ConfirmAuthorizationMiddlewareTest { mockk(), ) - every { store.state } returns MutableStateFlow(initialState) - // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, store) } + effects.map { effect -> effect to middleware.onEffect(effect, initialState) } .forEach { (effect, state) -> // THEN assertEquals( diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationReducerTest.kt index fdb7dc2..5c9f1b5 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/confirmation/ConfirmAuthorizationReducerTest.kt @@ -2,26 +2,23 @@ package org.timemates.app.authorization.ui.confirmation import io.mockk.every import io.mockk.mockk -import org.timemates.app.authorization.repositories.AuthorizationsRepository -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import io.timemates.sdk.common.constructor.createOrThrow +import kotlinx.coroutines.test.TestScope +import org.junit.jupiter.api.Test +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationsReducer import org.timemates.app.authorization.usecases.ConfirmEmailAuthorizationUseCase import org.timemates.app.authorization.validation.ConfirmationCodeValidator import org.timemates.app.foundation.mvi.reduce import org.timemates.app.foundation.random.nextString -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import io.timemates.sdk.common.constructor.createOrThrow -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestScope -import org.junit.jupiter.api.Test import kotlin.random.Random import kotlin.test.assertEquals import kotlin.test.assertFalse import kotlin.test.assertTrue class ConfirmAuthorizationReducerTest { - private val authorizationsRepository: AuthorizationsRepository = mockk() private val confirmationCodeValidator: ConfirmationCodeValidator = mockk() private val confirmEmailAuthorizationUseCase: ConfirmEmailAuthorizationUseCase = mockk() private val coroutineScope = TestScope() diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationReducerTest.kt index a1ea48b..c436cbf 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/initial_authorization/InitialAuthorizationReducerTest.kt @@ -2,14 +2,13 @@ package org.timemates.app.authorization.ui.initial_authorization import io.mockk.mockk import io.mockk.verify -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationReducer -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine.Event -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.reduce -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import org.junit.jupiter.api.Test +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationReducer +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Effect +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.Event +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.State +import org.timemates.app.foundation.mvi.reduce import kotlin.test.assertEquals class InitialAuthorizationReducerTest { @@ -20,7 +19,7 @@ class InitialAuthorizationReducerTest { @Test fun `reducing event OnStartClicked event should not update the state`() { //GIVEN - val state: EmptyState = EmptyState + val state = State val event: Event.OnStartClicked = Event.OnStartClicked //WHEN diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducerTest.kt index c67957a..f708b0f 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/new_account_info/mvi/NewAccountInfoReducerTest.kt @@ -2,51 +2,48 @@ package org.timemates.app.authorization.ui.new_account_info.mvi import io.mockk.mockk import io.mockk.verify -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartReducer -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.reduce -import org.timemates.app.foundation.random.nextString import io.timemates.sdk.authorization.email.types.value.VerificationHash import io.timemates.sdk.common.constructor.createOrThrow -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.Event +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.State +import org.timemates.app.foundation.mvi.reduce +import org.timemates.app.foundation.random.nextString import kotlin.random.Random import kotlin.test.Test import kotlin.test.assertEquals class NewAccountInfoReducerTest { private val verificationHash: VerificationHash = VerificationHash.createOrThrow(Random.nextString(VerificationHash.SIZE)) - private val sendEffect: (AfterStartStateMachine.Effect) -> Unit = mockk(relaxed = true) - private val reducer: AfterStartReducer = AfterStartReducer(verificationHash) + private val sendEffect: (NewAccountInfoScreenComponent.Effect) -> Unit = mockk(relaxed = true) + private val reducer: NewAccountInfoReducer = NewAccountInfoReducer(verificationHash) private val coroutineScope = TestScope() @Test fun `reducing event NextClicked event should not update the state`() { // GIVEN - val state: EmptyState = EmptyState - val event: AfterStartStateMachine.Event.NextClicked = AfterStartStateMachine.Event.NextClicked + val state = State + val event: Event.NextClicked = Event.NextClicked // WHEN val resultState = reducer.reduce(state, event, coroutineScope, sendEffect) // THEN - verify { sendEffect(AfterStartStateMachine.Effect.NavigateToConfirmation(verificationHash)) } + verify { sendEffect(NewAccountInfoScreenComponent.Effect.NavigateToAccountConfiguring(verificationHash)) } assertEquals(state, resultState) } @Test fun `reducing event OnBackClicked event should not update the state`() { // GIVEN - val state: EmptyState = EmptyState - - val event: AfterStartStateMachine.Event.OnChangeEmailClicked = AfterStartStateMachine.Event.OnChangeEmailClicked + val state = State + val event = Event.OnBackClicked // WHEN val resultState = reducer.reduce(state, event, coroutineScope, sendEffect) // THEN - verify { sendEffect(AfterStartStateMachine.Effect.OnChangeEmailClicked) } + verify { sendEffect(NewAccountInfoScreenComponent.Effect.NavigateToStart) } assertEquals(state, resultState) } } \ No newline at end of file diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationMiddlewareTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationMiddlewareTest.kt index e33834a..5d7f4d0 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationMiddlewareTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationMiddlewareTest.kt @@ -1,31 +1,26 @@ package org.timemates.app.authorization.ui.start -import io.mockk.every import io.mockk.mockk -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationMiddleware -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Effect -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.foundation.random.nextString import io.timemates.sdk.authorization.email.types.value.VerificationHash import io.timemates.sdk.common.constructor.createOrThrow -import kotlinx.coroutines.flow.MutableStateFlow +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Effect +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationMiddleware +import org.timemates.app.foundation.random.nextString import kotlin.random.Random import kotlin.test.Test class StartAuthorizationMiddlewareTest { - private val stateStore: StateStore = mockk() private val middleware: StartAuthorizationMiddleware = StartAuthorizationMiddleware() @Test fun `effects produced by network operations should remove loading status`() { // GIVEN val effects = listOf(Effect.TooManyAttempts, Effect.Failure(Exception())) - every { stateStore.state } returns MutableStateFlow(State(isLoading = true)) // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } // THEN .forEach { (effect, state) -> assert(!state.isLoading) { @@ -42,10 +37,9 @@ class StartAuthorizationMiddlewareTest { VerificationHash.createOrThrow(Random.nextString(VerificationHash.SIZE)) ) ) - every { stateStore.state } returns MutableStateFlow(State(isLoading = true)) // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } // THEN .forEach { (effect, state) -> assert(!state.isLoading) { diff --git a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationReducerTest.kt b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationReducerTest.kt index a799cd5..d1388f4 100644 --- a/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationReducerTest.kt +++ b/feature/authorization/presentation/src/jvmTest/kotlin/org/timemates/app/authorization/ui/start/StartAuthorizationReducerTest.kt @@ -2,14 +2,13 @@ package org.timemates.app.authorization.ui.start import io.mockk.every import io.mockk.mockk +import kotlinx.coroutines.test.TestScope +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.Event +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationReducer -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.Event -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State import org.timemates.app.authorization.usecases.AuthorizeByEmailUseCase import org.timemates.app.authorization.validation.EmailAddressValidator import org.timemates.app.foundation.mvi.reduce -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestScope import kotlin.test.Test import kotlin.test.assertEquals diff --git a/feature/common/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/middleware/AuthorizationFailureMiddleware.kt b/feature/common/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/middleware/AuthorizationFailureMiddleware.kt index 108620a..2c67b54 100644 --- a/feature/common/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/middleware/AuthorizationFailureMiddleware.kt +++ b/feature/common/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/middleware/AuthorizationFailureMiddleware.kt @@ -2,7 +2,6 @@ package org.timemates.app.feature.common.middleware import org.timemates.app.feature.common.handler.OnAuthorizationFailedHandler import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiState import io.timemates.sdk.common.exceptions.UnauthorizedException diff --git a/feature/common/presentation/src/jvmTest/kotlin/org/timemates/app/common/mvi/middleware/AuthorizationFailureMiddlewareTest.kt b/feature/common/presentation/src/jvmTest/kotlin/org/timemates/app/common/mvi/middleware/AuthorizationFailureMiddlewareTest.kt index fa25c92..bf1ff76 100644 --- a/feature/common/presentation/src/jvmTest/kotlin/org/timemates/app/common/mvi/middleware/AuthorizationFailureMiddlewareTest.kt +++ b/feature/common/presentation/src/jvmTest/kotlin/org/timemates/app/common/mvi/middleware/AuthorizationFailureMiddlewareTest.kt @@ -3,15 +3,13 @@ package org.timemates.app.common.mvi.middleware import io.mockk.clearAllMocks import io.mockk.mockk import io.mockk.verify -import org.timemates.app.feature.common.middleware.AuthorizationFailureMiddleware.AuthorizationFailureEffect +import io.timemates.sdk.common.exceptions.UnauthorizedException +import kotlinx.coroutines.flow.MutableStateFlow import org.timemates.app.feature.common.handler.OnAuthorizationFailedHandler import org.timemates.app.feature.common.middleware.AuthorizationFailureMiddleware -import org.timemates.app.foundation.mvi.StateStore +import org.timemates.app.feature.common.middleware.AuthorizationFailureMiddleware.AuthorizationFailureEffect import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiState -import io.timemates.sdk.common.exceptions.UnauthorizedException -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow import kotlin.test.AfterTest import kotlin.test.Test import kotlin.test.assertEquals @@ -46,7 +44,7 @@ class AuthorizationFailureMiddlewareTest { val effect = TestEffect.AuthorizationFailure(exception) // WHEN - middleware.onEffect(effect, createStateStore()) + middleware.onEffect(effect, TestState.Initial) // THEN verify(exactly = 1) { onAuthorizationFailed.onFailed(exception) } @@ -58,16 +56,9 @@ class AuthorizationFailureMiddlewareTest { val effect = TestEffect.AnyOther // WHEN - val result = middleware.onEffect(effect, createStateStore()) + val result = middleware.onEffect(effect, TestState.Initial) // THEN assertEquals(expected = TestState.Initial, actual = result) } - - private fun createStateStore(): StateStore { - return object : StateStore { - override val state: StateFlow - get() = stateFlow - } - } } diff --git a/feature/system/dependencies/src/commonMain/kotlin/org/timemates/app/feature/system/dependencies/screens/StartupModule.kt b/feature/system/dependencies/src/commonMain/kotlin/org/timemates/app/feature/system/dependencies/screens/StartupModule.kt index d59ab72..38c9de0 100644 --- a/feature/system/dependencies/src/commonMain/kotlin/org/timemates/app/feature/system/dependencies/screens/StartupModule.kt +++ b/feature/system/dependencies/src/commonMain/kotlin/org/timemates/app/feature/system/dependencies/screens/StartupModule.kt @@ -1,15 +1,16 @@ package org.timemates.app.feature.system.dependencies.screens -import org.timemates.app.feature.common.startup.mvi.StartupStateMachine -import org.timemates.app.feature.system.dependencies.SystemDataModule -import org.timemates.app.feature.system.repositories.AuthRepository +import com.arkivanov.decompose.ComponentContext import org.koin.core.annotation.Factory import org.koin.core.annotation.Module +import org.timemates.app.feature.common.startup.mvi.StartupScreenMVIComponent +import org.timemates.app.feature.system.dependencies.SystemDataModule +import org.timemates.app.feature.system.repositories.AuthRepository @Module(includes = [SystemDataModule::class]) class StartupModule { @Factory - fun stateMachine(authRepository: AuthRepository): StartupStateMachine { - return StartupStateMachine(authRepository) + fun mvi(componentContext: ComponentContext, authRepository: AuthRepository): StartupScreenMVIComponent { + return StartupScreenMVIComponent(componentContext, authRepository) } } \ No newline at end of file diff --git a/feature/system/presentation/build.gradle.kts b/feature/system/presentation/build.gradle.kts index 41f52f3..5db4c67 100644 --- a/feature/system/presentation/build.gradle.kts +++ b/feature/system/presentation/build.gradle.kts @@ -1,7 +1,7 @@ plugins { id(libs.plugins.configurations.compose.multiplatform.get().pluginId) id(libs.plugins.configurations.unit.tests.mockable.get().pluginId) - alias(libs.plugins.kotlin.parcelize) + alias(libs.plugins.kotlinx.serialization) } dependencies { diff --git a/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/StartupScreen.kt b/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/StartupScreen.kt index b4e9e12..9af5203 100644 --- a/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/StartupScreen.kt +++ b/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/StartupScreen.kt @@ -4,7 +4,6 @@ import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -12,24 +11,23 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import io.github.skeptick.libres.compose.painterResource +import kotlinx.coroutines.channels.consumeEach import org.timemates.app.feature.common.startup.mvi.StartupEffect import org.timemates.app.feature.common.startup.mvi.StartupEvent -import org.timemates.app.foundation.mvi.EmptyState -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.feature.common.startup.mvi.StartupScreenMVIComponent.State +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.style.system.Resources import org.timemates.app.style.system.theme.AppTheme -import kotlinx.coroutines.channels.consumeEach -import kotlinx.coroutines.launch @Composable fun StartupScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToAuth: () -> Unit, navigateToHome: () -> Unit, ) { LaunchedEffect(true) { - launch { stateMachine.dispatchEvent(StartupEvent.Started) } - stateMachine.effects.consumeEach { effect -> + mvi.dispatchEvent(StartupEvent.Started) + mvi.effects.consumeEach { effect -> when (effect) { StartupEffect.Authorized -> navigateToHome() StartupEffect.Unauthorized -> navigateToAuth() diff --git a/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupStateMachine.kt b/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupScreenMVIComponent.kt similarity index 67% rename from feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupStateMachine.kt rename to feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupScreenMVIComponent.kt index 038d136..caf737f 100644 --- a/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupStateMachine.kt +++ b/feature/system/presentation/src/commonMain/kotlin/org/timemates/app/feature/common/startup/mvi/StartupScreenMVIComponent.kt @@ -1,31 +1,43 @@ package org.timemates.app.feature.common.startup.mvi +import com.arkivanov.decompose.ComponentContext +import kotlinx.coroutines.launch +import kotlinx.serialization.Serializable +import org.timemates.app.feature.common.startup.mvi.StartupScreenMVIComponent.State import org.timemates.app.feature.system.repositories.AuthRepository -import org.timemates.app.foundation.mvi.EmptyState +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.ReducerScope -import org.timemates.app.foundation.mvi.StateMachine import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent -import kotlinx.coroutines.launch +import org.timemates.app.foundation.mvi.UiState +import org.timemates.app.foundation.mvi.mviComponent import org.timemates.app.foundation.mvi.Reducer as MviReducer /** * The global app state machine. Responsible for checking whether user is authorized * and for a new updates (TODO). */ -class StartupStateMachine( +class StartupScreenMVIComponent( + componentContext: ComponentContext, authRepository: AuthRepository, -) : StateMachine( - initState = EmptyState, +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "StartupScreen", + initState = State, reducer = Reducer(authRepository), ) { + @Serializable + data object State : UiState { + private fun readResolve(): Any = State + } + class Reducer( private val authRepository: AuthRepository, - ) : MviReducer { + ) : MviReducer { override fun ReducerScope.reduce( - state: EmptyState, + state: State, event: StartupEvent, - ): EmptyState { + ): State { return when (event) { StartupEvent.Started -> { machineScope.launch { diff --git a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerCreationModule.kt b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerCreationModule.kt index ac6a9ae..8bd1a32 100644 --- a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerCreationModule.kt +++ b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerCreationModule.kt @@ -1,18 +1,17 @@ package org.timemates.app.timers.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.timers.dependencies.TimersDataModule import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationMiddleware import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationReducer -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent import org.timemates.app.users.repositories.TimersRepository import org.timemates.app.users.usecases.TimerCreationUseCase import org.timemates.app.users.validation.TimerDescriptionValidator import org.timemates.app.users.validation.TimerNameValidator -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [TimersDataModule::class]) class TimerCreationModule { @@ -33,12 +32,14 @@ class TimerCreationModule { @Factory fun stateMachine( + componentContext: ComponentContext, timerCreationUseCase: TimerCreationUseCase, timerNameValidator: TimerNameValidator, timerDescriptionValidator: TimerDescriptionValidator, middleware: TimerCreationMiddleware, - ): TimerCreationStateMachine { - return TimerCreationStateMachine( + ): TimerCreationScreenComponent { + return TimerCreationScreenComponent( + componentContext = componentContext, reducer = TimerCreationReducer( timerCreationUseCase = timerCreationUseCase, timerNameValidator = timerNameValidator, diff --git a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerSettingsModule.kt b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerSettingsModule.kt index 8abacf7..ea5fff5 100644 --- a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerSettingsModule.kt +++ b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimerSettingsModule.kt @@ -1,19 +1,18 @@ package org.timemates.app.timers.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.timers.types.value.TimerId +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.timers.dependencies.TimersDataModule import org.timemates.app.timers.ui.settings.mvi.TimerSettingsMiddleware import org.timemates.app.timers.ui.settings.mvi.TimerSettingsReducer -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent import org.timemates.app.users.repositories.TimersRepository import org.timemates.app.users.usecases.TimerSettingsUseCase import org.timemates.app.users.validation.TimerDescriptionValidator import org.timemates.app.users.validation.TimerNameValidator -import io.timemates.sdk.timers.types.value.TimerId -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [TimersDataModule::class]) class TimerSettingsModule { @@ -34,13 +33,15 @@ class TimerSettingsModule { @Factory fun stateMachine( + componentContext: ComponentContext, timerId: TimerId, timerSettingsUseCase: TimerSettingsUseCase, timerNameValidator: TimerNameValidator, timerDescriptionValidator: TimerDescriptionValidator, middleware: TimerSettingsMiddleware, - ): TimerSettingsStateMachine { - return TimerSettingsStateMachine( + ): TimerSettingsScreenComponent { + return TimerSettingsScreenComponent( + componentContext = componentContext, reducer = TimerSettingsReducer( timerId = timerId, timerSettingsUseCase = timerSettingsUseCase, diff --git a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimersListModule.kt b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimersListModule.kt index 4850836..d499d84 100644 --- a/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimersListModule.kt +++ b/feature/timers/dependencies/src/commonMain/kotlin/org/timemates/app/timers/dependencies/screens/TimersListModule.kt @@ -1,16 +1,15 @@ package org.timemates.app.timers.dependencies.screens +import com.arkivanov.decompose.ComponentContext +import org.koin.core.annotation.Factory +import org.koin.core.annotation.Module +import org.koin.core.annotation.Singleton import org.timemates.app.timers.dependencies.TimersDataModule import org.timemates.app.timers.ui.timers_list.mvi.TimersListMiddleware import org.timemates.app.timers.ui.timers_list.mvi.TimersListReducer -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent import org.timemates.app.users.repositories.TimersRepository import org.timemates.app.users.usecases.GetUserTimersUseCase -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import org.koin.core.annotation.Factory -import org.koin.core.annotation.Module -import org.koin.core.annotation.Singleton @Module(includes = [TimersDataModule::class]) class TimersListModule { @@ -25,10 +24,12 @@ class TimersListModule { @Factory fun stateMachine( + componentContext: ComponentContext, getUserTimersUseCase: GetUserTimersUseCase, timersListMiddleware: TimersListMiddleware, - ): TimersListStateMachine { - return TimersListStateMachine( + ): TimersListScreenComponent { + return TimersListScreenComponent( + componentContext = componentContext, reducer = TimersListReducer( getUserTimersUseCase = getUserTimersUseCase, ), diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/ShimmerTimerItem.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/ShimmerTimerItem.kt index 49b90b6..62de1eb 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/ShimmerTimerItem.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/ShimmerTimerItem.kt @@ -34,17 +34,17 @@ import androidx.compose.ui.unit.dp import org.timemates.app.style.system.theme.AppTheme @Composable -fun PlaceholderTimerItem(){ +fun PlaceholderTimerItem() { OutlinedCard( modifier = Modifier.fillMaxWidth(), border = BorderStroke(1.dp, AppTheme.colors.secondary), colors = CardDefaults.outlinedCardColors(containerColor = AppTheme.colors.background), - ) { + ) { Row( modifier = Modifier.padding(12.dp), verticalAlignment = Alignment.CenterVertically, ) { - Column{ + Column { Box( modifier = Modifier .fillMaxWidth(0.4f) diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsScreen.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsScreen.kt index 26ba8aa..3fcc842 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsScreen.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsScreen.kt @@ -32,30 +32,30 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.types.value.Count +import io.timemates.sdk.timers.types.value.TimerDescription +import io.timemates.sdk.timers.types.value.TimerName +import kotlinx.coroutines.channels.consumeEach import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.ButtonWithProgress import org.timemates.app.style.system.text_field.SizedOutlinedTextField import org.timemates.app.style.system.theme.AppTheme -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Effect -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Event -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.types.value.Count -import io.timemates.sdk.timers.types.value.TimerDescription -import io.timemates.sdk.timers.types.value.TimerName -import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Effect +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Event +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit @Composable fun TimerSettingsScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToTimersScreen: () -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val nameSize = remember(state.name) { state.name.length } @@ -64,7 +64,7 @@ fun TimerSettingsScreen( val strings = LocalStrings.current LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> snackbarData.showSnackbar(message = effect.throwable.getDefaultDisplayMessage(strings)) @@ -107,7 +107,7 @@ fun TimerSettingsScreen( SizedOutlinedTextField( modifier = Modifier.fillMaxWidth(), value = state.name, - onValueChange = { stateMachine.dispatchEvent(Event.NameIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.NameIsChanged(it)) }, label = { Text(LocalStrings.current.name) }, isError = state.isNameSizeInvalid || nameSize > TimerName.SIZE_RANGE.last, singleLine = true, @@ -124,7 +124,7 @@ fun TimerSettingsScreen( SizedOutlinedTextField( modifier = Modifier.fillMaxWidth(), value = state.description, - onValueChange = { stateMachine.dispatchEvent(Event.DescriptionIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.DescriptionIsChanged(it)) }, label = { Text(LocalStrings.current.description) }, isError = state.isDescriptionSizeInvalid || descriptionSize > TimerDescription.SIZE_RANGE.last, maxLines = 5, @@ -154,7 +154,7 @@ fun TimerSettingsScreen( modifier = Modifier .weight(1f), value = state.workTime.toInt(unit = DurationUnit.MINUTES).toString(), - onValueChange = { stateMachine.dispatchEvent(Event.WorkTimeIsChanged(it.toInt().minutes)) }, + onValueChange = { mvi.dispatchEvent(Event.WorkTimeIsChanged(it.toInt().minutes)) }, label = { Text(LocalStrings.current.workTime) }, singleLine = true, enabled = !state.isLoading, @@ -167,7 +167,7 @@ fun TimerSettingsScreen( modifier = Modifier .weight(1f), value = state.restTime.toInt(unit = DurationUnit.MINUTES).toString(), - onValueChange = { stateMachine.dispatchEvent(Event.RestTimeIsChanged(it.toInt().minutes)) }, + onValueChange = { mvi.dispatchEvent(Event.RestTimeIsChanged(it.toInt().minutes)) }, label = { Text(LocalStrings.current.restTime) }, singleLine = true, enabled = !state.isLoading, @@ -182,7 +182,7 @@ fun TimerSettingsScreen( ) { Checkbox( checked = state.bigRestEnabled, - onCheckedChange = { stateMachine.dispatchEvent(Event.BigRestModeIsChanged(!state.bigRestEnabled)) }, + onCheckedChange = { mvi.dispatchEvent(Event.BigRestModeIsChanged(!state.bigRestEnabled)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -202,7 +202,7 @@ fun TimerSettingsScreen( OutlinedTextField( modifier = Modifier.weight(1f), value = state.bigRestPer.int.toString(), - onValueChange = { stateMachine.dispatchEvent(Event.BigRestPerIsChanged(Count.createOrThrow(it.toInt()))) }, + onValueChange = { mvi.dispatchEvent(Event.BigRestPerIsChanged(Count.createOrThrow(it.toInt()))) }, label = { Text(LocalStrings.current.every) }, singleLine = true, enabled = !state.isLoading, @@ -211,7 +211,7 @@ fun TimerSettingsScreen( OutlinedTextField( modifier = Modifier.weight(1f), value = state.bigRestTime.toInt(unit = DurationUnit.MINUTES).toString(), - onValueChange = { stateMachine.dispatchEvent(Event.BigRestTimeIsChanged(it.toInt().minutes)) }, + onValueChange = { mvi.dispatchEvent(Event.BigRestTimeIsChanged(it.toInt().minutes)) }, label = { Text(LocalStrings.current.minutes) }, singleLine = true, enabled = !state.isLoading, @@ -234,7 +234,7 @@ fun TimerSettingsScreen( ) { Checkbox( checked = state.isEveryoneCanPause, - onCheckedChange = { stateMachine.dispatchEvent(Event.TimerPauseControlAccessIsChanged(!state.isEveryoneCanPause)) }, + onCheckedChange = { mvi.dispatchEvent(Event.TimerPauseControlAccessIsChanged(!state.isEveryoneCanPause)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -251,7 +251,7 @@ fun TimerSettingsScreen( ) { Checkbox( checked = state.isConfirmationRequired, - onCheckedChange = { stateMachine.dispatchEvent(Event.ConfirmationRequirementChanged(!state.isConfirmationRequired)) }, + onCheckedChange = { mvi.dispatchEvent(Event.ConfirmationRequirementChanged(!state.isConfirmationRequired)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -278,7 +278,7 @@ fun TimerSettingsScreen( ButtonWithProgress( primary = true, modifier = Modifier.fillMaxWidth(), - onClick = { stateMachine.dispatchEvent(Event.OnDoneClicked) }, + onClick = { mvi.dispatchEvent(Event.OnDoneClicked) }, enabled = !state.isLoading, isLoading = state.isLoading ) { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsMiddleware.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsMiddleware.kt index 991ab73..fdffc24 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsMiddleware.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.timers.ui.settings.mvi import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Effect -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Effect +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State class TimerSettingsMiddleware : Middleware { override fun onEffect(effect: Effect, state: State): State { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsReducer.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsReducer.kt index 625f28e..13245dc 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsReducer.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsReducer.kt @@ -1,13 +1,5 @@ package org.timemates.app.timers.ui.settings.mvi -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Effect -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Event -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State -import org.timemates.app.users.usecases.TimerSettingsUseCase -import org.timemates.app.users.validation.TimerDescriptionValidator -import org.timemates.app.users.validation.TimerNameValidator import io.timemates.sdk.common.constructor.createOrThrow import io.timemates.sdk.timers.types.TimerSettings import io.timemates.sdk.timers.types.value.TimerDescription @@ -15,6 +7,14 @@ import io.timemates.sdk.timers.types.value.TimerId import io.timemates.sdk.timers.types.value.TimerName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Effect +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Event +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State +import org.timemates.app.users.usecases.TimerSettingsUseCase +import org.timemates.app.users.validation.TimerDescriptionValidator +import org.timemates.app.users.validation.TimerNameValidator class TimerSettingsReducer( diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsStateMachine.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsScreenComponent.kt similarity index 77% rename from feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsStateMachine.kt rename to feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsScreenComponent.kt index de37748..9bbebf1 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsStateMachine.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/settings/mvi/TimerSettingsScreenComponent.kt @@ -1,22 +1,27 @@ package org.timemates.app.timers.ui.settings.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.types.value.Count +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Effect -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Event -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.types.value.Count +import org.timemates.app.foundation.mvi.mviComponent +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Effect +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Event +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -class TimerSettingsStateMachine( +class TimerSettingsScreenComponent( + componentContext: ComponentContext, reducer: TimerSettingsReducer, middleware: TimerSettingsMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "TimerSettingsScreen", initState = State(), reducer = reducer, middlewares = listOf(middleware), diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationScreen.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationScreen.kt index c3bc23a..802ad71 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationScreen.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationScreen.kt @@ -35,31 +35,31 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.types.value.Count +import io.timemates.sdk.timers.types.value.TimerDescription +import io.timemates.sdk.timers.types.value.TimerName +import kotlinx.coroutines.channels.consumeEach import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.appbar.AppBar import org.timemates.app.style.system.button.ButtonWithProgress import org.timemates.app.style.system.text_field.SizedOutlinedTextField import org.timemates.app.style.system.theme.AppTheme -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Effect -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Event -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.State -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.types.value.Count -import io.timemates.sdk.timers.types.value.TimerDescription -import io.timemates.sdk.timers.types.value.TimerName -import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Effect +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Event +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.State import kotlin.time.Duration.Companion.minutes import kotlin.time.DurationUnit @Composable fun TimerCreationScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToTimersScreen: () -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val nameSize = remember(state.name) { state.name.length } @@ -68,7 +68,7 @@ fun TimerCreationScreen( val strings = LocalStrings.current LaunchedEffect(Unit) { - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> snackbarData.showSnackbar( @@ -117,7 +117,7 @@ fun TimerCreationScreen( SizedOutlinedTextField( modifier = Modifier.fillMaxWidth(), value = state.name, - onValueChange = { stateMachine.dispatchEvent(Event.NameIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.NameIsChanged(it)) }, label = { Text(LocalStrings.current.name) }, isError = state.isNameSizeInvalid || nameSize > TimerName.SIZE_RANGE.last, singleLine = true, @@ -134,7 +134,7 @@ fun TimerCreationScreen( SizedOutlinedTextField( modifier = Modifier.fillMaxWidth(), value = state.description, - onValueChange = { stateMachine.dispatchEvent(Event.DescriptionIsChanged(it)) }, + onValueChange = { mvi.dispatchEvent(Event.DescriptionIsChanged(it)) }, label = { Text(LocalStrings.current.description) }, isError = state.isDescriptionSizeInvalid || descriptionSize > TimerDescription.SIZE_RANGE.last, maxLines = 5, @@ -165,7 +165,7 @@ fun TimerCreationScreen( .weight(1f), value = state.workTime.toInt(unit = DurationUnit.MINUTES).toString(), onValueChange = { - stateMachine.dispatchEvent( + mvi.dispatchEvent( Event.WorkTimeIsChanged(it.toIntOrNull()?.minutes ?: state.workTime) ) }, @@ -181,7 +181,7 @@ fun TimerCreationScreen( modifier = Modifier .weight(1f), value = state.restTime.toInt(unit = DurationUnit.MINUTES).toString(), - onValueChange = { stateMachine.dispatchEvent(Event.RestTimeIsChanged(it.toInt().minutes)) }, + onValueChange = { mvi.dispatchEvent(Event.RestTimeIsChanged(it.toInt().minutes)) }, label = { Text(LocalStrings.current.restTime) }, singleLine = true, enabled = !state.isLoading, @@ -196,7 +196,7 @@ fun TimerCreationScreen( ) { Checkbox( checked = state.bigRestEnabled, - onCheckedChange = { stateMachine.dispatchEvent(Event.BigRestModeIsChanged(!state.bigRestEnabled)) }, + onCheckedChange = { mvi.dispatchEvent(Event.BigRestModeIsChanged(!state.bigRestEnabled)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -216,7 +216,7 @@ fun TimerCreationScreen( OutlinedTextField( modifier = Modifier.weight(1f), value = state.bigRestPer.int.toString(), - onValueChange = { stateMachine.dispatchEvent(Event.BigRestPerIsChanged(Count.createOrThrow(it.toInt()))) }, + onValueChange = { mvi.dispatchEvent(Event.BigRestPerIsChanged(Count.createOrThrow(it.toInt()))) }, label = { Text(LocalStrings.current.every) }, singleLine = true, enabled = !state.isLoading, @@ -225,7 +225,7 @@ fun TimerCreationScreen( OutlinedTextField( modifier = Modifier.weight(1f), value = state.bigRestTime.toInt(unit = DurationUnit.MINUTES).toString(), - onValueChange = { stateMachine.dispatchEvent(Event.BigRestTimeIsChanged(it.toInt().minutes)) }, + onValueChange = { mvi.dispatchEvent(Event.BigRestTimeIsChanged(it.toInt().minutes)) }, label = { Text(LocalStrings.current.minutes) }, singleLine = true, enabled = !state.isLoading, @@ -248,7 +248,7 @@ fun TimerCreationScreen( ) { Checkbox( checked = state.isEveryoneCanPause, - onCheckedChange = { stateMachine.dispatchEvent(Event.TimerPauseControlAccessIsChanged(!state.isEveryoneCanPause)) }, + onCheckedChange = { mvi.dispatchEvent(Event.TimerPauseControlAccessIsChanged(!state.isEveryoneCanPause)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -265,7 +265,7 @@ fun TimerCreationScreen( ) { Checkbox( checked = state.isConfirmationRequired, - onCheckedChange = { stateMachine.dispatchEvent(Event.ConfirmationRequirementChanged(!state.isConfirmationRequired)) }, + onCheckedChange = { mvi.dispatchEvent(Event.ConfirmationRequirementChanged(!state.isConfirmationRequired)) }, modifier = Modifier.align(Alignment.CenterVertically), colors = CheckboxDefaults.colors(checkedColor = AppTheme.colors.primary), ) @@ -292,7 +292,7 @@ fun TimerCreationScreen( ButtonWithProgress( primary = true, modifier = Modifier.fillMaxWidth(), - onClick = { stateMachine.dispatchEvent(Event.OnDoneClicked) }, + onClick = { mvi.dispatchEvent(Event.OnDoneClicked) }, enabled = !state.isLoading, isLoading = state.isLoading ) { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationMiddleware.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationMiddleware.kt index fba8b97..6101974 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationMiddleware.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.timers.ui.timer_creation.mvi import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Effect -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.State +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Effect +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.State class TimerCreationMiddleware : Middleware { override fun onEffect(effect: Effect, state: State): State { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationReducer.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationReducer.kt index aaa5a08..d53fde2 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationReducer.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationReducer.kt @@ -1,19 +1,19 @@ package org.timemates.app.timers.ui.timer_creation.mvi -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Effect -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Event -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.State -import org.timemates.app.users.usecases.TimerCreationUseCase -import org.timemates.app.users.validation.TimerDescriptionValidator -import org.timemates.app.users.validation.TimerNameValidator import io.timemates.sdk.common.constructor.createOrThrow import io.timemates.sdk.timers.types.TimerSettings import io.timemates.sdk.timers.types.value.TimerDescription import io.timemates.sdk.timers.types.value.TimerName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Effect +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Event +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.State +import org.timemates.app.users.usecases.TimerCreationUseCase +import org.timemates.app.users.validation.TimerDescriptionValidator +import org.timemates.app.users.validation.TimerNameValidator class TimerCreationReducer( private val timerCreationUseCase: TimerCreationUseCase, diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationStateMachine.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationScreenComponent.kt similarity index 82% rename from feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationStateMachine.kt rename to feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationScreenComponent.kt index 7ea4b7a..e513f12 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationStateMachine.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timer_creation/mvi/TimerCreationScreenComponent.kt @@ -1,27 +1,31 @@ package org.timemates.app.timers.ui.timer_creation.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.types.value.Count +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Effect -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Event -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.State -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.types.value.Count +import org.timemates.app.foundation.mvi.mviComponent +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Effect +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Event +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.State import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -class TimerCreationStateMachine( +class TimerCreationScreenComponent( + componentContext: ComponentContext, reducer: TimerCreationReducer, middleware: TimerCreationMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentContext = componentContext, + componentName = "TimerCreationScreen", initState = State(), reducer = reducer, middlewares = listOf(middleware), ) { - @Immutable data class State( val name: String = "", @@ -57,7 +61,7 @@ class TimerCreationStateMachine( data class ConfirmationRequirementChanged(val isConfirmationRequired: Boolean) : Event() - object OnDoneClicked : Event() + data object OnDoneClicked : Event() } sealed class Effect : UiEffect { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/TimersListScreen.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/TimersListScreen.kt index 5a82ea0..bd1a21b 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/TimersListScreen.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/TimersListScreen.kt @@ -30,31 +30,30 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import dev.icerock.moko.resources.compose.painterResource import io.github.skeptick.libres.compose.painterResource +import io.timemates.sdk.timers.types.value.TimerId +import kotlinx.coroutines.channels.consumeEach import org.timemates.app.feature.common.failures.getDefaultDisplayMessage -import org.timemates.app.foundation.mvi.StateMachine +import org.timemates.app.foundation.mvi.MVI import org.timemates.app.localization.compose.LocalStrings import org.timemates.app.style.system.Resources import org.timemates.app.style.system.appbar.AppBar +import org.timemates.app.style.system.button.FloatingActionButton +import org.timemates.app.style.system.theme.AppTheme import org.timemates.app.timers.ui.PlaceholderTimerItem import org.timemates.app.timers.ui.TimerItem -import org.timemates.app.style.system.theme.AppTheme -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Effect -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Event -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.State -import org.timemates.app.style.system.button.FloatingActionButton -import io.timemates.sdk.timers.types.value.TimerId -import kotlinx.coroutines.channels.consumeEach +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Effect +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Event +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.State @Composable fun TimersListScreen( - stateMachine: StateMachine, + mvi: MVI, navigateToSetting: () -> Unit, navigateToTimerCreationScreen: () -> Unit, navigateToTimer: (TimerId) -> Unit, ) { - val state by stateMachine.state.collectAsState() + val state by mvi.state.collectAsState() val snackbarData = remember { SnackbarHostState() } val timersListState = rememberLazyListState() @@ -63,9 +62,9 @@ fun TimersListScreen( val strings = LocalStrings.current LaunchedEffect(true) { - stateMachine.dispatchEvent(Event.Load) + mvi.dispatchEvent(Event.Load) - stateMachine.effects.consumeEach { effect -> + mvi.effects.consumeEach { effect -> when (effect) { is Effect.Failure -> snackbarData.showSnackbar(message = effect.throwable.getDefaultDisplayMessage(strings)) @@ -75,10 +74,10 @@ fun TimersListScreen( } } - if(state.hasMoreItems) { + if (state.hasMoreItems) { LaunchedEffect(timersListState.layoutInfo.visibleItemsInfo.lastOrNull()) { if (timersListState.isScrolledToTheEnd()) { - stateMachine.dispatchEvent(Event.Load) + mvi.dispatchEvent(Event.Load) } } } @@ -166,7 +165,7 @@ fun TimersListScreen( } } -fun LazyListState.isScrolledToTheEnd() : Boolean { +fun LazyListState.isScrolledToTheEnd(): Boolean { val lastItem = layoutInfo.visibleItemsInfo.lastOrNull() return lastItem == null || lastItem.size + lastItem.offset <= layoutInfo.viewportEndOffset } diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListMiddleware.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListMiddleware.kt index 5a91837..6851cb0 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListMiddleware.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListMiddleware.kt @@ -1,9 +1,8 @@ package org.timemates.app.timers.ui.timers_list.mvi import org.timemates.app.foundation.mvi.Middleware -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.State -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Effect +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Effect +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.State class TimersListMiddleware : Middleware { override fun onEffect(effect: Effect, state: State): State { diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListReducer.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListReducer.kt index 021161a..b4d4ca0 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListReducer.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListReducer.kt @@ -1,17 +1,15 @@ package org.timemates.app.timers.ui.timers_list.mvi -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Effect -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Event -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.State -import org.timemates.app.users.usecases.GetUserTimersUseCase import io.timemates.sdk.common.pagination.PagesIterator import io.timemates.sdk.timers.types.Timer import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.CoroutineStart -import kotlinx.coroutines.async import kotlinx.coroutines.launch +import org.timemates.app.foundation.mvi.Reducer +import org.timemates.app.foundation.mvi.ReducerScope +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Effect +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Event +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.State +import org.timemates.app.users.usecases.GetUserTimersUseCase class TimersListReducer ( private val getUserTimersUseCase: GetUserTimersUseCase, diff --git a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListStateMachine.kt b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListScreenComponent.kt similarity index 61% rename from feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListStateMachine.kt rename to feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListScreenComponent.kt index f357ddc..9576fbc 100644 --- a/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListStateMachine.kt +++ b/feature/timers/presentation/src/commonMain/kotlin/org/timemates/app/timers/ui/timers_list/mvi/TimersListScreenComponent.kt @@ -1,19 +1,24 @@ package org.timemates.app.timers.ui.timers_list.mvi import androidx.compose.runtime.Immutable -import org.timemates.app.foundation.mvi.StateMachine +import com.arkivanov.decompose.ComponentContext +import io.timemates.sdk.timers.types.Timer +import org.timemates.app.foundation.mvi.MVIComponent import org.timemates.app.foundation.mvi.UiEffect import org.timemates.app.foundation.mvi.UiEvent import org.timemates.app.foundation.mvi.UiState -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Effect -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.Event -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine.State -import io.timemates.sdk.timers.types.Timer +import org.timemates.app.foundation.mvi.mviComponent +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Effect +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.Event +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.State -class TimersListStateMachine( +class TimersListScreenComponent( + componentContext: ComponentContext, reducer: TimersListReducer, middleware: TimersListMiddleware, -) : StateMachine( +) : MVIComponent by mviComponent( + componentName = "TimersListScreen", + componentContext = componentContext, initState = State(), reducer = reducer, middlewares = listOf(middleware), diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsMiddlewareTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsMiddlewareTest.kt index 2b84a32..ac7d4dd 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsMiddlewareTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsMiddlewareTest.kt @@ -1,25 +1,19 @@ package org.timemates.app.timers.ui.settings -import io.mockk.every -import io.mockk.mockk -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsMiddleware -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine -import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Test +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsMiddleware +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent class TimerSettingsMiddlewareTest { - private val stateStore: StateStore = mockk() private val middleware: TimerSettingsMiddleware = TimerSettingsMiddleware() @Test fun `effects produced by network operations should remove loading status`() { //GIVEN - val effects = listOf(TimerSettingsStateMachine.Effect.Failure(Exception())) - every { stateStore.state } returns MutableStateFlow(TimerSettingsStateMachine.State(isLoading = true)) + val effects = listOf(TimerSettingsScreenComponent.Effect.Failure(Exception())) //WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, TimerSettingsScreenComponent.State(isLoading = true)) } //THEN .forEach { (effect, state) -> assert(!state.isLoading) { diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsReducerTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsReducerTest.kt index 4cbb42d..2a54ad2 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsReducerTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/settings/TimerSettingsReducerTest.kt @@ -2,19 +2,18 @@ package org.timemates.app.timers.ui.settings import io.mockk.every import io.mockk.mockk -import org.timemates.app.foundation.mvi.reduce -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsReducer -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.Event -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State -import org.timemates.app.users.usecases.TimerSettingsUseCase -import org.timemates.app.users.validation.TimerDescriptionValidator -import org.timemates.app.users.validation.TimerNameValidator import io.timemates.sdk.common.constructor.createOrThrow import io.timemates.sdk.common.types.value.Count import io.timemates.sdk.timers.types.value.TimerId -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.test.TestScope import org.junit.jupiter.api.Test +import org.timemates.app.foundation.mvi.reduce +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsReducer +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.Event +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State +import org.timemates.app.users.usecases.TimerSettingsUseCase +import org.timemates.app.users.validation.TimerDescriptionValidator +import org.timemates.app.users.validation.TimerNameValidator import kotlin.test.assertEquals import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationMiddlewareTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationMiddlewareTest.kt index cfff25d..1d40f8e 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationMiddlewareTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationMiddlewareTest.kt @@ -1,26 +1,20 @@ package org.timemates.app.timers.ui.timer_creation -import io.mockk.every -import io.mockk.mockk -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationMiddleware -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.Effect -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine.State -import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Test +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationMiddleware +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.Effect +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent.State class TimerCreationMiddlewareTest { - private val stateStore: StateStore = mockk() private val middleware: TimerCreationMiddleware = TimerCreationMiddleware() @Test fun `effects produced by network operations should remove loading status`() { //GIVEN val effects = listOf(Effect.Failure(Exception())) - every { stateStore.state } returns MutableStateFlow(State(isLoading = true)) //WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } //THEN .forEach { (effect, state) -> assert(!state.isLoading) { diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationReducerTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationReducerTest.kt index 0e4405b..927f92c 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationReducerTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timer_creation/TimerCreationReducerTest.kt @@ -2,17 +2,16 @@ package org.timemates.app.timers.ui.timer_creation import io.mockk.every import io.mockk.mockk +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.types.value.Count +import kotlinx.coroutines.test.TestScope +import org.junit.jupiter.api.Test import org.timemates.app.foundation.mvi.reduce import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationReducer -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent import org.timemates.app.users.usecases.TimerCreationUseCase import org.timemates.app.users.validation.TimerDescriptionValidator import org.timemates.app.users.validation.TimerNameValidator -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.types.value.Count -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestScope -import org.junit.jupiter.api.Test import kotlin.test.assertEquals import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes @@ -51,14 +50,14 @@ class TimerCreationReducerTest { // WHEN val result = reducer.reduce( - state = TimerCreationStateMachine.State(name = validName, description = validDescription), - event = TimerCreationStateMachine.Event.OnDoneClicked, + state = TimerCreationScreenComponent.State(name = validName, description = validDescription), + event = TimerCreationScreenComponent.Event.OnDoneClicked, machineScope = scope, ) {} // THEN assertEquals( - expected = TimerCreationStateMachine.State( + expected = TimerCreationScreenComponent.State( name = validName, description = validDescription, isNameSizeInvalid = false, @@ -79,14 +78,14 @@ class TimerCreationReducerTest { // WHEN val result = reducer.reduce( - TimerCreationStateMachine.State(name = validName, description = validDescription), - TimerCreationStateMachine.Event.OnDoneClicked, + TimerCreationScreenComponent.State(name = validName, description = validDescription), + TimerCreationScreenComponent.Event.OnDoneClicked, scope, ) {} // THEN assertEquals( - expected = TimerCreationStateMachine.State( + expected = TimerCreationScreenComponent.State( name = validName, description = validDescription, isNameSizeInvalid = false, @@ -107,14 +106,14 @@ class TimerCreationReducerTest { // WHEN val result = reducer.reduce( - state = TimerCreationStateMachine.State(name = invalidName, description = validDescription), - event = TimerCreationStateMachine.Event.OnDoneClicked, + state = TimerCreationScreenComponent.State(name = invalidName, description = validDescription), + event = TimerCreationScreenComponent.Event.OnDoneClicked, machineScope = scope, ) {} // THEN assertEquals( - expected = TimerCreationStateMachine.State( + expected = TimerCreationScreenComponent.State( name = invalidName, description = validDescription, isNameSizeInvalid = true, @@ -135,14 +134,14 @@ class TimerCreationReducerTest { // WHEN val result = reducer.reduce( - state = TimerCreationStateMachine.State(name = validName, description = invalidDescription), - event = TimerCreationStateMachine.Event.OnDoneClicked, + state = TimerCreationScreenComponent.State(name = validName, description = invalidDescription), + event = TimerCreationScreenComponent.Event.OnDoneClicked, machineScope = scope, ) {} // THEN assertEquals( - expected = TimerCreationStateMachine.State( + expected = TimerCreationScreenComponent.State( name = validName, description = invalidDescription, isNameSizeInvalid = false, @@ -163,7 +162,7 @@ class TimerCreationReducerTest { // WHEN val result = reducer.reduce( - state = TimerCreationStateMachine.State( + state = TimerCreationScreenComponent.State( name = validName, description = validDescription, workTime = workTime, @@ -174,13 +173,13 @@ class TimerCreationReducerTest { isEveryoneCanPause = isEveryoneCanPause, isConfirmationRequired = isConfirmationRequired ), - event = TimerCreationStateMachine.Event.OnDoneClicked, + event = TimerCreationScreenComponent.Event.OnDoneClicked, machineScope = scope, ) {} // THEN assertEquals( - expected = TimerCreationStateMachine.State( + expected = TimerCreationScreenComponent.State( name = validName, description = validDescription, workTime = workTime, diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListMiddlewareTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListMiddlewareTest.kt index 4e39640..47b422b 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListMiddlewareTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListMiddlewareTest.kt @@ -1,28 +1,23 @@ package org.timemates.app.timers.ui.timers_list -import io.mockk.every -import io.mockk.mockk -import org.timemates.app.foundation.mvi.StateStore -import org.timemates.app.timers.ui.timers_list.mvi.TimersListMiddleware -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine -import kotlinx.coroutines.flow.MutableStateFlow import org.junit.jupiter.api.Test +import org.timemates.app.timers.ui.timers_list.mvi.TimersListMiddleware +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent.State class TimersListMiddlewareTest { - private val stateStore: StateStore = mockk() private val middleware: TimersListMiddleware = TimersListMiddleware() @Test fun `effects produced by network operations should remove loading status`() { // GIVEN val effects = listOf( - TimersListStateMachine.Effect.Failure(Exception()), - TimersListStateMachine.Effect.NoMoreTimers + TimersListScreenComponent.Effect.Failure(Exception()), + TimersListScreenComponent.Effect.NoMoreTimers ) - every { stateStore.state } returns MutableStateFlow(TimersListStateMachine.State(isLoading = true)) // WHEN - effects.map { effect -> effect to middleware.onEffect(effect, stateStore) } + effects.map { effect -> effect to middleware.onEffect(effect, State(isLoading = true)) } // THEN .forEach { (effect, state) -> assert(!state.isLoading) { diff --git a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListReducerTest.kt b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListReducerTest.kt index 388bf32..97934c7 100644 --- a/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListReducerTest.kt +++ b/feature/timers/presentation/src/jvmTest/kotlin/org/timemates/app/timers/ui/timers_list/TimersListReducerTest.kt @@ -1,13 +1,12 @@ package org.timemates.app.timers.ui.timers_list import io.mockk.mockk +import kotlinx.coroutines.test.TestScope +import org.junit.jupiter.api.Test import org.timemates.app.foundation.mvi.reduce import org.timemates.app.timers.ui.timers_list.mvi.TimersListReducer -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent import org.timemates.app.users.usecases.GetUserTimersUseCase -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.test.TestScope -import org.junit.jupiter.api.Test import kotlin.test.assertEquals class TimersListReducerTest { @@ -22,14 +21,14 @@ class TimersListReducerTest { fun `Event Load must return the parameter isLoading true`() { // WHEN val result = reducer.reduce( - state = TimersListStateMachine.State(), - event = TimersListStateMachine.Event.Load, + state = TimersListScreenComponent.State(), + event = TimersListScreenComponent.Event.Load, machineScope = scope, ) {} // THEN assertEquals( - expected = TimersListStateMachine.State( + expected = TimersListScreenComponent.State( isLoading = true, ), actual = result, diff --git a/feature/users/data/src/commonMain/kotlin/org/timemates/app/users/data/UsersRepository.kt b/feature/users/data/src/commonMain/kotlin/org/timemates/app/users/data/UsersRepository.kt index d382ee2..6395ac9 100644 --- a/feature/users/data/src/commonMain/kotlin/org/timemates/app/users/data/UsersRepository.kt +++ b/feature/users/data/src/commonMain/kotlin/org/timemates/app/users/data/UsersRepository.kt @@ -1,6 +1,5 @@ package org.timemates.app.users.data -import org.timemates.app.foundation.time.TimeProvider import io.timemates.sdk.common.types.Empty import io.timemates.sdk.users.UserApi import io.timemates.sdk.users.profile.types.User @@ -11,13 +10,14 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.launch +import org.timemates.app.foundation.time.TimeProvider import org.timemates.app.users.repositories.UsersRepository as UserRepositoryContract class UsersRepository( private val userApi: UserApi, private val cachedUsersDataSource: CachedUsersDataSource, private val timeProvider: TimeProvider, - private val coroutineScope: CoroutineScope, + coroutineScope: CoroutineScope, ) : UserRepositoryContract { init { diff --git a/foundation/mvi/build.gradle.kts b/foundation/mvi/build.gradle.kts index 158c3bb..55d357c 100644 --- a/foundation/mvi/build.gradle.kts +++ b/foundation/mvi/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id(libs.plugins.configurations.multiplatform.library.get().pluginId) + alias(libs.plugins.kotlinx.serialization) } kotlin { @@ -9,6 +10,6 @@ kotlin { } dependencies { - commonMainApi(projects.foundation.viewmodel) commonMainImplementation(libs.kotlinx.coroutines) + commonMainApi(libs.decompose) } \ No newline at end of file diff --git a/foundation/mvi/koin-compose/src/androidMain/AndroidManifest.xml b/foundation/mvi/koin-compose/src/androidMain/AndroidManifest.xml deleted file mode 100644 index a1f5a90..0000000 --- a/foundation/mvi/koin-compose/src/androidMain/AndroidManifest.xml +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/foundation/mvi/koin-compose/src/androidMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt b/foundation/mvi/koin-compose/src/androidMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt deleted file mode 100644 index fd5f68a..0000000 --- a/foundation/mvi/koin-compose/src/androidMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt +++ /dev/null @@ -1,50 +0,0 @@ -@file:Suppress("USELESS_CAST") - -package org.timemates.app.mvi.compose - -import androidx.compose.runtime.Composable -import androidx.lifecycle.ViewModelStoreOwner -import androidx.lifecycle.viewmodel.CreationExtras -import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner -import org.timemates.androidx.viewmodel.ViewModel -import org.timemates.app.foundation.mvi.* -import org.koin.androidx.compose.defaultExtras -import org.koin.androidx.viewmodel.resolveViewModel -import org.koin.compose.LocalKoinScope -import org.koin.core.annotation.KoinInternalApi -import org.koin.core.parameter.ParametersDefinition -import org.koin.core.qualifier.Qualifier -import org.koin.core.scope.Scope - -/** - * Creates and returns an instance of the specified state machine using the provided factory. - * - * @param TSM The reified type of the state machine. - * @param TState The type of the state in the state machine. - * @param TEvent The type of the events in the state machine. - * @param TEffect The type of the effects in the state machine. - * @param factory The factory to create the state machine instance. - * @return The created instance of the state machine. - */ -@Composable -actual inline fun > stateMachine( - noinline parameters: ParametersDefinition?, -) = koinVM(parameters = parameters) - -@OptIn(KoinInternalApi::class) -@PublishedApi -@Composable -internal inline fun koinVM( - qualifier: Qualifier? = null, - viewModelStoreOwner: ViewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) { - "No ViewModelStoreOwner was provided via LocalViewModelStoreOwner" - }, - key: String? = null, - extras: CreationExtras = defaultExtras(viewModelStoreOwner), - scope: Scope = LocalKoinScope.current, - noinline parameters: ParametersDefinition? = null, -): T { - return resolveViewModel( - T::class, viewModelStoreOwner.viewModelStore, key, extras, qualifier, scope, parameters - ) -} diff --git a/foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt b/foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/mvi.common.kt similarity index 55% rename from foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt rename to foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/mvi.common.kt index 4cb2296..d36f36f 100644 --- a/foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt +++ b/foundation/mvi/koin-compose/src/commonMain/kotlin/org/timemates/app/mvi/compose/mvi.common.kt @@ -1,20 +1,22 @@ +@file:Suppress("USELESS_CAST") + package org.timemates.app.mvi.compose import androidx.compose.runtime.Composable -import org.timemates.app.foundation.mvi.StateMachine +import org.koin.compose.LocalKoinScope import org.koin.core.parameter.ParametersDefinition - +import org.timemates.app.foundation.mvi.* /** * Creates and returns an instance of the specified state machine using the provided factory. * * @param TSM The reified type of the state machine. - * @param TState The type of the state in the state machine. - * @param TEvent The type of the events in the state machine. - * @param TEffect The type of the effects in the state machine. + * @param factory The factory to create the state machine instance. * @return The created instance of the state machine. */ @Composable -expect inline fun > stateMachine( +inline fun > koinMviComponent( noinline parameters: ParametersDefinition? = null, -): TSM \ No newline at end of file +): TSM { + return LocalKoinScope.current.getKoin().get(parameters = parameters) +} diff --git a/foundation/mvi/koin-compose/src/jvmMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt b/foundation/mvi/koin-compose/src/jvmMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt deleted file mode 100644 index 8faa02a..0000000 --- a/foundation/mvi/koin-compose/src/jvmMain/kotlin/org/timemates/app/mvi/compose/stateMachine.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.timemates.app.mvi.compose - -import androidx.compose.runtime.Composable -import org.timemates.app.foundation.mvi.StateMachine -import org.koin.compose.koinInject -import org.koin.core.parameter.ParametersDefinition - -@Composable -actual inline fun > stateMachine( - noinline parameters: ParametersDefinition?, -): TSM = koinInject( - parameters = parameters, -) \ No newline at end of file diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVI.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVI.kt new file mode 100644 index 0000000..314c00f --- /dev/null +++ b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVI.kt @@ -0,0 +1,23 @@ +package org.timemates.app.foundation.mvi + +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.flow.StateFlow + +public interface MVI { + /** + * Represents the channel for emitting UI effects. + */ + public val effects: ReceiveChannel + + /** + * Represents the current state of the UI. + */ + public val state: StateFlow + + /** + * Processes an event from UI. + * + * @param event The event to be processed. + */ + public fun dispatchEvent(event: TEvent) +} \ No newline at end of file diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVIComponent.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVIComponent.kt new file mode 100644 index 0000000..c902081 --- /dev/null +++ b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/MVIComponent.kt @@ -0,0 +1,143 @@ +package org.timemates.app.foundation.mvi + +import com.arkivanov.decompose.ComponentContext +import com.arkivanov.essenty.lifecycle.doOnDestroy +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.update +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer +import kotlin.coroutines.CoroutineContext + +/** + * Creates and initializes an instance of an MVI (Model-View-Intent) component using class delegation. + * + * @param componentContext The context for the component, typically obtained from the Decompose library. + * @param initState The initial state of the UI. + * @param reducer An implementation of the Reducer interface responsible for handling state updates + * based on UI events. + * @param middlewares Optional list of Middleware instances to handle asynchronous tasks and state + * modifications triggered by UI effects. + * @return An instance of [MVI] representing the MVI component. + * + * @param TState The type representing the UI state. + * @param TEvent The type representing UI events. + * @param TEffect The type representing UI effects. + */ +public inline fun mviComponent( + componentContext: ComponentContext, + componentName: String, + initState: TState, + reducer: Reducer, + middlewares: List> = emptyList(), +): MVIComponent { + return MVIComponent( + componentContext = componentContext, + componentName = componentName, + initState = initState, + stateSerializer = serializer(), + reducer = reducer, + middlewares = middlewares, + ) +} + +public fun MVIComponent( + componentContext: ComponentContext, + componentName: String, + initState: TState, + stateSerializer: KSerializer, + reducer: Reducer, + middlewares: List> = emptyList(), +): MVIComponent { + return MVIComponentImpl(componentContext, componentName, initState, stateSerializer, reducer, middlewares) +} + +public interface MVIComponent : MVI, ComponentContext { + public val componentName: String +} + +/** + * The base abstract class for implementing a component in the Model-View-Intent (MVI) architecture + * using the Decompose library. This component manages the UI state, handles UI events, and emits + * UI effects in a structured and reactive manner. + * + * @param TState The type representing the UI state. + * @param TEvent The type representing UI events. + * @param TEffect The type representing effects to UI. + * @param componentContext The context for the component, typically obtained from the Decompose library. + * @param componentName The name of component that is used to retain state (like an identifier). + * @param initState The initial state of the UI. + * @property reducer An implementation of the Reducer interface responsible for handling state updates + * based on UI events. + * @property middlewares Optional list of Middleware instances to handle asynchronous tasks and state + * modifications triggered by UI effects. + */ +private class MVIComponentImpl( + private val componentContext: ComponentContext, + override val componentName: String, + initState: TState, + private val stateSerializer: KSerializer, + private val reducer: Reducer, + private val middlewares: List> = emptyList(), +) : MVIComponent, ComponentContext by componentContext { + + private val _state: MutableStateFlow by lazy { + MutableStateFlow(stateKeeper.consume(componentName, stateSerializer) ?: initState) + } + private val _effects: Channel = Channel(Channel.UNLIMITED) + + private val coroutineScope: CoroutineScope = coroutineScope(Dispatchers.Main + SupervisorJob()) + + init { + componentContext.stateKeeper.register(componentName, stateSerializer, _state::value) + } + + /** + * Represents the current state of the UI. + */ + override val state: StateFlow by ::_state + + /** + * Represents the channel for emitting UI effects. + */ + override val effects: ReceiveChannel by ::_effects + + private val sendEffect: (TEffect) -> Unit = { effect -> + middlewares.forEach { middleware -> updateState { middleware.onEffect(effect, it) } } + _effects.trySend(effect) + } + + private val reducerScope = ReducerScope(sendEffect, coroutineScope) + + /** + * Processes an event from UI. + * + * @param event The event to be processed. + */ + override fun dispatchEvent(event: TEvent) { + with(reducer) { + updateState { reducerScope.reduce(it, event) } + } + } + + /** + * A helper method to safely update the UI state. It takes a lambda that defines the state update logic. + * + * @param action A lambda function that defines how to update the state based on the current state. + */ + private fun updateState(action: (TState) -> TState) { + _state.update { action(it) } + } +} + +private fun ComponentContext.coroutineScope(context: CoroutineContext): CoroutineScope { + val coroutineScope = CoroutineScope(context) + lifecycle.doOnDestroy(coroutineScope::cancel) + return coroutineScope +} \ No newline at end of file diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/Reducer.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/Reducer.kt index 44f9875..319d0d9 100644 --- a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/Reducer.kt +++ b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/Reducer.kt @@ -16,11 +16,6 @@ public interface Reducer * * @param state The current state of the UI. * @param event The event to be processed. - * @param sendEffect The function to send effects to the UI. - * Call `sendEffect(effect)` to send an effect to be handled by the UI. - * The effect will be delivered asynchronously. - * Note: Ensure proper handling and validation of effects to avoid issues like - * infinite loops or unexpected behaviors. * @return The new state after processing the event. */ public fun ReducerScope.reduce( @@ -41,8 +36,12 @@ public fun Reducer( val sendEffect: (TEffect) -> Unit, diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateMachine.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateMachine.kt deleted file mode 100644 index 4655769..0000000 --- a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateMachine.kt +++ /dev/null @@ -1,49 +0,0 @@ -package org.timemates.app.foundation.mvi - -import org.timemates.androidx.viewmodel.ViewModel -import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.update - -public abstract class StateMachine( - protected val initState: TState, - private val reducer: Reducer, - private val middlewares: List> = emptyList(), -) : ViewModel(), StateStore { - private val _state: MutableStateFlow by lazy { MutableStateFlow(initState) } - private val _effects: Channel = Channel(Channel.UNLIMITED) - - /** - * Represents the current state of the UI. - */ - public final override val state: StateFlow by ::_state - - /** - * Represents the channel for emitting UI effects. - */ - public val effects: ReceiveChannel by ::_effects - - private val sendEffect: (TEffect) -> Unit = { effect -> - middlewares.forEach { middleware -> updateState { middleware.onEffect(effect, it) } } - _effects.trySend(effect) - } - - private val reducerScope = ReducerScope(sendEffect, coroutineScope) - - /** - * Processes an event from UI. - * - * @param event The event to be processed. - */ - public fun dispatchEvent(event: TEvent) { - with(reducer) { - updateState { reducerScope.reduce(it, event) } - } - } - - private fun updateState(action: (TState) -> TState) { - _state.update { action(it) } - } -} diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateStore.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateStore.kt deleted file mode 100644 index c0af385..0000000 --- a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/StateStore.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.timemates.app.foundation.mvi - -import kotlinx.coroutines.flow.StateFlow - -/** - * An interface representing a state store. - * - * @param TState The type of the state. - */ -public interface StateStore { - /** - * The state flow representing the current state. - */ - public val state: StateFlow -} diff --git a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/UiState.kt b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/UiState.kt index 7af5b53..82bb299 100644 --- a/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/UiState.kt +++ b/foundation/mvi/src/commonMain/kotlin/org/timemates/app/foundation/mvi/UiState.kt @@ -2,7 +2,8 @@ package org.timemates.app.foundation.mvi /** * Interface-marker for states that used by UI. + * + * **State should be unique for the every component to avoid crush on conflict of saving state from different + * .** */ -public interface UiState - -public object EmptyState : UiState \ No newline at end of file +public interface UiState \ No newline at end of file diff --git a/foundation/viewmodel/build.gradle.kts b/foundation/viewmodel/build.gradle.kts deleted file mode 100644 index 42f8eab..0000000 --- a/foundation/viewmodel/build.gradle.kts +++ /dev/null @@ -1,30 +0,0 @@ -plugins { - id(libs.plugins.configurations.multiplatform.library.get().pluginId) -} - -kotlin { - jvm() - androidTarget() - - sourceSets { - androidMain { - dependencies { - api(libs.androidx.lifecycle) - } - } - } -} - -android { - compileSdk = libs.versions.android.target.get().toInt() - - defaultConfig { - minSdk = libs.versions.android.min.get().toInt() - } - - namespace = "org.timemates.androidx.viewmodel" -} - -dependencies { - commonMainImplementation(libs.kotlinx.coroutines) -} \ No newline at end of file diff --git a/foundation/viewmodel/src/androidMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt b/foundation/viewmodel/src/androidMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt deleted file mode 100644 index ed29e75..0000000 --- a/foundation/viewmodel/src/androidMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.timemates.androidx.viewmodel - -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.CoroutineScope - -@Suppress("ACTUAL_CLASSIFIER_MUST_HAVE_THE_SAME_SUPERTYPES_AS_NON_FINAL_EXPECT_CLASSIFIER_WARNING") -actual abstract class ViewModel : ViewModel() { - /** - * The coroutine scope associated with this ViewModel. - * - * The `viewModelScope` is a [CoroutineScope] provided by the Android Jetpack's ViewModel library. - * It is used to launch coroutines that are scoped to the lifecycle of the ViewModel. - * Any coroutines launched in this scope will automatically be canceled when the ViewModel is cleared or destroyed. - * - * @see [ViewModel] - * @see [CoroutineScope] - */ - actual val coroutineScope: CoroutineScope by ::viewModelScope -} \ No newline at end of file diff --git a/foundation/viewmodel/src/commonMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt b/foundation/viewmodel/src/commonMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt deleted file mode 100644 index 9631c62..0000000 --- a/foundation/viewmodel/src/commonMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt +++ /dev/null @@ -1,14 +0,0 @@ -package org.timemates.androidx.viewmodel - -import kotlinx.coroutines.CoroutineScope - -/** - * Abstract class representing a ViewModel. - */ -expect abstract class ViewModel() { - /** - * The coroutine scope associated with this ViewModel. - * Automatically cancels when ViewModel is not within user scope. - */ - val coroutineScope: CoroutineScope -} diff --git a/foundation/viewmodel/src/jvmMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt b/foundation/viewmodel/src/jvmMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt deleted file mode 100644 index 7f2ed12..0000000 --- a/foundation/viewmodel/src/jvmMain/kotlin/org/timemates/androidx/viewmodel/ViewModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package org.timemates.androidx.viewmodel - -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job - -actual abstract class ViewModel { - /** - * The coroutine scope associated with this ViewModel. - * Automatically cancels when ViewModel is not within user scope. - */ - actual val coroutineScope: CoroutineScope by lazy { - CoroutineScope( - Job() + Dispatchers.Default - ) - } -} \ No newline at end of file diff --git a/navigation/src/commonMain/kotlin/org/timemates/app/navigation/TimeMatesAppEntry.kt b/navigation/src/commonMain/kotlin/org/timemates/app/navigation/TimeMatesAppEntry.kt index bd1cf90..33887bf 100644 --- a/navigation/src/commonMain/kotlin/org/timemates/app/navigation/TimeMatesAppEntry.kt +++ b/navigation/src/commonMain/kotlin/org/timemates/app/navigation/TimeMatesAppEntry.kt @@ -12,33 +12,33 @@ import com.arkivanov.decompose.router.stack.pop import com.arkivanov.decompose.router.stack.popTo import com.arkivanov.decompose.router.stack.push import com.arkivanov.decompose.router.stack.replaceAll +import io.timemates.sdk.authorization.email.types.value.VerificationHash +import io.timemates.sdk.common.constructor.createOrThrow +import io.timemates.sdk.common.exceptions.UnauthorizedException +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.channels.consumeEach +import org.koin.core.parameter.parametersOf import org.timemates.app.authorization.ui.afterstart.AfterStartScreen -import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartStateMachine +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent import org.timemates.app.authorization.ui.configure_account.ConfigureAccountScreen -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent import org.timemates.app.authorization.ui.confirmation.ConfirmAuthorizationScreen -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent import org.timemates.app.authorization.ui.initial_authorization.InitialAuthorizationScreen -import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationStateMachine +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent import org.timemates.app.authorization.ui.new_account_info.NewAccountInfoScreen -import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoStateMachine +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent import org.timemates.app.authorization.ui.start.StartAuthorizationScreen -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent import org.timemates.app.feature.common.startup.StartupScreen -import org.timemates.app.feature.common.startup.mvi.StartupStateMachine -import org.timemates.app.mvi.compose.stateMachine +import org.timemates.app.feature.common.startup.mvi.StartupScreenMVIComponent +import org.timemates.app.mvi.compose.koinMviComponent import org.timemates.app.timers.ui.settings.TimerSettingsScreen -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent import org.timemates.app.timers.ui.timer_creation.TimerCreationScreen -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent import org.timemates.app.timers.ui.timers_list.TimersListScreen -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine -import io.timemates.sdk.authorization.email.types.value.VerificationHash -import io.timemates.sdk.common.constructor.createOrThrow -import io.timemates.sdk.common.exceptions.UnauthorizedException -import kotlinx.coroutines.channels.ReceiveChannel -import kotlinx.coroutines.channels.consumeEach -import org.koin.core.parameter.parametersOf +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent @Composable fun TimeMatesAppEntry( @@ -52,6 +52,8 @@ fun TimeMatesAppEntry( } } + val componentContext = LocalComponentContext.current + ChildStack( source = navigation, initialStack = { listOf(initialScreen) }, @@ -59,13 +61,15 @@ fun TimeMatesAppEntry( ) { screen -> when (screen) { is Screen.Startup -> StartupScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent { + parametersOf(componentContext) + }, navigateToAuth = { navigation.push(Screen.InitialAuthorizationScreen) }, navigateToHome = { navigation.replaceAll(Screen.TimersList) }, ) is Screen.ConfirmAuthorization -> ConfirmAuthorizationScreen( - stateMachine = stateMachine { + mvi = koinMviComponent { parametersOf(VerificationHash.createOrThrow(screen.verificationHash)) }, onBack = { navigation.pop() }, @@ -78,21 +82,21 @@ fun TimeMatesAppEntry( ) Screen.InitialAuthorizationScreen -> InitialAuthorizationScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent(), navigateToStartAuthorization = { navigation.push(Screen.StartAuthorization) }, ) Screen.StartAuthorization -> StartAuthorizationScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent(), onNavigateToConfirmation = { navigation.push(Screen.AfterStart(it.string)) }, ) is Screen.AfterStart -> AfterStartScreen( - stateMachine = stateMachine { + mvi = koinMviComponent { parametersOf(VerificationHash.createOrThrow(screen.verificationHash)) }, navigateToConfirmation = { @@ -104,7 +108,7 @@ fun TimeMatesAppEntry( ) is Screen.NewAccountInfo -> NewAccountInfoScreen( - stateMachine = stateMachine { + mvi = koinMviComponent { parametersOf(VerificationHash.createOrThrow(screen.verificationHash)) }, navigateToConfigure = { @@ -116,7 +120,7 @@ fun TimeMatesAppEntry( ) is Screen.NewAccount -> ConfigureAccountScreen( - stateMachine = stateMachine { + mvi = koinMviComponent { parametersOf(VerificationHash.createOrThrow(screen.verificationHash)) }, onBack = { @@ -128,7 +132,7 @@ fun TimeMatesAppEntry( ) is Screen.TimersList -> TimersListScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent(), navigateToSetting = { // TODO when settings page is ready }, @@ -141,14 +145,14 @@ fun TimeMatesAppEntry( ) is Screen.TimerCreation -> TimerCreationScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent(), navigateToTimersScreen = { navigation.pop() }, ) is Screen.TimerSettings -> TimerSettingsScreen( - stateMachine = stateMachine(), + mvi = koinMviComponent(), navigateToTimersScreen = { navigation.pop() }, diff --git a/preview/build.gradle.kts b/preview/build.gradle.kts index 98d8a40..e3d91b4 100644 --- a/preview/build.gradle.kts +++ b/preview/build.gradle.kts @@ -42,6 +42,9 @@ dependencies { implementation(projects.localization) implementation(projects.localization.compose) + implementation(libs.decompose) + implementation(libs.decompose.jetbrains.compose) + implementation(projects.feature.authorization.presentation) implementation(projects.feature.timers.presentation) } diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/AfterStartScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/AfterStartScreen.kt index ea78237..20d2960 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/AfterStartScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/AfterStartScreen.kt @@ -2,9 +2,9 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine import org.timemates.app.authorization.ui.afterstart.AfterStartScreen -import org.timemates.app.foundation.mvi.EmptyState +import org.timemates.app.authorization.ui.afterstart.mvi.AfterStartScreenComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme @Preview @@ -12,7 +12,7 @@ import org.timemates.app.style.system.theme.AppTheme internal fun AfterStartScreenPreview() { AppTheme { AfterStartScreen( - stateMachine = fakeStateMachine(EmptyState), + mvi = fakeMvi(State), navigateToConfirmation = {}, navigateToStart = {} ) diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfigureNewAccountScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfigureNewAccountScreen.kt index 212dc1f..bdb8372 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfigureNewAccountScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfigureNewAccountScreen.kt @@ -2,9 +2,9 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine import org.timemates.app.authorization.ui.configure_account.ConfigureAccountScreen -import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountStateMachine.State +import org.timemates.app.authorization.ui.configure_account.mvi.ConfigureAccountScreenComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme @Preview @@ -12,7 +12,7 @@ import org.timemates.app.style.system.theme.AppTheme internal fun ConfigureNewAccountScreenPreview() { AppTheme { ConfigureAccountScreen( - stateMachine = fakeStateMachine(State()), + mvi = fakeMvi(State()), navigateToHome = {}, onBack = {}, ) diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfirmationScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfirmationScreen.kt index 769c36f..4fa781d 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfirmationScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/ConfirmationScreen.kt @@ -2,9 +2,9 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine import org.timemates.app.authorization.ui.confirmation.ConfirmAuthorizationScreen -import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationStateMachine.State +import org.timemates.app.authorization.ui.confirmation.mvi.ConfirmAuthorizationScreenComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme @Preview @@ -12,7 +12,7 @@ import org.timemates.app.style.system.theme.AppTheme internal fun ConfirmationScreenPreview() { AppTheme { ConfirmAuthorizationScreen( - stateMachine = fakeStateMachine(State()), + mvi = fakeMvi(State()), onBack = {}, navigateToConfiguring = {}, navigateToHome = {}, diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/InitialAuthorizationScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/InitialAuthorizationScreen.kt index 8be1201..3b71143 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/InitialAuthorizationScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/InitialAuthorizationScreen.kt @@ -2,9 +2,9 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine import org.timemates.app.authorization.ui.initial_authorization.InitialAuthorizationScreen -import org.timemates.app.foundation.mvi.EmptyState +import org.timemates.app.authorization.ui.initial_authorization.mvi.InitialAuthorizationScreenComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme @Preview @@ -12,7 +12,7 @@ import org.timemates.app.style.system.theme.AppTheme fun InitialScreenPreview() { AppTheme { InitialAuthorizationScreen( - stateMachine = fakeStateMachine(EmptyState), + mvi = fakeMvi(State), navigateToStartAuthorization = {}, ) } diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/NewAccountInfoScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/NewAccountInfoScreen.kt index 16b7bc4..02143ae 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/NewAccountInfoScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/NewAccountInfoScreen.kt @@ -2,9 +2,9 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine import org.timemates.app.authorization.ui.new_account_info.NewAccountInfoScreen -import org.timemates.app.foundation.mvi.EmptyState +import org.timemates.app.authorization.ui.new_account_info.mvi.NewAccountInfoScreenComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme @Preview @@ -12,7 +12,7 @@ import org.timemates.app.style.system.theme.AppTheme internal fun NewAccountInfoScreenPreview() { AppTheme { NewAccountInfoScreen( - stateMachine = fakeStateMachine(EmptyState), + mvi = fakeMvi(State), navigateToConfigure = {}, navigateToStart = {} ) diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/StartAuthorizationScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/StartAuthorizationScreen.kt index 6e5a370..d08a843 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/StartAuthorizationScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/authorization/StartAuthorizationScreen.kt @@ -3,14 +3,14 @@ package org.timemates.app.preview.feature.feature.authorization import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview import org.timemates.app.authorization.ui.start.StartAuthorizationScreen -import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationStateMachine.State +import org.timemates.app.authorization.ui.start.mvi.StartAuthorizationComponent.State +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme -import org.timemates.app.preview.feature.statemachine.fakeStateMachine @Preview @Composable internal fun StartAuthorizationScreenPreview() { AppTheme { - StartAuthorizationScreen(stateMachine = fakeStateMachine(State()), onNavigateToConfirmation = {}) + StartAuthorizationScreen(mvi = fakeMvi(State()), onNavigateToConfirmation = {}) } } \ No newline at end of file diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerCreationScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerCreationScreen.kt index b3f1e91..b375602 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerCreationScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerCreationScreen.kt @@ -2,17 +2,17 @@ package org.timemates.app.preview.feature.feature.timers import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme import org.timemates.app.timers.ui.timer_creation.TimerCreationScreen -import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationStateMachine +import org.timemates.app.timers.ui.timer_creation.mvi.TimerCreationScreenComponent @Preview @Composable internal fun TimerCreationScreenPreview() { AppTheme { TimerCreationScreen( - stateMachine = fakeStateMachine(TimerCreationStateMachine.State()), + mvi = fakeMvi(TimerCreationScreenComponent.State()), navigateToTimersScreen = {}, ) } diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerListScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerListScreen.kt index a96113c..63d7566 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerListScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerListScreen.kt @@ -2,17 +2,17 @@ package org.timemates.app.preview.feature.feature.timers import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme import org.timemates.app.timers.ui.timers_list.TimersListScreen -import org.timemates.app.timers.ui.timers_list.mvi.TimersListStateMachine +import org.timemates.app.timers.ui.timers_list.mvi.TimersListScreenComponent @Preview @Composable internal fun TimerListScreenPreview() { AppTheme { TimersListScreen( - stateMachine = fakeStateMachine(TimersListStateMachine.State()), + mvi = fakeMvi(TimersListScreenComponent.State()), navigateToSetting = {}, navigateToTimerCreationScreen = {}, navigateToTimer = {}, diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerSettingsScreen.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerSettingsScreen.kt index 28b34f6..4400d6d 100644 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerSettingsScreen.kt +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/feature/timers/TimerSettingsScreen.kt @@ -2,17 +2,17 @@ package org.timemates.app.preview.feature.feature.timers import androidx.compose.runtime.Composable import androidx.compose.ui.tooling.preview.Preview -import org.timemates.app.preview.feature.statemachine.fakeStateMachine +import org.timemates.app.preview.feature.mvi.fakeMvi import org.timemates.app.style.system.theme.AppTheme import org.timemates.app.timers.ui.settings.TimerSettingsScreen -import org.timemates.app.timers.ui.settings.mvi.TimerSettingsStateMachine.State +import org.timemates.app.timers.ui.settings.mvi.TimerSettingsScreenComponent.State @Preview @Composable internal fun TimerSettingsScreenPreview() { AppTheme { TimerSettingsScreen( - stateMachine = fakeStateMachine(State()), + mvi = fakeMvi(State()), navigateToTimersScreen = {}, ) } diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/mvi/fakeMviComponent.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/mvi/fakeMviComponent.kt new file mode 100644 index 0000000..8ef3b36 --- /dev/null +++ b/preview/src/main/kotlin/org/timemates/app/preview/feature/mvi/fakeMviComponent.kt @@ -0,0 +1,23 @@ +package org.timemates.app.preview.feature.mvi + +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.ReceiveChannel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import org.timemates.app.foundation.mvi.MVI +import org.timemates.app.foundation.mvi.UiEffect +import org.timemates.app.foundation.mvi.UiEvent +import org.timemates.app.foundation.mvi.UiState + +fun fakeMvi( + state: TState, +): MVI { + return object : MVI { + override val effects: ReceiveChannel = Channel() + override val state: StateFlow = MutableStateFlow(state) + + override fun dispatchEvent(event: TEvent) { + // no-op + } + } +} \ No newline at end of file diff --git a/preview/src/main/kotlin/org/timemates/app/preview/feature/statemachine/fakeStateMachine.kt b/preview/src/main/kotlin/org/timemates/app/preview/feature/statemachine/fakeStateMachine.kt deleted file mode 100644 index e998249..0000000 --- a/preview/src/main/kotlin/org/timemates/app/preview/feature/statemachine/fakeStateMachine.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.timemates.app.preview.feature.statemachine - -import org.timemates.app.foundation.mvi.Reducer -import org.timemates.app.foundation.mvi.ReducerScope -import org.timemates.app.foundation.mvi.StateMachine -import org.timemates.app.foundation.mvi.UiEffect -import org.timemates.app.foundation.mvi.UiEvent -import org.timemates.app.foundation.mvi.UiState - -internal fun fakeStateMachine( - state: TState, -): StateMachine { - val reducer = object : Reducer { - override fun ReducerScope.reduce(state: TState, event: TEvent): TState { - return state - } - } - - return object : StateMachine(state, reducer, listOf()) {} -} \ No newline at end of file diff --git a/style-system/src/commonMain/kotlin/org/timemates/app/style/system/text_field/SizedOutlinedTextField.kt b/style-system/src/commonMain/kotlin/org/timemates/app/style/system/text_field/SizedOutlinedTextField.kt index d422332..eae6542 100644 --- a/style-system/src/commonMain/kotlin/org/timemates/app/style/system/text_field/SizedOutlinedTextField.kt +++ b/style-system/src/commonMain/kotlin/org/timemates/app/style/system/text_field/SizedOutlinedTextField.kt @@ -1,7 +1,6 @@ package org.timemates.app.style.system.text_field import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.defaultMinSize @@ -13,7 +12,6 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextFieldDefaults import androidx.compose.material3.Text import androidx.compose.material3.TextFieldColors -import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @@ -45,7 +43,7 @@ fun SizedOutlinedTextField( maxLines: Int = Int.MAX_VALUE, interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }, shape: Shape = OutlinedTextFieldDefaults.shape, - colors: TextFieldColors = TextFieldDefaults.outlinedTextFieldColors(), + colors: TextFieldColors = OutlinedTextFieldDefaults.colors(), ) { OutlinedTextField( value = value,