From 45f52bd68e7e24b5c26e9f609a2e6f461e01eb2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Thu, 15 Jun 2023 14:41:59 +0200 Subject: [PATCH 1/3] Add events to propagate finished states --- .../account/setup/ui/AccountSetupContract.kt | 22 +++++++++++++++++++ .../account/setup/ui/AccountSetupScreen.kt | 7 ++++-- .../account/setup/ui/AccountSetupViewModel.kt | 7 ++++++ .../AccountAutoDiscoveryScreen.kt | 5 +++-- .../setup/ui/AccountSetupViewModelTest.kt | 11 ++++------ 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt index 9043235db..19e8c10e2 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt @@ -1,6 +1,10 @@ package app.k9mail.feature.account.setup.ui import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel +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.options.AccountOptionsContract +import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract interface AccountSetupContract { @@ -19,10 +23,28 @@ interface AccountSetupContract { sealed interface Event { object OnNext : Event + + data class OnAutoDiscoveryFinished( + val state: AccountAutoDiscoveryContract.State, + ) : Event + object OnBack : Event } sealed interface Effect { + + data class UpdateIncomingConfig( + val state: AccountIncomingConfigContract.State, + ) : Effect + + data class UpdateOutgoingConfig( + val state: AccountOutgoingConfigContract.State, + ) : Effect + + data class UpdateOptions( + val state: AccountOptionsContract.State, + ) : Effect + object NavigateNext : Effect object NavigateBack : Effect } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt index 6d3c00457..e24e675c3 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt @@ -33,15 +33,18 @@ fun AccountSetupScreen( ) { val (state, dispatch) = viewModel.observe { effect -> when (effect) { - Effect.NavigateBack -> onBack() + is Effect.UpdateIncomingConfig -> incomingViewModel.initState(effect.state) + is Effect.UpdateOutgoingConfig -> outgoingViewModel.initState(effect.state) + is Effect.UpdateOptions -> optionsViewModel.initState(effect.state) Effect.NavigateNext -> onFinish() + Effect.NavigateBack -> onBack() } } when (state.value.setupStep) { SetupStep.AUTO_CONFIG -> { AccountAutoDiscoveryScreen( - onNext = { dispatch(Event.OnNext) }, + onNext = { dispatch(Event.OnAutoDiscoveryFinished(it)) }, onBack = { dispatch(Event.OnBack) }, viewModel = autoDiscoveryViewModel, ) diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt index 7fa573d8d..6bcb2ba75 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt @@ -6,6 +6,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.State import app.k9mail.feature.account.setup.ui.AccountSetupContract.ViewModel +import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract class AccountSetupViewModel( initialState: State = State(), @@ -13,11 +14,17 @@ class AccountSetupViewModel( override fun event(event: Event) { when (event) { + is Event.OnAutoDiscoveryFinished -> handleAutoDiscoveryFinished(event.state) Event.OnBack -> onBack() Event.OnNext -> onNext() } } + private fun handleAutoDiscoveryFinished(autoDiscoveryState: AccountAutoDiscoveryContract.State) { + // TODO emmit effect to update ViewModels + onNext() + } + private fun onBack() { when (state.value.setupStep) { SetupStep.AUTO_CONFIG -> navigateBack() diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryScreen.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryScreen.kt index 570d1a86e..a1eb1b18a 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryScreen.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/autodiscovery/AccountAutoDiscoveryScreen.kt @@ -13,13 +13,14 @@ import app.k9mail.core.ui.compose.theme.ThunderbirdTheme 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.Event +import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.State import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract.ViewModel import app.k9mail.feature.account.setup.ui.common.AccountSetupBottomBar import app.k9mail.feature.account.setup.ui.common.AccountSetupTopHeader @Composable internal fun AccountAutoDiscoveryScreen( - onNext: () -> Unit, + onNext: (State) -> Unit, onBack: () -> Unit, viewModel: ViewModel, modifier: Modifier = Modifier, @@ -27,7 +28,7 @@ internal fun AccountAutoDiscoveryScreen( val (state, dispatch) = viewModel.observe { effect -> when (effect) { Effect.NavigateBack -> onBack() - Effect.NavigateNext -> onNext() + Effect.NavigateNext -> onNext(viewModel.state.value) } } diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt index e245e1ca0..73214abf6 100644 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt @@ -2,19 +2,16 @@ package app.k9mail.feature.account.setup.ui import app.cash.turbine.testIn import app.k9mail.core.ui.compose.testing.MainDispatcherRule -import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect.NavigateBack -import app.k9mail.feature.account.setup.ui.AccountSetupContract.Effect.NavigateNext +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 assertk.assertions.assertThatAndTurbinesConsumed import assertk.assertions.isEqualTo import assertk.assertions.prop -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class AccountSetupViewModelTest { @get:Rule @@ -35,7 +32,7 @@ class AccountSetupViewModelTest { prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) } - viewModel.event(AccountSetupContract.Event.OnNext) + viewModel.event(AccountSetupContract.Event.OnAutoDiscoveryFinished(AccountAutoDiscoveryContract.State())) assertThatAndTurbinesConsumed( actual = stateTurbine.awaitItem(), @@ -68,7 +65,7 @@ class AccountSetupViewModelTest { actual = effectTurbine.awaitItem(), turbines = turbines, ) { - isEqualTo(NavigateNext) + isEqualTo(Effect.NavigateNext) } } @@ -121,7 +118,7 @@ class AccountSetupViewModelTest { actual = effectTurbine.awaitItem(), turbines = turbines, ) { - isEqualTo(NavigateBack) + isEqualTo(Effect.NavigateBack) } } } From d2685dbedefef5a0d01374e9a41d9d56c85b300b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Fri, 16 Jun 2023 12:35:25 +0200 Subject: [PATCH 2/3] Add AutoDiscovery IncomingServerSettings and ConnectionSecurity mapping --- .../entity/AutoDiscoveryAuthenticationType.kt | 11 ++++++++ .../entity/AutoDiscoveryConnectionSecurity.kt | 7 ++++++ .../entity/IncomingServerSettingsExtension.kt | 11 ++++++++ .../AutoDiscoveryAuthenticationTypeKtTest.kt | 25 +++++++++++++++++++ .../AutoDiscoveryConnectionSecurityKtTest.kt | 24 ++++++++++++++++++ .../IncomingServerSettingsExtensionKtTest.kt | 25 +++++++++++++++++++ 6 files changed, 103 insertions(+) create mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationType.kt create mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtension.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationTypeKtTest.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurityKtTest.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtensionKtTest.kt diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationType.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationType.kt new file mode 100644 index 000000000..a6c2e0be7 --- /dev/null +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationType.kt @@ -0,0 +1,11 @@ +package app.k9mail.feature.account.setup.domain.entity + +typealias AutoDiscoveryAuthenticationType = app.k9mail.autodiscovery.api.AuthenticationType + +internal fun AutoDiscoveryAuthenticationType.toAuthenticationType(): AuthenticationType { + return when (this) { + AutoDiscoveryAuthenticationType.PasswordCleartext -> AuthenticationType.PasswordCleartext + AutoDiscoveryAuthenticationType.PasswordEncrypted -> AuthenticationType.PasswordEncrypted + AutoDiscoveryAuthenticationType.OAuth2 -> AuthenticationType.OAuth2 + } +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurity.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurity.kt index 56eac2603..840558284 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurity.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurity.kt @@ -1,3 +1,10 @@ package app.k9mail.feature.account.setup.domain.entity internal typealias AutoDiscoveryConnectionSecurity = app.k9mail.autodiscovery.api.ConnectionSecurity + +internal fun AutoDiscoveryConnectionSecurity.toConnectionSecurity(): ConnectionSecurity { + return when (this) { + AutoDiscoveryConnectionSecurity.StartTLS -> ConnectionSecurity.StartTLS + AutoDiscoveryConnectionSecurity.TLS -> ConnectionSecurity.TLS + } +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtension.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtension.kt new file mode 100644 index 000000000..f45819e1a --- /dev/null +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtension.kt @@ -0,0 +1,11 @@ +package app.k9mail.feature.account.setup.domain.entity + +import app.k9mail.autodiscovery.api.ImapServerSettings +import app.k9mail.autodiscovery.api.IncomingServerSettings + +internal fun IncomingServerSettings.toIncomingProtocolType(): IncomingProtocolType { + when (this) { + is ImapServerSettings -> return IncomingProtocolType.IMAP + else -> throw IllegalArgumentException("Unsupported incoming server settings type: $this") + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationTypeKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationTypeKtTest.kt new file mode 100644 index 000000000..9f71df260 --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryAuthenticationTypeKtTest.kt @@ -0,0 +1,25 @@ +package app.k9mail.feature.account.setup.domain.entity + +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +class AutoDiscoveryAuthenticationTypeKtTest { + + @Test + fun `should map all AutoDiscoveryAuthenticationTypes`() { + val types = AutoDiscoveryAuthenticationType.values() + + for (type in types) { + val authenticationType = type.toAuthenticationType() + + assertThat(authenticationType).isEqualTo( + when (type) { + AutoDiscoveryAuthenticationType.PasswordCleartext -> AuthenticationType.PasswordCleartext + AutoDiscoveryAuthenticationType.PasswordEncrypted -> AuthenticationType.PasswordEncrypted + AutoDiscoveryAuthenticationType.OAuth2 -> AuthenticationType.OAuth2 + }, + ) + } + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurityKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurityKtTest.kt new file mode 100644 index 000000000..0901951aa --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/AutoDiscoveryConnectionSecurityKtTest.kt @@ -0,0 +1,24 @@ +package app.k9mail.feature.account.setup.domain.entity + +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +class AutoDiscoveryConnectionSecurityKtTest { + + @Test + fun `should map all AutoDiscoveryConnectionSecurities`() { + val securities = AutoDiscoveryConnectionSecurity.values() + + for (security in securities) { + val connectionSecurity = security.toConnectionSecurity() + + assertThat(connectionSecurity).isEqualTo( + when (security) { + AutoDiscoveryConnectionSecurity.StartTLS -> ConnectionSecurity.StartTLS + AutoDiscoveryConnectionSecurity.TLS -> ConnectionSecurity.TLS + }, + ) + } + } +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtensionKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtensionKtTest.kt new file mode 100644 index 000000000..96bc1c01c --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/entity/IncomingServerSettingsExtensionKtTest.kt @@ -0,0 +1,25 @@ +package app.k9mail.feature.account.setup.domain.entity + +import app.k9mail.autodiscovery.api.AuthenticationType +import app.k9mail.autodiscovery.api.ImapServerSettings +import app.k9mail.core.common.net.toHostname +import app.k9mail.core.common.net.toPort +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +class IncomingServerSettingsExtensionKtTest { + + @Test + fun `should map all ImapServerSettings to IncomingProtocolType IMAP`() { + val imapServerSettings = ImapServerSettings( + hostname = "example.com".toHostname(), + port = 993.toPort(), + connectionSecurity = AutoDiscoveryConnectionSecurity.TLS, + authenticationType = AuthenticationType.PasswordCleartext, + username = "username", + ) + + assertThat(imapServerSettings.toIncomingProtocolType()).isEqualTo(IncomingProtocolType.IMAP) + } +} From b8269612871407d7b521b377bae4533d9942d4da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montwe=CC=81?= Date: Fri, 16 Jun 2023 13:45:11 +0200 Subject: [PATCH 3/3] Add AccountAutoDiscoveryStateMapper and propagate to other view models --- .../account/setup/ui/AccountSetupViewModel.kt | 7 +- .../mapper/AccountAutoDiscoveryStateMapper.kt | 76 +++++++ .../setup/ui/AccountSetupViewModelTest.kt | 14 ++ .../AccountAutoDiscoveryStateMapperKtTest.kt | 199 ++++++++++++++++++ 4 files changed, 295 insertions(+), 1 deletion(-) create mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapper.kt create mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapperKtTest.kt diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt index 6bcb2ba75..04672a468 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt @@ -7,6 +7,9 @@ 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.AccountSetupContract.ViewModel import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract +import app.k9mail.feature.account.setup.ui.common.mapper.toIncomingConfigState +import app.k9mail.feature.account.setup.ui.common.mapper.toOptionsState +import app.k9mail.feature.account.setup.ui.common.mapper.toOutgoingConfigState class AccountSetupViewModel( initialState: State = State(), @@ -21,7 +24,9 @@ class AccountSetupViewModel( } private fun handleAutoDiscoveryFinished(autoDiscoveryState: AccountAutoDiscoveryContract.State) { - // TODO emmit effect to update ViewModels + emitEffect(Effect.UpdateIncomingConfig(autoDiscoveryState.toIncomingConfigState())) + emitEffect(Effect.UpdateOutgoingConfig(autoDiscoveryState.toOutgoingConfigState())) + emitEffect(Effect.UpdateOptions(autoDiscoveryState.toOptionsState())) onNext() } diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapper.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapper.kt new file mode 100644 index 000000000..bef28bc12 --- /dev/null +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapper.kt @@ -0,0 +1,76 @@ +package app.k9mail.feature.account.setup.ui.common.mapper + +import app.k9mail.autodiscovery.api.ImapServerSettings +import app.k9mail.autodiscovery.api.SmtpServerSettings +import app.k9mail.feature.account.setup.domain.entity.ConnectionSecurity +import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType +import app.k9mail.feature.account.setup.domain.entity.toConnectionSecurity +import app.k9mail.feature.account.setup.domain.entity.toImapDefaultPort +import app.k9mail.feature.account.setup.domain.entity.toIncomingProtocolType +import app.k9mail.feature.account.setup.domain.entity.toSmtpDefaultPort +import app.k9mail.feature.account.setup.domain.input.NumberInputField +import app.k9mail.feature.account.setup.domain.input.StringInputField +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.options.AccountOptionsContract +import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract + +internal fun AccountAutoDiscoveryContract.State.toIncomingConfigState(): AccountIncomingConfigContract.State { + val incomingSettings = autoDiscoverySettings?.incomingServerSettings as? ImapServerSettings? + return AccountIncomingConfigContract.State( + protocolType = incomingSettings?.toIncomingProtocolType() ?: IncomingProtocolType.DEFAULT, + server = StringInputField( + value = prefillServer( + hostname = incomingSettings?.hostname?.value, + ), + ), + security = incomingSettings?.connectionSecurity?.toConnectionSecurity() ?: ConnectionSecurity.DEFAULT, + port = NumberInputField( + value = incomingSettings?.port?.value?.toLong() ?: ConnectionSecurity.DEFAULT.toImapDefaultPort(), + ), + username = StringInputField( + value = prefillUserName( + emailAddress = emailAddress.value, + username = incomingSettings?.username, + ), + ), + password = StringInputField(value = password.value), + ) +} + +internal fun AccountAutoDiscoveryContract.State.toOutgoingConfigState(): AccountOutgoingConfigContract.State { + val outgoingSettings = autoDiscoverySettings?.outgoingServerSettings as? SmtpServerSettings? + return AccountOutgoingConfigContract.State( + server = StringInputField( + value = prefillServer( + hostname = outgoingSettings?.hostname?.value, + ), + ), + security = outgoingSettings?.connectionSecurity?.toConnectionSecurity() ?: ConnectionSecurity.DEFAULT, + port = NumberInputField( + value = outgoingSettings?.port?.value?.toLong() ?: ConnectionSecurity.DEFAULT.toSmtpDefaultPort(), + ), + username = StringInputField( + value = prefillUserName( + emailAddress = emailAddress.value, + username = outgoingSettings?.username, + ), + ), + password = StringInputField(value = password.value), + ) +} + +private fun prefillServer(hostname: String?) = hostname.orEmpty() + +internal fun prefillUserName( + emailAddress: String, + username: String?, +): String { + return username.takeUnless { it.isNullOrEmpty() } ?: emailAddress +} + +internal fun AccountAutoDiscoveryContract.State.toOptionsState(): AccountOptionsContract.State { + return AccountOptionsContract.State( + accountName = StringInputField(value = emailAddress.value), + ) +} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt index 73214abf6..360333b3a 100644 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt @@ -5,6 +5,11 @@ import app.k9mail.core.ui.compose.testing.MainDispatcherRule 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.incoming.AccountIncomingConfigContract +import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract +import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract +import assertk.assertThat import assertk.assertions.assertThatAndTurbinesConsumed import assertk.assertions.isEqualTo import assertk.assertions.prop @@ -34,6 +39,15 @@ class AccountSetupViewModelTest { viewModel.event(AccountSetupContract.Event.OnAutoDiscoveryFinished(AccountAutoDiscoveryContract.State())) + assertThat(effectTurbine.awaitItem()) + .isEqualTo(Effect.UpdateIncomingConfig(AccountIncomingConfigContract.State())) + + assertThat(effectTurbine.awaitItem()) + .isEqualTo(Effect.UpdateOutgoingConfig(AccountOutgoingConfigContract.State())) + + assertThat(effectTurbine.awaitItem()) + .isEqualTo(Effect.UpdateOptions(AccountOptionsContract.State())) + assertThatAndTurbinesConsumed( actual = stateTurbine.awaitItem(), turbines = turbines, diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapperKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapperKtTest.kt new file mode 100644 index 000000000..fdf416599 --- /dev/null +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/common/mapper/AccountAutoDiscoveryStateMapperKtTest.kt @@ -0,0 +1,199 @@ +package app.k9mail.feature.account.setup.ui.common.mapper + +import app.k9mail.autodiscovery.api.AutoDiscoveryResult +import app.k9mail.autodiscovery.api.ImapServerSettings +import app.k9mail.autodiscovery.api.SmtpServerSettings +import app.k9mail.core.common.net.toHostname +import app.k9mail.core.common.net.toPort +import app.k9mail.feature.account.setup.domain.entity.AutoDiscoveryAuthenticationType +import app.k9mail.feature.account.setup.domain.entity.AutoDiscoveryConnectionSecurity +import app.k9mail.feature.account.setup.domain.entity.IncomingProtocolType +import app.k9mail.feature.account.setup.domain.entity.toConnectionSecurity +import app.k9mail.feature.account.setup.domain.input.NumberInputField +import app.k9mail.feature.account.setup.domain.input.StringInputField +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.options.AccountOptionsContract +import app.k9mail.feature.account.setup.ui.outgoing.AccountOutgoingConfigContract +import assertk.assertThat +import assertk.assertions.isEqualTo +import org.junit.Test + +class AccountAutoDiscoveryStateMapperKtTest { + + @Test + fun `should map to default IncomingConfigState when empty`() { + val incomingConfigState = EMPTY_STATE.toIncomingConfigState() + + assertThat(incomingConfigState).isEqualTo(AccountIncomingConfigContract.State()) + } + + @Test + fun `should map to IncomingConfigState when no AutoDiscovery`() { + val incomingConfigState = EMAIL_PASSWORD_STATE.toIncomingConfigState() + + assertThat(incomingConfigState).isEqualTo( + AccountIncomingConfigContract.State( + username = StringInputField(value = EMAIL_ADDRESS), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to IncomingConfigState when AutoDiscovery`() { + val incomingConfigState = AUTO_DISCOVERY_STATE.toIncomingConfigState() + + assertThat(incomingConfigState).isEqualTo( + AccountIncomingConfigContract.State( + protocolType = IncomingProtocolType.IMAP, + server = StringInputField(value = AUTO_DISCOVERY_HOSTNAME.value), + security = AUTO_DISCOVERY_SECURITY.toConnectionSecurity(), + port = NumberInputField(value = AUTO_DISCOVERY_PORT_IMAP.value.toLong()), + username = StringInputField(value = AUTO_DISCOVERY_USERNAME), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to email username IncomingConfigState when AutoDiscovery empty username`() { + val incomingConfigState = AUTO_DISCOVERY_STATE_USERNAME_EMPTY.toIncomingConfigState() + + assertThat(incomingConfigState).isEqualTo( + AccountIncomingConfigContract.State( + protocolType = IncomingProtocolType.IMAP, + server = StringInputField(value = AUTO_DISCOVERY_HOSTNAME.value), + security = AUTO_DISCOVERY_SECURITY.toConnectionSecurity(), + port = NumberInputField(value = AUTO_DISCOVERY_PORT_IMAP.value.toLong()), + username = StringInputField(value = EMAIL_ADDRESS), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to OutgoingConfigState when empty`() { + val outgoingConfigState = EMPTY_STATE.toOutgoingConfigState() + + assertThat(outgoingConfigState).isEqualTo(AccountOutgoingConfigContract.State()) + } + + @Test + fun `should map to OutgoingConfigState when no AutoDiscovery`() { + val outgoingConfigState = EMAIL_PASSWORD_STATE.toOutgoingConfigState() + + assertThat(outgoingConfigState).isEqualTo( + AccountOutgoingConfigContract.State( + username = StringInputField(value = EMAIL_ADDRESS), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to OutgoingConfigState when AutoDiscovery`() { + val outgoingConfigState = AUTO_DISCOVERY_STATE.toOutgoingConfigState() + + assertThat(outgoingConfigState).isEqualTo( + AccountOutgoingConfigContract.State( + server = StringInputField(value = AUTO_DISCOVERY_HOSTNAME.value), + security = AUTO_DISCOVERY_SECURITY.toConnectionSecurity(), + port = NumberInputField(value = AUTO_DISCOVERY_PORT_SMTP.value.toLong()), + username = StringInputField(value = AUTO_DISCOVERY_USERNAME), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to email username OutgoingConfigState when AutoDiscovery empty username`() { + val outgoingConfigState = AUTO_DISCOVERY_STATE_USERNAME_EMPTY.toOutgoingConfigState() + + assertThat(outgoingConfigState).isEqualTo( + AccountOutgoingConfigContract.State( + server = StringInputField(value = AUTO_DISCOVERY_HOSTNAME.value), + security = AUTO_DISCOVERY_SECURITY.toConnectionSecurity(), + port = NumberInputField(value = AUTO_DISCOVERY_PORT_SMTP.value.toLong()), + username = StringInputField(value = EMAIL_ADDRESS), + password = StringInputField(value = PASSWORD), + ), + ) + } + + @Test + fun `should map to OptionsState when empty`() { + val optionsState = EMPTY_STATE.toOptionsState() + + assertThat(optionsState).isEqualTo(AccountOptionsContract.State()) + } + + @Test + fun `should map to OptionsState when email and password set`() { + val optionsState = EMAIL_PASSWORD_STATE.toOptionsState() + + assertThat(optionsState).isEqualTo( + AccountOptionsContract.State( + accountName = StringInputField(value = EMAIL_ADDRESS), + ), + ) + } + + private companion object { + const val EMAIL_ADDRESS = "test@example.com" + const val PASSWORD = "password" + const val SERVER_IMAP = "imap.example.com" + const val SERVER_SMTP = "smtp.example.com" + + val AUTO_DISCOVERY_HOSTNAME = "incoming.example.com".toHostname() + val AUTO_DISCOVERY_PORT_IMAP = 143.toPort() + val AUTO_DISCOVERY_PORT_SMTP = 587.toPort() + val AUTO_DISCOVERY_SECURITY = AutoDiscoveryConnectionSecurity.StartTLS + val AUTO_DISCOVERY_AUTHENTICATION = AutoDiscoveryAuthenticationType.PasswordEncrypted + const val AUTO_DISCOVERY_USERNAME = "username" + + val EMPTY_STATE = AccountAutoDiscoveryContract.State() + + val EMAIL_PASSWORD_STATE = AccountAutoDiscoveryContract.State( + emailAddress = StringInputField(value = EMAIL_ADDRESS), + password = StringInputField(value = PASSWORD), + ) + + val AUTO_DISCOVERY_STATE = EMAIL_PASSWORD_STATE.copy( + autoDiscoverySettings = AutoDiscoveryResult.Settings( + incomingServerSettings = ImapServerSettings( + hostname = AUTO_DISCOVERY_HOSTNAME, + port = AUTO_DISCOVERY_PORT_IMAP, + connectionSecurity = AUTO_DISCOVERY_SECURITY, + authenticationType = AUTO_DISCOVERY_AUTHENTICATION, + username = AUTO_DISCOVERY_USERNAME, + ), + outgoingServerSettings = SmtpServerSettings( + hostname = AUTO_DISCOVERY_HOSTNAME, + port = AUTO_DISCOVERY_PORT_SMTP, + connectionSecurity = AUTO_DISCOVERY_SECURITY, + authenticationType = AUTO_DISCOVERY_AUTHENTICATION, + username = AUTO_DISCOVERY_USERNAME, + ), + isTrusted = true, + ), + ) + + val AUTO_DISCOVERY_STATE_USERNAME_EMPTY = AUTO_DISCOVERY_STATE.copy( + autoDiscoverySettings = AUTO_DISCOVERY_STATE.autoDiscoverySettings?.copy( + incomingServerSettings = ( + AUTO_DISCOVERY_STATE.autoDiscoverySettings + ?.incomingServerSettings as ImapServerSettings + ).copy( + username = "", + ), + outgoingServerSettings = ( + AUTO_DISCOVERY_STATE.autoDiscoverySettings + ?.outgoingServerSettings as SmtpServerSettings + ).copy( + username = "", + ), + ), + ) + } +}