Repository: thunderbird/thunderbird-android (language: kotlin)

Issue:
Account Settings - Add account name setting
Add the existing account name setting


Discussion hints:
nan

Pull request: Add account name setting
This adds the account name setting to the new account settings UI.

Depends on #9025

Resolves #8923

Test command: ./gradlew :legacy:core:testDebugUnitTest --tests "com.fsck.k9.PreferencesTest" :feature:account:settings:impl:testDebugUnitTest --tests "net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsStateTest" --tests "net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsViewModelTest" --tests "net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsScreenKtTest" --tests "net.thunderbird.feature.account.settings.impl.AccountSettingsModuleKtTest" --tests "net.thunderbird.feature.account.settings.impl.domain.usecase.UpdateGeneralPreferencesTest" --tests "net.thunderbird.feature.account.settings.impl.domain.usecase.GetGeneralPreferencesTest" :app-thunderbird:testFossDebugUnitTest --tests "net.thunderbird.android.DependencyInjectionTest" :app-k9mail:testFossDebugUnitTest --tests "app.k9mail.DependencyInjectionTest"

Fix patch:
diff --git a/app-common/build.gradle.kts b/app-common/build.gradle.kts
index 0ef3941d31b..9338077e3eb 100644
--- a/app-common/build.gradle.kts
+++ b/app-common/build.gradle.kts
@@ -9,7 +9,7 @@ android {
 dependencies {
     api(projects.legacy.common)
 
-    implementation(projects.feature.account.core)
+    api(projects.feature.account.core)
 
     implementation(projects.legacy.core)
     implementation(projects.legacy.account)
diff --git a/app-common/src/main/kotlin/net/thunderbird/app/common/account/data/CommonAccountProfileLocalDataSource.kt b/app-common/src/main/kotlin/net/thunderbird/app/common/account/data/CommonAccountProfileLocalDataSource.kt
index 7ea0c60b085..a73caf741ef 100644
--- a/app-common/src/main/kotlin/net/thunderbird/app/common/account/data/CommonAccountProfileLocalDataSource.kt
+++ b/app-common/src/main/kotlin/net/thunderbird/app/common/account/data/CommonAccountProfileLocalDataSource.kt
@@ -4,7 +4,6 @@ import app.k9mail.legacy.account.LegacyAccountWrapperManager
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.firstOrNull
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.onEach
 import net.thunderbird.feature.account.api.AccountId
 import net.thunderbird.feature.account.api.profile.AccountProfile
 import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
@@ -15,7 +14,6 @@ class CommonAccountProfileLocalDataSource(
 
     override fun getById(accountId: AccountId): Flow<AccountProfile?> {
         return accountManager.getById(accountId.value)
-            .onEach { println("Flow emitted account: $it") }
             .map { account ->
                 account?.let {
                     AccountProfile(
diff --git a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/api/Preference.kt b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/api/Preference.kt
index 59d459b2952..1b818674060 100644
--- a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/api/Preference.kt
+++ b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/api/Preference.kt
@@ -4,7 +4,6 @@ import android.os.Parcelable
 import androidx.compose.ui.graphics.vector.ImageVector
 import kotlinx.parcelize.IgnoredOnParcel
 import kotlinx.parcelize.Parcelize
-import kotlinx.parcelize.RawValue
 
 /**
  * A preference that can be displayed in a preference screen.
@@ -22,9 +21,9 @@ sealed interface PreferenceSetting<T> : Preference {
     @Parcelize
     data class Text(
         override val id: String,
-        val title: String,
-        val description: String? = null,
-        val icon: @RawValue ImageVector? = null,
+        val title: () -> String,
+        val description: () -> String? = { null },
+        val icon: () -> ImageVector? = { null },
         override val value: String,
     ) : PreferenceSetting<String> {
         @IgnoredOnParcel
diff --git a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/dialog/PreferenceDialogTextView.kt b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/dialog/PreferenceDialogTextView.kt
index 2521887c2ad..7866e278866 100644
--- a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/dialog/PreferenceDialogTextView.kt
+++ b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/dialog/PreferenceDialogTextView.kt
@@ -23,8 +23,8 @@ internal fun PreferenceDialogTextView(
     val currentPreference = remember { mutableStateOf(preference) }
 
     PreferenceDialogLayout(
-        title = preference.title,
-        icon = preference.icon,
+        title = preference.title(),
+        icon = preference.icon(),
         onConfirmClick = {
             onConfirmClick(currentPreference.value)
         },
@@ -32,7 +32,7 @@ internal fun PreferenceDialogTextView(
         onDismissRequest = onDismissRequest,
         modifier = modifier,
     ) {
-        preference.description?.let {
+        preference.description()?.let {
             TextBodyMedium(text = it)
 
             Spacer(modifier = Modifier.height(MainTheme.spacings.default))
diff --git a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/list/PreferenceItemTextView.kt b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/list/PreferenceItemTextView.kt
index a04011f88e0..a07672cfb84 100644
--- a/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/list/PreferenceItemTextView.kt
+++ b/core/ui/compose/preference/src/main/kotlin/net/thunderbird/core/ui/compose/preference/ui/components/list/PreferenceItemTextView.kt
@@ -14,10 +14,10 @@ internal fun PreferenceItemTextView(
 ) {
     PreferenceItemLayout(
         onClick = onClick,
-        icon = preference.icon,
+        icon = preference.icon(),
         modifier = modifier,
     ) {
-        TextTitleMedium(text = preference.title)
+        TextTitleMedium(text = preference.title())
         TextBodyMedium(text = preference.value)
     }
 }
diff --git a/feature/account/core/src/main/kotlin/net/thunderbird/feature/account/core/data/DefaultAccountProfileRepository.kt b/feature/account/core/src/main/kotlin/net/thunderbird/feature/account/core/data/DefaultAccountProfileRepository.kt
index c34549d2d81..cc5f2728e71 100644
--- a/feature/account/core/src/main/kotlin/net/thunderbird/feature/account/core/data/DefaultAccountProfileRepository.kt
+++ b/feature/account/core/src/main/kotlin/net/thunderbird/feature/account/core/data/DefaultAccountProfileRepository.kt
@@ -5,18 +5,18 @@ import kotlinx.coroutines.flow.distinctUntilChanged
 import net.thunderbird.feature.account.api.AccountId
 import net.thunderbird.feature.account.api.profile.AccountProfile
 import net.thunderbird.feature.account.api.profile.AccountProfileRepository
-import net.thunderbird.feature.account.core.AccountCoreExternalContract
+import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
 
 class DefaultAccountProfileRepository(
-    private val localStore: AccountCoreExternalContract.AccountProfileLocalDataSource,
+    private val localDataSource: AccountProfileLocalDataSource,
 ) : AccountProfileRepository {
 
     override fun getById(accountId: AccountId): Flow<AccountProfile?> {
-        return localStore.getById(accountId)
+        return localDataSource.getById(accountId)
             .distinctUntilChanged()
     }
 
     override suspend fun update(accountProfile: AccountProfile) {
-        localStore.update(accountProfile)
+        localDataSource.update(accountProfile)
     }
 }
diff --git a/feature/account/settings/impl/build.gradle.kts b/feature/account/settings/impl/build.gradle.kts
index 4ebd2f9bc29..68351f2301d 100644
--- a/feature/account/settings/impl/build.gradle.kts
+++ b/feature/account/settings/impl/build.gradle.kts
@@ -9,6 +9,9 @@ android {
 
 dependencies {
     api(projects.feature.account.settings.api)
+    implementation(projects.feature.account.core)
+
+    implementation(projects.core.outcome)
 
     implementation(projects.core.ui.compose.designsystem)
     implementation(projects.core.ui.compose.navigation)
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/AccountSettingsModule.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/AccountSettingsModule.kt
index b97e32674bf..328830ffad0 100644
--- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/AccountSettingsModule.kt
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/AccountSettingsModule.kt
@@ -2,8 +2,43 @@ package net.thunderbird.feature.account.settings
 
 import net.thunderbird.feature.account.settings.api.AccountSettingsNavigation
 import net.thunderbird.feature.account.settings.impl.DefaultAccountSettingsNavigation
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.ResourceProvider
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.UseCase
+import net.thunderbird.feature.account.settings.impl.domain.usecase.GetGeneralPreferences
+import net.thunderbird.feature.account.settings.impl.domain.usecase.UpdateGeneralPreferences
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralResourceProvider
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsViewModel
+import org.koin.android.ext.koin.androidContext
+import org.koin.androidx.viewmodel.dsl.viewModel
 import org.koin.dsl.module
 
 val featureAccountSettingsModule = module {
     single<AccountSettingsNavigation> { DefaultAccountSettingsNavigation() }
+
+    factory<ResourceProvider.GeneralResourceProvider> {
+        GeneralResourceProvider(
+            context = androidContext(),
+        )
+    }
+
+    factory<UseCase.GetGeneralPreferences> {
+        GetGeneralPreferences(
+            repository = get(),
+            resourceProvider = get(),
+        )
+    }
+
+    factory<UseCase.UpdateGeneralPreferences> {
+        UpdateGeneralPreferences(
+            repository = get(),
+        )
+    }
+
+    viewModel { params ->
+        GeneralSettingsViewModel(
+            accountId = params.get(),
+            getGeneralPreferences = get(),
+            updateGeneralPreferences = get(),
+        )
+    }
 }
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/DefaultAccountSettingsNavigation.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/DefaultAccountSettingsNavigation.kt
index d96817e32c4..99bbfaf5da3 100644
--- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/DefaultAccountSettingsNavigation.kt
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/DefaultAccountSettingsNavigation.kt
@@ -3,11 +3,12 @@ package net.thunderbird.feature.account.settings.impl
 import androidx.navigation.NavGraphBuilder
 import androidx.navigation.toRoute
 import app.k9mail.core.ui.compose.navigation.deepLinkComposable
+import net.thunderbird.feature.account.api.AccountId
 import net.thunderbird.feature.account.settings.api.AccountSettingsNavigation
 import net.thunderbird.feature.account.settings.api.AccountSettingsRoute
-import net.thunderbird.feature.account.settings.impl.ui.AccountSettingsScreen
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsScreen
 
-class DefaultAccountSettingsNavigation : AccountSettingsNavigation {
+internal class DefaultAccountSettingsNavigation : AccountSettingsNavigation {
 
     override fun registerRoutes(
         navGraphBuilder: NavGraphBuilder,
@@ -19,9 +20,10 @@ class DefaultAccountSettingsNavigation : AccountSettingsNavigation {
                 basePath = AccountSettingsRoute.GeneralSettings.Companion.BASE_PATH,
             ) { backStackEntry ->
                 val generalSettingsRoute = backStackEntry.toRoute<AccountSettingsRoute.GeneralSettings>()
+                val accountId = AccountId.from(generalSettingsRoute.accountId)
 
-                AccountSettingsScreen(
-                    accountId = generalSettingsRoute.accountId,
+                GeneralSettingsScreen(
+                    accountId = accountId,
                     onBack = onBack,
                 )
             }
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/AccountSettingsDomainContract.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/AccountSettingsDomainContract.kt
new file mode 100644
index 00000000000..b756a85f4c4
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/AccountSettingsDomainContract.kt
@@ -0,0 +1,43 @@
+package net.thunderbird.feature.account.settings.impl.domain
+
+import androidx.compose.ui.graphics.vector.ImageVector
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.coroutines.flow.Flow
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.Preference
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.SettingsError
+
+internal typealias AccountSettingsOutcome = Outcome<ImmutableList<Preference>, SettingsError>
+
+internal interface AccountSettingsDomainContract {
+
+    interface UseCase {
+
+        fun interface GetGeneralPreferences {
+            operator fun invoke(accountId: AccountId): Flow<AccountSettingsOutcome>
+        }
+
+        fun interface UpdateGeneralPreferences {
+            suspend operator fun invoke(
+                accountId: AccountId,
+                preference: PreferenceSetting<*>,
+            ): Outcome<Unit, SettingsError>
+        }
+    }
+
+    interface ResourceProvider {
+        interface GeneralResourceProvider {
+            val nameTitle: () -> String
+            val nameDescription: () -> String?
+            val nameIcon: () -> ImageVector?
+        }
+    }
+
+    sealed interface SettingsError {
+        data class NotFound(
+            val message: String,
+        ) : SettingsError
+    }
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/entity/GeneralPreference.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/entity/GeneralPreference.kt
new file mode 100644
index 00000000000..ccc3017528f
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/entity/GeneralPreference.kt
@@ -0,0 +1,11 @@
+package net.thunderbird.feature.account.settings.impl.domain.entity
+
+import net.thunderbird.feature.account.api.AccountId
+
+internal enum class GeneralPreference {
+    NAME,
+}
+
+internal fun GeneralPreference.generateId(accountId: AccountId): String {
+    return "${accountId.value}-general-${this.name.lowercase()}"
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferences.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferences.kt
new file mode 100644
index 00000000000..053b6868c7c
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferences.kt
@@ -0,0 +1,49 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.Preference
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.api.profile.AccountProfileRepository
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.ResourceProvider
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.SettingsError
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.UseCase
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsOutcome
+import net.thunderbird.feature.account.settings.impl.domain.entity.GeneralPreference
+import net.thunderbird.feature.account.settings.impl.domain.entity.generateId
+
+internal class GetGeneralPreferences(
+    private val repository: AccountProfileRepository,
+    private val resourceProvider: ResourceProvider.GeneralResourceProvider,
+) : UseCase.GetGeneralPreferences {
+    override fun invoke(accountId: AccountId): Flow<AccountSettingsOutcome> {
+        return repository.getById(accountId).map { profile ->
+            if (profile != null) {
+                Outcome.success(generatePreferences(accountId, profile))
+            } else {
+                Outcome.failure(
+                    SettingsError.NotFound(
+                        message = "Account profile not found for accountId: ${accountId.value}",
+                    ),
+                )
+            }
+        }
+    }
+
+    private fun generatePreferences(accountId: AccountId, profile: AccountProfile): ImmutableList<Preference> {
+        return persistentListOf(
+            PreferenceSetting.Text(
+                id = GeneralPreference.NAME.generateId(accountId),
+                title = resourceProvider.nameTitle,
+                description = resourceProvider.nameDescription,
+                icon = resourceProvider.nameIcon,
+                value = profile.name,
+            ),
+        )
+    }
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferences.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferences.kt
new file mode 100644
index 00000000000..041c938cac0
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferences.kt
@@ -0,0 +1,52 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import kotlinx.coroutines.flow.firstOrNull
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.api.profile.AccountProfileRepository
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.SettingsError
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.UseCase
+import net.thunderbird.feature.account.settings.impl.domain.entity.GeneralPreference
+import net.thunderbird.feature.account.settings.impl.domain.entity.generateId
+
+internal class UpdateGeneralPreferences(
+    private val repository: AccountProfileRepository,
+) : UseCase.UpdateGeneralPreferences {
+    override suspend fun invoke(
+        accountId: AccountId,
+        preference: PreferenceSetting<*>,
+    ): Outcome<Unit, SettingsError> {
+        return when (preference.id) {
+            GeneralPreference.NAME.generateId(accountId) -> {
+                updateAccountProfile(accountId) {
+                    copy(name = preference.value as String)
+                }
+            }
+
+            else -> Outcome.failure(
+                SettingsError.NotFound(
+                    message = "Unknown preference id: ${preference.id}",
+                ),
+            )
+        }
+    }
+
+    private suspend fun updateAccountProfile(
+        accountId: AccountId,
+        update: AccountProfile.() -> AccountProfile,
+    ): Outcome<Unit, SettingsError> {
+        val accountProfile = repository.getById(accountId).firstOrNull()
+            ?: return Outcome.failure(
+                SettingsError.NotFound(
+                    message = "Account profile not found for accountId: $accountId",
+                ),
+            )
+        val updatedAccountProfile = update(accountProfile)
+
+        repository.update(updatedAccountProfile)
+
+        return Outcome.success(Unit)
+    }
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/AccountSettingsScreen.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/AccountSettingsScreen.kt
deleted file mode 100644
index b5f4823998c..00000000000
--- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/AccountSettingsScreen.kt
+++ /dev/null
@@ -1,76 +0,0 @@
-package net.thunderbird.feature.account.settings.impl.ui
-
-import androidx.activity.compose.BackHandler
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.ui.Modifier
-import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
-import kotlinx.collections.immutable.toImmutableList
-import net.thunderbird.core.ui.compose.preference.api.Preference
-import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
-import net.thunderbird.core.ui.compose.preference.ui.PreferenceView
-
-@Composable
-fun AccountSettingsScreen(
-    accountId: String,
-    onBack: () -> Unit,
-    modifier: Modifier = Modifier,
-) {
-    val preferences = remember {
-        mutableStateOf(
-            listOf<Preference>(
-                PreferenceSetting.Text(
-                    id = "1",
-                    title = "Title 1",
-                    description = "Description 1",
-                    icon = Icons.Outlined.Delete,
-                    value = "Value 1",
-                ),
-                PreferenceSetting.Text(
-                    id = "2",
-                    title = "Title 2",
-                    description = "Description 2",
-                    icon = Icons.Outlined.Delete,
-                    value = "Value 2",
-                ),
-                PreferenceSetting.Text(
-                    id = "3",
-                    title = "Title 3",
-                    description = "Description 3",
-                    icon = Icons.Outlined.Folder,
-                    value = "Value 3",
-                ),
-            ),
-        )
-    }
-
-    BackHandler(onBack = onBack)
-
-    PreferenceView(
-        title = "Account settings",
-        subtitle = accountId,
-        preferences = preferences.value.toImmutableList(),
-        onPreferenceChange = { preference ->
-            preferences.value = updatePreference(
-                preferences = preferences.value,
-                preference = preference,
-            )
-        },
-        onBack = onBack,
-        modifier = modifier,
-    )
-}
-
-private fun updatePreference(
-    preferences: List<Preference>,
-    preference: PreferenceSetting<*>,
-): List<Preference> {
-    return preferences.map {
-        if (it is PreferenceSetting<*> && it.id == preference.id) {
-            preference
-        } else {
-            it
-        }
-    }
-}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralResourceProvider.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralResourceProvider.kt
new file mode 100644
index 00000000000..71bad15153c
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralResourceProvider.kt
@@ -0,0 +1,17 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import android.content.Context
+import androidx.compose.ui.graphics.vector.ImageVector
+import net.thunderbird.feature.account.settings.R
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.ResourceProvider
+
+internal class GeneralResourceProvider(
+    private val context: Context,
+) : ResourceProvider.GeneralResourceProvider {
+
+    override val nameTitle: () -> String = {
+        context.getString(R.string.account_settings_general_name_title)
+    }
+    override val nameDescription: () -> String? = { null }
+    override val nameIcon: () -> ImageVector? = { null }
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt
new file mode 100644
index 00000000000..e75200a10cc
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContent.kt
@@ -0,0 +1,27 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.res.stringResource
+import net.thunderbird.core.ui.compose.preference.ui.PreferenceView
+import net.thunderbird.feature.account.settings.R
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Event
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+
+@Composable
+internal fun GeneralSettingsContent(
+    state: State,
+    onEvent: (Event) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    PreferenceView(
+        title = stringResource(R.string.account_settings_general_title),
+        subtitle = state.subtitle,
+        preferences = state.preferences,
+        onPreferenceChange = { preference ->
+            onEvent(Event.OnPreferenceSettingChange(preference))
+        },
+        onBack = { onEvent(Event.OnBackPressed) },
+        modifier = modifier,
+    )
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContract.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContract.kt
new file mode 100644
index 00000000000..a7c2b333f1e
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsContract.kt
@@ -0,0 +1,31 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import androidx.compose.runtime.Stable
+import app.k9mail.core.ui.compose.common.mvi.UnidirectionalViewModel
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import net.thunderbird.core.ui.compose.preference.api.Preference
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+
+internal interface GeneralSettingsContract {
+
+    interface ViewModel : UnidirectionalViewModel<State, Event, Effect>
+
+    @Stable
+    data class State(
+        val subtitle: String? = null,
+        val preferences: ImmutableList<Preference> = persistentListOf<Preference>(),
+    )
+
+    sealed interface Event {
+        data class OnPreferenceSettingChange(
+            val preference: PreferenceSetting<*>,
+        ) : Event
+
+        data object OnBackPressed : Event
+    }
+
+    sealed interface Effect {
+        object NavigateBack : Effect
+    }
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt
new file mode 100644
index 00000000000..e7111dead6f
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreen.kt
@@ -0,0 +1,31 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import androidx.activity.compose.BackHandler
+import androidx.compose.runtime.Composable
+import app.k9mail.core.ui.compose.common.mvi.observe
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Effect
+import org.koin.androidx.compose.koinViewModel
+import org.koin.core.parameter.parametersOf
+
+@Composable
+internal fun GeneralSettingsScreen(
+    accountId: AccountId,
+    onBack: () -> Unit,
+    viewModel: GeneralSettingsContract.ViewModel = koinViewModel<GeneralSettingsViewModel> {
+        parametersOf(accountId)
+    },
+) {
+    val (state, dispatch) = viewModel.observe { effect ->
+        when (effect) {
+            is Effect.NavigateBack -> onBack()
+        }
+    }
+
+    BackHandler(onBack = onBack)
+
+    GeneralSettingsContent(
+        state = state.value,
+        onEvent = { dispatch(it) },
+    )
+}
diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModel.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModel.kt
new file mode 100644
index 00000000000..6819e5dd7d0
--- /dev/null
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModel.kt
@@ -0,0 +1,50 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import androidx.lifecycle.viewModelScope
+import app.k9mail.core.ui.compose.common.mvi.BaseViewModel
+import kotlinx.coroutines.launch
+import net.thunderbird.core.outcome.handle
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.UseCase
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Effect
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Event
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+
+internal class GeneralSettingsViewModel(
+    private val accountId: AccountId,
+    private val getGeneralPreferences: UseCase.GetGeneralPreferences,
+    private val updateGeneralPreferences: UseCase.UpdateGeneralPreferences,
+    initialState: State = State(),
+) : BaseViewModel<State, Event, Effect>(initialState), GeneralSettingsContract.ViewModel {
+
+    init {
+        viewModelScope.launch {
+            getGeneralPreferences(accountId).collect { outcome ->
+                outcome.handle(
+                    onSuccess = { preferences ->
+                        updateState { state ->
+                            state.copy(
+                                preferences = preferences,
+                            )
+                        }
+                    },
+                    onFailure = {},
+                )
+            }
+        }
+    }
+
+    override fun event(event: Event) {
+        when (event) {
+            is Event.OnPreferenceSettingChange -> updatePreference(event.preference)
+            is Event.OnBackPressed -> emitEffect(Effect.NavigateBack)
+        }
+    }
+
+    private fun updatePreference(preference: PreferenceSetting<*>) {
+        viewModelScope.launch {
+            updateGeneralPreferences(accountId, preference)
+        }
+    }
+}
diff --git a/feature/account/settings/impl/src/main/res/values/strings.xml b/feature/account/settings/impl/src/main/res/values/strings.xml
new file mode 100644
index 00000000000..7c0c8f030ee
--- /dev/null
+++ b/feature/account/settings/impl/src/main/res/values/strings.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <string name="account_settings_general_title">General Settings</string>
+
+    <string name="account_settings_general_name_title">Account name</string>
+
+</resources>
diff --git a/legacy/core/src/main/java/com/fsck/k9/Preferences.kt b/legacy/core/src/main/java/com/fsck/k9/Preferences.kt
index 02fa0c55826..b3af621dd18 100644
--- a/legacy/core/src/main/java/com/fsck/k9/Preferences.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/Preferences.kt
@@ -222,6 +222,8 @@ class Preferences internal constructor(
             val editor = createStorageEditor()
             accountPreferenceSerializer.save(editor, storage, account)
             editor.commit()
+
+            loadAccounts()
         }
 
         notifyAccountsChangeListeners()


Test patch:
diff --git a/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt b/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
index b13839514cc..1d7197f291e 100644
--- a/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
+++ b/app-k9mail/src/test/kotlin/app/k9mail/DependencyInjectionTest.kt
@@ -12,6 +12,7 @@ import com.fsck.k9.ui.changelog.ChangelogViewModel
 import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
 import com.fsck.k9.ui.endtoend.AutocryptKeyTransferPresenter
 import com.fsck.k9.ui.helper.SizeFormatter
+import net.thunderbird.feature.account.api.AccountId
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.koin.core.annotation.KoinInternalApi
@@ -52,6 +53,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
             withInstance(authStateStorage)
             withInstance(lifecycleOwner)
             withInstance(mock<WorkerParameters>())
+            withInstance(AccountId.create())
         }
     }
 }
diff --git a/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt b/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
index 7d13578c020..41316368273 100644
--- a/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
+++ b/app-thunderbird/src/test/kotlin/net/thunderbird/android/DependencyInjectionTest.kt
@@ -11,6 +11,7 @@ import com.fsck.k9.ui.changelog.ChangelogViewModel
 import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
 import com.fsck.k9.ui.endtoend.AutocryptKeyTransferPresenter
 import com.fsck.k9.ui.helper.SizeFormatter
+import net.thunderbird.feature.account.api.AccountId
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.koin.core.annotation.KoinInternalApi
@@ -53,6 +54,7 @@ class DependencyInjectionTest : AutoCloseKoinTest() {
             withInstance(authStateStorage)
             withInstance(lifecycleOwner)
             withInstance(mock<WorkerParameters>())
+            withInstance(AccountId.create())
         }
     }
 }
diff --git a/core/ui/compose/preference/src/debug/kotlin/net/thunderbird/core/ui/compose/preference/ui/fake/FakePreferenceData.kt b/core/ui/compose/preference/src/debug/kotlin/net/thunderbird/core/ui/compose/preference/ui/fake/FakePreferenceData.kt
index d0cfed6e330..a0aff575e16 100644
--- a/core/ui/compose/preference/src/debug/kotlin/net/thunderbird/core/ui/compose/preference/ui/fake/FakePreferenceData.kt
+++ b/core/ui/compose/preference/src/debug/kotlin/net/thunderbird/core/ui/compose/preference/ui/fake/FakePreferenceData.kt
@@ -8,9 +8,9 @@ object FakePreferenceData {
 
     val textPreference = PreferenceSetting.Text(
         id = "text",
-        icon = Icons.Outlined.Delete,
-        title = "Title",
-        description = "Description",
+        icon = { Icons.Outlined.Delete },
+        title = { "Title" },
+        description = { "Description" },
         value = "Value",
     )
 
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/AccountSettingsModuleKtTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/AccountSettingsModuleKtTest.kt
new file mode 100644
index 00000000000..e02d4d9944e
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/AccountSettingsModuleKtTest.kt
@@ -0,0 +1,53 @@
+package net.thunderbird.feature.account.settings.impl
+
+import kotlin.test.Test
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.api.profile.AccountProfileRepository
+import net.thunderbird.feature.account.settings.featureAccountSettingsModule
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsViewModel
+import org.junit.runner.RunWith
+import org.koin.android.ext.koin.androidContext
+import org.koin.core.annotation.KoinExperimentalAPI
+import org.koin.core.module.Module
+import org.koin.dsl.module
+import org.koin.test.AutoCloseKoinTest
+import org.koin.test.check.checkKoinModules
+import org.koin.test.verify.verify
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.RuntimeEnvironment
+
+@OptIn(KoinExperimentalAPI::class)
+@RunWith(RobolectricTestRunner::class)
+internal class AccountSettingsModuleKtTest : AutoCloseKoinTest() {
+
+    private val externalModule: Module = module {
+        single<AccountProfileRepository> {
+            object : AccountProfileRepository {
+                override fun getById(accountId: AccountId): Flow<AccountProfile?> = flow { null }
+
+                override suspend fun update(accountProfile: AccountProfile) = Unit
+            }
+        }
+    }
+
+    @Test
+    fun `should hava a valid di module`() {
+        featureAccountSettingsModule.verify(
+            extraTypes = listOf(
+                AccountId::class,
+                GeneralSettingsContract.State::class,
+            ),
+        )
+
+        checkKoinModules(
+            modules = listOf(externalModule, featureAccountSettingsModule),
+            appDeclaration = { androidContext(RuntimeEnvironment.getApplication()) },
+        ) {
+            withParameter<GeneralSettingsViewModel> { AccountId.create() }
+        }
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeAccountProfileRepository.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeAccountProfileRepository.kt
new file mode 100644
index 00000000000..522a9a1bb0c
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeAccountProfileRepository.kt
@@ -0,0 +1,27 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.update
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.api.profile.AccountProfileRepository
+
+internal class FakeAccountProfileRepository(
+    initialAccountProfile: AccountProfile? = null,
+) : AccountProfileRepository {
+
+    private val accountProfileState = MutableStateFlow<AccountProfile?>(initialAccountProfile)
+    private val accountProfile: StateFlow<AccountProfile?> = accountProfileState
+
+    override fun getById(accountId: AccountId): Flow<AccountProfile?> {
+        return accountProfile
+    }
+
+    override suspend fun update(accountProfile: AccountProfile) {
+        accountProfileState.update {
+            accountProfile
+        }
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeGeneralResourceProvider.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeGeneralResourceProvider.kt
new file mode 100644
index 00000000000..402cd76b8ff
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/FakeGeneralResourceProvider.kt
@@ -0,0 +1,10 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import androidx.compose.ui.graphics.vector.ImageVector
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.ResourceProvider
+
+internal class FakeGeneralResourceProvider : ResourceProvider.GeneralResourceProvider {
+    override val nameTitle: () -> String = { "Name" }
+    override val nameDescription: () -> String? = { null }
+    override val nameIcon: () -> ImageVector? = { null }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferencesTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferencesTest.kt
new file mode 100644
index 00000000000..eacaecc2dfe
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/GetGeneralPreferencesTest.kt
@@ -0,0 +1,79 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import app.cash.turbine.test
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import assertk.assertions.isInstanceOf
+import kotlin.test.Test
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.coroutines.test.runTest
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.ResourceProvider
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.SettingsError
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.UseCase
+
+internal class GetGeneralPreferencesTest {
+
+    @Test
+    fun `should emit preferences when account profile present`() = runTest {
+        // Arrange
+        val accountId = AccountId.create()
+        val accountProfile = AccountProfile(
+            accountId = accountId,
+            name = "Test Account",
+            color = 0xFF0000,
+        )
+        val resourceProvider = FakeGeneralResourceProvider()
+        val testSubject = createTestSubject(accountProfile)
+
+        // Act & Assert
+        testSubject(accountId).test {
+            val outcome = awaitItem()
+            assertThat(outcome).isInstanceOf(Outcome.Success::class)
+
+            val success = outcome as Outcome.Success
+            assertThat(success.data).isEqualTo(
+                persistentListOf(
+                    PreferenceSetting.Text(
+                        id = "${accountId.value}-general-name",
+                        title = resourceProvider.nameTitle,
+                        description = resourceProvider.nameDescription,
+                        icon = resourceProvider.nameIcon,
+                        value = accountProfile.name,
+                    ),
+                ),
+            )
+        }
+    }
+
+    @Test
+    fun `should emit NotFound when account profile not found`() = runTest {
+        // Arrange
+        val accountId = AccountId.create()
+        val testSubject = createTestSubject()
+
+        // Act & Assert
+        testSubject(accountId).test {
+            assertThat(awaitItem()).isEqualTo(
+                Outcome.failure(
+                    SettingsError.NotFound(
+                        message = "Account profile not found for accountId: ${accountId.value}",
+                    ),
+                ),
+            )
+        }
+    }
+
+    private fun createTestSubject(
+        accountProfile: AccountProfile? = null,
+        resourceProvider: ResourceProvider.GeneralResourceProvider = FakeGeneralResourceProvider(),
+    ): UseCase.GetGeneralPreferences {
+        return GetGeneralPreferences(
+            repository = FakeAccountProfileRepository(accountProfile),
+            resourceProvider = resourceProvider,
+        )
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferencesTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferencesTest.kt
new file mode 100644
index 00000000000..00c8ed7c035
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/domain/usecase/UpdateGeneralPreferencesTest.kt
@@ -0,0 +1,72 @@
+package net.thunderbird.feature.account.settings.impl.domain.usecase
+
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import assertk.assertions.isInstanceOf
+import kotlin.test.Test
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.runTest
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.api.profile.AccountProfile
+import net.thunderbird.feature.account.settings.impl.domain.AccountSettingsDomainContract.SettingsError
+import net.thunderbird.feature.account.settings.impl.domain.entity.GeneralPreference
+import net.thunderbird.feature.account.settings.impl.domain.entity.generateId
+
+class UpdateGeneralPreferencesTest {
+
+    @Test
+    fun `should update account profile`() = runTest {
+        // Arrange
+        val accountId = AccountId.create()
+        val accountProfile = AccountProfile(
+            accountId = accountId,
+            name = "Test Account",
+            color = 0xFF0000,
+        )
+        val newName = "Updated Account Name"
+        val preference = PreferenceSetting.Text(
+            id = GeneralPreference.NAME.generateId(accountId),
+            title = { "Name" },
+            description = { "Account name" },
+            icon = { null },
+            value = newName,
+        )
+        val repository = FakeAccountProfileRepository(
+            initialAccountProfile = accountProfile,
+        )
+        val testSubject = UpdateGeneralPreferences(repository)
+
+        // Act
+        val result = testSubject(accountId, preference)
+
+        // Assert
+        assertThat(result).isInstanceOf(Outcome.Success::class)
+        assertThat(repository.getById(accountId).firstOrNull()).isEqualTo(
+            accountProfile.copy(name = newName),
+        )
+    }
+
+    @Test
+    fun `should emit NotFound when account profile not found`() = runTest {
+        // Arrange
+        val accountId = AccountId.create()
+        val preference = PreferenceSetting.Text(
+            id = GeneralPreference.NAME.generateId(accountId),
+            title = { "Name" },
+            description = { "Account name" },
+            icon = { null },
+            value = "Updated Account Name",
+        )
+        val repository = FakeAccountProfileRepository()
+        val testSubject = UpdateGeneralPreferences(repository)
+
+        // Act
+        val result = testSubject(accountId, preference)
+
+        // Assert
+        assertThat(result).isInstanceOf(Outcome.Failure::class)
+        assertThat((result as Outcome.Failure).error).isInstanceOf(SettingsError.NotFound::class)
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeData.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeData.kt
new file mode 100644
index 00000000000..7a34c049ea4
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeData.kt
@@ -0,0 +1,19 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import net.thunderbird.core.ui.compose.preference.api.Preference
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+
+internal object FakeData {
+
+    val preferences: ImmutableList<Preference> = persistentListOf(
+        PreferenceSetting.Text(
+            id = "test_id",
+            title = { "Title" },
+            description = { "Description" },
+            icon = { null },
+            value = "Test",
+        ),
+    )
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeGeneralSettingsViewModel.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeGeneralSettingsViewModel.kt
new file mode 100644
index 00000000000..ec63b489d6e
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/FakeGeneralSettingsViewModel.kt
@@ -0,0 +1,11 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import app.k9mail.core.ui.compose.testing.BaseFakeViewModel
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Effect
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Event
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.ViewModel
+
+internal class FakeGeneralSettingsViewModel(
+    initialState: State = State(),
+) : BaseFakeViewModel<State, Event, Effect>(initialState), ViewModel
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreenKtTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreenKtTest.kt
new file mode 100644
index 00000000000..2d5e7b887d1
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsScreenKtTest.kt
@@ -0,0 +1,58 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import app.k9mail.core.ui.compose.testing.ComposeTest
+import app.k9mail.core.ui.compose.testing.pressBack
+import app.k9mail.core.ui.compose.testing.setContentWithTheme
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Effect
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+
+internal class GeneralSettingsScreenKtTest : ComposeTest() {
+
+    @Test
+    fun `should call onBack when back button is pressed`() {
+        val initialState = State()
+        val accountId = AccountId.create()
+        val viewModel = FakeGeneralSettingsViewModel(initialState)
+        var onBackCounter = 0
+
+        setContentWithTheme {
+            GeneralSettingsScreen(
+                accountId = accountId,
+                onBack = { onBackCounter++ },
+                viewModel = viewModel,
+            )
+        }
+
+        assertThat(onBackCounter).isEqualTo(0)
+
+        pressBack()
+
+        assertThat(onBackCounter).isEqualTo(1)
+    }
+
+    @Test
+    fun `should call onBack when navigate back effect received`() {
+        val initialState = State()
+        val accountId = AccountId.create()
+        val viewModel = FakeGeneralSettingsViewModel(initialState)
+        var onBackCounter = 0
+
+        setContentWithTheme {
+            GeneralSettingsScreen(
+                accountId = accountId,
+                onBack = { onBackCounter++ },
+                viewModel = viewModel,
+            )
+        }
+
+        assertThat(onBackCounter).isEqualTo(0)
+
+        viewModel.effect(Effect.NavigateBack)
+
+        assertThat(onBackCounter).isEqualTo(1)
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsStateTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsStateTest.kt
new file mode 100644
index 00000000000..b926add26fe
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsStateTest.kt
@@ -0,0 +1,24 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.collections.immutable.persistentListOf
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+
+internal class GeneralSettingsStateTest {
+
+    @Test
+    fun `should set default values`() {
+        // Arrange
+        val state = State()
+
+        // Assert
+        assertThat(state).isEqualTo(
+            State(
+                subtitle = null,
+                preferences = persistentListOf(),
+            ),
+        )
+    }
+}
diff --git a/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModelTest.kt b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModelTest.kt
new file mode 100644
index 00000000000..f5dbdad1374
--- /dev/null
+++ b/feature/account/settings/impl/src/test/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/GeneralSettingsViewModelTest.kt
@@ -0,0 +1,165 @@
+package net.thunderbird.feature.account.settings.impl.ui.general
+
+import app.k9mail.core.ui.compose.testing.MainDispatcherRule
+import app.k9mail.core.ui.compose.testing.mvi.MviContext
+import app.k9mail.core.ui.compose.testing.mvi.MviTurbines
+import app.k9mail.core.ui.compose.testing.mvi.runMviTest
+import app.k9mail.core.ui.compose.testing.mvi.turbinesWithInitialStateCheck
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.collections.immutable.ImmutableList
+import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toImmutableList
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.test.StandardTestDispatcher
+import net.thunderbird.core.outcome.Outcome
+import net.thunderbird.core.ui.compose.preference.api.Preference
+import net.thunderbird.core.ui.compose.preference.api.PreferenceSetting
+import net.thunderbird.feature.account.api.AccountId
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.Effect
+import net.thunderbird.feature.account.settings.impl.ui.general.GeneralSettingsContract.State
+import org.junit.Rule
+
+class GeneralSettingsViewModelTest {
+
+    @get:Rule
+    val mainDispatcherRule = MainDispatcherRule(StandardTestDispatcher())
+
+    @Test
+    fun `should load general settings`() = runMviTest {
+        val accountId = AccountId.create()
+        val initialState = State(
+            subtitle = "Subtitle",
+            preferences = persistentListOf(),
+        )
+        val preferences = FakeData.preferences
+
+        generalSettingsRobot(accountId, initialState, preferences) {
+            verifyGeneralSettingsLoaded(preferences)
+        }
+    }
+
+    @Test
+    fun `should navigate back when back is pressed`() = runMviTest {
+        val accountId = AccountId.create()
+        val initialState = State(
+            subtitle = "Subtitle",
+            preferences = persistentListOf(),
+        )
+        val preferences = FakeData.preferences
+
+        generalSettingsRobot(accountId, initialState, preferences) {
+            verifyGeneralSettingsLoaded(preferences)
+            pressBack()
+            verifyBackNavigation()
+        }
+    }
+
+    @Test
+    fun `should update preference when changed`() = runMviTest {
+        val accountId = AccountId.create()
+        val initialState = State(
+            subtitle = "Subtitle",
+            preferences = persistentListOf(),
+        )
+        val preferences = FakeData.preferences
+
+        generalSettingsRobot(accountId, initialState, preferences) {
+            verifyGeneralSettingsLoaded(preferences)
+            val updatedPreference = (preferences.first() as PreferenceSetting.Text).copy(
+                title = { "Updated Title" },
+                description = { "Updated Description" },
+            )
+            updatePreference(updatedPreference)
+
+            verifyPreferenceUpdated(updatedPreference)
+        }
+    }
+}
+
+private suspend fun MviContext.generalSettingsRobot(
+    accountId: AccountId,
+    initialState: State,
+    preferences: ImmutableList<Preference>,
+    interaction: suspend GeneralSettingsRobot.() -> Unit,
+) = GeneralSettingsRobot(this, accountId, initialState, preferences).apply {
+    initialize()
+    interaction()
+}
+
+private class GeneralSettingsRobot(
+    private val mviContext: MviContext,
+    private val accountId: AccountId,
+    private val initialState: State = State(),
+    private val preferences: ImmutableList<Preference>,
+) {
+    private lateinit var preferencesState: MutableStateFlow<ImmutableList<Preference>>
+    private lateinit var turbines: MviTurbines<State, Effect>
+
+    private val viewModel: GeneralSettingsContract.ViewModel by lazy {
+        GeneralSettingsViewModel(
+            accountId = accountId,
+            getGeneralPreferences = {
+                preferencesState.map {
+                    println("Loading preferences: $it")
+                    Outcome.success(it)
+                }
+            },
+            updateGeneralPreferences = { _, preference ->
+                preferencesState.value = preferencesState.value.map { existingPreference ->
+                    if (existingPreference is PreferenceSetting<*> && existingPreference.id == preference.id) {
+                        println("Updating preference: ${preference.id}")
+                        println("Old preference: $existingPreference")
+                        println("New preference: $preference")
+                        preference
+                    } else {
+                        existingPreference
+                    }
+                }.toImmutableList()
+                Outcome.success(Unit)
+            },
+            initialState = initialState,
+        )
+    }
+
+    suspend fun initialize() {
+        preferencesState = MutableStateFlow(preferences)
+
+        turbines = mviContext.turbinesWithInitialStateCheck(
+            initialState = initialState,
+            viewModel = viewModel,
+        )
+    }
+
+    suspend fun verifyGeneralSettingsLoaded(preferences: ImmutableList<Preference>) {
+        assertThat(turbines.awaitStateItem()).isEqualTo(
+            initialState.copy(
+                preferences = preferences,
+            ),
+        )
+    }
+
+    fun pressBack() {
+        viewModel.event(GeneralSettingsContract.Event.OnBackPressed)
+    }
+
+    suspend fun verifyBackNavigation() {
+        assertThat(turbines.awaitEffectItem()).isEqualTo(
+            Effect.NavigateBack,
+        )
+    }
+
+    fun updatePreference(preference: PreferenceSetting<*>) {
+        viewModel.event(GeneralSettingsContract.Event.OnPreferenceSettingChange(preference))
+    }
+
+    suspend fun verifyPreferenceUpdated(preference: PreferenceSetting<*>) {
+        val updatedPreference = turbines.awaitStateItem().preferences
+            .filterIsInstance<PreferenceSetting<*>>()
+            .find { it.id == preference.id }
+
+        assertThat(updatedPreference).isEqualTo(preference)
+    }
+}
diff --git a/legacy/core/src/test/java/com/fsck/k9/PreferencesTest.kt b/legacy/core/src/test/java/com/fsck/k9/PreferencesTest.kt
index 4243854bded..8a719081720 100644
--- a/legacy/core/src/test/java/com/fsck/k9/PreferencesTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/PreferencesTest.kt
@@ -1,6 +1,8 @@
 package com.fsck.k9
 
+import app.k9mail.legacy.account.LegacyAccount
 import assertk.assertThat
+import assertk.assertions.isEqualTo
 import assertk.assertions.isSameInstanceAs
 import com.fsck.k9.mail.AuthType
 import com.fsck.k9.mail.ConnectionSecurity
@@ -33,8 +35,8 @@ class PreferencesTest {
 
     @Test
     fun `reloading accounts should return same Account instance`() {
-        createAccount(ACCOUNT_UUID_ONE)
-        createAccount(ACCOUNT_UUID_TWO)
+        createAndSaveAccount(ACCOUNT_UUID_ONE)
+        createAndSaveAccount(ACCOUNT_UUID_TWO)
         val firstAccountOne = preferences.getAccount(ACCOUNT_UUID_ONE)
 
         preferences.loadAccounts()
@@ -43,7 +45,32 @@ class PreferencesTest {
         assertThat(firstAccountTwo).isSameInstanceAs(firstAccountOne)
     }
 
-    private fun createAccount(accountUuid: String) {
+    @Test
+    fun `saving accounts should return updated Account instance`() {
+        val account = createAccount(ACCOUNT_UUID_ONE)
+        preferences.saveAccount(account)
+
+        val updatedAccount = createAccount(ACCOUNT_UUID_ONE).apply {
+            name = "New name"
+        }
+
+        preferences.saveAccount(updatedAccount)
+
+        val currentAccountOne = preferences.getAccount(ACCOUNT_UUID_ONE)!!
+        assertThat(currentAccountOne.name).isEqualTo("New name")
+    }
+
+    private fun createAccount(accountId: String): LegacyAccount {
+        return LegacyAccount(
+            uuid = accountId,
+            isSensitiveDebugLoggingEnabled = { false },
+        ).apply {
+            incomingServerSettings = SERVER_SETTINGS
+            outgoingServerSettings = SERVER_SETTINGS
+        }
+    }
+
+    private fun createAndSaveAccount(accountUuid: String) {
         val account = preferences.newAccount(accountUuid).apply {
             // To be able to persist `Account` we need to set server settings
             incomingServerSettings = SERVER_SETTINGS