Integrate CreateAccountScreen into account setup flow

This commit is contained in:
cketti 2023-11-14 16:20:21 +01:00
parent 849f150138
commit c0024a2f66
8 changed files with 53 additions and 57 deletions

View file

@ -13,6 +13,7 @@ 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
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.AccountOptionsValidator
import app.k9mail.feature.account.setup.ui.options.AccountOptionsViewModel
@ -57,7 +58,6 @@ val featureAccountSetupModule: Module = module {
viewModel {
AccountSetupViewModel(
createAccount = get(),
accountStateRepository = get(),
)
}
@ -76,4 +76,11 @@ val featureAccountSetupModule: Module = module {
accountStateRepository = get(),
)
}
viewModel {
CreateAccountViewModel(
createAccount = get(),
accountStateRepository = get(),
)
}
}

View file

@ -1,6 +1,7 @@
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 {
@ -11,6 +12,7 @@ interface AccountSetupContract {
OUTGOING_CONFIG,
OUTGOING_VALIDATION,
OPTIONS,
CREATE_ACCOUNT,
}
interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
@ -27,6 +29,8 @@ interface AccountSetupContract {
object OnNext : Event
object OnBack : Event
data class OnAccountCreated(val accountUuid: AccountUuid) : Event
}
sealed interface Effect {

View file

@ -20,6 +20,9 @@ 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
@ -39,6 +42,7 @@ fun AccountSetupScreen(
outgoingValidationViewModel: ServerValidationContract.ViewModel =
koinViewModel<OutgoingServerValidationViewModel>(),
optionsViewModel: AccountOptionsContract.ViewModel = koinViewModel<AccountOptionsViewModel>(),
createAccountViewModel: CreateAccountContract.ViewModel = koinViewModel<CreateAccountViewModel>(),
) {
val (state, dispatch) = viewModel.observe { effect ->
when (effect) {
@ -101,6 +105,14 @@ fun AccountSetupScreen(
viewModel = optionsViewModel,
)
}
SetupStep.CREATE_ACCOUNT -> {
CreateAccountScreen(
onNext = { accountUuid -> dispatch(Event.OnAccountCreated(accountUuid)) },
onBack = { dispatch(Event.OnBack) },
viewModel = createAccountViewModel,
)
}
}
}

View file

@ -1,20 +1,16 @@
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.common.domain.AccountDomainContract
import app.k9mail.feature.account.common.domain.entity.AuthorizationState
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
import app.k9mail.feature.account.setup.domain.DomainContract.UseCase
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
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
class AccountSetupViewModel(
private val createAccount: UseCase.CreateAccount,
private val accountStateRepository: AccountDomainContract.AccountStateRepository,
initialState: State = State(),
) : BaseViewModel<State, Event, Effect>(initialState), AccountSetupContract.ViewModel {
@ -25,6 +21,8 @@ class AccountSetupViewModel(
Event.OnBack -> onBack()
Event.OnNext -> onNext()
is Event.OnAccountCreated -> navigateNext(event.accountUuid)
}
}
@ -70,7 +68,11 @@ class AccountSetupViewModel(
changeToSetupStep(SetupStep.OPTIONS)
}
SetupStep.OPTIONS -> onFinish()
SetupStep.OPTIONS -> {
changeToSetupStep(SetupStep.CREATE_ACCOUNT)
}
SetupStep.CREATE_ACCOUNT -> Unit
}
}
@ -100,6 +102,8 @@ class AccountSetupViewModel(
} else {
changeToSetupStep(SetupStep.OUTGOING_CONFIG)
}
SetupStep.CREATE_ACCOUNT -> changeToSetupStep(SetupStep.OPTIONS)
}
}
@ -115,27 +119,7 @@ class AccountSetupViewModel(
}
}
private fun onFinish() {
val accountState = accountStateRepository.getState()
viewModelScope.launch {
val result = createAccount.execute(
emailAddress = accountState.emailAddress ?: "",
incomingServerSettings = accountState.incomingServerSettings!!,
outgoingServerSettings = accountState.outgoingServerSettings!!,
authorizationState = accountState.authorizationState?.state,
options = accountState.options!!,
)
if (result is AccountCreatorResult.Success) {
navigateNext(result.accountUuid)
} else {
error("Creating account failed")
}
}
}
private fun navigateNext(accountUuid: String) = emitEffect(Effect.NavigateNext(accountUuid))
private fun navigateNext(accountUuid: AccountUuid) = emitEffect(Effect.NavigateNext(accountUuid.value))
private fun navigateBack() = emitEffect(Effect.NavigateBack)
}

View file

@ -14,6 +14,7 @@ import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCrea
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
import com.fsck.k9.mail.oauth.AuthStateStorage
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
@ -79,6 +80,7 @@ class AccountSetupModuleKtTest : KoinTest {
Boolean::class,
Class.forName("net.openid.appauth.AppAuthConfiguration").kotlin,
InteractionMode::class,
CreateAccountContract.State::class,
),
)

View file

@ -11,6 +11,7 @@ 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
@ -35,6 +36,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
outgoingViewModel = FakeOutgoingServerSettingsViewModel(),
outgoingValidationViewModel = FakeServerValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
createAccountViewModel = FakeCreateAccountViewModel(),
)
}
}
@ -64,6 +66,7 @@ class AccountSetupScreenKtTest : ComposeTest() {
outgoingViewModel = FakeOutgoingServerSettingsViewModel(),
outgoingValidationViewModel = FakeServerValidationViewModel(),
optionsViewModel = FakeAccountOptionsViewModel(),
createAccountViewModel = FakeCreateAccountViewModel(),
)
}
}
@ -89,5 +92,6 @@ class AccountSetupScreenKtTest : ComposeTest() {
SetupStep.OUTGOING_CONFIG -> "OutgoingServerSettingsContent"
SetupStep.OUTGOING_VALIDATION -> "AccountValidationContent"
SetupStep.OPTIONS -> "AccountOptionsContent"
SetupStep.CREATE_ACCOUNT -> "CreateAccountContent"
}
}

View file

@ -7,13 +7,11 @@ 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.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
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.assertThat
import assertk.assertions.isEqualTo
import assertk.assertions.isNull
import assertk.assertions.prop
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.ServerSettings
@ -29,22 +27,8 @@ class AccountSetupViewModelTest {
@Test
fun `should forward step state on next event`() = runTest {
var createAccountEmailAddress: String? = null
var createAccountIncomingServerSettings: ServerSettings? = null
var createAccountOutgoingServerSettings: ServerSettings? = null
var createAccountAuthorizationState: String? = null
var createAccountOptions: AccountOptions? = null
val accountStateRepository = InMemoryAccountStateRepository()
val viewModel = AccountSetupViewModel(
createAccount = { emailAddress, incomingServerSettings, outgoingServerSettings, authState, options ->
createAccountEmailAddress = emailAddress
createAccountIncomingServerSettings = incomingServerSettings
createAccountOutgoingServerSettings = outgoingServerSettings
createAccountAuthorizationState = authState
createAccountOptions = options
AccountCreatorResult.Success("accountUuid")
},
accountStateRepository = accountStateRepository,
)
val turbines = turbinesWithInitialStateCheck(viewModel, State(setupStep = SetupStep.AUTO_CONFIG))
@ -133,9 +117,18 @@ class AccountSetupViewModelTest {
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.OnNext)
viewModel.event(AccountSetupContract.Event.OnAccountCreated(AccountUuid("accountUuid")))
assertThatAndMviTurbinesConsumed(
actual = turbines.effectTurbine.awaitItem(),
@ -143,19 +136,12 @@ class AccountSetupViewModelTest {
) {
isEqualTo(Effect.NavigateNext("accountUuid"))
}
assertThat(createAccountEmailAddress).isEqualTo(EMAIL_ADDRESS)
assertThat(createAccountIncomingServerSettings).isEqualTo(expectedAccountState.incomingServerSettings)
assertThat(createAccountOutgoingServerSettings).isEqualTo(expectedAccountState.outgoingServerSettings)
assertThat(createAccountAuthorizationState).isNull()
assertThat(createAccountOptions).isEqualTo(expectedAccountState.options)
}
@Test
fun `should rewind step state on back event`() = runTest {
val initialState = State(setupStep = SetupStep.OPTIONS)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> AccountCreatorResult.Success("accountUuid") },
accountStateRepository = InMemoryAccountStateRepository(),
initialState = initialState,
)
@ -205,7 +191,6 @@ class AccountSetupViewModelTest {
isAutomaticConfig = true,
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> AccountCreatorResult.Success("accountUuid") },
accountStateRepository = InMemoryAccountStateRepository(),
initialState = initialState,
)
@ -237,7 +222,6 @@ class AccountSetupViewModelTest {
isAutomaticConfig = true,
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> AccountCreatorResult.Success("accountUuid") },
accountStateRepository = InMemoryAccountStateRepository(),
initialState = initialState,
)
@ -269,7 +253,6 @@ class AccountSetupViewModelTest {
isAutomaticConfig = true,
)
val viewModel = AccountSetupViewModel(
createAccount = { _, _, _, _, _ -> AccountCreatorResult.Success("accountUuid") },
accountStateRepository = InMemoryAccountStateRepository(),
initialState = initialState,
)

View file

@ -6,7 +6,7 @@ import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountContract.E
import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountContract.State
import app.k9mail.feature.account.setup.ui.createaccount.CreateAccountContract.ViewModel
class FakeCreateAccountViewModel(initialState: State) :
class FakeCreateAccountViewModel(initialState: State = State()) :
BaseViewModel<State, Event, Effect>(initialState), ViewModel {
val events = mutableListOf<Event>()