Change AutoDiscovery to use AccountSetupState

This commit is contained in:
Wolf-Martell Montwé 2023-08-02 16:28:48 +02:00
parent cdb7f77d2e
commit a5a8291e55
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
11 changed files with 213 additions and 40 deletions

View file

@ -4,6 +4,7 @@ import app.k9mail.autodiscovery.api.AutoDiscoveryService
import app.k9mail.autodiscovery.service.RealAutoDiscoveryService
import app.k9mail.core.common.coreCommonModule
import app.k9mail.feature.account.oauth.featureAccountOAuthModule
import app.k9mail.feature.account.setup.data.InMemoryAccountSetupStateRepository
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.GetAutoDiscovery
@ -55,6 +56,8 @@ val featureAccountSetupModule: Module = module {
)
}
single<DomainContract.AccountSetupStateRepository> { InMemoryAccountSetupStateRepository() }
factory<DomainContract.UseCase.ValidateServerSettings> { (authStateStorage: AuthStateStorage) ->
ValidateServerSettings(
authStateStorage = authStateStorage,
@ -89,16 +92,16 @@ val featureAccountSetupModule: Module = module {
AccountSetupViewModel(
createAccount = get(),
autoDiscoveryViewModel = get(),
incomingViewModel = get(),
incomingValidationViewModel = get(named(NAME_INCOMING_VALIDATION)) { parametersOf(authStateStorage) },
outgoingViewModel = get(),
outgoingValidationViewModel = get(named(NAME_OUTGOING_VALIDATION)) { parametersOf(authStateStorage) },
optionsViewModel = get(),
authStateStorage = authStateStorage,
accountSetupStateRepository = get(),
)
}
factory<AccountAutoDiscoveryContract.ViewModel> {
viewModel {
AccountAutoDiscoveryViewModel(
validator = get(),
getAutoDiscovery = get(),

View file

@ -0,0 +1,58 @@
package app.k9mail.feature.account.setup.domain
import app.k9mail.autodiscovery.api.ImapServerSettings
import app.k9mail.autodiscovery.api.IncomingServerSettings
import app.k9mail.autodiscovery.api.OutgoingServerSettings
import app.k9mail.autodiscovery.api.SmtpServerSettings
import app.k9mail.feature.account.setup.domain.entity.toAuthType
import app.k9mail.feature.account.setup.domain.entity.toAuthenticationType
import app.k9mail.feature.account.setup.domain.entity.toConnectionSecurity
import app.k9mail.feature.account.setup.domain.entity.toMailConnectionSecurity
import com.fsck.k9.mail.ServerSettings
internal fun IncomingServerSettings.toServerSettings(password: String?): ServerSettings {
return when (this) {
is ImapServerSettings -> this.toImapServerSettings(password)
else -> throw IllegalArgumentException("Unknown server settings type: $this")
}
}
private fun ImapServerSettings.toImapServerSettings(password: String?): ServerSettings {
return ServerSettings(
type = "imap",
host = hostname.value,
port = port.value,
connectionSecurity = connectionSecurity.toConnectionSecurity().toMailConnectionSecurity(),
authenticationType = authenticationTypes.first().toAuthenticationType().toAuthType(),
username = username,
password = password,
clientCertificateAlias = null,
extra = emptyMap(),
)
}
/**
* Convert [OutgoingServerSettings] to [ServerSettings].
*
* @throws IllegalArgumentException if the server settings type is unknown.
*/
internal fun OutgoingServerSettings.toServerSettings(password: String?): ServerSettings {
return when (this) {
is SmtpServerSettings -> this.toSmtpServerSettings(password)
else -> throw IllegalArgumentException("Unknown server settings type: $this")
}
}
private fun SmtpServerSettings.toSmtpServerSettings(password: String?): ServerSettings {
return ServerSettings(
type = "smtp",
host = hostname.value,
port = port.value,
connectionSecurity = connectionSecurity.toConnectionSecurity().toMailConnectionSecurity(),
authenticationType = authenticationTypes.first().toAuthenticationType().toAuthType(),
username = username,
password = password,
clientCertificateAlias = null,
extra = emptyMap(),
)
}

View file

@ -19,7 +19,6 @@ interface AccountSetupContract {
}
interface ViewModel : UnidirectionalViewModel<State, Event, Effect> {
val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel
val incomingViewModel: AccountIncomingConfigContract.ViewModel
val incomingValidationViewModel: AccountValidationContract.ViewModel
val outgoingViewModel: AccountOutgoingConfigContract.ViewModel

View file

@ -8,6 +8,7 @@ import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event
import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep
import app.k9mail.feature.account.setup.ui.AccountSetupContract.ViewModel
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryScreen
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryViewModel
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigScreen
import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen
import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigScreen
@ -40,7 +41,7 @@ fun AccountSetupScreen(
)
},
onBack = { dispatch(Event.OnBack) },
viewModel = viewModel.autoDiscoveryViewModel,
viewModel = koinViewModel<AccountAutoDiscoveryViewModel>(),
)
}

View file

@ -2,15 +2,14 @@ package app.k9mail.feature.account.setup.ui
import androidx.lifecycle.viewModelScope
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
import app.k9mail.feature.account.setup.domain.DomainContract
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect
import app.k9mail.feature.account.setup.ui.AccountSetupContract.Event
import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep
import app.k9mail.feature.account.setup.ui.AccountSetupContract.State
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
import app.k9mail.feature.account.setup.ui.autodiscovery.toIncomingConfigState
import app.k9mail.feature.account.setup.ui.autodiscovery.toOptionsState
import app.k9mail.feature.account.setup.ui.autodiscovery.toOutgoingConfigState
import app.k9mail.feature.account.setup.ui.autodiscovery.toAccountSetupState
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
import app.k9mail.feature.account.setup.ui.incoming.toServerSettings
import app.k9mail.feature.account.setup.ui.incoming.toValidationState
@ -26,26 +25,19 @@ import kotlinx.coroutines.launch
@Suppress("LongParameterList")
class AccountSetupViewModel(
private val createAccount: UseCase.CreateAccount,
override val autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel,
override val incomingViewModel: AccountIncomingConfigContract.ViewModel,
override val incomingValidationViewModel: AccountValidationContract.ViewModel,
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel,
override val outgoingValidationViewModel: AccountValidationContract.ViewModel,
override val optionsViewModel: AccountOptionsContract.ViewModel,
private val authStateStorage: AuthStateStorage,
private val accountSetupStateRepository: DomainContract.AccountSetupStateRepository,
initialState: State = State(),
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
override fun event(event: Event) {
when (event) {
is Event.OnAutoDiscoveryFinished -> {
updateState {
it.copy(
isAutomaticConfig = event.isAutomaticConfig,
)
}
onAutoDiscoveryFinished(event.state)
}
is Event.OnAutoDiscoveryFinished -> onAutoDiscoveryFinished(event.state, event.isAutomaticConfig)
Event.OnBack -> onBack()
Event.OnNext -> onNext()
@ -54,11 +46,17 @@ class AccountSetupViewModel(
private fun onAutoDiscoveryFinished(
autoDiscoveryState: AccountAutoDiscoveryContract.State,
isAutomaticConfig: Boolean,
) {
authStateStorage.updateAuthorizationState(autoDiscoveryState.authorizationState?.state)
incomingViewModel.initState(autoDiscoveryState.toIncomingConfigState())
outgoingViewModel.initState(autoDiscoveryState.toOutgoingConfigState())
optionsViewModel.initState(autoDiscoveryState.toOptionsState())
updateState {
it.copy(
isAutomaticConfig = isAutomaticConfig,
)
}
accountSetupStateRepository.save(autoDiscoveryState.toAccountSetupState())
authStateStorage.updateAuthorizationState(autoDiscoveryState.authorizationState?.state) //TODO use account setup state?
onNext()
}
@ -142,14 +140,15 @@ class AccountSetupViewModel(
}
private fun onFinish() {
val autoDiscoveryState = autoDiscoveryViewModel.state.value
val incomingState = incomingViewModel.state.value
val outgoingState = outgoingViewModel.state.value
val optionsState = optionsViewModel.state.value
val accountSetupState = accountSetupStateRepository.getState()
viewModelScope.launch {
val result = createAccount.execute(
emailAddress = autoDiscoveryState.emailAddress.value,
emailAddress = accountSetupState.emailAddress!!,
incomingServerSettings = incomingState.toServerSettings(),
outgoingServerSettings = outgoingState.toServerSettings(),
authorizationState = authStateStorage.getAuthorizationState(),

View file

@ -36,16 +36,16 @@ interface AccountAutoDiscoveryContract {
val isLoading: Boolean = false,
)
sealed class Event {
data class EmailAddressChanged(val emailAddress: String) : Event()
data class PasswordChanged(val password: String) : Event()
data class ConfigurationApprovalChanged(val confirmed: Boolean) : Event()
data class OnOAuthResult(val result: OAuthResult) : Event()
sealed interface Event {
data class EmailAddressChanged(val emailAddress: String) : Event
data class PasswordChanged(val password: String) : Event
data class ConfigurationApprovalChanged(val confirmed: Boolean) : Event
data class OnOAuthResult(val result: OAuthResult) : Event
object OnNextClicked : Event()
object OnBackClicked : Event()
object OnRetryClicked : Event()
object OnEditConfigurationClicked : Event()
object OnNextClicked : Event
object OnBackClicked : Event
object OnRetryClicked : Event
object OnEditConfigurationClicked : Event
}
sealed class Effect {

View file

@ -2,15 +2,27 @@ package app.k9mail.feature.account.setup.ui.autodiscovery
import app.k9mail.autodiscovery.api.ImapServerSettings
import app.k9mail.autodiscovery.api.SmtpServerSettings
import app.k9mail.feature.account.setup.domain.entity.AccountSetupState
import app.k9mail.feature.account.setup.domain.entity.toAuthenticationType
import app.k9mail.feature.account.setup.domain.entity.toConnectionSecurity
import app.k9mail.feature.account.setup.domain.entity.toIncomingProtocolType
import app.k9mail.feature.account.setup.domain.input.NumberInputField
import app.k9mail.feature.account.setup.domain.input.StringInputField
import app.k9mail.feature.account.setup.domain.toServerSettings
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.outgoing.AccountOutgoingConfigContract
internal fun AccountAutoDiscoveryContract.State.toAccountSetupState(): AccountSetupState {
return AccountSetupState(
emailAddress = emailAddress.value,
incomingServerSettings = autoDiscoverySettings?.incomingServerSettings?.toServerSettings(password.value),
outgoingServerSettings = autoDiscoverySettings?.outgoingServerSettings?.toServerSettings(password.value),
authorizationState = authorizationState,
options = null,
)
}
internal fun AccountAutoDiscoveryContract.State.toIncomingConfigState(): AccountIncomingConfigContract.State {
val incomingSettings = autoDiscoverySettings?.incomingServerSettings as? ImapServerSettings?
return if (incomingSettings == null) {

View file

@ -0,0 +1,94 @@
package app.k9mail.feature.account.setup.domain
import app.k9mail.autodiscovery.api.AuthenticationType
import app.k9mail.autodiscovery.api.ConnectionSecurity
import app.k9mail.autodiscovery.api.ImapServerSettings
import app.k9mail.autodiscovery.api.IncomingServerSettings
import app.k9mail.autodiscovery.api.OutgoingServerSettings
import app.k9mail.autodiscovery.api.SmtpServerSettings
import app.k9mail.core.common.net.Hostname
import app.k9mail.core.common.net.Port
import app.k9mail.feature.account.setup.domain.entity.MailConnectionSecurity
import assertk.assertThat
import assertk.assertions.isEqualTo
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ServerSettings
import kotlin.test.assertFailsWith
import org.junit.Test
class AutoDiscoveryMapperKtTest {
@Test
fun `should map IncomingServerSettings to ServerSettings`() {
val incomingServerSettings = ImapServerSettings(
hostname = Hostname("imap.example.org"),
port = Port(993),
connectionSecurity = ConnectionSecurity.TLS,
authenticationTypes = listOf(AuthenticationType.PasswordCleartext),
username = "user",
)
val password = "password"
val serverSettings = incomingServerSettings.toServerSettings(password)
assertThat(serverSettings).isEqualTo(
ServerSettings(
type = "imap",
host = "imap.example.org",
port = 993,
connectionSecurity = MailConnectionSecurity.SSL_TLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "user",
password = "password",
clientCertificateAlias = null,
extra = emptyMap(),
),
)
}
@Test
fun `should throw error when IncomingServerSettings not known`() {
val incomingServerSettings = object : IncomingServerSettings {}
assertFailsWith(IllegalArgumentException::class) {
incomingServerSettings.toServerSettings("password")
}
}
@Test
fun `should map OutgoingServerSettings to ServerSettings`() {
val outgoingServerSettings = SmtpServerSettings(
hostname = Hostname("smtp.example.org"),
port = Port(587),
connectionSecurity = ConnectionSecurity.StartTLS,
authenticationTypes = listOf(AuthenticationType.PasswordCleartext),
username = "user",
)
val password = "password"
val serverSettings = outgoingServerSettings.toServerSettings(password)
assertThat(serverSettings).isEqualTo(
ServerSettings(
type = "smtp",
host = "smtp.example.org",
port = 587,
connectionSecurity = MailConnectionSecurity.STARTTLS_REQUIRED,
authenticationType = AuthType.PLAIN,
username = "user",
password = "password",
clientCertificateAlias = null,
extra = emptyMap(),
),
)
}
@Test
fun `should throw error when OutgoingServerSettings not known`() {
val outgoingServerSettings = object : OutgoingServerSettings {}
assertFailsWith(IllegalArgumentException::class) {
outgoingServerSettings.toServerSettings("password")
}
}
}

View file

@ -8,6 +8,7 @@ import app.k9mail.core.common.net.toPort
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
import app.k9mail.core.ui.compose.testing.mvi.assertThatAndMviTurbinesConsumed
import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck
import app.k9mail.feature.account.setup.data.InMemoryAccountSetupStateRepository
import app.k9mail.feature.account.setup.domain.entity.AccountOptions
import app.k9mail.feature.account.setup.domain.entity.AuthenticationType
import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity
@ -20,7 +21,6 @@ import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect
import app.k9mail.feature.account.setup.ui.AccountSetupContract.SetupStep
import app.k9mail.feature.account.setup.ui.AccountSetupContract.State
import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract
import app.k9mail.feature.account.setup.ui.autodiscovery.FakeAccountAutoDiscoveryViewModel
import app.k9mail.feature.account.setup.ui.incoming.AccountIncomingConfigContract
import app.k9mail.feature.account.setup.ui.incoming.FakeAccountIncomingConfigViewModel
import app.k9mail.feature.account.setup.ui.incoming.toServerSettings
@ -50,7 +50,6 @@ class AccountSetupViewModelTest {
@get:Rule
val mainDispatcherRule = MainDispatcherRule()
private val autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel()
private val incomingViewModel = FakeAccountIncomingConfigViewModel()
private val incomingValidationViewModel = FakeAccountValidationViewModel()
private val outgoingViewModel = FakeAccountOutgoingConfigViewModel()
@ -75,17 +74,17 @@ class AccountSetupViewModelTest {
"accountUuid"
},
autoDiscoveryViewModel = autoDiscoveryViewModel,
incomingViewModel = incomingViewModel,
incomingValidationViewModel = incomingValidationViewModel,
outgoingViewModel = outgoingViewModel,
outgoingValidationViewModel = outgoingValidationViewModel,
optionsViewModel = optionsViewModel,
authStateStorage = authStateStorage,
accountSetupStateRepository = InMemoryAccountSetupStateRepository(),
)
val turbines = turbinesWithInitialStateCheck(viewModel, State(setupStep = SetupStep.AUTO_CONFIG))
autoDiscoveryViewModel.initState(AUTODISCOVERY_STATE)
// FIXME autoDiscoveryViewModel.initState(AUTODISCOVERY_STATE)
viewModel.event(
AccountSetupContract.Event.OnAutoDiscoveryFinished(
state = AUTODISCOVERY_STATE,
@ -208,13 +207,13 @@ class AccountSetupViewModelTest {
val initialState = State(setupStep = SetupStep.OPTIONS)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> "accountUuid" },
autoDiscoveryViewModel = autoDiscoveryViewModel,
incomingViewModel = FakeAccountIncomingConfigViewModel(),
incomingValidationViewModel = FakeAccountValidationViewModel(),
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
outgoingValidationViewModel = FakeAccountValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
authStateStorage = authStateStorage,
accountSetupStateRepository = InMemoryAccountSetupStateRepository(),
initialState = initialState,
)
val turbines = turbinesWithInitialStateCheck(viewModel, initialState)
@ -264,13 +263,13 @@ class AccountSetupViewModelTest {
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> "accountUuid" },
autoDiscoveryViewModel = autoDiscoveryViewModel,
incomingViewModel = FakeAccountIncomingConfigViewModel(),
incomingValidationViewModel = FakeAccountValidationViewModel(),
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
outgoingValidationViewModel = FakeAccountValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
authStateStorage = authStateStorage,
accountSetupStateRepository = InMemoryAccountSetupStateRepository(),
initialState = initialState,
)
val turbines = turbinesWithInitialStateCheck(viewModel, initialState)
@ -302,13 +301,13 @@ class AccountSetupViewModelTest {
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> "accountUuid" },
autoDiscoveryViewModel = autoDiscoveryViewModel,
incomingViewModel = FakeAccountIncomingConfigViewModel(),
incomingValidationViewModel = FakeAccountValidationViewModel(),
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
outgoingValidationViewModel = FakeAccountValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
authStateStorage = authStateStorage,
accountSetupStateRepository = InMemoryAccountSetupStateRepository(),
initialState = initialState,
)
val turbines = turbinesWithInitialStateCheck(viewModel, initialState)
@ -340,13 +339,13 @@ class AccountSetupViewModelTest {
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> "accountUuid" },
autoDiscoveryViewModel = autoDiscoveryViewModel,
incomingViewModel = FakeAccountIncomingConfigViewModel(),
incomingValidationViewModel = FakeAccountValidationViewModel(),
outgoingViewModel = FakeAccountOutgoingConfigViewModel(),
outgoingValidationViewModel = FakeAccountValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
authStateStorage = authStateStorage,
accountSetupStateRepository = InMemoryAccountSetupStateRepository(),
initialState = initialState,
)
val turbines = turbinesWithInitialStateCheck(viewModel, initialState)

View file

@ -15,7 +15,6 @@ import app.k9mail.feature.account.setup.ui.validation.AccountValidationContract
import app.k9mail.feature.account.setup.ui.validation.FakeAccountValidationViewModel
internal class FakeAccountSetupViewModel(
override val autoDiscoveryViewModel: FakeAccountAutoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel(),
override val incomingViewModel: AccountIncomingConfigContract.ViewModel = FakeAccountIncomingConfigViewModel(),
override val incomingValidationViewModel: AccountValidationContract.ViewModel = FakeAccountValidationViewModel(),
override val outgoingViewModel: AccountOutgoingConfigContract.ViewModel = FakeAccountOutgoingConfigViewModel(),

View file

@ -21,6 +21,15 @@ import org.junit.Test
class AccountAutoDiscoveryStateMapperKtTest {
@Test
fun `should map to empty AccountSetupState when empty`() {
val accountSetupState = EMPTY_STATE.toAccountSetupState()
assertThat(accountSetupState).isEqualTo(
AccountAutoDiscoveryContract.State().toAccountSetupState(),
)
}
@Test
fun `should map to default IncomingConfigState when empty`() {
val incomingConfigState = EMPTY_STATE.toIncomingConfigState()