From 1fac2f5ab2f943775b64b1a72dccd99b11579c2a Mon Sep 17 00:00:00 2001 From: cketti Date: Tue, 14 Nov 2023 18:43:10 +0100 Subject: [PATCH] Replace `AccountSetupScreen` with `AccountSetupNavHost` --- .../account/setup/AccountSetupModule.kt | 6 - .../setup/navigation/AccountSetupNavHost.kt | 128 ++++++++ .../navigation/AccountSetupNavigation.kt | 23 +- .../account/setup/ui/AccountSetupContract.kt | 44 --- .../account/setup/ui/AccountSetupScreen.kt | 126 -------- .../account/setup/ui/AccountSetupViewModel.kt | 125 -------- .../account/setup/AccountSetupModuleKtTest.kt | 2 - .../setup/ui/AccountSetupScreenKtTest.kt | 97 ------ .../account/setup/ui/AccountSetupStateTest.kt | 20 -- .../setup/ui/AccountSetupViewModelTest.kt | 289 ------------------ .../setup/ui/FakeAccountSetupViewModel.kt | 25 -- .../main/navigation/OnboardingNavHost.kt | 19 +- 12 files changed, 139 insertions(+), 765 deletions(-) create mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavHost.kt delete mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt delete mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt delete mode 100644 feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt delete mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt delete mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt delete mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt delete mode 100644 feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/AccountSetupModule.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/AccountSetupModule.kt index 552ad72605a..e6e723aef9e 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/AccountSetupModule.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/AccountSetupModule.kt @@ -9,7 +9,6 @@ import app.k9mail.feature.account.server.validation.featureAccountServerValidati 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 -import app.k9mail.feature.account.setup.ui.AccountSetupViewModel import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryValidator import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryViewModel @@ -56,11 +55,6 @@ val featureAccountSetupModule: Module = module { factory { AccountAutoDiscoveryValidator() } factory { AccountOptionsValidator() } - viewModel { - AccountSetupViewModel( - accountStateRepository = get(), - ) - } viewModel { AccountAutoDiscoveryViewModel( validator = get(), diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavHost.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavHost.kt new file mode 100644 index 00000000000..c834ebf0767 --- /dev/null +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavHost.kt @@ -0,0 +1,128 @@ +package app.k9mail.feature.account.setup.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSettingsScreen +import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSettingsViewModel +import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsScreen +import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsViewModel +import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel +import app.k9mail.feature.account.server.validation.ui.ServerValidationScreen +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.createaccount.CreateAccountScreen +import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountViewModel +import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen +import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel +import org.koin.androidx.compose.koinViewModel + +private const val NESTED_NAVIGATION_AUTO_CONFIG = "autoconfig" +private const val NESTED_NAVIGATION_INCOMING_SERVER_CONFIG = "incoming-server/config" +private const val NESTED_NAVIGATION_INCOMING_SERVER_VALIDATION = "incoming-server/validation" +private const val NESTED_NAVIGATION_OUTGOING_SERVER_CONFIG = "outgoing-server/config" +private const val NESTED_NAVIGATION_OUTGOING_SERVER_VALIDATION = "outgoing-server/validation" +private const val NESTED_NAVIGATION_ACCOUNT_OPTIONS = "account-options" +private const val NESTED_NAVIGATION_CREATE_ACCOUNT = "create-account" + +@Suppress("LongMethod") +@Composable +fun AccountSetupNavHost( + onBack: () -> Unit, + onFinish: (String) -> Unit, +) { + val navController = rememberNavController() + var isAutomaticConfig by rememberSaveable { mutableStateOf(false) } + + NavHost( + navController = navController, + startDestination = NESTED_NAVIGATION_AUTO_CONFIG, + ) { + composable(route = NESTED_NAVIGATION_AUTO_CONFIG) { + AccountAutoDiscoveryScreen( + onNext = { automaticConfig -> + isAutomaticConfig = automaticConfig + if (isAutomaticConfig) { + navController.navigate(NESTED_NAVIGATION_INCOMING_SERVER_VALIDATION) + } else { + navController.navigate(NESTED_NAVIGATION_INCOMING_SERVER_CONFIG) + } + }, + onBack = onBack, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_INCOMING_SERVER_CONFIG) { + IncomingServerSettingsScreen( + onNext = { navController.navigate(NESTED_NAVIGATION_INCOMING_SERVER_VALIDATION) }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_INCOMING_SERVER_VALIDATION) { + ServerValidationScreen( + onNext = { + if (isAutomaticConfig) { + navController.navigate(NESTED_NAVIGATION_OUTGOING_SERVER_VALIDATION) { + popUpTo(NESTED_NAVIGATION_AUTO_CONFIG) + } + } else { + navController.navigate(NESTED_NAVIGATION_OUTGOING_SERVER_CONFIG) { + popUpTo(NESTED_NAVIGATION_INCOMING_SERVER_CONFIG) + } + } + }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_OUTGOING_SERVER_CONFIG) { + OutgoingServerSettingsScreen( + onNext = { navController.navigate(NESTED_NAVIGATION_OUTGOING_SERVER_VALIDATION) }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_OUTGOING_SERVER_VALIDATION) { + ServerValidationScreen( + onNext = { + navController.navigate(NESTED_NAVIGATION_ACCOUNT_OPTIONS) { + if (isAutomaticConfig) { + popUpTo(NESTED_NAVIGATION_AUTO_CONFIG) + } else { + popUpTo(NESTED_NAVIGATION_OUTGOING_SERVER_CONFIG) + } + } + }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_ACCOUNT_OPTIONS) { + AccountOptionsScreen( + onNext = { navController.navigate(NESTED_NAVIGATION_CREATE_ACCOUNT) }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + + composable(route = NESTED_NAVIGATION_CREATE_ACCOUNT) { + CreateAccountScreen( + onNext = { accountUuid -> onFinish(accountUuid.value) }, + onBack = { navController.popBackStack() }, + viewModel = koinViewModel(), + ) + } + } +} diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt index 74083567529..762752b2372 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/navigation/AccountSetupNavigation.kt @@ -1,37 +1,16 @@ package app.k9mail.feature.account.setup.navigation -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder -import androidx.navigation.NavOptions -import androidx.navigation.compose.composable import app.k9mail.core.ui.compose.common.navigation.deepLinkComposable -import app.k9mail.feature.account.setup.ui.AccountSetupScreen const val NAVIGATION_ROUTE_ACCOUNT_SETUP = "account/setup" -fun NavController.navigateToAccountSetup(navOptions: NavOptions? = null) { - navigate(NAVIGATION_ROUTE_ACCOUNT_SETUP, navOptions) -} - fun NavGraphBuilder.accountSetupRoute( onBack: () -> Unit, onFinish: (String) -> Unit, ) { deepLinkComposable(route = NAVIGATION_ROUTE_ACCOUNT_SETUP) { - AccountSetupScreen( - onBack = onBack, - onFinish = onFinish, - ) - } -} - -fun NavGraphBuilder.nestedAccountSetupRoute( - route: String, - onBack: () -> Unit, - onFinish: (String) -> Unit, -) { - composable(route) { - AccountSetupScreen( + AccountSetupNavHost( onBack = onBack, onFinish = onFinish, ) 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 deleted file mode 100644 index dc47b83e2c0..00000000000 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupContract.kt +++ /dev/null @@ -1,44 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel -import app.k9mail.feature.account.setup.domain.entity.AccountUuid - -interface AccountSetupContract { - - enum class SetupStep { - AUTO_CONFIG, - INCOMING_CONFIG, - INCOMING_VALIDATION, - OUTGOING_CONFIG, - OUTGOING_VALIDATION, - OPTIONS, - CREATE_ACCOUNT, - } - - interface ViewModel : UnidirectionalViewModel - - data class State( - val setupStep: SetupStep = SetupStep.AUTO_CONFIG, - val isAutomaticConfig: Boolean = false, - ) - - sealed interface Event { - data class OnAutoDiscoveryFinished( - val isAutomaticConfig: Boolean, - ) : Event - - object OnNext : Event - object OnBack : Event - - data class OnAccountCreated(val accountUuid: AccountUuid) : Event - } - - sealed interface Effect { - - data class NavigateNext( - val accountUuid: String, - ) : 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 deleted file mode 100644 index 929ea15c1c6..00000000000 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreen.kt +++ /dev/null @@ -1,126 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import androidx.compose.runtime.Composable -import androidx.compose.ui.tooling.preview.Preview -import app.k9mail.core.ui.compose.common.mvi.observe -import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSettingsContract -import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSettingsScreen -import app.k9mail.feature.account.server.settings.ui.incoming.IncomingServerSettingsViewModel -import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsContract -import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsScreen -import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSettingsViewModel -import app.k9mail.feature.account.server.validation.ui.IncomingServerValidationViewModel -import app.k9mail.feature.account.server.validation.ui.OutgoingServerValidationViewModel -import app.k9mail.feature.account.server.validation.ui.ServerValidationContract -import app.k9mail.feature.account.server.validation.ui.ServerValidationScreen -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.ViewModel -import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract -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.createaccount.CreateAccountContract -import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountScreen -import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountViewModel -import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract -import app.k9mail.feature.account.setup.ui.options.AccountOptionsScreen -import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel -import org.koin.androidx.compose.koinViewModel - -@Suppress("LongMethod", "ViewModelForwarding") -@Composable -fun AccountSetupScreen( - onFinish: (String) -> Unit, - onBack: () -> Unit, - viewModel: ViewModel = koinViewModel(), - autoDiscoveryViewModel: AccountAutoDiscoveryContract.ViewModel = koinViewModel(), - incomingViewModel: IncomingServerSettingsContract.ViewModel = koinViewModel(), - incomingValidationViewModel: ServerValidationContract.ViewModel = - koinViewModel(), - outgoingViewModel: OutgoingServerSettingsContract.ViewModel = koinViewModel(), - outgoingValidationViewModel: ServerValidationContract.ViewModel = - koinViewModel(), - optionsViewModel: AccountOptionsContract.ViewModel = koinViewModel(), - createAccountViewModel: CreateAccountContract.ViewModel = koinViewModel(), -) { - val (state, dispatch) = viewModel.observe { effect -> - when (effect) { - is Effect.NavigateNext -> onFinish(effect.accountUuid) - Effect.NavigateBack -> onBack() - } - } - - when (state.value.setupStep) { - SetupStep.AUTO_CONFIG -> { - AccountAutoDiscoveryScreen( - onNext = { isAutomaticConfig -> - dispatch( - Event.OnAutoDiscoveryFinished( - isAutomaticConfig, - ), - ) - }, - onBack = { dispatch(Event.OnBack) }, - viewModel = autoDiscoveryViewModel, - ) - } - - SetupStep.INCOMING_CONFIG -> { - IncomingServerSettingsScreen( - onNext = { dispatch(Event.OnNext) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = incomingViewModel, - ) - } - - SetupStep.INCOMING_VALIDATION -> { - ServerValidationScreen( - onNext = { dispatch(Event.OnNext) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = incomingValidationViewModel, - ) - } - - SetupStep.OUTGOING_CONFIG -> { - OutgoingServerSettingsScreen( - onNext = { dispatch(Event.OnNext) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = outgoingViewModel, - ) - } - - SetupStep.OUTGOING_VALIDATION -> { - ServerValidationScreen( - onNext = { dispatch(Event.OnNext) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = outgoingValidationViewModel, - ) - } - - SetupStep.OPTIONS -> { - AccountOptionsScreen( - onNext = { dispatch(Event.OnNext) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = optionsViewModel, - ) - } - - SetupStep.CREATE_ACCOUNT -> { - CreateAccountScreen( - onNext = { accountUuid -> dispatch(Event.OnAccountCreated(accountUuid)) }, - onBack = { dispatch(Event.OnBack) }, - viewModel = createAccountViewModel, - ) - } - } -} - -@Preview(showBackground = true) -@Composable -internal fun AccountSetupScreenPreview() { - AccountSetupScreen( - onFinish = {}, - onBack = {}, - ) -} 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 deleted file mode 100644 index d79f2eecccb..00000000000 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModel.kt +++ /dev/null @@ -1,125 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import app.k9mail.core.ui.compose.common.mvi.BaseViewModel -import app.k9mail.feature.account.common.domain.AccountDomainContract -import app.k9mail.feature.account.common.domain.entity.AuthorizationState -import app.k9mail.feature.account.setup.domain.entity.AccountUuid -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 - -@Suppress("LongParameterList") -class AccountSetupViewModel( - private val accountStateRepository: AccountDomainContract.AccountStateRepository, - initialState: State = State(), -) : BaseViewModel(initialState), AccountSetupContract.ViewModel { - - override fun event(event: Event) { - when (event) { - is Event.OnAutoDiscoveryFinished -> onAutoDiscoveryFinished(event.isAutomaticConfig) - - Event.OnBack -> onBack() - Event.OnNext -> onNext() - - is Event.OnAccountCreated -> navigateNext(event.accountUuid) - } - } - - private fun onAutoDiscoveryFinished( - isAutomaticConfig: Boolean, - ) { - updateState { - it.copy( - isAutomaticConfig = isAutomaticConfig, - ) - } - - onNext() - } - - private fun onNext() { - when (state.value.setupStep) { - SetupStep.AUTO_CONFIG -> { - if (state.value.isAutomaticConfig) { - changeToSetupStep(SetupStep.INCOMING_VALIDATION) - } else { - changeToSetupStep(SetupStep.INCOMING_CONFIG) - } - } - - SetupStep.INCOMING_CONFIG -> { - changeToSetupStep(SetupStep.INCOMING_VALIDATION) - } - - SetupStep.INCOMING_VALIDATION -> { - if (state.value.isAutomaticConfig) { - changeToSetupStep(SetupStep.OUTGOING_VALIDATION) - } else { - changeToSetupStep(SetupStep.OUTGOING_CONFIG) - } - } - - SetupStep.OUTGOING_CONFIG -> { - changeToSetupStep(SetupStep.OUTGOING_VALIDATION) - } - - SetupStep.OUTGOING_VALIDATION -> { - changeToSetupStep(SetupStep.OPTIONS) - } - - SetupStep.OPTIONS -> { - changeToSetupStep(SetupStep.CREATE_ACCOUNT) - } - - SetupStep.CREATE_ACCOUNT -> Unit - } - } - - private fun onBack() { - when (state.value.setupStep) { - SetupStep.AUTO_CONFIG -> navigateBack() - SetupStep.INCOMING_CONFIG -> changeToSetupStep(SetupStep.AUTO_CONFIG) - SetupStep.INCOMING_VALIDATION -> { - if (state.value.isAutomaticConfig) { - changeToSetupStep(SetupStep.AUTO_CONFIG) - } else { - changeToSetupStep(SetupStep.INCOMING_CONFIG) - } - } - - SetupStep.OUTGOING_CONFIG -> changeToSetupStep(SetupStep.INCOMING_CONFIG) - SetupStep.OUTGOING_VALIDATION -> { - if (state.value.isAutomaticConfig) { - changeToSetupStep(SetupStep.AUTO_CONFIG) - } else { - changeToSetupStep(SetupStep.OUTGOING_CONFIG) - } - } - - SetupStep.OPTIONS -> if (state.value.isAutomaticConfig) { - changeToSetupStep(SetupStep.AUTO_CONFIG) - } else { - changeToSetupStep(SetupStep.OUTGOING_CONFIG) - } - - SetupStep.CREATE_ACCOUNT -> changeToSetupStep(SetupStep.OPTIONS) - } - } - - private fun changeToSetupStep(setupStep: SetupStep) { - if (setupStep == SetupStep.AUTO_CONFIG) { - accountStateRepository.setAuthorizationState(AuthorizationState(null)) - } - - updateState { - it.copy( - setupStep = setupStep, - ) - } - } - - private fun navigateNext(accountUuid: AccountUuid) = emitEffect(Effect.NavigateNext(accountUuid.value)) - - private fun navigateBack() = emitEffect(Effect.NavigateBack) -} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/AccountSetupModuleKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/AccountSetupModuleKtTest.kt index 606b177082f..1c953fa36f7 100644 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/AccountSetupModuleKtTest.kt +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/AccountSetupModuleKtTest.kt @@ -12,7 +12,6 @@ import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSett import app.k9mail.feature.account.server.validation.ui.ServerValidationContract import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult -import app.k9mail.feature.account.setup.ui.AccountSetupContract import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryContract import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountContract import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract @@ -66,7 +65,6 @@ class AccountSetupModuleKtTest : KoinTest { featureAccountSetupModule.verify( extraTypes = listOf( AccountCommonExternalContract.AccountStateLoader::class, - AccountSetupContract.State::class, AccountAutoDiscoveryContract.State::class, AccountOAuthContract.State::class, ServerValidationContract.State::class, diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt deleted file mode 100644 index 997892f802e..00000000000 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupScreenKtTest.kt +++ /dev/null @@ -1,97 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import app.k9mail.core.ui.compose.testing.ComposeTest -import app.k9mail.core.ui.compose.testing.onNodeWithTag -import app.k9mail.core.ui.compose.testing.setContent -import app.k9mail.core.ui.compose.theme.ThunderbirdTheme -import app.k9mail.feature.account.server.settings.ui.incoming.fake.FakeIncomingServerSettingsViewModel -import app.k9mail.feature.account.server.settings.ui.outgoing.fake.FakeOutgoingServerSettingsViewModel -import app.k9mail.feature.account.server.validation.ui.fake.FakeServerValidationViewModel -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.FakeAccountAutoDiscoveryViewModel -import app.k9mail.feature.account.setup.ui.createaccount.FakeCreateAccountViewModel -import app.k9mail.feature.account.setup.ui.options.FakeAccountOptionsViewModel -import assertk.assertThat -import assertk.assertions.isEqualTo -import kotlinx.coroutines.test.runTest -import org.junit.Test - -class AccountSetupScreenKtTest : ComposeTest() { - - @Test - fun `should display correct screen for every setup step`() = runTest { - val viewModel = FakeAccountSetupViewModel() - - setContent { - ThunderbirdTheme { - AccountSetupScreen( - onFinish = { }, - onBack = { }, - viewModel = viewModel, - autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel(), - incomingViewModel = FakeIncomingServerSettingsViewModel(), - incomingValidationViewModel = FakeServerValidationViewModel(), - outgoingViewModel = FakeOutgoingServerSettingsViewModel(), - outgoingValidationViewModel = FakeServerValidationViewModel(), - optionsViewModel = FakeAccountOptionsViewModel(), - createAccountViewModel = FakeCreateAccountViewModel(), - ) - } - } - - for (step in SetupStep.values()) { - viewModel.state { it.copy(setupStep = step) } - onNodeWithTag(getTagForStep(step)).assertExists() - } - } - - @Test - fun `should delegate navigation effects`() = runTest { - val initialState = State() - val viewModel = FakeAccountSetupViewModel(initialState = initialState) - var onFinishCounter = 0 - var onBackCounter = 0 - - setContent { - ThunderbirdTheme { - AccountSetupScreen( - onFinish = { onFinishCounter++ }, - onBack = { onBackCounter++ }, - viewModel = viewModel, - autoDiscoveryViewModel = FakeAccountAutoDiscoveryViewModel(), - incomingViewModel = FakeIncomingServerSettingsViewModel(), - incomingValidationViewModel = FakeServerValidationViewModel(), - outgoingViewModel = FakeOutgoingServerSettingsViewModel(), - outgoingValidationViewModel = FakeServerValidationViewModel(), - optionsViewModel = FakeAccountOptionsViewModel(), - createAccountViewModel = FakeCreateAccountViewModel(), - ) - } - } - - assertThat(onFinishCounter).isEqualTo(0) - assertThat(onBackCounter).isEqualTo(0) - - viewModel.effect(Effect.NavigateNext("accountUuid")) - - assertThat(onFinishCounter).isEqualTo(1) - assertThat(onBackCounter).isEqualTo(0) - - viewModel.effect(Effect.NavigateBack) - - assertThat(onFinishCounter).isEqualTo(1) - assertThat(onBackCounter).isEqualTo(1) - } - - private fun getTagForStep(step: SetupStep): String = when (step) { - SetupStep.AUTO_CONFIG -> "AccountAutoDiscoveryContent" - SetupStep.INCOMING_CONFIG -> "IncomingServerSettingsContent" - SetupStep.INCOMING_VALIDATION -> "AccountValidationContent" - SetupStep.OUTGOING_CONFIG -> "OutgoingServerSettingsContent" - SetupStep.OUTGOING_VALIDATION -> "AccountValidationContent" - SetupStep.OPTIONS -> "AccountOptionsContent" - SetupStep.CREATE_ACCOUNT -> "CreateAccountContent" - } -} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt deleted file mode 100644 index c1763c34c61..00000000000 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupStateTest.kt +++ /dev/null @@ -1,20 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import app.k9mail.feature.account.setup.ui.AccountSetupContract.State -import assertk.all -import assertk.assertThat -import assertk.assertions.isEqualTo -import assertk.assertions.prop -import org.junit.Test - -class AccountSetupStateTest { - - @Test - fun `should set default values`() { - val state = State() - - assertThat(state).all { - prop(State::setupStep).isEqualTo(AccountSetupContract.SetupStep.AUTO_CONFIG) - } - } -} 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 deleted file mode 100644 index 82c23897c38..00000000000 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/AccountSetupViewModelTest.kt +++ /dev/null @@ -1,289 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -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.common.data.InMemoryAccountStateRepository -import app.k9mail.feature.account.common.domain.entity.AccountOptions -import app.k9mail.feature.account.common.domain.entity.AccountState -import app.k9mail.feature.account.common.domain.entity.MailConnectionSecurity -import app.k9mail.feature.account.setup.domain.entity.AccountUuid -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.isEqualTo -import assertk.assertions.prop -import com.fsck.k9.mail.AuthType -import com.fsck.k9.mail.ServerSettings -import kotlinx.coroutines.test.runTest -import org.junit.Rule -import org.junit.Test - -@Suppress("LongMethod") -class AccountSetupViewModelTest { - - @get:Rule - val mainDispatcherRule = MainDispatcherRule() - - @Test - fun `should forward step state on next event`() = runTest { - val accountStateRepository = InMemoryAccountStateRepository() - val viewModel = AccountSetupViewModel( - accountStateRepository = accountStateRepository, - ) - val turbines = turbinesWithInitialStateCheck(viewModel, State(setupStep = SetupStep.AUTO_CONFIG)) - - viewModel.event( - AccountSetupContract.Event.OnAutoDiscoveryFinished( - isAutomaticConfig = false, - ), - ) - - val expectedAccountState = AccountState( - emailAddress = "test@domain.example", - incomingServerSettings = ServerSettings( - type = "imap", - host = INCOMING_SERVER_NAME, - port = INCOMING_SERVER_PORT, - connectionSecurity = MailConnectionSecurity.SSL_TLS_REQUIRED, - authenticationType = AuthType.CRAM_MD5, - username = USERNAME, - password = PASSWORD, - clientCertificateAlias = null, - extra = emptyMap(), - ), - outgoingServerSettings = ServerSettings( - type = "smtp", - host = OUTGOING_SERVER_NAME, - port = OUTGOING_SERVER_PORT, - connectionSecurity = MailConnectionSecurity.SSL_TLS_REQUIRED, - authenticationType = AuthType.CRAM_MD5, - username = USERNAME, - password = PASSWORD, - clientCertificateAlias = null, - extra = emptyMap(), - ), - authorizationState = null, - options = AccountOptions( - accountName = "account name", - displayName = "display name", - emailSignature = "signature", - checkFrequencyInMinutes = 15, - messageDisplayCount = 25, - showNotification = true, - ), - ) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.INCOMING_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnNext) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.INCOMING_VALIDATION) - } - - viewModel.event(AccountSetupContract.Event.OnNext) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.OUTGOING_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnNext) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.OUTGOING_VALIDATION) - } - - viewModel.event(AccountSetupContract.Event.OnNext) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.OPTIONS) - } - - viewModel.event(AccountSetupContract.Event.OnNext) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.CREATE_ACCOUNT) - } - - accountStateRepository.setState(expectedAccountState) - - viewModel.event(AccountSetupContract.Event.OnAccountCreated(AccountUuid("accountUuid"))) - - assertThatAndMviTurbinesConsumed( - actual = turbines.effectTurbine.awaitItem(), - turbines = turbines, - ) { - isEqualTo(Effect.NavigateNext("accountUuid")) - } - } - - @Test - fun `should rewind step state on back event`() = runTest { - val initialState = State(setupStep = SetupStep.OPTIONS) - val viewModel = AccountSetupViewModel( - accountStateRepository = InMemoryAccountStateRepository(), - initialState = initialState, - ) - val turbines = turbinesWithInitialStateCheck(viewModel, initialState) - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.OUTGOING_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.INCOMING_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.effectTurbine.awaitItem(), - turbines = turbines, - ) { - isEqualTo(Effect.NavigateBack) - } - } - - @Test - fun `should go back from OPTIONS step on back event when isAutomaticConfig enabled`() = runTest { - val initialState = State( - setupStep = SetupStep.OPTIONS, - isAutomaticConfig = true, - ) - val viewModel = AccountSetupViewModel( - accountStateRepository = InMemoryAccountStateRepository(), - initialState = initialState, - ) - val turbines = turbinesWithInitialStateCheck(viewModel, initialState) - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.effectTurbine.awaitItem(), - turbines = turbines, - ) { - isEqualTo(Effect.NavigateBack) - } - } - - @Test - fun `should go back from OUTGOING_VALIDATION step state on back event when isAutomaticConfig enabled`() = runTest { - val initialState = State( - setupStep = SetupStep.OUTGOING_VALIDATION, - isAutomaticConfig = true, - ) - val viewModel = AccountSetupViewModel( - accountStateRepository = InMemoryAccountStateRepository(), - initialState = initialState, - ) - val turbines = turbinesWithInitialStateCheck(viewModel, initialState) - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.effectTurbine.awaitItem(), - turbines = turbines, - ) { - isEqualTo(Effect.NavigateBack) - } - } - - @Test - fun `should go back from INCOMING_VALIDATION step state on back event when isAutomaticConfig enabled`() = runTest { - val initialState = State( - setupStep = SetupStep.OUTGOING_VALIDATION, - isAutomaticConfig = true, - ) - val viewModel = AccountSetupViewModel( - accountStateRepository = InMemoryAccountStateRepository(), - initialState = initialState, - ) - val turbines = turbinesWithInitialStateCheck(viewModel, initialState) - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.stateTurbine.awaitItem(), - turbines = turbines, - ) { - prop(State::setupStep).isEqualTo(SetupStep.AUTO_CONFIG) - } - - viewModel.event(AccountSetupContract.Event.OnBack) - - assertThatAndMviTurbinesConsumed( - actual = turbines.effectTurbine.awaitItem(), - turbines = turbines, - ) { - isEqualTo(Effect.NavigateBack) - } - } - - companion object { - private const val EMAIL_ADDRESS = "test@domain.example" - private const val USERNAME = EMAIL_ADDRESS - private const val PASSWORD = "password" - private const val INCOMING_SERVER_NAME = "imap.domain.example" - private const val INCOMING_SERVER_PORT = 993 - private const val OUTGOING_SERVER_NAME = "smtp.domain.example" - private const val OUTGOING_SERVER_PORT = 465 - } -} diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt deleted file mode 100644 index 2f5ebf1451f..00000000000 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/ui/FakeAccountSetupViewModel.kt +++ /dev/null @@ -1,25 +0,0 @@ -package app.k9mail.feature.account.setup.ui - -import app.k9mail.core.ui.compose.common.mvi.BaseViewModel -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.State - -internal class FakeAccountSetupViewModel( - initialState: State = State(), -) : BaseViewModel(initialState), AccountSetupContract.ViewModel { - - val events = mutableListOf() - - fun state(update: (State) -> (State)) { - updateState { update(it) } - } - - override fun event(event: Event) { - events.add(event) - } - - fun effect(effect: Effect) { - emitEffect(effect) - } -} diff --git a/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt b/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt index 3d49d1da3c3..22d85e8d77d 100644 --- a/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt +++ b/feature/onboarding/main/src/main/kotlin/app/k9mail/feature/onboarding/main/navigation/OnboardingNavHost.kt @@ -9,7 +9,7 @@ import androidx.navigation.NavController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController -import app.k9mail.feature.account.setup.navigation.nestedAccountSetupRoute +import app.k9mail.feature.account.setup.navigation.AccountSetupNavHost import app.k9mail.feature.onboarding.permissions.ui.PermissionsScreen import app.k9mail.feature.onboarding.welcome.ui.OnboardingScreen @@ -45,14 +45,15 @@ fun OnboardingNavHost( ) } - nestedAccountSetupRoute( - route = NESTED_NAVIGATION_ROUTE_ACCOUNT_SETUP, - onBack = onBack, - onFinish = { createdAccountUuid -> - accountUuid = createdAccountUuid - navController.navigateToPermissions() - }, - ) + composable(route = NESTED_NAVIGATION_ROUTE_ACCOUNT_SETUP) { + AccountSetupNavHost( + onBack = onBack, + onFinish = { createdAccountUuid: String -> + accountUuid = createdAccountUuid + navController.navigateToPermissions() + }, + ) + } composable(route = NESTED_NAVIGATION_ROUTE_PERMISSIONS) { PermissionsScreen(