Add OAuth to auto discovery flow
This commit is contained in:
parent
6ff458da6c
commit
4efbf3d0d9
28 changed files with 267 additions and 79 deletions
|
@ -59,6 +59,8 @@ class AccountCreator(
|
||||||
|
|
||||||
newAccount.outgoingServerSettings = account.outgoingServerSettings
|
newAccount.outgoingServerSettings = account.outgoingServerSettings
|
||||||
|
|
||||||
|
newAccount.oAuthState = account.authorizationState
|
||||||
|
|
||||||
newAccount.name = account.options.accountName
|
newAccount.name = account.options.accountName
|
||||||
newAccount.senderName = account.options.displayName
|
newAccount.senderName = account.options.displayName
|
||||||
if (account.options.emailSignature != null) {
|
if (account.options.emailSignature != null) {
|
||||||
|
|
|
@ -3,7 +3,10 @@ package com.fsck.k9
|
||||||
import android.view.ContextThemeWrapper
|
import android.view.ContextThemeWrapper
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
|
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase.ValidateServerSettings
|
||||||
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||||
import com.fsck.k9.job.MailSyncWorker
|
import com.fsck.k9.job.MailSyncWorker
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
import com.fsck.k9.ui.R
|
import com.fsck.k9.ui.R
|
||||||
import com.fsck.k9.ui.changelog.ChangeLogMode
|
import com.fsck.k9.ui.changelog.ChangeLogMode
|
||||||
import com.fsck.k9.ui.changelog.ChangelogViewModel
|
import com.fsck.k9.ui.changelog.ChangelogViewModel
|
||||||
|
@ -17,6 +20,7 @@ import org.junit.runner.RunWith
|
||||||
import org.koin.core.annotation.KoinInternalApi
|
import org.koin.core.annotation.KoinInternalApi
|
||||||
import org.koin.core.logger.PrintLogger
|
import org.koin.core.logger.PrintLogger
|
||||||
import org.koin.core.parameter.parametersOf
|
import org.koin.core.parameter.parametersOf
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.java.KoinJavaComponent
|
import org.koin.java.KoinJavaComponent
|
||||||
import org.koin.test.AutoCloseKoinTest
|
import org.koin.test.AutoCloseKoinTest
|
||||||
import org.koin.test.check.checkModules
|
import org.koin.test.check.checkModules
|
||||||
|
@ -34,6 +38,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
|
||||||
on { lifecycle } doReturn mock()
|
on { lifecycle } doReturn mock()
|
||||||
}
|
}
|
||||||
private val autocryptTransferView = mock<AutocryptKeyTransferActivity>()
|
private val autocryptTransferView = mock<AutocryptKeyTransferActivity>()
|
||||||
|
private val authStateStorage = mock<AuthStateStorage>()
|
||||||
|
|
||||||
@KoinInternalApi
|
@KoinInternalApi
|
||||||
@Test
|
@Test
|
||||||
|
@ -53,6 +58,9 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
|
||||||
withParameters(clazz = Class.forName("com.fsck.k9.view.K9WebViewClient").kotlin) {
|
withParameters(clazz = Class.forName("com.fsck.k9.view.K9WebViewClient").kotlin) {
|
||||||
parametersOf(null, null)
|
parametersOf(null, null)
|
||||||
}
|
}
|
||||||
|
withParameter<AccountValidationContract.ViewModel>(named("incoming_validation")) { authStateStorage }
|
||||||
|
withParameter<AccountValidationContract.ViewModel>(named("outgoing_validation")) { authStateStorage }
|
||||||
|
withParameter<ValidateServerSettings> { authStateStorage }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,6 +19,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(projects.core.ui.compose.designsystem)
|
implementation(projects.core.ui.compose.designsystem)
|
||||||
implementation(projects.core.common)
|
implementation(projects.core.common)
|
||||||
|
implementation(projects.mail.common)
|
||||||
implementation(projects.feature.account.common)
|
implementation(projects.feature.account.common)
|
||||||
|
|
||||||
implementation(libs.appauth)
|
implementation(libs.appauth)
|
||||||
|
|
|
@ -9,10 +9,10 @@ import app.k9mail.feature.account.oauth.domain.usecase.CheckIsGoogleSignIn
|
||||||
import app.k9mail.feature.account.oauth.domain.usecase.FinishOAuthSignIn
|
import app.k9mail.feature.account.oauth.domain.usecase.FinishOAuthSignIn
|
||||||
import app.k9mail.feature.account.oauth.domain.usecase.GetOAuthRequestIntent
|
import app.k9mail.feature.account.oauth.domain.usecase.GetOAuthRequestIntent
|
||||||
import app.k9mail.feature.account.oauth.domain.usecase.SuggestServerName
|
import app.k9mail.feature.account.oauth.domain.usecase.SuggestServerName
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
import app.k9mail.feature.account.oauth.ui.AccountOAuthViewModel
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthViewModel
|
||||||
import net.openid.appauth.AuthorizationService
|
import net.openid.appauth.AuthorizationService
|
||||||
import org.koin.android.ext.koin.androidApplication
|
import org.koin.android.ext.koin.androidApplication
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
|
@ -48,7 +48,7 @@ val featureAccountOAuthModule: Module = module {
|
||||||
|
|
||||||
factory<UseCase.CheckIsGoogleSignIn> { CheckIsGoogleSignIn() }
|
factory<UseCase.CheckIsGoogleSignIn> { CheckIsGoogleSignIn() }
|
||||||
|
|
||||||
viewModel {
|
factory<AccountOAuthContract.ViewModel> {
|
||||||
AccountOAuthViewModel(
|
AccountOAuthViewModel(
|
||||||
getOAuthRequestIntent = get(),
|
getOAuthRequestIntent = get(),
|
||||||
finishOAuthSignIn = get(),
|
finishOAuthSignIn = get(),
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package app.k9mail.feature.account.oauth.ui
|
package app.k9mail.feature.account.oauth.ui
|
||||||
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
@ -8,16 +9,15 @@ import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.platform.testTag
|
import androidx.compose.ui.platform.testTag
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import app.k9mail.core.ui.compose.common.DevicePreviews
|
import app.k9mail.core.ui.compose.common.DevicePreviews
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.molecule.ErrorView
|
||||||
|
import app.k9mail.core.ui.compose.designsystem.molecule.LoadingView
|
||||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||||
import app.k9mail.feature.account.common.ui.ContentListView
|
|
||||||
import app.k9mail.feature.account.common.ui.item.ErrorItem
|
|
||||||
import app.k9mail.feature.account.common.ui.item.LoadingItem
|
|
||||||
import app.k9mail.feature.account.oauth.R
|
import app.k9mail.feature.account.oauth.R
|
||||||
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Event
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Event
|
||||||
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.State
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.State
|
||||||
import app.k9mail.feature.account.oauth.ui.item.SignInItem
|
import app.k9mail.feature.account.oauth.ui.view.SignInView
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun AccountOAuthContent(
|
internal fun AccountOAuthContent(
|
||||||
|
@ -27,35 +27,29 @@ internal fun AccountOAuthContent(
|
||||||
) {
|
) {
|
||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
|
|
||||||
ContentListView(
|
Column(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.testTag("AccountOAuthContent")
|
.testTag("AccountOAuthContent")
|
||||||
.then(modifier),
|
.then(modifier),
|
||||||
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double, Alignment.CenterVertically),
|
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.double, Alignment.CenterVertically),
|
||||||
) {
|
) {
|
||||||
if (state.isLoading) {
|
if (state.isLoading) {
|
||||||
item(key = "loading") {
|
LoadingView(
|
||||||
LoadingItem(
|
|
||||||
message = stringResource(id = R.string.account_oauth_loading_message),
|
message = stringResource(id = R.string.account_oauth_loading_message),
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else if (state.error != null) {
|
} else if (state.error != null) {
|
||||||
item(key = "error") {
|
ErrorView(
|
||||||
ErrorItem(
|
|
||||||
title = stringResource(id = R.string.account_oauth_loading_error),
|
title = stringResource(id = R.string.account_oauth_loading_error),
|
||||||
message = state.error.toResourceString(resources),
|
message = state.error.toResourceString(resources),
|
||||||
onRetry = { onEvent(Event.OnRetryClicked) },
|
onRetry = { onEvent(Event.OnRetryClicked) },
|
||||||
)
|
)
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
item(key = "sign_in") {
|
SignInView(
|
||||||
SignInItem(
|
|
||||||
onSignInClick = { onEvent(Event.SignInClicked) },
|
onSignInClick = { onEvent(Event.SignInClicked) },
|
||||||
isGoogleSignIn = state.isGoogleSignIn,
|
isGoogleSignIn = state.isGoogleSignIn,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
@ -0,0 +1,14 @@
|
||||||
|
package app.k9mail.feature.account.oauth.ui
|
||||||
|
|
||||||
|
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||||
|
|
||||||
|
// Only used by @DevicePreviews functions
|
||||||
|
class DummyAccountOAuthViewModel :
|
||||||
|
BaseViewModel<AccountOAuthContract.State, AccountOAuthContract.Event, AccountOAuthContract.Effect>(
|
||||||
|
AccountOAuthContract.State(),
|
||||||
|
),
|
||||||
|
AccountOAuthContract.ViewModel {
|
||||||
|
|
||||||
|
override fun initState(state: AccountOAuthContract.State) = Unit
|
||||||
|
override fun event(event: AccountOAuthContract.Event) = Unit
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
package app.k9mail.feature.account.oauth.ui.item
|
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyItemScope
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import app.k9mail.feature.account.common.ui.item.ListItem
|
|
||||||
import app.k9mail.feature.account.oauth.ui.view.SignInView
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun LazyItemScope.SignInItem(
|
|
||||||
onSignInClick: () -> Unit,
|
|
||||||
isGoogleSignIn: Boolean,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
ListItem(
|
|
||||||
modifier = modifier,
|
|
||||||
) {
|
|
||||||
SignInView(
|
|
||||||
onSignInClick = onSignInClick,
|
|
||||||
isGoogleSignIn = isGoogleSignIn,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,7 @@ package app.k9mail.feature.account.setup
|
||||||
import app.k9mail.autodiscovery.api.AutoDiscoveryService
|
import app.k9mail.autodiscovery.api.AutoDiscoveryService
|
||||||
import app.k9mail.autodiscovery.service.RealAutoDiscoveryService
|
import app.k9mail.autodiscovery.service.RealAutoDiscoveryService
|
||||||
import app.k9mail.core.common.coreCommonModule
|
import app.k9mail.core.common.coreCommonModule
|
||||||
|
import app.k9mail.feature.account.oauth.featureAccountOAuthModule
|
||||||
import app.k9mail.feature.account.setup.domain.DomainContract
|
import app.k9mail.feature.account.setup.domain.DomainContract
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.CreateAccount
|
import app.k9mail.feature.account.setup.domain.usecase.CreateAccount
|
||||||
import app.k9mail.feature.account.setup.domain.usecase.GetAutoDiscovery
|
import app.k9mail.feature.account.setup.domain.usecase.GetAutoDiscovery
|
||||||
|
@ -22,17 +23,20 @@ import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigValidat
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigViewModel
|
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigViewModel
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationViewModel
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationViewModel
|
||||||
|
import app.k9mail.feature.account.setup.ui.validation.InMemoryAuthStateStorage
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
import com.fsck.k9.mail.store.imap.ImapServerSettingsValidator
|
import com.fsck.k9.mail.store.imap.ImapServerSettingsValidator
|
||||||
import com.fsck.k9.mail.store.pop3.Pop3ServerSettingsValidator
|
import com.fsck.k9.mail.store.pop3.Pop3ServerSettingsValidator
|
||||||
import com.fsck.k9.mail.transport.smtp.SmtpServerSettingsValidator
|
import com.fsck.k9.mail.transport.smtp.SmtpServerSettingsValidator
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import org.koin.androidx.viewmodel.dsl.viewModel
|
import org.koin.androidx.viewmodel.dsl.viewModel
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.parameter.parametersOf
|
||||||
import org.koin.core.qualifier.named
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
|
|
||||||
val featureAccountSetupModule: Module = module {
|
val featureAccountSetupModule: Module = module {
|
||||||
includes(coreCommonModule)
|
includes(coreCommonModule, featureAccountOAuthModule)
|
||||||
|
|
||||||
single<OkHttpClient> {
|
single<OkHttpClient> {
|
||||||
OkHttpClient()
|
OkHttpClient()
|
||||||
|
@ -51,8 +55,9 @@ val featureAccountSetupModule: Module = module {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
factory<DomainContract.UseCase.ValidateServerSettings> {
|
factory<DomainContract.UseCase.ValidateServerSettings> { (authStateStorage: AuthStateStorage) ->
|
||||||
ValidateServerSettings(
|
ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = ImapServerSettingsValidator(
|
imapValidator = ImapServerSettingsValidator(
|
||||||
trustedSocketFactory = get(),
|
trustedSocketFactory = get(),
|
||||||
oAuth2TokenProviderFactory = get(),
|
oAuth2TokenProviderFactory = get(),
|
||||||
|
@ -80,20 +85,24 @@ val featureAccountSetupModule: Module = module {
|
||||||
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
||||||
|
|
||||||
viewModel {
|
viewModel {
|
||||||
|
val authStateStorage = InMemoryAuthStateStorage()
|
||||||
|
|
||||||
AccountSetupViewModel(
|
AccountSetupViewModel(
|
||||||
createAccount = get(),
|
createAccount = get(),
|
||||||
autoDiscoveryViewModel = get(),
|
autoDiscoveryViewModel = get(),
|
||||||
incomingViewModel = get(),
|
incomingViewModel = get(),
|
||||||
incomingValidationViewModel = get(named(NAME_INCOMING_VALIDATION)),
|
incomingValidationViewModel = get(named(NAME_INCOMING_VALIDATION)) { parametersOf(authStateStorage) },
|
||||||
outgoingViewModel = get(),
|
outgoingViewModel = get(),
|
||||||
outgoingValidationViewModel = get(named(NAME_OUTGOING_VALIDATION)),
|
outgoingValidationViewModel = get(named(NAME_OUTGOING_VALIDATION)) { parametersOf(authStateStorage) },
|
||||||
optionsViewModel = get(),
|
optionsViewModel = get(),
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
factory<AccountAutoDiscoveryContract.ViewModel> {
|
factory<AccountAutoDiscoveryContract.ViewModel> {
|
||||||
AccountAutoDiscoveryViewModel(
|
AccountAutoDiscoveryViewModel(
|
||||||
validator = get(),
|
validator = get(),
|
||||||
getAutoDiscovery = get(),
|
getAutoDiscovery = get(),
|
||||||
|
oAuthViewModel = get(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
factory<AccountIncomingConfigContract.ViewModel> {
|
factory<AccountIncomingConfigContract.ViewModel> {
|
||||||
|
@ -102,8 +111,10 @@ val featureAccountSetupModule: Module = module {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
factory<AccountValidationContract.ViewModel>(named(NAME_INCOMING_VALIDATION)) {
|
factory<AccountValidationContract.ViewModel>(named(NAME_INCOMING_VALIDATION)) {
|
||||||
|
(authStateStorage: AuthStateStorage) ->
|
||||||
|
|
||||||
AccountValidationViewModel(
|
AccountValidationViewModel(
|
||||||
validateServerSettings = get(),
|
validateServerSettings = get { parametersOf(authStateStorage) },
|
||||||
initialState = AccountValidationContract.State(
|
initialState = AccountValidationContract.State(
|
||||||
isIncomingValidation = true,
|
isIncomingValidation = true,
|
||||||
),
|
),
|
||||||
|
@ -115,8 +126,10 @@ val featureAccountSetupModule: Module = module {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
factory<AccountValidationContract.ViewModel>(named(NAME_OUTGOING_VALIDATION)) {
|
factory<AccountValidationContract.ViewModel>(named(NAME_OUTGOING_VALIDATION)) {
|
||||||
|
(authStateStorage: AuthStateStorage) ->
|
||||||
|
|
||||||
AccountValidationViewModel(
|
AccountValidationViewModel(
|
||||||
validateServerSettings = get(),
|
validateServerSettings = get { parametersOf(authStateStorage) },
|
||||||
initialState = AccountValidationContract.State(
|
initialState = AccountValidationContract.State(
|
||||||
isIncomingValidation = false,
|
isIncomingValidation = false,
|
||||||
),
|
),
|
||||||
|
|
|
@ -22,6 +22,7 @@ interface DomainContract {
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
incomingServerSettings: ServerSettings,
|
incomingServerSettings: ServerSettings,
|
||||||
outgoingServerSettings: ServerSettings,
|
outgoingServerSettings: ServerSettings,
|
||||||
|
authorizationState: String?,
|
||||||
options: AccountOptions,
|
options: AccountOptions,
|
||||||
): String
|
): String
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,5 +6,6 @@ data class Account(
|
||||||
val emailAddress: String,
|
val emailAddress: String,
|
||||||
val incomingServerSettings: ServerSettings,
|
val incomingServerSettings: ServerSettings,
|
||||||
val outgoingServerSettings: ServerSettings,
|
val outgoingServerSettings: ServerSettings,
|
||||||
|
val authorizationState: String?,
|
||||||
val options: AccountOptions,
|
val options: AccountOptions,
|
||||||
)
|
)
|
||||||
|
|
|
@ -14,12 +14,14 @@ class CreateAccount(
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
incomingServerSettings: ServerSettings,
|
incomingServerSettings: ServerSettings,
|
||||||
outgoingServerSettings: ServerSettings,
|
outgoingServerSettings: ServerSettings,
|
||||||
|
authorizationState: String?,
|
||||||
options: AccountOptions,
|
options: AccountOptions,
|
||||||
): String {
|
): String {
|
||||||
val account = Account(
|
val account = Account(
|
||||||
emailAddress = emailAddress,
|
emailAddress = emailAddress,
|
||||||
incomingServerSettings = incomingServerSettings,
|
incomingServerSettings = incomingServerSettings,
|
||||||
outgoingServerSettings = outgoingServerSettings,
|
outgoingServerSettings = outgoingServerSettings,
|
||||||
|
authorizationState = authorizationState,
|
||||||
options = options,
|
options = options,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ package app.k9mail.feature.account.setup.domain.usecase
|
||||||
|
|
||||||
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
|
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
|
||||||
import com.fsck.k9.mail.ServerSettings
|
import com.fsck.k9.mail.ServerSettings
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||||
import com.fsck.k9.mail.server.ServerSettingsValidator
|
import com.fsck.k9.mail.server.ServerSettingsValidator
|
||||||
import kotlinx.coroutines.CoroutineDispatcher
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
@ -9,20 +10,22 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
internal class ValidateServerSettings(
|
internal class ValidateServerSettings(
|
||||||
|
private val authStateStorage: AuthStateStorage,
|
||||||
private val imapValidator: ServerSettingsValidator,
|
private val imapValidator: ServerSettingsValidator,
|
||||||
private val pop3Validator: ServerSettingsValidator,
|
private val pop3Validator: ServerSettingsValidator,
|
||||||
private val smtpValidator: ServerSettingsValidator,
|
private val smtpValidator: ServerSettingsValidator,
|
||||||
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
|
||||||
) : UseCase.ValidateServerSettings {
|
) : UseCase.ValidateServerSettings {
|
||||||
override suspend fun execute(settings: ServerSettings): ServerSettingsValidationResult =
|
override suspend fun execute(settings: ServerSettings): ServerSettingsValidationResult {
|
||||||
withContext(coroutineDispatcher) {
|
return withContext(coroutineDispatcher) {
|
||||||
return@withContext when (settings.type) {
|
when (settings.type) {
|
||||||
"imap" -> imapValidator.checkServerSettings(settings, authStateStorage = null)
|
"imap" -> imapValidator.checkServerSettings(settings, authStateStorage)
|
||||||
"pop3" -> pop3Validator.checkServerSettings(settings, authStateStorage = null)
|
"pop3" -> pop3Validator.checkServerSettings(settings, authStateStorage)
|
||||||
"smtp" -> smtpValidator.checkServerSettings(settings, authStateStorage = null)
|
"smtp" -> smtpValidator.checkServerSettings(settings, authStateStorage)
|
||||||
else -> {
|
else -> {
|
||||||
throw IllegalArgumentException("Unsupported server type: ${settings.type}")
|
throw IllegalArgumentException("Unsupported server type: ${settings.type}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,8 +20,10 @@ import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContrac
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
class AccountSetupViewModel(
|
class AccountSetupViewModel(
|
||||||
private val createAccount: UseCase.CreateAccount,
|
private val createAccount: UseCase.CreateAccount,
|
||||||
override val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel,
|
override val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel,
|
||||||
|
@ -30,6 +32,7 @@ class AccountSetupViewModel(
|
||||||
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel,
|
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel,
|
||||||
override val outgoingValidationViewModel: AccountValidationContract.ViewModel,
|
override val outgoingValidationViewModel: AccountValidationContract.ViewModel,
|
||||||
override val optionsViewModel: AccountOptionsContract.ViewModel,
|
override val optionsViewModel: AccountOptionsContract.ViewModel,
|
||||||
|
private val authStateStorage: AuthStateStorage,
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
|
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
|
||||||
|
|
||||||
|
@ -52,6 +55,7 @@ class AccountSetupViewModel(
|
||||||
private fun onAutoDiscoveryFinished(
|
private fun onAutoDiscoveryFinished(
|
||||||
autoDiscoveryState: AccountAutoDiscoveryContract.State,
|
autoDiscoveryState: AccountAutoDiscoveryContract.State,
|
||||||
) {
|
) {
|
||||||
|
authStateStorage.updateAuthorizationState(autoDiscoveryState.authorizationState?.state)
|
||||||
incomingViewModel.initState(autoDiscoveryState.toIncomingConfigState())
|
incomingViewModel.initState(autoDiscoveryState.toIncomingConfigState())
|
||||||
outgoingViewModel.initState(autoDiscoveryState.toOutgoingConfigState())
|
outgoingViewModel.initState(autoDiscoveryState.toOutgoingConfigState())
|
||||||
optionsViewModel.initState(autoDiscoveryState.toOptionsState())
|
optionsViewModel.initState(autoDiscoveryState.toOptionsState())
|
||||||
|
@ -122,6 +126,10 @@ class AccountSetupViewModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun changeToSetupStep(setupStep: SetupStep) {
|
private fun changeToSetupStep(setupStep: SetupStep) {
|
||||||
|
if (setupStep == SetupStep.AUTO_CONFIG) {
|
||||||
|
authStateStorage.updateAuthorizationState(authorizationState = null)
|
||||||
|
}
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
setupStep = setupStep,
|
setupStep = setupStep,
|
||||||
|
@ -140,6 +148,7 @@ class AccountSetupViewModel(
|
||||||
emailAddress = autoDiscoveryState.emailAddress.value,
|
emailAddress = autoDiscoveryState.emailAddress.value,
|
||||||
incomingServerSettings = incomingState.toServerSettings(),
|
incomingServerSettings = incomingState.toServerSettings(),
|
||||||
outgoingServerSettings = outgoingState.toServerSettings(),
|
outgoingServerSettings = outgoingState.toServerSettings(),
|
||||||
|
authorizationState = authStateStorage.getAuthorizationState(),
|
||||||
options = optionsState.toAccountOptions(),
|
options = optionsState.toAccountOptions(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ import app.k9mail.core.ui.compose.theme.MainTheme
|
||||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||||
import app.k9mail.feature.account.common.ui.item.ErrorItem
|
import app.k9mail.feature.account.common.ui.item.ErrorItem
|
||||||
import app.k9mail.feature.account.common.ui.item.LoadingItem
|
import app.k9mail.feature.account.common.ui.item.LoadingItem
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
|
import app.k9mail.feature.account.oauth.ui.DummyAccountOAuthViewModel
|
||||||
import app.k9mail.feature.account.setup.R
|
import app.k9mail.feature.account.setup.R
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
||||||
|
@ -29,6 +31,7 @@ import app.k9mail.feature.account.setup.ui.autodiscovery.item.contentItems
|
||||||
internal fun AccountAutoDiscoveryContent(
|
internal fun AccountAutoDiscoveryContent(
|
||||||
state: State,
|
state: State,
|
||||||
onEvent: (Event) -> Unit,
|
onEvent: (Event) -> Unit,
|
||||||
|
oAuthViewModel: AccountOAuthContract.ViewModel,
|
||||||
contentPadding: PaddingValues,
|
contentPadding: PaddingValues,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
|
@ -65,6 +68,7 @@ internal fun AccountAutoDiscoveryContent(
|
||||||
contentItems(
|
contentItems(
|
||||||
state = state,
|
state = state,
|
||||||
onEvent = onEvent,
|
onEvent = onEvent,
|
||||||
|
oAuthViewModel = oAuthViewModel,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,6 +83,7 @@ internal fun AccountAutoDiscoveryContentK9Preview() {
|
||||||
state = State(),
|
state = State(),
|
||||||
onEvent = {},
|
onEvent = {},
|
||||||
contentPadding = PaddingValues(),
|
contentPadding = PaddingValues(),
|
||||||
|
oAuthViewModel = DummyAccountOAuthViewModel(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -91,6 +96,7 @@ internal fun AccountAutoDiscoveryContentThunderbirdPreview() {
|
||||||
state = State(),
|
state = State(),
|
||||||
onEvent = {},
|
onEvent = {},
|
||||||
contentPadding = PaddingValues(),
|
contentPadding = PaddingValues(),
|
||||||
|
oAuthViewModel = DummyAccountOAuthViewModel(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,9 @@ package app.k9mail.feature.account.setup.ui.autodiscovery
|
||||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
||||||
|
import app.k9mail.feature.account.oauth.domain.entity.AuthorizationState
|
||||||
|
import app.k9mail.feature.account.oauth.domain.entity.OAuthResult
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
import app.k9mail.feature.account.setup.domain.input.BooleanInputField
|
import app.k9mail.feature.account.setup.domain.input.BooleanInputField
|
||||||
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
||||||
|
|
||||||
|
@ -15,6 +18,8 @@ interface AccountAutoDiscoveryContract {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect> {
|
interface ViewModel : UnidirectionalViewModel<State, Event, Effect> {
|
||||||
|
val oAuthViewModel: AccountOAuthContract.ViewModel
|
||||||
|
|
||||||
fun initState(state: State)
|
fun initState(state: State)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +29,9 @@ interface AccountAutoDiscoveryContract {
|
||||||
val password: StringInputField = StringInputField(),
|
val password: StringInputField = StringInputField(),
|
||||||
val autoDiscoverySettings: AutoDiscoveryResult.Settings? = null,
|
val autoDiscoverySettings: AutoDiscoveryResult.Settings? = null,
|
||||||
val configurationApproved: BooleanInputField = BooleanInputField(),
|
val configurationApproved: BooleanInputField = BooleanInputField(),
|
||||||
|
val authorizationState: AuthorizationState? = null,
|
||||||
|
|
||||||
|
val isSuccess: Boolean = false,
|
||||||
val error: Error? = null,
|
val error: Error? = null,
|
||||||
val isLoading: Boolean = false,
|
val isLoading: Boolean = false,
|
||||||
)
|
)
|
||||||
|
@ -32,6 +40,7 @@ interface AccountAutoDiscoveryContract {
|
||||||
data class EmailAddressChanged(val emailAddress: String) : Event()
|
data class EmailAddressChanged(val emailAddress: String) : Event()
|
||||||
data class PasswordChanged(val password: String) : Event()
|
data class PasswordChanged(val password: String) : Event()
|
||||||
data class ConfigurationApprovalChanged(val confirmed: Boolean) : Event()
|
data class ConfigurationApprovalChanged(val confirmed: Boolean) : Event()
|
||||||
|
data class OnOAuthResult(val result: OAuthResult) : Event()
|
||||||
|
|
||||||
object OnNextClicked : Event()
|
object OnNextClicked : Event()
|
||||||
object OnBackClicked : Event()
|
object OnBackClicked : Event()
|
||||||
|
@ -40,9 +49,8 @@ interface AccountAutoDiscoveryContract {
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class Effect {
|
sealed class Effect {
|
||||||
data class NavigateNext(
|
data class NavigateNext(val isAutomaticConfig: Boolean) : Effect()
|
||||||
val isAutomaticConfig: Boolean,
|
|
||||||
) : Effect()
|
|
||||||
object NavigateBack : Effect()
|
object NavigateBack : Effect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import app.k9mail.core.ui.compose.theme.K9Theme
|
||||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||||
import app.k9mail.feature.account.common.ui.AppTitleTopHeader
|
import app.k9mail.feature.account.common.ui.AppTitleTopHeader
|
||||||
import app.k9mail.feature.account.common.ui.WizardNavigationBar
|
import app.k9mail.feature.account.common.ui.WizardNavigationBar
|
||||||
|
import app.k9mail.feature.account.oauth.ui.DummyAccountOAuthViewModel
|
||||||
import app.k9mail.feature.account.setup.R
|
import app.k9mail.feature.account.setup.R
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
||||||
|
@ -55,6 +56,7 @@ internal fun AccountAutoDiscoveryScreen(
|
||||||
AccountAutoDiscoveryContent(
|
AccountAutoDiscoveryContent(
|
||||||
state = state.value,
|
state = state.value,
|
||||||
onEvent = { dispatch(it) },
|
onEvent = { dispatch(it) },
|
||||||
|
oAuthViewModel = viewModel.oAuthViewModel,
|
||||||
contentPadding = innerPadding,
|
contentPadding = innerPadding,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -70,6 +72,7 @@ internal fun AccountAutoDiscoveryScreenK9Preview() {
|
||||||
viewModel = AccountAutoDiscoveryViewModel(
|
viewModel = AccountAutoDiscoveryViewModel(
|
||||||
validator = AccountAutoDiscoveryValidator(),
|
validator = AccountAutoDiscoveryValidator(),
|
||||||
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
||||||
|
oAuthViewModel = DummyAccountOAuthViewModel(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -85,6 +88,7 @@ internal fun AccountAutoDiscoveryScreenThunderbirdPreview() {
|
||||||
viewModel = AccountAutoDiscoveryViewModel(
|
viewModel = AccountAutoDiscoveryViewModel(
|
||||||
validator = AccountAutoDiscoveryValidator(),
|
validator = AccountAutoDiscoveryValidator(),
|
||||||
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
||||||
|
oAuthViewModel = DummyAccountOAuthViewModel(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,13 @@ package app.k9mail.feature.account.setup.ui.autodiscovery
|
||||||
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||||
|
import app.k9mail.autodiscovery.api.ImapServerSettings
|
||||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||||
|
import app.k9mail.feature.account.oauth.domain.entity.OAuthResult
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
|
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
|
||||||
|
import app.k9mail.feature.account.setup.domain.entity.AutoDiscoveryAuthenticationType
|
||||||
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
import app.k9mail.feature.account.setup.domain.input.StringInputField
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ConfigStep
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ConfigStep
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
||||||
|
@ -12,7 +16,6 @@ import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryCon
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Validator
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Validator
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ViewModel
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
@Suppress("TooManyFunctions")
|
@Suppress("TooManyFunctions")
|
||||||
|
@ -20,7 +23,8 @@ internal class AccountAutoDiscoveryViewModel(
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
private val validator: Validator,
|
private val validator: Validator,
|
||||||
private val getAutoDiscovery: UseCase.GetAutoDiscovery,
|
private val getAutoDiscovery: UseCase.GetAutoDiscovery,
|
||||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
override val oAuthViewModel: AccountOAuthContract.ViewModel,
|
||||||
|
) : BaseViewModel<State, Event, Effect>(initialState), AccountAutoDiscoveryContract.ViewModel {
|
||||||
|
|
||||||
override fun initState(state: State) {
|
override fun initState(state: State) {
|
||||||
updateState {
|
updateState {
|
||||||
|
@ -33,6 +37,7 @@ internal class AccountAutoDiscoveryViewModel(
|
||||||
is Event.EmailAddressChanged -> changeEmailAddress(event.emailAddress)
|
is Event.EmailAddressChanged -> changeEmailAddress(event.emailAddress)
|
||||||
is Event.PasswordChanged -> changePassword(event.password)
|
is Event.PasswordChanged -> changePassword(event.password)
|
||||||
is Event.ConfigurationApprovalChanged -> changeConfigurationApproval(event.confirmed)
|
is Event.ConfigurationApprovalChanged -> changeConfigurationApproval(event.confirmed)
|
||||||
|
is Event.OnOAuthResult -> onOAuthResult(event.result)
|
||||||
|
|
||||||
Event.OnNextClicked -> onNext()
|
Event.OnNextClicked -> onNext()
|
||||||
Event.OnBackClicked -> onBack()
|
Event.OnBackClicked -> onBack()
|
||||||
|
@ -120,7 +125,7 @@ internal class AccountAutoDiscoveryViewModel(
|
||||||
|
|
||||||
val result = getAutoDiscovery.execute(state.value.emailAddress.value)
|
val result = getAutoDiscovery.execute(state.value.emailAddress.value)
|
||||||
when (result) {
|
when (result) {
|
||||||
AutoDiscoveryResult.NoUsableSettingsFound -> updateAutoDiscoverySettings(null)
|
AutoDiscoveryResult.NoUsableSettingsFound -> updateNoSettingsFound()
|
||||||
is AutoDiscoveryResult.Settings -> updateAutoDiscoverySettings(result)
|
is AutoDiscoveryResult.Settings -> updateAutoDiscoverySettings(result)
|
||||||
is AutoDiscoveryResult.NetworkError -> updateError(Error.NetworkError)
|
is AutoDiscoveryResult.NetworkError -> updateError(Error.NetworkError)
|
||||||
is AutoDiscoveryResult.UnexpectedException -> updateError(Error.UnknownError)
|
is AutoDiscoveryResult.UnexpectedException -> updateError(Error.UnknownError)
|
||||||
|
@ -128,12 +133,36 @@ internal class AccountAutoDiscoveryViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateAutoDiscoverySettings(settings: AutoDiscoveryResult.Settings?) {
|
private fun updateNoSettingsFound() {
|
||||||
|
updateState {
|
||||||
|
it.copy(
|
||||||
|
isLoading = false,
|
||||||
|
autoDiscoverySettings = null,
|
||||||
|
configStep = ConfigStep.PASSWORD,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateAutoDiscoverySettings(settings: AutoDiscoveryResult.Settings) {
|
||||||
|
val imapServerSettings = settings.incomingServerSettings as ImapServerSettings
|
||||||
|
val isOAuth = imapServerSettings.authenticationTypes.first() == AutoDiscoveryAuthenticationType.OAuth2
|
||||||
|
|
||||||
|
if (isOAuth) {
|
||||||
|
oAuthViewModel.initState(
|
||||||
|
AccountOAuthContract.State(
|
||||||
|
hostname = imapServerSettings.hostname.value,
|
||||||
|
emailAddress = state.value.emailAddress.value,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: disable next button if isOAuth = true
|
||||||
|
|
||||||
updateState {
|
updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
isLoading = false,
|
isLoading = false,
|
||||||
autoDiscoverySettings = settings,
|
autoDiscoverySettings = settings,
|
||||||
configStep = ConfigStep.PASSWORD, // TODO use oauth if applicable
|
configStep = if (isOAuth) ConfigStep.OAUTH else ConfigStep.PASSWORD,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,14 +218,28 @@ internal class AccountAutoDiscoveryViewModel(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigStep.PASSWORD -> updateState {
|
ConfigStep.OAUTH,
|
||||||
|
ConfigStep.PASSWORD,
|
||||||
|
-> updateState {
|
||||||
it.copy(
|
it.copy(
|
||||||
configStep = ConfigStep.EMAIL_ADDRESS,
|
configStep = ConfigStep.EMAIL_ADDRESS,
|
||||||
password = StringInputField(),
|
password = StringInputField(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ConfigStep.OAUTH -> TODO()
|
private fun onOAuthResult(result: OAuthResult) {
|
||||||
|
if (result is OAuthResult.Success) {
|
||||||
|
updateState {
|
||||||
|
it.copy(authorizationState = result.authorizationState)
|
||||||
|
}
|
||||||
|
|
||||||
|
navigateNext(isAutomaticConfig = true)
|
||||||
|
} else {
|
||||||
|
updateState {
|
||||||
|
it.copy(authorizationState = null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package app.k9mail.feature.account.setup.ui.autodiscovery.item
|
package app.k9mail.feature.account.setup.ui.autodiscovery.item
|
||||||
|
|
||||||
import androidx.compose.foundation.lazy.LazyListScope
|
import androidx.compose.foundation.lazy.LazyListScope
|
||||||
|
import app.k9mail.feature.account.common.ui.item.ListItem
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthView
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ConfigStep
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ConfigStep
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
||||||
|
@ -8,8 +11,9 @@ import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryCon
|
||||||
internal fun LazyListScope.contentItems(
|
internal fun LazyListScope.contentItems(
|
||||||
state: State,
|
state: State,
|
||||||
onEvent: (Event) -> Unit,
|
onEvent: (Event) -> Unit,
|
||||||
|
oAuthViewModel: AccountOAuthContract.ViewModel,
|
||||||
) {
|
) {
|
||||||
if (state.configStep == ConfigStep.PASSWORD) {
|
if (state.configStep != ConfigStep.EMAIL_ADDRESS) {
|
||||||
item(key = "autodiscovery") {
|
item(key = "autodiscovery") {
|
||||||
AutoDiscoveryStatusItem(
|
AutoDiscoveryStatusItem(
|
||||||
autoDiscoverySettings = state.autoDiscoverySettings,
|
autoDiscoverySettings = state.autoDiscoverySettings,
|
||||||
|
@ -43,5 +47,14 @@ internal fun LazyListScope.contentItems(
|
||||||
onPasswordChange = { onEvent(Event.PasswordChanged(it)) },
|
onPasswordChange = { onEvent(Event.PasswordChanged(it)) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
} else if (state.configStep == ConfigStep.OAUTH) {
|
||||||
|
item(key = "oauth") {
|
||||||
|
ListItem {
|
||||||
|
AccountOAuthView(
|
||||||
|
onOAuthResult = { result -> onEvent(Event.OnOAuthResult(result)) },
|
||||||
|
viewModel = oAuthViewModel,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.Error
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.Error
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.Event
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.Event
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.State
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.State
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract.ViewModel
|
|
||||||
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
import com.fsck.k9.mail.server.ServerSettingsValidationResult
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
@ -17,7 +16,7 @@ private const val CONTINUE_NEXT_DELAY = 2000L
|
||||||
internal class AccountValidationViewModel(
|
internal class AccountValidationViewModel(
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
private val validateServerSettings: DomainContract.UseCase.ValidateServerSettings,
|
private val validateServerSettings: DomainContract.UseCase.ValidateServerSettings,
|
||||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
) : BaseViewModel<State, Event, Effect>(initialState), AccountValidationContract.ViewModel {
|
||||||
|
|
||||||
override fun initState(state: State) {
|
override fun initState(state: State) {
|
||||||
updateState {
|
updateState {
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package app.k9mail.feature.account.setup.ui.validation
|
||||||
|
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
|
|
||||||
|
class InMemoryAuthStateStorage : AuthStateStorage {
|
||||||
|
private var authorizationState: String? = null
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun getAuthorizationState(): String? {
|
||||||
|
return authorizationState
|
||||||
|
}
|
||||||
|
|
||||||
|
@Synchronized
|
||||||
|
override fun updateAuthorizationState(authorizationState: String?) {
|
||||||
|
this.authorizationState = authorizationState
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,19 @@
|
||||||
package app.k9mail.feature.account.setup
|
package app.k9mail.feature.account.setup
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
|
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator
|
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator
|
||||||
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
|
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
|
||||||
|
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase.ValidateServerSettings
|
||||||
import app.k9mail.feature.account.setup.ui.AccountSetupContract
|
import app.k9mail.feature.account.setup.ui.AccountSetupContract
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
|
||||||
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
|
||||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract
|
||||||
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
|
||||||
|
import app.k9mail.feature.account.setup.ui.validation.InMemoryAuthStateStorage
|
||||||
|
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||||
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
import com.fsck.k9.mail.oauth.OAuth2TokenProviderFactory
|
||||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||||
|
@ -18,6 +23,7 @@ import org.junit.runner.RunWith
|
||||||
import org.koin.android.ext.koin.androidContext
|
import org.koin.android.ext.koin.androidContext
|
||||||
import org.koin.core.annotation.KoinExperimentalAPI
|
import org.koin.core.annotation.KoinExperimentalAPI
|
||||||
import org.koin.core.module.Module
|
import org.koin.core.module.Module
|
||||||
|
import org.koin.core.qualifier.named
|
||||||
import org.koin.dsl.koinApplication
|
import org.koin.dsl.koinApplication
|
||||||
import org.koin.dsl.module
|
import org.koin.dsl.module
|
||||||
import org.koin.test.KoinTest
|
import org.koin.test.KoinTest
|
||||||
|
@ -55,17 +61,26 @@ class AccountSetupModuleKtTest : KoinTest {
|
||||||
extraTypes = listOf(
|
extraTypes = listOf(
|
||||||
AccountSetupContract.State::class,
|
AccountSetupContract.State::class,
|
||||||
AccountAutoDiscoveryContract.State::class,
|
AccountAutoDiscoveryContract.State::class,
|
||||||
|
AccountOAuthContract.State::class,
|
||||||
AccountValidationContract.State::class,
|
AccountValidationContract.State::class,
|
||||||
AccountIncomingConfigContract.State::class,
|
AccountIncomingConfigContract.State::class,
|
||||||
AccountOutgoingConfigContract.State::class,
|
AccountOutgoingConfigContract.State::class,
|
||||||
AccountOptionsContract.State::class,
|
AccountOptionsContract.State::class,
|
||||||
|
AuthStateStorage::class,
|
||||||
|
Context::class,
|
||||||
|
Class.forName("net.openid.appauth.AppAuthConfiguration").kotlin,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
koinApplication {
|
koinApplication {
|
||||||
modules(externalModule, featureAccountSetupModule)
|
modules(externalModule, featureAccountSetupModule)
|
||||||
androidContext(RuntimeEnvironment.getApplication())
|
androidContext(RuntimeEnvironment.getApplication())
|
||||||
checkModules()
|
checkModules {
|
||||||
|
val authStateStorage = InMemoryAuthStateStorage()
|
||||||
|
withParameter<AccountValidationContract.ViewModel>(named(NAME_INCOMING_VALIDATION)) { authStateStorage }
|
||||||
|
withParameter<AccountValidationContract.ViewModel>(named(NAME_OUTGOING_VALIDATION)) { authStateStorage }
|
||||||
|
withParameter<ValidateServerSettings> { authStateStorage }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,7 @@ class CreateAccountTest {
|
||||||
password = "password",
|
password = "password",
|
||||||
clientCertificateAlias = null,
|
clientCertificateAlias = null,
|
||||||
)
|
)
|
||||||
|
val authorizationState = "authorization state"
|
||||||
val options = AccountOptions(
|
val options = AccountOptions(
|
||||||
accountName = "accountName",
|
accountName = "accountName",
|
||||||
displayName = "displayName",
|
displayName = "displayName",
|
||||||
|
@ -53,7 +54,13 @@ class CreateAccountTest {
|
||||||
showNotification = true,
|
showNotification = true,
|
||||||
)
|
)
|
||||||
|
|
||||||
val result = createAccount.execute(emailAddress, incomingServerSettings, outgoingServerSettings, options)
|
val result = createAccount.execute(
|
||||||
|
emailAddress,
|
||||||
|
incomingServerSettings,
|
||||||
|
outgoingServerSettings,
|
||||||
|
authorizationState,
|
||||||
|
options,
|
||||||
|
)
|
||||||
|
|
||||||
assertThat(result).isEqualTo("uuid")
|
assertThat(result).isEqualTo("uuid")
|
||||||
assertThat(recordedAccount).isEqualTo(
|
assertThat(recordedAccount).isEqualTo(
|
||||||
|
@ -61,6 +68,7 @@ class CreateAccountTest {
|
||||||
emailAddress = emailAddress,
|
emailAddress = emailAddress,
|
||||||
incomingServerSettings = incomingServerSettings,
|
incomingServerSettings = incomingServerSettings,
|
||||||
outgoingServerSettings = outgoingServerSettings,
|
outgoingServerSettings = outgoingServerSettings,
|
||||||
|
authorizationState = authorizationState,
|
||||||
options = options,
|
options = options,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package app.k9mail.feature.account.setup.domain.usecase
|
package app.k9mail.feature.account.setup.domain.usecase
|
||||||
|
|
||||||
|
import app.k9mail.feature.account.setup.ui.validation.InMemoryAuthStateStorage
|
||||||
import assertk.assertThat
|
import assertk.assertThat
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
import com.fsck.k9.mail.AuthType
|
import com.fsck.k9.mail.AuthType
|
||||||
|
@ -11,10 +12,12 @@ import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
class ValidateServerSettingsTest {
|
class ValidateServerSettingsTest {
|
||||||
|
private val authStateStorage = InMemoryAuthStateStorage()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should check with imap validator when protocol is imap`() = runTest {
|
fun `should check with imap validator when protocol is imap`() = runTest {
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
imapValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||||
|
@ -29,6 +32,7 @@ class ValidateServerSettingsTest {
|
||||||
fun `should check with imap validator when protocol is imap and return failure`() = runTest {
|
fun `should check with imap validator when protocol is imap and return failure`() = runTest {
|
||||||
val failure = ServerSettingsValidationResult.ServerError("Failed")
|
val failure = ServerSettingsValidationResult.ServerError("Failed")
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> failure },
|
imapValidator = { _, _ -> failure },
|
||||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||||
|
@ -42,6 +46,7 @@ class ValidateServerSettingsTest {
|
||||||
@Test
|
@Test
|
||||||
fun `should check with pop3 validator when protocol is pop3`() = runTest {
|
fun `should check with pop3 validator when protocol is pop3`() = runTest {
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.Success },
|
pop3Validator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||||
|
@ -56,6 +61,7 @@ class ValidateServerSettingsTest {
|
||||||
fun `should check with pop3 validator when protocol is pop3 and return failure`() = runTest {
|
fun `should check with pop3 validator when protocol is pop3 and return failure`() = runTest {
|
||||||
val failure = ServerSettingsValidationResult.ServerError("Failed POP3")
|
val failure = ServerSettingsValidationResult.ServerError("Failed POP3")
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||||
pop3Validator = { _, _ -> failure },
|
pop3Validator = { _, _ -> failure },
|
||||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
smtpValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed SMTP")) },
|
||||||
|
@ -69,6 +75,7 @@ class ValidateServerSettingsTest {
|
||||||
@Test
|
@Test
|
||||||
fun `should check with smtp validator when protocol is smtp`() = runTest {
|
fun `should check with smtp validator when protocol is smtp`() = runTest {
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||||
smtpValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
smtpValidator = { _, _ -> ServerSettingsValidationResult.Success },
|
||||||
|
@ -83,6 +90,7 @@ class ValidateServerSettingsTest {
|
||||||
fun `should check with smtp validator when protocol is smtp and return failure`() = runTest {
|
fun `should check with smtp validator when protocol is smtp and return failure`() = runTest {
|
||||||
val failure = ServerSettingsValidationResult.ServerError("Failed SMTP")
|
val failure = ServerSettingsValidationResult.ServerError("Failed SMTP")
|
||||||
val testSubject = ValidateServerSettings(
|
val testSubject = ValidateServerSettings(
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
imapValidator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed IMAP")) },
|
||||||
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
pop3Validator = { _, _ -> ServerSettingsValidationResult.NetworkError(IOException("Failed POP3")) },
|
||||||
smtpValidator = { _, _ -> failure },
|
smtpValidator = { _, _ -> failure },
|
||||||
|
|
|
@ -31,9 +31,11 @@ import app.k9mail.feature.account.setup.ui.outgoing.FakeAccountOutgoingConfigVie
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
import app.k9mail.feature.account.setup.ui.outgoing.toServerSettings
|
||||||
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
import app.k9mail.feature.account.setup.ui.outgoing.toValidationState
|
||||||
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
|
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
|
||||||
|
import app.k9mail.feature.account.setup.ui.validation.InMemoryAuthStateStorage
|
||||||
import assertk.assertThat
|
import assertk.assertThat
|
||||||
import assertk.assertions.assertThatAndTurbinesConsumed
|
import assertk.assertions.assertThatAndTurbinesConsumed
|
||||||
import assertk.assertions.isEqualTo
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isNull
|
||||||
import assertk.assertions.prop
|
import assertk.assertions.prop
|
||||||
import com.fsck.k9.mail.ServerSettings
|
import com.fsck.k9.mail.ServerSettings
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
|
@ -54,18 +56,21 @@ class AccountSetupViewModelTest {
|
||||||
private val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
private val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
|
||||||
private val outgoingValidationViewModel = FakeAccountValidationViewModel()
|
private val outgoingValidationViewModel = FakeAccountValidationViewModel()
|
||||||
private val optionsViewModel = FakeAccountOptionsViewModel()
|
private val optionsViewModel = FakeAccountOptionsViewModel()
|
||||||
|
private val authStateStorage = InMemoryAuthStateStorage()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `should forward step state on next event`() = runTest {
|
fun `should forward step state on next event`() = runTest {
|
||||||
var createAccountEmailAddress: String? = null
|
var createAccountEmailAddress: String? = null
|
||||||
var createAccountIncomingServerSettings: ServerSettings? = null
|
var createAccountIncomingServerSettings: ServerSettings? = null
|
||||||
var createAccountOutgoingServerSettings: ServerSettings? = null
|
var createAccountOutgoingServerSettings: ServerSettings? = null
|
||||||
|
var createAccountAuthorizationState: String? = null
|
||||||
var createAccountOptions: AccountOptions? = null
|
var createAccountOptions: AccountOptions? = null
|
||||||
val viewModel = AccountSetupViewModel(
|
val viewModel = AccountSetupViewModel(
|
||||||
createAccount = { emailAddress, incomingServerSettings, outgoingServerSettings, options ->
|
createAccount = { emailAddress, incomingServerSettings, outgoingServerSettings, authState, options ->
|
||||||
createAccountEmailAddress = emailAddress
|
createAccountEmailAddress = emailAddress
|
||||||
createAccountIncomingServerSettings = incomingServerSettings
|
createAccountIncomingServerSettings = incomingServerSettings
|
||||||
createAccountOutgoingServerSettings = outgoingServerSettings
|
createAccountOutgoingServerSettings = outgoingServerSettings
|
||||||
|
createAccountAuthorizationState = authState
|
||||||
createAccountOptions = options
|
createAccountOptions = options
|
||||||
|
|
||||||
"accountUuid"
|
"accountUuid"
|
||||||
|
@ -76,6 +81,7 @@ class AccountSetupViewModelTest {
|
||||||
outgoingViewModel = outgoingViewModel,
|
outgoingViewModel = outgoingViewModel,
|
||||||
outgoingValidationViewModel = outgoingValidationViewModel,
|
outgoingValidationViewModel = outgoingValidationViewModel,
|
||||||
optionsViewModel = optionsViewModel,
|
optionsViewModel = optionsViewModel,
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
)
|
)
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||||
|
@ -194,6 +200,7 @@ class AccountSetupViewModelTest {
|
||||||
assertThat(createAccountEmailAddress).isEqualTo(EMAIL_ADDRESS)
|
assertThat(createAccountEmailAddress).isEqualTo(EMAIL_ADDRESS)
|
||||||
assertThat(createAccountIncomingServerSettings).isEqualTo(expectedIncomingConfigState.toServerSettings())
|
assertThat(createAccountIncomingServerSettings).isEqualTo(expectedIncomingConfigState.toServerSettings())
|
||||||
assertThat(createAccountOutgoingServerSettings).isEqualTo(expectedOutgoingConfigState.toServerSettings())
|
assertThat(createAccountOutgoingServerSettings).isEqualTo(expectedOutgoingConfigState.toServerSettings())
|
||||||
|
assertThat(createAccountAuthorizationState).isNull()
|
||||||
assertThat(createAccountOptions).isEqualTo(
|
assertThat(createAccountOptions).isEqualTo(
|
||||||
AccountOptions(
|
AccountOptions(
|
||||||
accountName = "account name",
|
accountName = "account name",
|
||||||
|
@ -210,13 +217,14 @@ class AccountSetupViewModelTest {
|
||||||
fun `should rewind step state on back event`() = runTest {
|
fun `should rewind step state on back event`() = runTest {
|
||||||
val initialState = State(setupStep = SetupStep.OPTIONS)
|
val initialState = State(setupStep = SetupStep.OPTIONS)
|
||||||
val viewModel = AccountSetupViewModel(
|
val viewModel = AccountSetupViewModel(
|
||||||
createAccount = { _, _, _, _ -> "accountUuid" },
|
createAccount = { _, _, _, _, _ -> "accountUuid" },
|
||||||
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
autoDiscoveryViewModel = autoDiscoveryViewModel,
|
||||||
incomingViewModel = FakeAccountIncomingConfigViewModel(),
|
incomingViewModel = FakeAccountIncomingConfigViewModel(),
|
||||||
incomingValidationViewModel = FakeAccountValidationViewModel(),
|
incomingValidationViewModel = FakeAccountValidationViewModel(),
|
||||||
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
|
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
|
||||||
outgoingValidationViewModel = FakeAccountValidationViewModel(),
|
outgoingValidationViewModel = FakeAccountValidationViewModel(),
|
||||||
optionsViewModel = FakeAccountOptionsViewModel(),
|
optionsViewModel = FakeAccountOptionsViewModel(),
|
||||||
|
authStateStorage = authStateStorage,
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
)
|
)
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
|
|
|
@ -33,6 +33,7 @@ class AccountAutoDiscoveryViewModelTest {
|
||||||
delay(50)
|
delay(50)
|
||||||
AutoDiscoveryResult.NoUsableSettingsFound
|
AutoDiscoveryResult.NoUsableSettingsFound
|
||||||
},
|
},
|
||||||
|
oAuthViewModel = FakeAccountOAuthViewModel(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -97,6 +98,7 @@ class AccountAutoDiscoveryViewModelTest {
|
||||||
delay(50)
|
delay(50)
|
||||||
autoDiscoverySettings
|
autoDiscoverySettings
|
||||||
},
|
},
|
||||||
|
oAuthViewModel = FakeAccountOAuthViewModel(),
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
)
|
)
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
|
@ -153,6 +155,7 @@ class AccountAutoDiscoveryViewModelTest {
|
||||||
delay(50)
|
delay(50)
|
||||||
AutoDiscoveryResult.UnexpectedException(discoveryError)
|
AutoDiscoveryResult.UnexpectedException(discoveryError)
|
||||||
},
|
},
|
||||||
|
oAuthViewModel = FakeAccountOAuthViewModel(),
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
)
|
)
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
|
@ -234,6 +237,7 @@ class AccountAutoDiscoveryViewModelTest {
|
||||||
emailAddressAnswer = ValidationResult.Failure(TestError),
|
emailAddressAnswer = ValidationResult.Failure(TestError),
|
||||||
),
|
),
|
||||||
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
||||||
|
oAuthViewModel = FakeAccountOAuthViewModel(),
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -317,6 +321,7 @@ class AccountAutoDiscoveryViewModelTest {
|
||||||
passwordAnswer = ValidationResult.Failure(TestError),
|
passwordAnswer = ValidationResult.Failure(TestError),
|
||||||
),
|
),
|
||||||
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
getAutoDiscovery = { AutoDiscoveryResult.NoUsableSettingsFound },
|
||||||
|
oAuthViewModel = FakeAccountOAuthViewModel(),
|
||||||
initialState = initialState,
|
initialState = initialState,
|
||||||
)
|
)
|
||||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
package app.k9mail.feature.account.setup.ui.autodiscovery
|
package app.k9mail.feature.account.setup.ui.autodiscovery
|
||||||
|
|
||||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Effect
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.Event
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State
|
||||||
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ViewModel
|
|
||||||
|
|
||||||
class FakeAccountAutoDiscoveryViewModel(
|
class FakeAccountAutoDiscoveryViewModel(
|
||||||
initialState: State = State(),
|
initialState: State = State(),
|
||||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
) : BaseViewModel<State, Event, Effect>(initialState), AccountAutoDiscoveryContract.ViewModel {
|
||||||
|
|
||||||
val events = mutableListOf<Event>()
|
val events = mutableListOf<Event>()
|
||||||
|
|
||||||
|
override val oAuthViewModel: AccountOAuthContract.ViewModel = FakeAccountOAuthViewModel()
|
||||||
|
|
||||||
override fun initState(state: State) {
|
override fun initState(state: State) {
|
||||||
updateState { state }
|
updateState { state }
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
package app.k9mail.feature.account.setup.ui.autodiscovery
|
||||||
|
|
||||||
|
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Effect
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.Event
|
||||||
|
import app.k9mail.feature.account.oauth.ui.AccountOAuthContract.State
|
||||||
|
|
||||||
|
internal class FakeAccountOAuthViewModel(
|
||||||
|
initialState: State = State(),
|
||||||
|
) : BaseViewModel<State, Event, Effect>(initialState), AccountOAuthContract.ViewModel {
|
||||||
|
|
||||||
|
val events = mutableListOf<Event>()
|
||||||
|
|
||||||
|
override fun initState(state: State) {
|
||||||
|
updateState { state }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun event(event: Event) {
|
||||||
|
events.add(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun effect(effect: Effect) {
|
||||||
|
emitEffect(effect)
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ data class ServerSettings @JvmOverloads constructor(
|
||||||
) {
|
) {
|
||||||
val isMissingCredentials: Boolean = when (authenticationType) {
|
val isMissingCredentials: Boolean = when (authenticationType) {
|
||||||
AuthType.EXTERNAL -> clientCertificateAlias == null
|
AuthType.EXTERNAL -> clientCertificateAlias == null
|
||||||
|
AuthType.XOAUTH2 -> username.isBlank()
|
||||||
else -> username.isNotBlank() && password == null
|
else -> username.isNotBlank() && password == null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue