Loading app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt +2 −9 Original line number Diff line number Diff line Loading @@ -3,8 +3,6 @@ package com.fsck.k9 import android.view.ContextThemeWrapper import androidx.lifecycle.LifecycleOwner import androidx.work.WorkerParameters import app.k9mail.feature.account.server.validation.KOIN_NAME_INCOMING_SERVER_VALIDATION import app.k9mail.feature.account.server.validation.KOIN_NAME_OUTGOING_SERVER_VALIDATION import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.ui.ServerValidationContract import com.fsck.k9.account.AccountRemoverWorker Loading @@ -23,7 +21,6 @@ import org.junit.runner.RunWith import org.koin.core.annotation.KoinInternalApi import org.koin.core.logger.PrintLogger import org.koin.core.parameter.parametersOf import org.koin.core.qualifier.named import org.koin.java.KoinJavaComponent import org.koin.test.AutoCloseKoinTest import org.koin.test.check.checkModules Loading Loading @@ -61,12 +58,8 @@ class DependencyInjectionTest : AutoCloseKoinTest() { withParameters(clazz = Class.forName("com.fsck.k9.view.K9WebViewClient").kotlin) { parametersOf(null, null) } withParameter<ServerValidationContract.ViewModel>( named(KOIN_NAME_INCOMING_SERVER_VALIDATION), ) { authStateStorage } withParameter<ServerValidationContract.ViewModel>( named(KOIN_NAME_OUTGOING_SERVER_VALIDATION), ) { authStateStorage } withParameter<ServerValidationContract.IncomingViewModel> { authStateStorage } withParameter<ServerValidationContract.OutgoingViewModel> { authStateStorage } withParameter<ServerValidationDomainContract.UseCase.ValidateServerSettings> { authStateStorage } withParameter<AccountRemoverWorker> { mock<WorkerParameters>() } } Loading core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt +42 −0 Original line number Diff line number Diff line Loading @@ -102,3 +102,45 @@ inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, dispatch = dispatch, ) } /** * Composable function that observes a UnidirectionalViewModel without handling side effects. * * Example usage: * ``` * @Composable * fun MyScreen( * viewModel: MyUnidirectionalViewModel<MyState, MyEvent, MyEffect>, * onNavigateNext: () -> Unit, * onNavigateBack: () -> Unit, * ) { * val (state, dispatch) = viewModel.observeWithoutEffect() * * MyContent( * onNextClick = { * dispatch(MyEvent.OnNext) * }, * onBackClick = { * dispatch(MyEvent.OnBack) * }, * state = state.value, * ) * } * ``` * * @param STATE The type that represents the state of the ViewModel. * @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. * * @return A [StateDispatch] containing the state and a dispatch function. */ @Suppress("MaxLineLength") @Composable inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, EFFECT>.observeWithoutEffect(): StateDispatch<STATE, EVENT> { val collectedState = state.collectAsStateWithLifecycle() val dispatch: (EVENT) -> Unit = { event(it) } return StateDispatch( state = collectedState, dispatch = dispatch, ) } feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt +6 −11 Original line number Diff line number Diff line Loading @@ -6,12 +6,12 @@ import app.k9mail.feature.account.oauth.featureAccountOAuthModule import app.k9mail.feature.account.server.certificate.featureAccountServerCertificateModule import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.domain.usecase.ValidateServerSettings import app.k9mail.feature.account.server.validation.ui.ServerValidationViewModel import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel import com.fsck.k9.mail.store.imap.ImapServerSettingsValidator import com.fsck.k9.mail.store.pop3.Pop3ServerSettingsValidator import com.fsck.k9.mail.transport.smtp.SmtpServerSettingsValidator import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.qualifier.named import org.koin.dsl.module val featureAccountServerValidationModule = module { Loading Loading @@ -40,28 +40,23 @@ val featureAccountServerValidationModule = module { ) } viewModel(named(KOIN_NAME_INCOMING_SERVER_VALIDATION)) { ServerValidationViewModel( viewModel { IncomingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), isIncomingValidation = true, ) } viewModel(named(KOIN_NAME_OUTGOING_SERVER_VALIDATION)) { ServerValidationViewModel( viewModel { OutgoingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), isIncomingValidation = false, ) } } const val KOIN_NAME_INCOMING_SERVER_VALIDATION = "incoming_server_validation" const val KOIN_NAME_OUTGOING_SERVER_VALIDATION = "outgoing_server_validation" feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModel.kt→feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import kotlinx.coroutines.launch private const val CONTINUE_NEXT_DELAY = 2000L @Suppress("TooManyFunctions") class ServerValidationViewModel( abstract class BaseServerValidationViewModel( private val accountStateRepository: AccountDomainContract.AccountStateRepository, private val validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, private val authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, Loading feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt 0 → 100644 +25 −0 Original line number Diff line number Diff line package app.k9mail.feature.account.server.validation.ui import app.k9mail.feature.account.common.domain.AccountDomainContract import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract import app.k9mail.feature.account.oauth.ui.AccountOAuthContract import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract.UseCase class IncomingServerValidationViewModel( accountStateRepository: AccountDomainContract.AccountStateRepository, validateServerSettings: UseCase.ValidateServerSettings, authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, oAuthViewModel: AccountOAuthContract.ViewModel, initialState: ServerValidationContract.State? = null, ) : BaseServerValidationViewModel( accountStateRepository = accountStateRepository, validateServerSettings = validateServerSettings, authorizationStateRepository = authorizationStateRepository, certificateErrorRepository = certificateErrorRepository, oAuthViewModel = oAuthViewModel, initialState = initialState, isIncomingValidation = true, ), ServerValidationContract.IncomingViewModel Loading
app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt +2 −9 Original line number Diff line number Diff line Loading @@ -3,8 +3,6 @@ package com.fsck.k9 import android.view.ContextThemeWrapper import androidx.lifecycle.LifecycleOwner import androidx.work.WorkerParameters import app.k9mail.feature.account.server.validation.KOIN_NAME_INCOMING_SERVER_VALIDATION import app.k9mail.feature.account.server.validation.KOIN_NAME_OUTGOING_SERVER_VALIDATION import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.ui.ServerValidationContract import com.fsck.k9.account.AccountRemoverWorker Loading @@ -23,7 +21,6 @@ import org.junit.runner.RunWith import org.koin.core.annotation.KoinInternalApi import org.koin.core.logger.PrintLogger import org.koin.core.parameter.parametersOf import org.koin.core.qualifier.named import org.koin.java.KoinJavaComponent import org.koin.test.AutoCloseKoinTest import org.koin.test.check.checkModules Loading Loading @@ -61,12 +58,8 @@ class DependencyInjectionTest : AutoCloseKoinTest() { withParameters(clazz = Class.forName("com.fsck.k9.view.K9WebViewClient").kotlin) { parametersOf(null, null) } withParameter<ServerValidationContract.ViewModel>( named(KOIN_NAME_INCOMING_SERVER_VALIDATION), ) { authStateStorage } withParameter<ServerValidationContract.ViewModel>( named(KOIN_NAME_OUTGOING_SERVER_VALIDATION), ) { authStateStorage } withParameter<ServerValidationContract.IncomingViewModel> { authStateStorage } withParameter<ServerValidationContract.OutgoingViewModel> { authStateStorage } withParameter<ServerValidationDomainContract.UseCase.ValidateServerSettings> { authStateStorage } withParameter<AccountRemoverWorker> { mock<WorkerParameters>() } } Loading
core/ui/compose/common/src/main/kotlin/app/k9mail/core/ui/compose/common/mvi/UnidirectionalViewModel.kt +42 −0 Original line number Diff line number Diff line Loading @@ -102,3 +102,45 @@ inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, dispatch = dispatch, ) } /** * Composable function that observes a UnidirectionalViewModel without handling side effects. * * Example usage: * ``` * @Composable * fun MyScreen( * viewModel: MyUnidirectionalViewModel<MyState, MyEvent, MyEffect>, * onNavigateNext: () -> Unit, * onNavigateBack: () -> Unit, * ) { * val (state, dispatch) = viewModel.observeWithoutEffect() * * MyContent( * onNextClick = { * dispatch(MyEvent.OnNext) * }, * onBackClick = { * dispatch(MyEvent.OnBack) * }, * state = state.value, * ) * } * ``` * * @param STATE The type that represents the state of the ViewModel. * @param EVENT The type that represents user actions that can occur and should be handled by the ViewModel. * * @return A [StateDispatch] containing the state and a dispatch function. */ @Suppress("MaxLineLength") @Composable inline fun <reified STATE, EVENT, EFFECT> UnidirectionalViewModel<STATE, EVENT, EFFECT>.observeWithoutEffect(): StateDispatch<STATE, EVENT> { val collectedState = state.collectAsStateWithLifecycle() val dispatch: (EVENT) -> Unit = { event(it) } return StateDispatch( state = collectedState, dispatch = dispatch, ) }
feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ServerValidationModule.kt +6 −11 Original line number Diff line number Diff line Loading @@ -6,12 +6,12 @@ import app.k9mail.feature.account.oauth.featureAccountOAuthModule import app.k9mail.feature.account.server.certificate.featureAccountServerCertificateModule import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract import app.k9mail.feature.account.server.validation.domain.usecase.ValidateServerSettings import app.k9mail.feature.account.server.validation.ui.ServerValidationViewModel import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel import com.fsck.k9.mail.store.imap.ImapServerSettingsValidator import com.fsck.k9.mail.store.pop3.Pop3ServerSettingsValidator import com.fsck.k9.mail.transport.smtp.SmtpServerSettingsValidator import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.core.qualifier.named import org.koin.dsl.module val featureAccountServerValidationModule = module { Loading Loading @@ -40,28 +40,23 @@ val featureAccountServerValidationModule = module { ) } viewModel(named(KOIN_NAME_INCOMING_SERVER_VALIDATION)) { ServerValidationViewModel( viewModel { IncomingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), isIncomingValidation = true, ) } viewModel(named(KOIN_NAME_OUTGOING_SERVER_VALIDATION)) { ServerValidationViewModel( viewModel { OutgoingServerValidationViewModel( validateServerSettings = get(), accountStateRepository = get(), authorizationStateRepository = get(), certificateErrorRepository = get(), oAuthViewModel = get(), isIncomingValidation = false, ) } } const val KOIN_NAME_INCOMING_SERVER_VALIDATION = "incoming_server_validation" const val KOIN_NAME_OUTGOING_SERVER_VALIDATION = "outgoing_server_validation"
feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/ServerValidationViewModel.kt→feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/BaseServerValidationViewModel.kt +1 −1 Original line number Diff line number Diff line Loading @@ -22,7 +22,7 @@ import kotlinx.coroutines.launch private const val CONTINUE_NEXT_DELAY = 2000L @Suppress("TooManyFunctions") class ServerValidationViewModel( abstract class BaseServerValidationViewModel( private val accountStateRepository: AccountDomainContract.AccountStateRepository, private val validateServerSettings: ServerValidationDomainContract.UseCase.ValidateServerSettings, private val authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, Loading
feature/account/server/validation/src/main/kotlin/app/k9mail/feature/account/server/validation/ui/IncomingServerValidationViewModel.kt 0 → 100644 +25 −0 Original line number Diff line number Diff line package app.k9mail.feature.account.server.validation.ui import app.k9mail.feature.account.common.domain.AccountDomainContract import app.k9mail.feature.account.oauth.domain.AccountOAuthDomainContract import app.k9mail.feature.account.oauth.ui.AccountOAuthContract import app.k9mail.feature.account.server.certificate.domain.ServerCertificateDomainContract import app.k9mail.feature.account.server.validation.domain.ServerValidationDomainContract.UseCase class IncomingServerValidationViewModel( accountStateRepository: AccountDomainContract.AccountStateRepository, validateServerSettings: UseCase.ValidateServerSettings, authorizationStateRepository: AccountOAuthDomainContract.AuthorizationStateRepository, certificateErrorRepository: ServerCertificateDomainContract.ServerCertificateErrorRepository, oAuthViewModel: AccountOAuthContract.ViewModel, initialState: ServerValidationContract.State? = null, ) : BaseServerValidationViewModel( accountStateRepository = accountStateRepository, validateServerSettings = validateServerSettings, authorizationStateRepository = authorizationStateRepository, certificateErrorRepository = certificateErrorRepository, oAuthViewModel = oAuthViewModel, initialState = initialState, isIncomingValidation = true, ), ServerValidationContract.IncomingViewModel