Skip to content

Commit

Permalink
Merge pull request #7172 from thundernest/change_server_validation_in…
Browse files Browse the repository at this point in the history
…jection

Change injection for server validation
  • Loading branch information
wmontwe authored Sep 14, 2023
2 parents 9715cfc + 72de4c6 commit 8f91165
Show file tree
Hide file tree
Showing 14 changed files with 363 additions and 99 deletions.
11 changes: 2 additions & 9 deletions app/k9mail/src/test/java/com/fsck/k9/DependencyInjectionTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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>() }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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"
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
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 OutgoingServerValidationViewModel(
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 = false,
),
ServerValidationContract.OutgoingViewModel
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ interface ServerValidationContract {
val oAuthViewModel: AccountOAuthContract.ViewModel
}

interface OutgoingViewModel : ViewModel
interface IncomingViewModel : ViewModel

data class State(
val emailAddress: String? = null,
val serverSettings: ServerSettings? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,26 @@ package app.k9mail.feature.account.server.validation.ui

import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import app.k9mail.core.ui.compose.common.DevicePreviews
import app.k9mail.core.ui.compose.common.mvi.observeWithoutEffect
import app.k9mail.core.ui.compose.designsystem.template.Scaffold
import app.k9mail.core.ui.compose.theme.K9Theme
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
import app.k9mail.feature.account.common.ui.AppTitleTopHeader
import app.k9mail.feature.account.common.ui.WizardNavigationBar
import app.k9mail.feature.account.common.ui.WizardNavigationBarState
import app.k9mail.feature.account.common.ui.preview.PreviewAccountStateRepository
import app.k9mail.feature.account.oauth.ui.preview.PreviewAccountOAuthViewModel
import app.k9mail.feature.account.server.certificate.data.InMemoryServerCertificateErrorRepository
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel
import com.fsck.k9.mail.server.ServerSettingsValidationResult
import app.k9mail.feature.account.server.validation.ui.fake.FakeIncomingServerValidationViewModel
import app.k9mail.feature.account.server.validation.ui.fake.FakeOutgoingServerValidationViewModel

@Composable
internal fun ServerValidationMainScreen(
viewModel: ViewModel,
modifier: Modifier = Modifier,
) {
val state = viewModel.state.collectAsStateWithLifecycle()
val dispatch = { event: Event -> viewModel.event(event) }
val (state, dispatch) = viewModel.observeWithoutEffect()

Scaffold(
topBar = {
Expand Down Expand Up @@ -55,15 +53,8 @@ internal fun ServerValidationMainScreen(
internal fun IncomingServerValidationScreenK9Preview() {
K9Theme {
ServerValidationMainScreen(
viewModel = ServerValidationViewModel(
validateServerSettings = {
ServerSettingsValidationResult.Success
},
accountStateRepository = PreviewAccountStateRepository(),
authorizationStateRepository = { true },
certificateErrorRepository = InMemoryServerCertificateErrorRepository(),
viewModel = FakeIncomingServerValidationViewModel(
oAuthViewModel = PreviewAccountOAuthViewModel(),
isIncomingValidation = true,
),
)
}
Expand All @@ -74,15 +65,8 @@ internal fun IncomingServerValidationScreenK9Preview() {
internal fun IncomingServerValidationScreenThunderbirdPreview() {
ThunderbirdTheme {
ServerValidationMainScreen(
viewModel = ServerValidationViewModel(
validateServerSettings = {
ServerSettingsValidationResult.Success
},
accountStateRepository = PreviewAccountStateRepository(),
authorizationStateRepository = { true },
certificateErrorRepository = InMemoryServerCertificateErrorRepository(),
viewModel = FakeIncomingServerValidationViewModel(
oAuthViewModel = PreviewAccountOAuthViewModel(),
isIncomingValidation = true,
),
)
}
Expand All @@ -93,15 +77,8 @@ internal fun IncomingServerValidationScreenThunderbirdPreview() {
internal fun AccountOutgoingValidationScreenK9Preview() {
K9Theme {
ServerValidationMainScreen(
viewModel = ServerValidationViewModel(
validateServerSettings = {
ServerSettingsValidationResult.Success
},
accountStateRepository = PreviewAccountStateRepository(),
authorizationStateRepository = { true },
certificateErrorRepository = InMemoryServerCertificateErrorRepository(),
viewModel = FakeOutgoingServerValidationViewModel(
oAuthViewModel = PreviewAccountOAuthViewModel(),
isIncomingValidation = false,
),
)
}
Expand All @@ -112,15 +89,8 @@ internal fun AccountOutgoingValidationScreenK9Preview() {
internal fun AccountOutgoingValidationScreenThunderbirdPreview() {
ThunderbirdTheme {
ServerValidationMainScreen(
viewModel = ServerValidationViewModel(
validateServerSettings = {
ServerSettingsValidationResult.Success
},
accountStateRepository = PreviewAccountStateRepository(),
authorizationStateRepository = { true },
certificateErrorRepository = InMemoryServerCertificateErrorRepository(),
viewModel = FakeOutgoingServerValidationViewModel(
oAuthViewModel = PreviewAccountOAuthViewModel(),
isIncomingValidation = false,
),
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package app.k9mail.feature.account.server.validation.ui.fake

import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Effect
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel

class FakeIncomingServerValidationViewModel(
override val oAuthViewModel: AccountOAuthContract.ViewModel = FakeAccountOAuthViewModel(),
override val isIncomingValidation: Boolean = true,
initialState: State = State(),
) : BaseViewModel<State, Event, Effect>(initialState), ServerValidationContract.IncomingViewModel {

val events = mutableListOf<Event>()

override fun event(event: Event) {
events.add(event)
}

fun effect(effect: Effect) {
emitEffect(effect)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package app.k9mail.feature.account.server.validation.ui.fake

import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
import app.k9mail.feature.account.oauth.ui.fake.FakeAccountOAuthViewModel
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Effect
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.Event
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.State
import app.k9mail.feature.account.server.validation.ui.ServerValidationContract.ViewModel

class FakeOutgoingServerValidationViewModel(
override val oAuthViewModel: AccountOAuthContract.ViewModel = FakeAccountOAuthViewModel(),
override val isIncomingValidation: Boolean = true,
initialState: State = State(),
) : BaseViewModel<State, Event, Effect>(initialState), ServerValidationContract.OutgoingViewModel {

val events = mutableListOf<Event>()

override fun event(event: Event) {
events.add(event)
}

fun effect(effect: Effect) {
emitEffect(effect)
}
}
Loading

0 comments on commit 8f91165

Please sign in to comment.