From a7ca61883c4f7a2efdfecf7d5da0f25cefdda740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Fri, 15 Sep 2023 11:03:58 +0200 Subject: [PATCH 1/4] Add account uuid --- .../k9mail/feature/account/common/domain/entity/Account.kt | 1 + .../feature/account/common/domain/entity/AccountState.kt | 1 + .../common/data/InMemoryAccountStateRepositoryTest.kt | 5 +++++ .../feature/account/setup/domain/usecase/CreateAccount.kt | 3 +++ .../account/setup/domain/usecase/CreateAccountTest.kt | 2 ++ 5 files changed, 12 insertions(+) diff --git a/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/Account.kt b/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/Account.kt index a5f0c7fe976..9fed2db7b78 100644 --- a/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/Account.kt +++ b/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/Account.kt @@ -3,6 +3,7 @@ package app.k9mail.feature.account.common.domain.entity import com.fsck.k9.mail.ServerSettings data class Account( + val uuid: String, val emailAddress: String, val incomingServerSettings: ServerSettings, val outgoingServerSettings: ServerSettings, diff --git a/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/AccountState.kt b/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/AccountState.kt index b683854de37..ae10bd2f3ac 100644 --- a/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/AccountState.kt +++ b/feature/account/common/src/main/kotlin/app/k9mail/feature/account/common/domain/entity/AccountState.kt @@ -3,6 +3,7 @@ package app.k9mail.feature.account.common.domain.entity import com.fsck.k9.mail.ServerSettings data class AccountState( + val uuid: String? = null, val emailAddress: String? = null, val incomingServerSettings: ServerSettings? = null, val outgoingServerSettings: ServerSettings? = null, diff --git a/feature/account/common/src/test/kotlin/app/k9mail/feature/account/common/data/InMemoryAccountStateRepositoryTest.kt b/feature/account/common/src/test/kotlin/app/k9mail/feature/account/common/data/InMemoryAccountStateRepositoryTest.kt index 3628d45ef00..0e22332bfb5 100644 --- a/feature/account/common/src/test/kotlin/app/k9mail/feature/account/common/data/InMemoryAccountStateRepositoryTest.kt +++ b/feature/account/common/src/test/kotlin/app/k9mail/feature/account/common/data/InMemoryAccountStateRepositoryTest.kt @@ -20,6 +20,7 @@ class InMemoryAccountStateRepositoryTest { assertThat(result).isEqualTo( AccountState( + uuid = null, emailAddress = null, incomingServerSettings = null, outgoingServerSettings = null, @@ -33,6 +34,7 @@ class InMemoryAccountStateRepositoryTest { fun `should save state`() { val testSubject = InMemoryAccountStateRepository( AccountState( + uuid = "uuid", emailAddress = "emailAddress", incomingServerSettings = INCOMING_SERVER_SETTINGS, outgoingServerSettings = OUTGOING_SERVER_SETTINGS, @@ -41,6 +43,7 @@ class InMemoryAccountStateRepositoryTest { ), ) val newState = AccountState( + uuid = "uuid2", emailAddress = "emailAddress2", incomingServerSettings = INCOMING_SERVER_SETTINGS.copy(host = "imap2.example.org"), outgoingServerSettings = OUTGOING_SERVER_SETTINGS.copy(host = "smtp2.example.org"), @@ -114,6 +117,7 @@ class InMemoryAccountStateRepositoryTest { fun `should clear state`() { val testSubject = InMemoryAccountStateRepository( AccountState( + uuid = "uuid", emailAddress = "emailAddress", incomingServerSettings = INCOMING_SERVER_SETTINGS, outgoingServerSettings = OUTGOING_SERVER_SETTINGS, @@ -126,6 +130,7 @@ class InMemoryAccountStateRepositoryTest { assertThat(testSubject.getState()).isEqualTo( AccountState( + uuid = null, emailAddress = null, incomingServerSettings = null, outgoingServerSettings = null, diff --git a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccount.kt b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccount.kt index a9a199278f4..392ee058fdb 100644 --- a/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccount.kt +++ b/feature/account/setup/src/main/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccount.kt @@ -6,9 +6,11 @@ import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCrea import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult import app.k9mail.feature.account.setup.domain.DomainContract.UseCase import com.fsck.k9.mail.ServerSettings +import java.util.UUID class CreateAccount( private val accountCreator: AccountCreator, + private val uuidGenerator: () -> String = { UUID.randomUUID().toString() }, ) : UseCase.CreateAccount { override suspend fun execute( emailAddress: String, @@ -18,6 +20,7 @@ class CreateAccount( options: AccountOptions, ): String { val account = Account( + uuid = uuidGenerator(), emailAddress = emailAddress, incomingServerSettings = incomingServerSettings, outgoingServerSettings = outgoingServerSettings, diff --git a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccountTest.kt b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccountTest.kt index 81249044e97..eac495081d1 100644 --- a/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccountTest.kt +++ b/feature/account/setup/src/test/kotlin/app/k9mail/feature/account/setup/domain/usecase/CreateAccountTest.kt @@ -21,6 +21,7 @@ class CreateAccountTest { recordedAccount = account AccountCreatorResult.Success(accountUuid = "uuid") }, + uuidGenerator = { "uuid" }, ) val emailAddress = "user@example.com" @@ -65,6 +66,7 @@ class CreateAccountTest { assertThat(result).isEqualTo("uuid") assertThat(recordedAccount).isEqualTo( Account( + uuid = "uuid", emailAddress = emailAddress, incomingServerSettings = incomingServerSettings, outgoingServerSettings = outgoingServerSettings, From 5694e0934bc4454aaf7799a529eb08e4a596c5d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Wed, 6 Sep 2023 15:18:33 +0200 Subject: [PATCH 2/4] Add `account:edit` module --- feature/account/edit/build.gradle.kts | 28 +++++++++++++++++++ .../feature/account/edit/AccountEditModule.kt | 9 ++++++ settings.gradle.kts | 1 + 3 files changed, 38 insertions(+) create mode 100644 feature/account/edit/build.gradle.kts create mode 100644 feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditModule.kt diff --git a/feature/account/edit/build.gradle.kts b/feature/account/edit/build.gradle.kts new file mode 100644 index 00000000000..3542be52672 --- /dev/null +++ b/feature/account/edit/build.gradle.kts @@ -0,0 +1,28 @@ +plugins { + id(ThunderbirdPlugins.Library.androidCompose) +} + +android { + namespace = "app.k9mail.feature.account.edit" + resourcePrefix = "account_edit_" + + buildTypes { + debug { + manifestPlaceholders["appAuthRedirectScheme"] = "FIXME: override this in your app project" + } + release { + manifestPlaceholders["appAuthRedirectScheme"] = "FIXME: override this in your app project" + } + } +} + +dependencies { + implementation(projects.core.ui.compose.designsystem) + implementation(projects.core.common) + + implementation(projects.feature.account.common) + implementation(projects.feature.account.oauth) + implementation(projects.feature.account.server.validation) + + testImplementation(projects.core.ui.compose.testing) +} diff --git a/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditModule.kt b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditModule.kt new file mode 100644 index 00000000000..03413ba6c4b --- /dev/null +++ b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditModule.kt @@ -0,0 +1,9 @@ +package app.k9mail.feature.account.edit + +import app.k9mail.core.common.coreCommonModule +import app.k9mail.feature.account.oauth.featureAccountOAuthModule +import org.koin.dsl.module + +val accountEditModule = module { + includes(coreCommonModule, featureAccountOAuthModule) +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 4e7da6fab2b..391b8e99f6b 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -45,6 +45,7 @@ include( include( ":feature:account:common", + ":feature:account:edit", ":feature:account:oauth", ":feature:account:setup", ":feature:account:server:certificate", From 61d955f774843e827e7ecccab04402ac6add3a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Wed, 13 Sep 2023 17:53:41 +0200 Subject: [PATCH 3/4] Add AccountUpdater contract and implementation --- app-feature-preview/build.gradle.kts | 1 + .../k9mail/feature/preview/FeatureModule.kt | 17 +++++-- .../feature/preview/account/AccountCreator.kt | 13 ------ .../preview/account/InMemoryAccountStore.kt | 28 ++++++++++++ app/k9mail/build.gradle.kts | 1 + .../java/com/fsck/k9/account/AccountModule.kt | 7 +++ .../com/fsck/k9/account/AccountUpdater.kt | 44 +++++++++++++++++++ .../edit/AccountEditExternalContract.kt | 15 +++++++ 8 files changed, 110 insertions(+), 16 deletions(-) delete mode 100644 app-feature-preview/src/main/java/app/k9mail/feature/preview/account/AccountCreator.kt create mode 100644 app-feature-preview/src/main/java/app/k9mail/feature/preview/account/InMemoryAccountStore.kt create mode 100644 app/k9mail/src/main/java/com/fsck/k9/account/AccountUpdater.kt create mode 100644 feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditExternalContract.kt diff --git a/app-feature-preview/build.gradle.kts b/app-feature-preview/build.gradle.kts index a37b72158ae..53b5803c206 100644 --- a/app-feature-preview/build.gradle.kts +++ b/app-feature-preview/build.gradle.kts @@ -70,6 +70,7 @@ dependencies { implementation(projects.feature.onboarding) implementation(projects.feature.account.setup) + implementation(projects.feature.account.edit) implementation(libs.appauth) implementation(libs.okhttp) diff --git a/app-feature-preview/src/main/java/app/k9mail/feature/preview/FeatureModule.kt b/app-feature-preview/src/main/java/app/k9mail/feature/preview/FeatureModule.kt index 5edb78b6cdc..43ddc813f0e 100644 --- a/app-feature-preview/src/main/java/app/k9mail/feature/preview/FeatureModule.kt +++ b/app-feature-preview/src/main/java/app/k9mail/feature/preview/FeatureModule.kt @@ -1,10 +1,11 @@ package app.k9mail.feature.preview import app.k9mail.core.common.oauth.OAuthConfigurationFactory +import app.k9mail.feature.account.edit.AccountEditExternalContract import app.k9mail.feature.account.setup.AccountSetupExternalContract import app.k9mail.feature.account.setup.featureAccountSetupModule -import app.k9mail.feature.preview.account.AccountCreator import app.k9mail.feature.preview.account.AccountOwnerNameProvider +import app.k9mail.feature.preview.account.InMemoryAccountStore import app.k9mail.feature.preview.auth.AndroidKeyStoreDirectoryProvider import app.k9mail.feature.preview.auth.AppOAuthConfigurationFactory import app.k9mail.feature.preview.auth.DefaultTrustedSocketFactory @@ -15,11 +16,18 @@ import com.fsck.k9.mail.ssl.LocalKeyStore import com.fsck.k9.mail.ssl.TrustManagerFactory import com.fsck.k9.mail.ssl.TrustedSocketFactory import org.koin.core.module.Module +import org.koin.dsl.binds import org.koin.dsl.module val accountModule: Module = module { + single { InMemoryAccountStore() } + .binds( + arrayOf( + AccountSetupExternalContract.AccountCreator::class, + AccountEditExternalContract.AccountUpdater::class, + ), + ) factory { AccountOwnerNameProvider() } - factory { AccountCreator() } } val featureModule: Module = module { @@ -31,5 +39,8 @@ val featureModule: Module = module { single { DefaultTrustedSocketFactory(get(), get()) } single { RealOAuth2TokenProviderFactory(context = get()) } - includes(featureAccountSetupModule, accountModule) + includes( + accountModule, + featureAccountSetupModule, + ) } diff --git a/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/AccountCreator.kt b/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/AccountCreator.kt deleted file mode 100644 index 5048b578c30..00000000000 --- a/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/AccountCreator.kt +++ /dev/null @@ -1,13 +0,0 @@ -package app.k9mail.feature.preview.account - -import app.k9mail.feature.account.common.domain.entity.Account -import app.k9mail.feature.account.setup.AccountSetupExternalContract -import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult -import java.util.UUID - -class AccountCreator : AccountSetupExternalContract.AccountCreator { - - override suspend fun createAccount(account: Account): AccountCreatorResult { - return AccountCreatorResult.Success(UUID.randomUUID().toString()) - } -} diff --git a/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/InMemoryAccountStore.kt b/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/InMemoryAccountStore.kt new file mode 100644 index 00000000000..40787f79f97 --- /dev/null +++ b/app-feature-preview/src/main/java/app/k9mail/feature/preview/account/InMemoryAccountStore.kt @@ -0,0 +1,28 @@ +package app.k9mail.feature.preview.account + +import app.k9mail.feature.account.common.domain.entity.Account +import app.k9mail.feature.account.edit.AccountEditExternalContract.AccountUpdater +import app.k9mail.feature.account.edit.AccountEditExternalContract.AccountUpdater.AccountUpdaterResult +import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator +import app.k9mail.feature.account.setup.AccountSetupExternalContract.AccountCreator.AccountCreatorResult + +class InMemoryAccountStore( + private val accountMap: MutableMap = mutableMapOf(), +) : AccountCreator, AccountUpdater { + + override suspend fun createAccount(account: Account): AccountCreatorResult { + accountMap[account.uuid] = account + + return AccountCreatorResult.Success(account.uuid) + } + + override suspend fun updateAccount(account: Account): AccountUpdaterResult { + return if (!accountMap.containsKey(account.uuid)) { + AccountUpdaterResult.Error("Account not found") + } else { + accountMap[account.uuid] = account + + AccountUpdaterResult.Success(account.uuid) + } + } +} diff --git a/app/k9mail/build.gradle.kts b/app/k9mail/build.gradle.kts index fd0f33f5118..bcf60947b3f 100644 --- a/app/k9mail/build.gradle.kts +++ b/app/k9mail/build.gradle.kts @@ -21,6 +21,7 @@ dependencies { implementation(projects.feature.launcher) implementation(projects.feature.account.setup) + implementation(projects.feature.account.edit) implementation(libs.androidx.appcompat) implementation(libs.androidx.core.ktx) diff --git a/app/k9mail/src/main/java/com/fsck/k9/account/AccountModule.kt b/app/k9mail/src/main/java/com/fsck/k9/account/AccountModule.kt index e1a250f68d6..a695964a636 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/account/AccountModule.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/account/AccountModule.kt @@ -1,5 +1,6 @@ package com.fsck.k9.account +import app.k9mail.feature.account.edit.AccountEditExternalContract import app.k9mail.feature.account.setup.AccountSetupExternalContract import org.koin.android.ext.koin.androidApplication import org.koin.dsl.module @@ -19,4 +20,10 @@ val newAccountModule = module { context = androidApplication(), ) } + + factory { + AccountUpdater( + preferences = get(), + ) + } } diff --git a/app/k9mail/src/main/java/com/fsck/k9/account/AccountUpdater.kt b/app/k9mail/src/main/java/com/fsck/k9/account/AccountUpdater.kt new file mode 100644 index 00000000000..3a5bd414317 --- /dev/null +++ b/app/k9mail/src/main/java/com/fsck/k9/account/AccountUpdater.kt @@ -0,0 +1,44 @@ +package com.fsck.k9.account + +import app.k9mail.feature.account.common.domain.entity.Account +import app.k9mail.feature.account.edit.AccountEditExternalContract +import app.k9mail.feature.account.edit.AccountEditExternalContract.AccountUpdater.AccountUpdaterResult +import com.fsck.k9.Preferences +import com.fsck.k9.logging.Timber +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +class AccountUpdater( + private val preferences: Preferences, + private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO, +) : AccountEditExternalContract.AccountUpdater { + + @Suppress("TooGenericExceptionCaught") + override suspend fun updateAccount(account: Account): AccountUpdaterResult { + return try { + withContext(coroutineDispatcher) { + AccountUpdaterResult.Success(update(account)) + } + } catch (e: Exception) { + Timber.e(e, "Error while updating account") + + AccountUpdaterResult.Error(e.message ?: "Unknown update account error") + } + } + + private fun update(account: Account): String { + val uuid = account.uuid + require(uuid.isNotEmpty()) { "Can't update account without uuid" } + + val existingAccount = preferences.getAccount(uuid) + require(existingAccount != null) { "Can't update non-existing account" } + + existingAccount.incomingServerSettings = account.incomingServerSettings + existingAccount.outgoingServerSettings = account.outgoingServerSettings + + preferences.saveAccount(existingAccount) + + return uuid + } +} diff --git a/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditExternalContract.kt b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditExternalContract.kt new file mode 100644 index 00000000000..d7c65cd0e10 --- /dev/null +++ b/feature/account/edit/src/main/kotlin/app/k9mail/feature/account/edit/AccountEditExternalContract.kt @@ -0,0 +1,15 @@ +package app.k9mail.feature.account.edit + +import app.k9mail.feature.account.common.domain.entity.Account + +interface AccountEditExternalContract { + + fun interface AccountUpdater { + suspend fun updateAccount(account: Account): AccountUpdaterResult + + sealed interface AccountUpdaterResult { + data class Success(val accountId: String) : AccountUpdaterResult + data class Error(val message: String) : AccountUpdaterResult + } + } +} From 8549bfc554d044702a5f569ed390b91993d968b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolf-Martell=20Montw=C3=A9?= Date: Tue, 12 Sep 2023 16:35:46 +0200 Subject: [PATCH 4/4] Add newAccount method to preferences to allow creation with given UUID --- app/core/src/main/java/com/fsck/k9/Preferences.kt | 5 ++++- .../src/main/java/com/fsck/k9/account/AccountCreator.kt | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/core/src/main/java/com/fsck/k9/Preferences.kt b/app/core/src/main/java/com/fsck/k9/Preferences.kt index 5b20a58fc60..a8ab74c4fe4 100644 --- a/app/core/src/main/java/com/fsck/k9/Preferences.kt +++ b/app/core/src/main/java/com/fsck/k9/Preferences.kt @@ -122,7 +122,6 @@ class Preferences internal constructor( } } - @OptIn(ExperimentalCoroutinesApi::class) override fun getAccountFlow(accountUuid: String): Flow { return callbackFlow { val initialAccount = getAccount(accountUuid) @@ -169,6 +168,10 @@ class Preferences internal constructor( fun newAccount(): Account { val accountUuid = UUID.randomUUID().toString() + return newAccount(accountUuid) + } + + fun newAccount(accountUuid: String): Account { val account = Account(accountUuid) accountPreferenceSerializer.loadDefaults(account) diff --git a/app/k9mail/src/main/java/com/fsck/k9/account/AccountCreator.kt b/app/k9mail/src/main/java/com/fsck/k9/account/AccountCreator.kt index 80496250ae7..d5b5dbcab5e 100644 --- a/app/k9mail/src/main/java/com/fsck/k9/account/AccountCreator.kt +++ b/app/k9mail/src/main/java/com/fsck/k9/account/AccountCreator.kt @@ -40,7 +40,7 @@ class AccountCreator( } private fun create(account: Account): String { - val newAccount = preferences.newAccount() + val newAccount = preferences.newAccount(account.uuid) newAccount.email = account.emailAddress