Remove AccountOptions
This commit is contained in:
parent
38a1be4f07
commit
34d0da026b
17 changed files with 22 additions and 901 deletions
|
@ -15,9 +15,10 @@ import app.k9mail.feature.account.setup.ui.autodiscovery.AccountAutoDiscoveryCon
|
|||
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
|
||||
import app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsValidator
|
||||
import app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.sync.SyncOptionsViewModel
|
||||
import app.k9mail.feature.account.setup.ui.specialfolders.SpecialFoldersContract
|
||||
import app.k9mail.feature.account.setup.ui.specialfolders.SpecialFoldersFormUiModel
|
||||
import app.k9mail.feature.account.setup.ui.specialfolders.SpecialFoldersViewModel
|
||||
|
@ -61,7 +62,7 @@ val featureAccountSetupModule: Module = module {
|
|||
}
|
||||
|
||||
factory<AccountAutoDiscoveryContract.Validator> { AccountAutoDiscoveryValidator() }
|
||||
factory<AccountOptionsContract.Validator> { AccountOptionsValidator() }
|
||||
factory<DisplayOptionsContract.Validator> { DisplayOptionsValidator() }
|
||||
|
||||
viewModel {
|
||||
AccountAutoDiscoveryViewModel(
|
||||
|
@ -107,12 +108,18 @@ val featureAccountSetupModule: Module = module {
|
|||
}
|
||||
|
||||
viewModel {
|
||||
AccountOptionsViewModel(
|
||||
DisplayOptionsViewModel(
|
||||
validator = get(),
|
||||
accountStateRepository = get(),
|
||||
)
|
||||
}
|
||||
|
||||
viewModel {
|
||||
SyncOptionsViewModel(
|
||||
accountStateRepository = get(),
|
||||
)
|
||||
}
|
||||
|
||||
viewModel {
|
||||
CreateAccountViewModel(
|
||||
createAccount = get(),
|
||||
|
|
|
@ -11,7 +11,7 @@ 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.toServerSettings
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsContract
|
||||
|
||||
internal fun AccountAutoDiscoveryContract.State.toAccountState(): AccountState {
|
||||
return AccountState(
|
||||
|
@ -67,8 +67,8 @@ internal fun AccountAutoDiscoveryContract.State.toOutgoingConfigState(): Outgoin
|
|||
}
|
||||
}
|
||||
|
||||
internal fun AccountAutoDiscoveryContract.State.toOptionsState(): AccountOptionsContract.State {
|
||||
return AccountOptionsContract.State(
|
||||
internal fun AccountAutoDiscoveryContract.State.toOptionsState(): DisplayOptionsContract.State {
|
||||
return DisplayOptionsContract.State(
|
||||
accountName = StringInputField(value = emailAddress.value),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.imePadding
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.requiredHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.testTag
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.k9mail.core.ui.compose.common.PreviewDevices
|
||||
import app.k9mail.core.ui.compose.designsystem.atom.text.TextOverline
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.SelectInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.SwitchInput
|
||||
import app.k9mail.core.ui.compose.designsystem.molecule.input.TextInput
|
||||
import app.k9mail.core.ui.compose.designsystem.template.ResponsiveWidthContainer
|
||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||
import app.k9mail.core.ui.compose.theme.MainTheme
|
||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||
import app.k9mail.feature.account.common.ui.item.defaultHeadlineItemPadding
|
||||
import app.k9mail.feature.account.common.ui.item.defaultItemPadding
|
||||
import app.k9mail.feature.account.setup.R
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Suppress("LongMethod")
|
||||
@Composable
|
||||
internal fun AccountOptionsContent(
|
||||
state: State,
|
||||
onEvent: (Event) -> Unit,
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val resources = LocalContext.current.resources
|
||||
|
||||
ResponsiveWidthContainer(
|
||||
modifier = Modifier
|
||||
.testTag("AccountOptionsContent")
|
||||
.consumeWindowInsets(contentPadding)
|
||||
.padding(contentPadding)
|
||||
.then(modifier),
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.imePadding(),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
verticalArrangement = Arrangement.spacedBy(MainTheme.spacings.default),
|
||||
) {
|
||||
item {
|
||||
TextOverline(
|
||||
text = stringResource(id = R.string.account_setup_options_section_display_options),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(defaultHeadlineItemPadding()),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextInput(
|
||||
text = state.accountName.value,
|
||||
errorMessage = state.accountName.error?.toResourceString(resources),
|
||||
onTextChange = { onEvent(Event.OnAccountNameChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_options_account_name_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextInput(
|
||||
text = state.displayName.value,
|
||||
errorMessage = state.displayName.error?.toResourceString(resources),
|
||||
onTextChange = { onEvent(Event.OnDisplayNameChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_options_display_name_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
isRequired = true,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextInput(
|
||||
text = state.emailSignature.value,
|
||||
errorMessage = state.emailSignature.error?.toResourceString(resources),
|
||||
onTextChange = { onEvent(Event.OnEmailSignatureChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_options_email_signature_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
isSingleLine = false,
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
TextOverline(
|
||||
text = stringResource(id = R.string.account_setup_options_section_sync_options),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(defaultHeadlineItemPadding()),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SelectInput(
|
||||
options = EmailCheckFrequency.all(),
|
||||
optionToStringTransformation = { it.toResourceString(resources) },
|
||||
selectedOption = state.checkFrequency,
|
||||
onOptionChange = { onEvent(Event.OnCheckFrequencyChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_options_account_check_frequency_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SelectInput(
|
||||
options = EmailDisplayCount.all(),
|
||||
optionToStringTransformation = { it.toResourceString(resources) },
|
||||
selectedOption = state.messageDisplayCount,
|
||||
onOptionChange = { onEvent(Event.OnMessageDisplayCountChanged(it)) },
|
||||
label = stringResource(id = R.string.account_setup_options_email_display_count_label),
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
SwitchInput(
|
||||
text = stringResource(id = R.string.account_setup_options_show_notifications_label),
|
||||
checked = state.showNotification,
|
||||
onCheckedChange = { onEvent(Event.OnShowNotificationChanged(it)) },
|
||||
contentPadding = defaultItemPadding(),
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
Spacer(modifier = Modifier.requiredHeight(MainTheme.sizes.smaller))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewDevices
|
||||
internal fun AccountOptionsContentK9Preview() {
|
||||
K9Theme {
|
||||
AccountOptionsContent(
|
||||
state = State(),
|
||||
onEvent = {},
|
||||
contentPadding = PaddingValues(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewDevices
|
||||
internal fun AccountOptionsContentThunderbirdPreview() {
|
||||
ThunderbirdTheme {
|
||||
AccountOptionsContent(
|
||||
state = State(),
|
||||
onEvent = {},
|
||||
contentPadding = PaddingValues(),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
|
||||
import app.k9mail.feature.account.common.domain.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
|
||||
interface AccountOptionsContract {
|
||||
|
||||
interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
|
||||
|
||||
data class State(
|
||||
val accountName: StringInputField = StringInputField(),
|
||||
val displayName: StringInputField = StringInputField(),
|
||||
val emailSignature: StringInputField = StringInputField(),
|
||||
val checkFrequency: EmailCheckFrequency = EmailCheckFrequency.DEFAULT,
|
||||
val messageDisplayCount: EmailDisplayCount = EmailDisplayCount.DEFAULT,
|
||||
val showNotification: Boolean = false,
|
||||
)
|
||||
|
||||
sealed interface Event {
|
||||
data class OnAccountNameChanged(val accountName: String) : Event
|
||||
data class OnDisplayNameChanged(val displayName: String) : Event
|
||||
data class OnEmailSignatureChanged(val emailSignature: String) : Event
|
||||
data class OnCheckFrequencyChanged(val checkFrequency: EmailCheckFrequency) : Event
|
||||
data class OnMessageDisplayCountChanged(val messageDisplayCount: EmailDisplayCount) : Event
|
||||
data class OnShowNotificationChanged(val showNotification: Boolean) : Event
|
||||
|
||||
object LoadAccountState : Event
|
||||
|
||||
object OnNextClicked : Event
|
||||
object OnBackClicked : Event
|
||||
}
|
||||
|
||||
sealed interface Effect {
|
||||
object NavigateNext : Effect
|
||||
object NavigateBack : Effect
|
||||
}
|
||||
|
||||
interface Validator {
|
||||
fun validateAccountName(accountName: String): ValidationResult
|
||||
fun validateDisplayName(displayName: String): ValidationResult
|
||||
fun validateEmailSignature(emailSignature: String): ValidationResult
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import app.k9mail.core.ui.compose.common.PreviewDevices
|
||||
import app.k9mail.core.ui.compose.common.mvi.observe
|
||||
import app.k9mail.core.ui.compose.designsystem.template.Scaffold
|
||||
import app.k9mail.core.ui.compose.theme.K9Theme
|
||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||
import app.k9mail.feature.account.common.ui.AccountTopAppBar
|
||||
import app.k9mail.feature.account.common.ui.WizardNavigationBar
|
||||
import app.k9mail.feature.account.common.ui.preview.PreviewAccountStateRepository
|
||||
import app.k9mail.feature.account.setup.R.string
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.ViewModel
|
||||
|
||||
@Composable
|
||||
internal fun AccountOptionsScreen(
|
||||
onNext: () -> Unit,
|
||||
onBack: () -> Unit,
|
||||
viewModel: ViewModel,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val (state, dispatch) = viewModel.observe { effect ->
|
||||
when (effect) {
|
||||
Effect.NavigateBack -> onBack()
|
||||
Effect.NavigateNext -> onNext()
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = Unit) {
|
||||
dispatch(Event.LoadAccountState)
|
||||
}
|
||||
|
||||
BackHandler {
|
||||
dispatch(Event.OnBackClicked)
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
topBar = {
|
||||
AccountTopAppBar(
|
||||
title = stringResource(id = string.account_setup_options_top_bar_title),
|
||||
)
|
||||
},
|
||||
bottomBar = {
|
||||
WizardNavigationBar(
|
||||
onNextClick = { dispatch(Event.OnNextClicked) },
|
||||
onBackClick = { dispatch(Event.OnBackClicked) },
|
||||
)
|
||||
},
|
||||
modifier = modifier,
|
||||
) { innerPadding ->
|
||||
AccountOptionsContent(
|
||||
state = state.value,
|
||||
onEvent = { dispatch(it) },
|
||||
contentPadding = innerPadding,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewDevices
|
||||
internal fun AccountOptionsScreenK9Preview() {
|
||||
K9Theme {
|
||||
AccountOptionsScreen(
|
||||
onNext = {},
|
||||
onBack = {},
|
||||
viewModel = AccountOptionsViewModel(
|
||||
validator = AccountOptionsValidator(),
|
||||
accountStateRepository = PreviewAccountStateRepository(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@PreviewDevices
|
||||
internal fun AccountOptionsScreenThunderbirdPreview() {
|
||||
ThunderbirdTheme {
|
||||
AccountOptionsScreen(
|
||||
onNext = {},
|
||||
onBack = {},
|
||||
viewModel = AccountOptionsViewModel(
|
||||
validator = AccountOptionsValidator(),
|
||||
accountStateRepository = PreviewAccountStateRepository(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,39 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
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.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
|
||||
internal fun AccountState.toAccountOptionsState(): State {
|
||||
val options = options
|
||||
return if (options == null) {
|
||||
State(
|
||||
accountName = StringInputField(emailAddress ?: ""),
|
||||
// displayName = StringInputField(""),
|
||||
// TODO: get display name from: preferences.defaultAccount?.senderName ?: ""
|
||||
)
|
||||
} else {
|
||||
State(
|
||||
accountName = StringInputField(options.accountName),
|
||||
displayName = StringInputField(options.displayName),
|
||||
emailSignature = StringInputField(options.emailSignature ?: ""),
|
||||
checkFrequency = EmailCheckFrequency.fromMinutes(options.checkFrequencyInMinutes),
|
||||
messageDisplayCount = EmailDisplayCount.fromCount(options.messageDisplayCount),
|
||||
showNotification = options.showNotification,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun State.toAccountOptions(): AccountOptions {
|
||||
return AccountOptions(
|
||||
accountName = accountName.value,
|
||||
displayName = displayName.value,
|
||||
emailSignature = emailSignature.value.takeIf { it.isNotEmpty() },
|
||||
checkFrequencyInMinutes = checkFrequency.minutes,
|
||||
messageDisplayCount = messageDisplayCount.count,
|
||||
showNotification = showNotification,
|
||||
)
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import android.content.res.Resources
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.feature.account.setup.R
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateAccountName.ValidateAccountNameError
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateAccountName.ValidateAccountNameError.BlankAccountName
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateDisplayName.ValidateDisplayNameError
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateDisplayName.ValidateDisplayNameError.EmptyDisplayName
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailSignature.ValidateEmailSignatureError
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailSignature.ValidateEmailSignatureError.BlankEmailSignature
|
||||
|
||||
internal fun EmailDisplayCount.toResourceString(resources: Resources) = resources.getQuantityString(
|
||||
R.plurals.account_setup_options_email_display_count_messages,
|
||||
count,
|
||||
count,
|
||||
)
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal fun EmailCheckFrequency.toResourceString(resources: Resources): String {
|
||||
return when (minutes) {
|
||||
-1 -> resources.getString(R.string.account_setup_options_email_check_frequency_never)
|
||||
|
||||
in 1..59 -> resources.getQuantityString(
|
||||
R.plurals.account_setup_options_email_check_frequency_minutes,
|
||||
minutes,
|
||||
minutes,
|
||||
)
|
||||
|
||||
else -> resources.getQuantityString(
|
||||
R.plurals.account_setup_options_email_check_frequency_hours,
|
||||
(minutes / 60),
|
||||
(minutes / 60),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun ValidationError.toResourceString(resources: Resources): String {
|
||||
return when (this) {
|
||||
is ValidateAccountNameError -> toAccountNameErrorString(resources)
|
||||
is ValidateDisplayNameError -> toDisplayNameErrorString(resources)
|
||||
is ValidateEmailSignatureError -> toEmailSignatureErrorString(resources)
|
||||
else -> throw IllegalArgumentException("Unknown error: $this")
|
||||
}
|
||||
}
|
||||
|
||||
private fun ValidateAccountNameError.toAccountNameErrorString(resources: Resources): String {
|
||||
return when (this) {
|
||||
is BlankAccountName -> resources.getString(R.string.account_setup_options_account_name_error_blank)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ValidateDisplayNameError.toDisplayNameErrorString(resources: Resources): String {
|
||||
return when (this) {
|
||||
is EmptyDisplayName -> resources.getString(R.string.account_setup_options_display_name_error_required)
|
||||
}
|
||||
}
|
||||
|
||||
private fun ValidateEmailSignatureError.toEmailSignatureErrorString(resources: Resources): String {
|
||||
return when (this) {
|
||||
is BlankEmailSignature -> resources.getString(R.string.account_setup_options_email_signature_error_blank)
|
||||
}
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateAccountName
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateDisplayName
|
||||
import app.k9mail.feature.account.setup.domain.usecase.ValidateEmailSignature
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Validator
|
||||
|
||||
internal class AccountOptionsValidator(
|
||||
private val accountNameValidator: ValidateAccountName = ValidateAccountName(),
|
||||
private val displayNameValidator: ValidateDisplayName = ValidateDisplayName(),
|
||||
private val emailSignatureValidator: ValidateEmailSignature = ValidateEmailSignature(),
|
||||
) : Validator {
|
||||
override fun validateAccountName(accountName: String): ValidationResult {
|
||||
return accountNameValidator.execute(accountName)
|
||||
}
|
||||
|
||||
override fun validateDisplayName(displayName: String): ValidationResult {
|
||||
return displayNameValidator.execute(displayName)
|
||||
}
|
||||
|
||||
override fun validateEmailSignature(emailSignature: String): ValidationResult {
|
||||
return emailSignatureValidator.execute(emailSignature)
|
||||
}
|
||||
}
|
|
@ -1,100 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||
import app.k9mail.feature.account.common.domain.AccountDomainContract
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Validator
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.ViewModel
|
||||
|
||||
internal class AccountOptionsViewModel(
|
||||
private val validator: Validator,
|
||||
private val accountStateRepository: AccountDomainContract.AccountStateRepository,
|
||||
initialState: State? = null,
|
||||
) : BaseViewModel<State, Event, Effect>(
|
||||
initialState = initialState ?: accountStateRepository.getState().toAccountOptionsState(),
|
||||
),
|
||||
ViewModel {
|
||||
|
||||
override fun event(event: Event) {
|
||||
when (event) {
|
||||
Event.LoadAccountState -> handleOneTimeEvent(event, ::loadAccountState)
|
||||
|
||||
is Event.OnAccountNameChanged -> updateState { state ->
|
||||
state.copy(
|
||||
accountName = state.accountName.updateValue(event.accountName),
|
||||
)
|
||||
}
|
||||
|
||||
is Event.OnDisplayNameChanged -> updateState {
|
||||
it.copy(
|
||||
displayName = it.displayName.updateValue(event.displayName),
|
||||
)
|
||||
}
|
||||
|
||||
is Event.OnEmailSignatureChanged -> updateState {
|
||||
it.copy(
|
||||
emailSignature = it.emailSignature.updateValue(event.emailSignature),
|
||||
)
|
||||
}
|
||||
|
||||
is Event.OnCheckFrequencyChanged -> updateState {
|
||||
it.copy(
|
||||
checkFrequency = event.checkFrequency,
|
||||
)
|
||||
}
|
||||
|
||||
is Event.OnMessageDisplayCountChanged -> updateState { state ->
|
||||
state.copy(
|
||||
messageDisplayCount = event.messageDisplayCount,
|
||||
)
|
||||
}
|
||||
|
||||
is Event.OnShowNotificationChanged -> updateState { state ->
|
||||
state.copy(
|
||||
showNotification = event.showNotification,
|
||||
)
|
||||
}
|
||||
|
||||
Event.OnNextClicked -> submit()
|
||||
Event.OnBackClicked -> navigateBack()
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAccountState() {
|
||||
updateState {
|
||||
accountStateRepository.getState().toAccountOptionsState()
|
||||
}
|
||||
}
|
||||
|
||||
private fun submit() = with(state.value) {
|
||||
val accountNameResult = validator.validateAccountName(accountName.value)
|
||||
val displayNameResult = validator.validateDisplayName(displayName.value)
|
||||
val emailSignatureResult = validator.validateEmailSignature(emailSignature.value)
|
||||
|
||||
val hasError = listOf(
|
||||
accountNameResult,
|
||||
displayNameResult,
|
||||
emailSignatureResult,
|
||||
).any { it is ValidationResult.Failure }
|
||||
|
||||
updateState {
|
||||
it.copy(
|
||||
accountName = it.accountName.updateFromValidationResult(accountNameResult),
|
||||
displayName = it.displayName.updateFromValidationResult(displayNameResult),
|
||||
emailSignature = it.emailSignature.updateFromValidationResult(emailSignatureResult),
|
||||
)
|
||||
}
|
||||
|
||||
if (!hasError) {
|
||||
accountStateRepository.setOptions(state.value.toAccountOptions())
|
||||
navigateNext()
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateBack() = emitEffect(Effect.NavigateBack)
|
||||
|
||||
private fun navigateNext() = emitEffect(Effect.NavigateNext)
|
||||
}
|
|
@ -14,7 +14,8 @@ import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCrea
|
|||
import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult
|
||||
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 app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.sync.SyncOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.specialfolders.SpecialFoldersContract
|
||||
import com.fsck.k9.mail.oauth.AuthStateStorage
|
||||
import com.fsck.k9.mail.oauth.OAuth2TokenProvider
|
||||
|
@ -71,7 +72,8 @@ class AccountSetupModuleKtTest : KoinTest {
|
|||
ServerValidationContract.State::class,
|
||||
IncomingServerSettingsContract.State::class,
|
||||
OutgoingServerSettingsContract.State::class,
|
||||
AccountOptionsContract.State::class,
|
||||
DisplayOptionsContract.State::class,
|
||||
SyncOptionsContract.State::class,
|
||||
AccountState::class,
|
||||
ServerCertificateErrorContract.State::class,
|
||||
AuthStateStorage::class,
|
||||
|
|
|
@ -15,7 +15,7 @@ import app.k9mail.feature.account.server.settings.ui.outgoing.OutgoingServerSett
|
|||
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.toConnectionSecurity
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract
|
||||
import app.k9mail.feature.account.setup.ui.options.display.DisplayOptionsContract
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
@ -146,7 +146,7 @@ class AccountAutoDiscoveryStateMapperKtTest {
|
|||
fun `should map to OptionsState when empty`() {
|
||||
val optionsState = EMPTY_STATE.toOptionsState()
|
||||
|
||||
assertThat(optionsState).isEqualTo(AccountOptionsContract.State())
|
||||
assertThat(optionsState).isEqualTo(DisplayOptionsContract.State())
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -154,7 +154,7 @@ class AccountAutoDiscoveryStateMapperKtTest {
|
|||
val optionsState = EMAIL_PASSWORD_STATE.toOptionsState()
|
||||
|
||||
assertThat(optionsState).isEqualTo(
|
||||
AccountOptionsContract.State(
|
||||
DisplayOptionsContract.State(
|
||||
accountName = StringInputField(value = EMAIL_ADDRESS),
|
||||
),
|
||||
)
|
||||
|
|
|
@ -1,45 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.ui.compose.testing.ComposeTest
|
||||
import app.k9mail.core.ui.compose.testing.setContent
|
||||
import app.k9mail.core.ui.compose.theme.ThunderbirdTheme
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class AccountOptionsScreenKtTest : ComposeTest() {
|
||||
|
||||
@Test
|
||||
fun `should delegate navigation effects`() = runTest {
|
||||
val initialState = State()
|
||||
val viewModel = FakeAccountOptionsViewModel(initialState)
|
||||
var onNextCounter = 0
|
||||
var onBackCounter = 0
|
||||
|
||||
setContent {
|
||||
ThunderbirdTheme {
|
||||
AccountOptionsScreen(
|
||||
onNext = { onNextCounter++ },
|
||||
onBack = { onBackCounter++ },
|
||||
viewModel = viewModel,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(0)
|
||||
assertThat(onBackCounter).isEqualTo(0)
|
||||
|
||||
viewModel.effect(Effect.NavigateNext)
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(1)
|
||||
assertThat(onBackCounter).isEqualTo(0)
|
||||
|
||||
viewModel.effect(Effect.NavigateBack)
|
||||
|
||||
assertThat(onNextCounter).isEqualTo(1)
|
||||
assertThat(onBackCounter).isEqualTo(1)
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.feature.account.common.domain.entity.AccountOptions
|
||||
import app.k9mail.feature.account.common.domain.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import assertk.assertions.isNull
|
||||
import org.junit.Test
|
||||
|
||||
class AccountOptionsStateMapperKtTest {
|
||||
|
||||
@Test
|
||||
fun `should map state to account options`() {
|
||||
val state = AccountOptionsContract.State(
|
||||
accountName = StringInputField("accountName"),
|
||||
displayName = StringInputField("displayName"),
|
||||
emailSignature = StringInputField("emailSignature"),
|
||||
checkFrequency = EmailCheckFrequency.EVERY_2_HOURS,
|
||||
messageDisplayCount = EmailDisplayCount.MESSAGES_100,
|
||||
showNotification = true,
|
||||
)
|
||||
|
||||
val result = state.toAccountOptions()
|
||||
|
||||
assertThat(result).isEqualTo(
|
||||
AccountOptions(
|
||||
accountName = "accountName",
|
||||
displayName = "displayName",
|
||||
emailSignature = "emailSignature",
|
||||
checkFrequencyInMinutes = 120,
|
||||
messageDisplayCount = 100,
|
||||
showNotification = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `empty signature should map to null`() {
|
||||
val state = AccountOptionsContract.State(emailSignature = StringInputField(""))
|
||||
|
||||
val result = state.toAccountOptions()
|
||||
|
||||
assertThat(result.emailSignature).isNull()
|
||||
}
|
||||
}
|
|
@ -1,28 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.feature.account.common.domain.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
class AccountOptionsStateTest {
|
||||
|
||||
@Test
|
||||
fun `should set default values`() {
|
||||
val state = State()
|
||||
|
||||
assertThat(state).isEqualTo(
|
||||
State(
|
||||
accountName = StringInputField(),
|
||||
displayName = StringInputField(),
|
||||
emailSignature = StringInputField(),
|
||||
checkFrequency = EmailCheckFrequency.DEFAULT,
|
||||
messageDisplayCount = EmailDisplayCount.DEFAULT,
|
||||
showNotification = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,192 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.cash.turbine.testIn
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationError
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.core.ui.compose.testing.MainDispatcherRule
|
||||
import app.k9mail.core.ui.compose.testing.mvi.eventStateTest
|
||||
import app.k9mail.feature.account.common.data.InMemoryAccountStateRepository
|
||||
import app.k9mail.feature.account.common.domain.input.StringInputField
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailCheckFrequency
|
||||
import app.k9mail.feature.account.setup.domain.entity.EmailDisplayCount
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.assertThatAndTurbinesConsumed
|
||||
import assertk.assertions.isEqualTo
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class AccountOptionsViewModelTest {
|
||||
|
||||
@get:Rule
|
||||
val mainDispatcherRule = MainDispatcherRule()
|
||||
|
||||
private val testSubject = AccountOptionsViewModel(
|
||||
validator = FakeAccountOptionsValidator(),
|
||||
accountStateRepository = InMemoryAccountStateRepository(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `should change state when OnAccountNameChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnAccountNameChanged("accountName"),
|
||||
expectedState = State(accountName = StringInputField(value = "accountName")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when OnDisplayNameChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnDisplayNameChanged("displayName"),
|
||||
expectedState = State(displayName = StringInputField(value = "displayName")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when OnEmailSignatureChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnEmailSignatureChanged("emailSignature"),
|
||||
expectedState = State(emailSignature = StringInputField(value = "emailSignature")),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when OnCheckFrequencyChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnCheckFrequencyChanged(EmailCheckFrequency.EVERY_12_HOURS),
|
||||
expectedState = State(checkFrequency = EmailCheckFrequency.EVERY_12_HOURS),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when OnMessageDisplayCountChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnMessageDisplayCountChanged(EmailDisplayCount.MESSAGES_1000),
|
||||
expectedState = State(messageDisplayCount = EmailDisplayCount.MESSAGES_1000),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state when OnShowNotificationChanged event is received`() = runTest {
|
||||
eventStateTest(
|
||||
viewModel = testSubject,
|
||||
initialState = State(),
|
||||
event = Event.OnShowNotificationChanged(true),
|
||||
expectedState = State(showNotification = true),
|
||||
coroutineScope = backgroundScope,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state and emit NavigateNext effect when OnNextClicked event received and input valid`() =
|
||||
runTest {
|
||||
val viewModel = testSubject
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
viewModel.event(Event.OnNextClicked)
|
||||
|
||||
assertThat(stateTurbine.awaitItem()).isEqualTo(
|
||||
State(
|
||||
accountName = StringInputField(value = "", isValid = true),
|
||||
displayName = StringInputField(value = "", isValid = true),
|
||||
emailSignature = StringInputField(value = "", isValid = true),
|
||||
),
|
||||
)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.NavigateNext)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should change state and not emit effect when OnNextClicked event received and input invalid`() =
|
||||
runTest {
|
||||
val viewModel = AccountOptionsViewModel(
|
||||
validator = FakeAccountOptionsValidator(
|
||||
accountNameAnswer = ValidationResult.Failure(TestError),
|
||||
),
|
||||
accountStateRepository = InMemoryAccountStateRepository(),
|
||||
)
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
viewModel.event(Event.OnNextClicked)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(
|
||||
State(
|
||||
accountName = StringInputField(value = "", error = TestError, isValid = false),
|
||||
displayName = StringInputField(value = "", isValid = true),
|
||||
emailSignature = StringInputField(value = "", isValid = true),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should emit NavigateBack effect when OnBackClicked event received`() = runTest {
|
||||
val viewModel = testSubject
|
||||
val stateTurbine = viewModel.state.testIn(backgroundScope)
|
||||
val effectTurbine = viewModel.effect.testIn(backgroundScope)
|
||||
val turbines = listOf(stateTurbine, effectTurbine)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = stateTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(State())
|
||||
}
|
||||
|
||||
viewModel.event(Event.OnBackClicked)
|
||||
|
||||
assertThatAndTurbinesConsumed(
|
||||
actual = effectTurbine.awaitItem(),
|
||||
turbines = turbines,
|
||||
) {
|
||||
isEqualTo(Effect.NavigateBack)
|
||||
}
|
||||
}
|
||||
|
||||
private object TestError : ValidationError
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.common.domain.usecase.validation.ValidationResult
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Validator
|
||||
|
||||
internal class FakeAccountOptionsValidator(
|
||||
private val accountNameAnswer: ValidationResult = ValidationResult.Success,
|
||||
private val displayNameAnswer: ValidationResult = ValidationResult.Success,
|
||||
private val emailSignatureAnswer: ValidationResult = ValidationResult.Success,
|
||||
) : Validator {
|
||||
override fun validateAccountName(accountName: String): ValidationResult = accountNameAnswer
|
||||
override fun validateDisplayName(displayName: String): ValidationResult = displayNameAnswer
|
||||
override fun validateEmailSignature(emailSignature: String): ValidationResult = emailSignatureAnswer
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
package app.k9mail.feature.account.setup.ui.options
|
||||
|
||||
import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Effect
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.Event
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.State
|
||||
import app.k9mail.feature.account.setup.ui.options.AccountOptionsContract.ViewModel
|
||||
|
||||
class FakeAccountOptionsViewModel(
|
||||
initialState: State = State(),
|
||||
) : BaseViewModel<State, Event, Effect>(initialState), ViewModel {
|
||||
|
||||
val events = mutableListOf<Event>()
|
||||
|
||||
override fun event(event: Event) {
|
||||
events.add(event)
|
||||
}
|
||||
|
||||
fun effect(effect: Effect) {
|
||||
emitEffect(effect)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue