fix(avatar): add missing storage migration to prepopulate the avatar monogram

Fixes #9330

The storage migration was missing, so the new fields couldn't be prepopulated as expected.

App Crashes Upon Navigating to Account Specific General Settings
### Checklist

- [x] I have used the search function to see if someone else has already submitted the same bug report.
- [x] I will describe the problem with as much detail as possible.

### App

Thunderbird for Android

### App version

12.0-SNAPSHOT

### Where did you get the app from?

Other

### Android version

16

### Device model

_No response_

### Steps to reproduce

1. Launch App.
2. Click on Menu icon (Top left of screen) & open menu.
3. On Menu click on setting gear icon at the bottom & nagivate to `Settings `screen
4. On `Settings  `screen under `Account `section click on any account & navigate to `Account Settings `screen.
5.  On `Account Settings ` click on `General settings` and app  will nagivate to `Genenral Settings` screen and crash.

https://github.com/user-attachments/assets/b83a2d21-3151-43db-9d40-14b4a89f7339

### Expected behavior

`Genenral Settings` screen should display General Settings of corresponding account.

### Actual behavior

App Crashes.

### Logs

[log.txt](https://github.com/user-attachments/files/20744541/log.txt)

The settings migration should have populated the monogram already. It seems it doesn't work. Need to investigate that.
Thanks for the finding, seems like there are two migrations necessary, one for the settings import/export and one for the preferences storage. I didn't write one for the later one, hence the fields in the database are empty.

diff --git a/app-common/build.gradle.kts b/app-common/build.gradle.kts
index d50b338b77e..2aa99af696c 100644
--- a/app-common/build.gradle.kts
+++ b/app-common/build.gradle.kts
@@ -25,6 +25,8 @@ dependencies {
     implementation(projects.core.featureflag)
     implementation(projects.core.ui.legacy.theme2.common)

+    implementation(projects.feature.account.avatar.api)
+    implementation(projects.feature.account.avatar.impl)
     implementation(projects.feature.account.setup)
     implementation(projects.feature.mail.account.api)
     implementation(projects.feature.migration.provider)
diff --git a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt
index 44fbd2cd65e..0768ffd2714 100644
--- a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt
+++ b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AccountCreator.kt
@@ -24,9 +24,13 @@ import kotlinx.coroutines.withContext
 import net.thunderbird.core.android.account.LegacyAccount
 import net.thunderbird.core.common.mail.Protocols
 import net.thunderbird.core.logging.legacy.Log
+import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
+import net.thunderbird.feature.account.storage.profile.AvatarDto
+import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
 import net.thunderbird.feature.mail.folder.api.SpecialFolderSelection

 // TODO Move to feature/account/setup
+@Suppress("LongParameterList")
 internal class AccountCreator(
     private val accountColorPicker: AccountColorPicker,
     private val localFoldersCreator: SpecialLocalFoldersCreator,
@@ -34,8 +38,9 @@ internal class AccountCreator(
     private val context: Context,
     private val messagingController: MessagingController,
     private val deletePolicyProvider: DeletePolicyProvider,
-    private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
+    private val avatarMonogramCreator: AvatarMonogramCreator,
     private val unifiedInboxConfigurator: UnifiedInboxConfigurator,
+    private val coroutineDispatcher: CoroutineDispatcher = Dispatchers.IO,
 ) : AccountSetupExternalContract.AccountCreator {

     @Suppress("TooGenericExceptionCaught")
@@ -54,6 +59,13 @@ internal class AccountCreator(

         newAccount.email = account.emailAddress

+        newAccount.avatar = AvatarDto(
+            avatarType = AvatarTypeDto.MONOGRAM,
+            avatarMonogram = avatarMonogramCreator.create(account.options.accountName, account.emailAddress),
+            avatarImageUri = null,
+            avatarIconName = null,
+        )
+
         newAccount.setIncomingServerSettings(account.incomingServerSettings)
         newAccount.outgoingServerSettings = account.outgoingServerSettings

diff --git a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AppCommonAccountModule.kt b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AppCommonAccountModule.kt
index a363efd7285..125b31cab16 100644
--- a/app-common/src/main/kotlin/net/thunderbird/app/common/account/AppCommonAccountModule.kt
+++ b/app-common/src/main/kotlin/net/thunderbird/app/common/account/AppCommonAccountModule.kt
@@ -5,6 +5,8 @@ import net.thunderbird.app.common.account.data.DefaultAccountProfileLocalDataSou
 import net.thunderbird.app.common.account.data.DefaultLegacyAccountWrapperManager
 import net.thunderbird.core.android.account.AccountDefaultsProvider
 import net.thunderbird.core.android.account.LegacyAccountWrapperManager
+import net.thunderbird.feature.account.avatar.AvatarMonogramCreator
+import net.thunderbird.feature.account.avatar.DefaultAvatarMonogramCreator
 import net.thunderbird.feature.account.core.AccountCoreExternalContract.AccountProfileLocalDataSource
 import net.thunderbird.feature.account.core.featureAccountCoreModule
 import net.thunderbird.feature.account.storage.legacy.featureAccountStorageLegacyModule
@@ -45,6 +47,10 @@ internal val appCommonAccountModule = module {
         )
     }

+    factory<AvatarMonogramCreator> {
+        DefaultAvatarMonogramCreator()
+    }
+
     factory<AccountSetupExternalContract.AccountCreator> {
         AccountCreator(
             accountColorPicker = get(),
@@ -53,6 +59,7 @@ internal val appCommonAccountModule = module {
             context = androidApplication(),
             deletePolicyProvider = get(),
             messagingController = get(),
+            avatarMonogramCreator = get(),
             unifiedInboxConfigurator = get(),
         )
     }
diff --git a/feature/account/avatar/api/build.gradle.kts b/feature/account/avatar/api/build.gradle.kts
new file mode 100644
index 00000000000..117e38851d5
--- /dev/null
+++ b/feature/account/avatar/api/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+    id(ThunderbirdPlugins.Library.kmp)
+}
+
+android {
+    namespace = "net.thunderbird.feature.account.avatar"
+}
diff --git a/feature/account/avatar/api/src/commonMain/kotlin/net/thunderbird/feature/account/avatar/AvatarMonogramCreator.kt b/feature/account/avatar/api/src/commonMain/kotlin/net/thunderbird/feature/account/avatar/AvatarMonogramCreator.kt
new file mode 100644
index 00000000000..8a9d6071a42
--- /dev/null
+++ b/feature/account/avatar/api/src/commonMain/kotlin/net/thunderbird/feature/account/avatar/AvatarMonogramCreator.kt
@@ -0,0 +1,18 @@
+package net.thunderbird.feature.account.avatar
+
+/**
+ * Interface for creating a monogram based on a name or email address.
+ *
+ * This interface is used to generate a monogram, which is typically the initials of a person's name,
+ * or a representation based on an email address. Implementations should handle null or empty inputs gracefully.
+ */
+fun interface AvatarMonogramCreator {
+    /**
+     * Creates a monogram for the given name or email.
+     *
+     * @param name The name to generate a monogram for.
+     * @param email The email address to generate a monogram for.
+     * @return A string representing the monogram, or an empty string if the name or email is null or empty.
+     */
+    fun create(name: String?, email: String?): String
+}
diff --git a/feature/account/avatar/build.gradle.kts b/feature/account/avatar/impl/build.gradle.kts
similarity index 70%
rename from feature/account/avatar/build.gradle.kts
rename to feature/account/avatar/impl/build.gradle.kts
index 5052d5bc1d1..a6dfbf7ff7d 100644
--- a/feature/account/avatar/build.gradle.kts
+++ b/feature/account/avatar/impl/build.gradle.kts
@@ -3,7 +3,7 @@ plugins {
 }

 android {
-    namespace = "app.k9mail.feature.account.avatar"
+    namespace = "net.thunderbird.feature.account.avatar.impl"
     resourcePrefix = "account_avatar_"
 }

@@ -11,5 +11,7 @@ dependencies {
     implementation(projects.core.ui.compose.designsystem)
     implementation(projects.core.common)

+    implementation(projects.feature.account.avatar.api)
+
     testImplementation(projects.core.ui.compose.testing)
 }
diff --git a/feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlinedPreview.kt b/feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlinedPreview.kt
similarity index 93%
rename from feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlinedPreview.kt
rename to feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlinedPreview.kt
index 7f0f359cf66..8071c49e812 100644
--- a/feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlinedPreview.kt
+++ b/feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlinedPreview.kt
@@ -1,4 +1,4 @@
-package app.k9mail.feature.account.avatar.ui
+package net.thunderbird.feature.account.avatar.ui

 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
diff --git a/feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarPreview.kt b/feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarPreview.kt
similarity index 93%
rename from feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarPreview.kt
rename to feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarPreview.kt
index ea18b0274aa..7da64b0a133 100644
--- a/feature/account/avatar/src/debug/kotlin/app/k9mail/feature/account/avatar/ui/AvatarPreview.kt
+++ b/feature/account/avatar/impl/src/debug/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarPreview.kt
@@ -1,4 +1,4 @@
-package app.k9mail.feature.account.avatar.ui
+package net.thunderbird.feature.account.avatar.ui

 import androidx.compose.runtime.Composable
 import androidx.compose.ui.graphics.Color
diff --git a/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreator.kt b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreator.kt
new file mode 100644
index 00000000000..e0ae23c371f
--- /dev/null
+++ b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreator.kt
@@ -0,0 +1,27 @@
+package net.thunderbird.feature.account.avatar
+
+/**
+ * Creates a monogram based on a name or email address.
+ *
+ * This implementation generates a monogram by taking the first two characters of the name or email,
+ * removing spaces, and converting them to uppercase.
+ */
+class DefaultAvatarMonogramCreator : AvatarMonogramCreator {
+    override fun create(name: String?, email: String?): String {
+        return if (name != null && name.isNotEmpty()) {
+            composeAvatarMonogram(name)
+        } else if (email != null && email.isNotEmpty()) {
+            composeAvatarMonogram(email)
+        } else {
+            AVATAR_MONOGRAM_DEFAULT
+        }
+    }
+
+    private fun composeAvatarMonogram(name: String): String {
+        return name.replace(" ", "").take(2).uppercase()
+    }
+
+    private companion object {
+        private const val AVATAR_MONOGRAM_DEFAULT = "XX"
+    }
+}
diff --git a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/Avatar.kt b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/Avatar.kt
similarity index 98%
rename from feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/Avatar.kt
rename to feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/Avatar.kt
index def04e48b57..c41d601f6cc 100644
--- a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/Avatar.kt
+++ b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/Avatar.kt
@@ -1,4 +1,4 @@
-package app.k9mail.feature.account.avatar.ui
+package net.thunderbird.feature.account.avatar.ui

 import androidx.compose.animation.core.animateDpAsState
 import androidx.compose.foundation.border
diff --git a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlined.kt b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlined.kt
similarity index 98%
rename from feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlined.kt
rename to feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlined.kt
index d6ebb4ccede..f11deb022fa 100644
--- a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarOutlined.kt
+++ b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarOutlined.kt
@@ -1,4 +1,4 @@
-package app.k9mail.feature.account.avatar.ui
+package net.thunderbird.feature.account.avatar.ui

 import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
diff --git a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarSize.kt b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarSize.kt
similarity index 50%
rename from feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarSize.kt
rename to feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarSize.kt
index fb840869aa1..66eff2803b4 100644
--- a/feature/account/avatar/src/main/kotlin/app/k9mail/feature/account/avatar/ui/AvatarSize.kt
+++ b/feature/account/avatar/impl/src/main/kotlin/net/thunderbird/feature/account/avatar/ui/AvatarSize.kt
@@ -1,4 +1,4 @@
-package app.k9mail.feature.account.avatar.ui
+package net.thunderbird.feature.account.avatar.ui

 enum class AvatarSize {
     MEDIUM,
diff --git a/feature/account/settings/impl/build.gradle.kts b/feature/account/settings/impl/build.gradle.kts
index ca74d53a7d1..cf5c51d77f4 100644
--- a/feature/account/settings/impl/build.gradle.kts
+++ b/feature/account/settings/impl/build.gradle.kts
@@ -10,7 +10,7 @@ android {
 dependencies {
     api(projects.feature.account.settings.api)
     implementation(projects.feature.account.core)
-    implementation(projects.feature.account.avatar)
+    implementation(projects.feature.account.avatar.impl)

     implementation(projects.core.outcome)

diff --git a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/components/GeneralSettingsProfileView.kt b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/components/GeneralSettingsProfileView.kt
index f52f325b463..dffb8d789e7 100644
--- a/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/components/GeneralSettingsProfileView.kt
+++ b/feature/account/settings/impl/src/main/kotlin/net/thunderbird/feature/account/settings/impl/ui/general/components/GeneralSettingsProfileView.kt
@@ -16,8 +16,8 @@ import app.k9mail.core.ui.compose.designsystem.atom.card.CardElevated
 import app.k9mail.core.ui.compose.designsystem.atom.text.TextBodyLarge
 import app.k9mail.core.ui.compose.designsystem.atom.text.TextHeadlineSmall
 import app.k9mail.core.ui.compose.theme2.MainTheme
-import app.k9mail.feature.account.avatar.ui.AvatarOutlined
-import app.k9mail.feature.account.avatar.ui.AvatarSize
+import net.thunderbird.feature.account.avatar.ui.AvatarOutlined
+import net.thunderbird.feature.account.avatar.ui.AvatarSize

 @Composable
 internal fun GeneralSettingsProfileView(
diff --git a/feature/navigation/drawer/dropdown/build.gradle.kts b/feature/navigation/drawer/dropdown/build.gradle.kts
index 8005eb0c018..b88f3c7eb8a 100644
--- a/feature/navigation/drawer/dropdown/build.gradle.kts
+++ b/feature/navigation/drawer/dropdown/build.gradle.kts
@@ -14,7 +14,7 @@ dependencies {
     implementation(projects.core.ui.theme.api)
     implementation(projects.core.ui.compose.designsystem)

-    implementation(projects.feature.account.avatar)
+    implementation(projects.feature.account.avatar.impl)
     implementation(projects.feature.mail.account.api)
     implementation(projects.feature.mail.folder.api)

diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountAvatar.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountAvatar.kt
index b6b56f9038b..de84fd9dac8 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountAvatar.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/account/AccountAvatar.kt
@@ -12,7 +12,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.Surface
 import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelSmall
 import app.k9mail.core.ui.compose.theme2.ColorRoles
 import app.k9mail.core.ui.compose.theme2.toColorRoles
-import app.k9mail.feature.account.avatar.ui.Avatar
+import net.thunderbird.feature.account.avatar.ui.Avatar
 import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.DisplayAccount
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.common.labelForCount

diff --git a/feature/navigation/drawer/siderail/build.gradle.kts b/feature/navigation/drawer/siderail/build.gradle.kts
index df97ec7ffe8..a84ee9d27da 100644
--- a/feature/navigation/drawer/siderail/build.gradle.kts
+++ b/feature/navigation/drawer/siderail/build.gradle.kts
@@ -13,7 +13,7 @@ dependencies {
     implementation(projects.core.ui.theme.api)
     implementation(projects.core.ui.compose.designsystem)

-    implementation(projects.feature.account.avatar)
+    implementation(projects.feature.account.avatar.impl)
     implementation(projects.feature.mail.account.api)
     implementation(projects.feature.mail.folder.api)

diff --git a/feature/navigation/drawer/siderail/src/main/kotlin/net/thunderbird/feature/navigation/drawer/siderail/ui/account/AccountAvatar.kt b/feature/navigation/drawer/siderail/src/main/kotlin/net/thunderbird/feature/navigation/drawer/siderail/ui/account/AccountAvatar.kt
index e9a2808751f..19b40331e74 100644
--- a/feature/navigation/drawer/siderail/src/main/kotlin/net/thunderbird/feature/navigation/drawer/siderail/ui/account/AccountAvatar.kt
+++ b/feature/navigation/drawer/siderail/src/main/kotlin/net/thunderbird/feature/navigation/drawer/siderail/ui/account/AccountAvatar.kt
@@ -12,7 +12,7 @@ import app.k9mail.core.ui.compose.designsystem.atom.Surface
 import app.k9mail.core.ui.compose.designsystem.atom.text.TextLabelSmall
 import app.k9mail.core.ui.compose.theme2.ColorRoles
 import app.k9mail.core.ui.compose.theme2.toColorRoles
-import app.k9mail.feature.account.avatar.ui.Avatar
+import net.thunderbird.feature.account.avatar.ui.Avatar
 import net.thunderbird.feature.navigation.drawer.siderail.domain.entity.DisplayAccount
 import net.thunderbird.feature.navigation.drawer.siderail.ui.common.labelForCount

diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java b/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java
index 60f57349da1..ea46efd33f2 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java
+++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/K9StoragePersister.java
@@ -23,7 +23,7 @@
 import net.thunderbird.core.preference.storage.StorageUpdater;

 public class K9StoragePersister implements StoragePersister {
-    private static final int DB_VERSION = 26;
+    private static final int DB_VERSION = 27;
     private static final String DB_NAME = "preferences_storage";

     private final Context context;
diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo27.kt b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo27.kt
new file mode 100644
index 00000000000..42d9eb254b4
--- /dev/null
+++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrationTo27.kt
@@ -0,0 +1,84 @@
+package com.fsck.k9.preferences.migration
+
+import android.database.sqlite.SQLiteDatabase
+import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
+
+/**
+ * Migration to add avatar monograms for accounts that have the MONOGRAM avatar type
+ * and do not have an existing avatar monogram.
+ */
+class StorageMigrationTo27(
+    private val db: SQLiteDatabase,
+    private val migrationsHelper: StorageMigrationHelper,
+) {
+    fun addAvatarMonogram() {
+        val accountUuidsValue = migrationsHelper.readValue(db, "accountUuids")
+        if (accountUuidsValue.isNullOrEmpty()) {
+            return
+        }
+
+        val accountUuids = accountUuidsValue.split(",")
+        for (accountUuid in accountUuids) {
+            addAvatarMonogramToAccount(accountUuid)
+        }
+    }
+
+    private fun addAvatarMonogramToAccount(accountUuid: String) {
+        val avatarType = readAvatarType(accountUuid)
+        val avatarMonogram = readAvatarMonogram(accountUuid)
+
+        if (avatarType == AvatarTypeDto.MONOGRAM.name && avatarMonogram.isEmpty()) {
+            val monogram = generateAvatarMonogram(accountUuid)
+            insertAvatarMonogram(accountUuid, monogram)
+        }
+    }
+
+    private fun generateAvatarMonogram(accountUuid: String): String {
+        val name = readName(accountUuid)
+        val email = readEmail(accountUuid)
+        return getAvatarMonogram(name, email)
+    }
+
+    private fun getAvatarMonogram(name: String?, email: String?): String {
+        return if (name != null && name.isNotEmpty()) {
+            composeAvatarMonogram(name)
+        } else if (email != null && email.isNotEmpty()) {
+            composeAvatarMonogram(email)
+        } else {
+            AVATAR_MONOGRAM_DEFAULT
+        }
+    }
+
+    private fun composeAvatarMonogram(name: String): String {
+        return name.replace(" ", "").take(2).uppercase()
+    }
+
+    private fun readAvatarType(accountUuid: String): String {
+        return migrationsHelper.readValue(db, "$accountUuid.$AVATAR_TYPE_KEY") ?: ""
+    }
+
+    private fun readAvatarMonogram(accountUuid: String): String {
+        return migrationsHelper.readValue(db, "$accountUuid.$AVATAR_MONOGRAM_KEY") ?: ""
+    }
+
+    private fun readName(accountUuid: String): String {
+        return migrationsHelper.readValue(db, "$accountUuid.$NAME_KEY") ?: ""
+    }
+
+    private fun readEmail(accountUuid: String): String {
+        return migrationsHelper.readValue(db, "$accountUuid.$EMAIL_KEY") ?: ""
+    }
+
+    private fun insertAvatarMonogram(accountUuid: String, monogram: String) {
+        migrationsHelper.insertValue(db, "$accountUuid.$AVATAR_MONOGRAM_KEY", monogram)
+    }
+
+    private companion object {
+        const val NAME_KEY = "name.0"
+        const val EMAIL_KEY = "email.0"
+        const val AVATAR_TYPE_KEY = "avatarType"
+        const val AVATAR_MONOGRAM_KEY = "avatarMonogram"
+
+        private const val AVATAR_MONOGRAM_DEFAULT = "XX"
+    }
+}
diff --git a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt
index c33e60f1c93..553d73675de 100644
--- a/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt
+++ b/legacy/storage/src/main/java/com/fsck/k9/preferences/migration/StorageMigrations.kt
@@ -33,5 +33,6 @@ internal object StorageMigrations {
         if (oldVersion < 24) StorageMigrationTo24(db, migrationsHelper).removeLegacyAuthenticationModes()
         if (oldVersion < 25) StorageMigrationTo25(db, migrationsHelper).convertToAuthTypeNone()
         if (oldVersion < 26) StorageMigrationTo26(db, migrationsHelper).fixIdentities()
+        if (oldVersion < 27) StorageMigrationTo27(db, migrationsHelper).addAvatarMonogram()
     }
 }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 87b66f7b02c..eaf2b63eb4c 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -63,7 +63,8 @@ include(

 include(
     ":feature:account:api",
-    ":feature:account:avatar",
+    ":feature:account:avatar:api",
+    ":feature:account:avatar:impl",
     ":feature:account:core",
     ":feature:account:common",
     ":feature:account:edit",

diff --git a/feature/account/avatar/impl/src/test/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreatorTest.kt b/feature/account/avatar/impl/src/test/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreatorTest.kt
new file mode 100644
index 00000000000..74a3ca93cfb
--- /dev/null
+++ b/feature/account/avatar/impl/src/test/kotlin/net/thunderbird/feature/account/avatar/DefaultAvatarMonogramCreatorTest.kt
@@ -0,0 +1,41 @@
+package net.thunderbird.feature.account.avatar
+
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+
+class DefaultAvatarMonogramCreatorTest {
+
+    private val testSubject = DefaultAvatarMonogramCreator()
+
+    @Test
+    fun `create returns correct monogram for name`() {
+        val name = "John Doe"
+        val expectedMonogram = "JO"
+
+        val result = testSubject.create(name, null)
+
+        assertThat(result).isEqualTo(expectedMonogram)
+    }
+
+    @Test
+    fun `create returns correct monogram for email`() {
+        val email = "test@example.com"
+        val expectedMonogram = "TE"
+
+        val result = testSubject.create(null, email)
+
+        assertThat(result).isEqualTo(expectedMonogram)
+    }
+
+    @Test
+    fun `create returns default monogram for null or empty inputs`() {
+        val expectedMonogram = "XX"
+
+        val resultWithNulls = testSubject.create(null, null)
+        assertThat(resultWithNulls).isEqualTo(expectedMonogram)
+
+        val resultWithEmptyStrings = testSubject.create("", "")
+        assertThat(resultWithEmptyStrings).isEqualTo(expectedMonogram)
+    }
+}
diff --git a/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo27Test.kt b/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo27Test.kt
new file mode 100644
index 00000000000..72e535ccf32
--- /dev/null
+++ b/legacy/storage/src/test/java/com/fsck/k9/preferences/migration/StorageMigrationTo27Test.kt
@@ -0,0 +1,109 @@
+package com.fsck.k9.preferences.migration
+
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import assertk.assertions.key
+import com.fsck.k9.preferences.createPreferencesDatabase
+import java.util.UUID
+import kotlin.test.Test
+import net.thunderbird.core.logging.legacy.Log
+import net.thunderbird.core.logging.testing.TestLogger
+import net.thunderbird.feature.account.storage.profile.AvatarTypeDto
+import org.junit.After
+import org.junit.Before
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+
+@RunWith(RobolectricTestRunner::class)
+class StorageMigrationTo27Test {
+    private val database = createPreferencesDatabase()
+    private val migrationHelper = DefaultStorageMigrationHelper()
+    private val migration = StorageMigrationTo27(database, migrationHelper)
+
+    @Before
+    fun setUp() {
+        Log.logger = TestLogger()
+    }
+
+    @After
+    fun tearDown() {
+        database.close()
+    }
+
+    @Test
+    fun `avatar monogram should be added for accounts with MONOGRAM avatar type and no monogram, name and email`() {
+        val accountUuid = createAccount(
+            "avatarType" to AvatarTypeDto.MONOGRAM.name,
+            "avatarMonogram" to null,
+            "name.0" to null,
+            "email.0" to null,
+        )
+        writeAccountUuids(accountUuid)
+
+        migration.addAvatarMonogram()
+
+        assertThat(migrationHelper.readAllValues(database))
+            .key("$accountUuid.avatarMonogram").isEqualTo("XX")
+    }
+
+    @Test
+    fun `avatar monogram should not be added for accounts with MONOGRAM avatar type and existing monogram`() {
+        val accountUuid = createAccount(
+            "avatarType" to AvatarTypeDto.MONOGRAM.name,
+            "avatarMonogram" to "AB",
+        )
+        writeAccountUuids(accountUuid)
+
+        migration.addAvatarMonogram()
+
+        assertThat(migrationHelper.readAllValues(database))
+            .key("$accountUuid.avatarMonogram").isEqualTo("AB")
+    }
+
+    @Test
+    fun `avatar monogram should be added for accounts with name and no monogram`() {
+        val accountUuid = createAccount(
+            "avatarType" to AvatarTypeDto.MONOGRAM.name,
+            "avatarMonogram" to null,
+            "name.0" to "John Doe",
+            "email.0" to "test@example.com",
+        )
+        writeAccountUuids(accountUuid)
+
+        migration.addAvatarMonogram()
+
+        assertThat(migrationHelper.readAllValues(database))
+            .key("$accountUuid.avatarMonogram").isEqualTo("JO")
+    }
+
+    @Test
+    fun `avatar monogram should be added for accounts with no name and monogram but email`() {
+        val accountUuid = createAccount(
+            "avatarType" to AvatarTypeDto.MONOGRAM.name,
+            "avatarMonogram" to null,
+            "name.0" to null,
+            "email.0" to "test@example.com",
+        )
+        writeAccountUuids(accountUuid)
+
+        migration.addAvatarMonogram()
+
+        assertThat(migrationHelper.readAllValues(database))
+            .key("$accountUuid.avatarMonogram").isEqualTo("TE")
+    }
+
+    private fun writeAccountUuids(vararg accounts: String) {
+        val accountUuids = accounts.joinToString(separator = ",")
+        migrationHelper.insertValue(database, "accountUuids", accountUuids)
+    }
+
+    private fun createAccount(vararg pairs: Pair<String, String?>): String {
+        val accountUuid = UUID.randomUUID().toString()
+
+        for ((key, value) in pairs) {
+            migrationHelper.insertValue(database, "$accountUuid.$key", value)
+        }
+
+        return accountUuid
+    }
+}


- Fixes #9298
### Implementation Details:
 - Rewrite the com.fsck.k9.mail.store.imap.SearchResponse class from Java to Kotlin using idiomatic Kotlin conventions.
 - Update `com.fsck.k9.mail.store.imap.SearchResponseTest `to use AssertK assertions instead of JUnit assertions.
 - Update `com.fsck.k9.mail.store.imap.SearchResponseTest `to adopt to new test guideline.

Convert SearchResponse class from  Java to Kotlin
We should migrate the `com.fsck.k9.mail.store.imap.SearchResponse` class to Kotlin to align with the ongoing modernization efforts and Kotlin-first development approach across the codebase.

./gradlew :mail:protocols:imap:test --tests "com.fsck.k9.mail.store.imap.SearchResponseTest" --tests "com.fsck.k9.mail.store.imap.SearchResponseTest"

diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java
deleted file mode 100644
index 3fac2905df5..00000000000
--- a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.fsck.k9.mail.store.imap;
-
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static com.fsck.k9.mail.store.imap.ImapResponseParser.equalsIgnoreCase;
-
-
-class SearchResponse {
-    private final List<Long> numbers;
-
-
-    private SearchResponse(List<Long> numbers) {
-        this.numbers = numbers;
-    }
-
-    public static SearchResponse parse(List<ImapResponse> responses) {
-        List<Long> numbers = new ArrayList<>();
-
-        for (ImapResponse response : responses) {
-            parseSingleLine(response, numbers);
-        }
-
-        return new SearchResponse(numbers);
-    }
-
-    private static void parseSingleLine(ImapResponse response, List<Long> numbers) {
-        if (response.isTagged() || response.size() < 2 || !equalsIgnoreCase(response.get(0), Responses.SEARCH)) {
-            return;
-        }
-
-        int end = response.size();
-        for (int i = 1; i < end; i++) {
-            try {
-                long number = response.getLong(i);
-                numbers.add(number);
-            } catch (NumberFormatException e) {
-                return;
-            }
-        }
-    }
-
-    /**
-     * @return A mutable list of numbers from the SEARCH response(s).
-     */
-    public List<Long> getNumbers() {
-        return numbers;
-    }
-}
diff --git a/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt
new file mode 100644
index 00000000000..c5e43560008
--- /dev/null
+++ b/mail/protocols/imap/src/main/java/com/fsck/k9/mail/store/imap/SearchResponse.kt
@@ -0,0 +1,43 @@
+package com.fsck.k9.mail.store.imap
+
+internal class SearchResponse private constructor(
+    /**
+     * @return A list of numbers from the SEARCH response(s).
+     */
+    val numbers: List<Long>,
+) {
+    companion object {
+        @JvmStatic
+        fun parse(responses: List<ImapResponse>): SearchResponse {
+            val numbers = mutableListOf<Long>()
+
+            for (response in responses) {
+                parseSingleLine(response, numbers)
+            }
+
+            return SearchResponse(numbers)
+        }
+
+        private fun parseSingleLine(response: ImapResponse, numbers: MutableList<Long>) {
+            if (response.isTagged ||
+                response.size < 2 ||
+                !ImapResponseParser.equalsIgnoreCase(
+                    response[0],
+                    Responses.SEARCH,
+                )
+            ) {
+                return
+            }
+
+            val end = response.size
+            for (i in 1..<end) {
+                try {
+                    val number = response.getLong(i)
+                    numbers.add(number)
+                } catch (_: NumberFormatException) {
+                    return
+                }
+            }
+        }
+    }
+}

diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java
deleted file mode 100644
index ec7bc2c7346..00000000000
--- a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.java
+++ /dev/null
@@ -1,89 +0,0 @@
-package com.fsck.k9.mail.store.imap;
-
-
-import java.util.Collections;
-import java.util.List;
-
-import org.junit.Test;
-
-import static com.fsck.k9.mail.store.imap.ImapResponseHelper.createImapResponseList;
-import static java.util.Arrays.asList;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-
-
-public class SearchResponseTest {
-    @Test
-    public void parse_withSingleSearchResponse_shouldExtractNumbers() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList(
-                "* SEARCH 1 2 3",
-                "* 23 EXISTS",
-                "* SEARCH 4",
-                "1 OK SEARCH completed");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(asList(1L, 2L, 3L, 4L), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withMultipleSearchResponses_shouldExtractNumbers() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList(
-                "* SEARCH 1 2 3",
-                "* 23 EXISTS",
-                "* SEARCH 4",
-                "1 OK SEARCH completed",
-                "* SEARCH 5 6",
-                "* 19 EXPUNGED",
-                "* SEARCH 7",
-                "2 OK SEARCH completed",
-                "* SEARCH 8",
-                "3 OK SEARCH completed");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(asList(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleTaggedSearchResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("x SEARCH 7 8 9");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleTooShortResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* SEARCH");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleNoSearchResponse_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* 23 EXPUNGE");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-
-    @Test
-    public void parse_withSingleSearchResponseContainingInvalidNumber_shouldReturnEmptyList() throws Exception {
-        List<ImapResponse> imapResponses = createImapResponseList("* SEARCH A");
-
-        SearchResponse result = SearchResponse.parse(imapResponses);
-
-        assertNotNull(result);
-        assertEquals(Collections.emptyList(), result.getNumbers());
-    }
-}
diff --git a/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt
new file mode 100644
index 00000000000..ac17740f24e
--- /dev/null
+++ b/mail/protocols/imap/src/test/java/com/fsck/k9/mail/store/imap/SearchResponseTest.kt
@@ -0,0 +1,128 @@
+package com.fsck.k9.mail.store.imap
+
+import assertk.all
+import assertk.assertThat
+import assertk.assertions.containsExactly
+import assertk.assertions.isEmpty
+import assertk.assertions.prop
+import com.fsck.k9.mail.store.imap.SearchResponse.Companion.parse
+import jdk.dynalink.linker.support.Guards.isNotNull
+import kotlin.test.Test
+import org.junit.Assert
+
+class SearchResponseTest {
+    @Test
+    fun parse_withSingleSearchResponse_shouldExtractNumbers() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList(
+            "* SEARCH 1 2 3",
+            "* 23 EXISTS",
+            "* SEARCH 4",
+            "1 OK SEARCH completed",
+        )
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .containsExactly(1L, 2L, 3L, 4L)
+        }
+    }
+
+    @Test
+    fun parse_withMultipleSearchResponses_shouldExtractNumbers() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList(
+            "* SEARCH 1 2 3",
+            "* 23 EXISTS",
+            "* SEARCH 4",
+            "1 OK SEARCH completed",
+            "* SEARCH 5 6",
+            "* 19 EXPUNGED",
+            "* SEARCH 7",
+            "2 OK SEARCH completed",
+            "* SEARCH 8",
+            "3 OK SEARCH completed",
+        )
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .containsExactly(1L, 2L, 3L, 4L, 5L, 6L, 7L, 8L)
+        }
+    }
+
+    @Test
+    fun parse_withSingleTaggedSearchResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("x SEARCH 7 8 9")
+
+        // Act
+        val result = parse(imapResponses)
+
+        Assert.assertNotNull(result)
+        Assert.assertEquals(emptyList<Any>(), result.numbers)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleTooShortResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* SEARCH")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleNoSearchResponse_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* 23 EXPUNGE")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+
+    @Test
+    fun parse_withSingleSearchResponseContainingInvalidNumber_shouldReturnEmptyList() {
+        // Arrange
+        val imapResponses = ImapResponseHelper.createImapResponseList("* SEARCH A")
+
+        // Act
+        val result = parse(imapResponses)
+
+        // Assert
+        assertThat(result).all {
+            isNotNull()
+            prop("numbers") { it.numbers }
+                .isEmpty()
+        }
+    }
+}


feat(drawer): add feature flag for drawer dropdown ui

Enabled for `debug` and `daily`, disabled for `beta` and `release`.

Moves shared code to a dedicated class.

Resolves #9188

Dropdown Drawer - add feature flag to hoist ui changes
Further drawer UI changes should be done on a new module and guarded by a feature flag.

Update: no need to create a new module, the changes could be guarded by a feature flag inside the module.

diff --git a/app-k9mail/src/debug/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt b/app-k9mail/src/debug/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
index 00fe9f58f7a..c7d99ec832e 100644
--- a/app-k9mail/src/debug/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
+++ b/app-k9mail/src/debug/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
@@ -12,6 +12,7 @@ class K9FeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = true),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = true),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = true),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = true),
         )
     }
 }
diff --git a/app-k9mail/src/release/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt b/app-k9mail/src/release/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
index 9a8c47673b7..645b9764142 100644
--- a/app-k9mail/src/release/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
+++ b/app-k9mail/src/release/kotlin/app/k9mail/featureflag/K9FeatureFlagFactory.kt
@@ -12,6 +12,7 @@ class K9FeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = false),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = false),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = false),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = false),
         )
     }
 }
diff --git a/app-thunderbird/src/beta/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt b/app-thunderbird/src/beta/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
index 8d9d37a180b..3ac7c4ed113 100644
--- a/app-thunderbird/src/beta/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
+++ b/app-thunderbird/src/beta/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
@@ -15,6 +15,7 @@ class TbFeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = true),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = true),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = true),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = false),
         )
     }
 }
diff --git a/app-thunderbird/src/daily/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt b/app-thunderbird/src/daily/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
index f648fd51604..692add1003e 100644
--- a/app-thunderbird/src/daily/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
+++ b/app-thunderbird/src/daily/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
@@ -15,6 +15,7 @@ class TbFeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = true),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = true),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = true),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = true),
         )
     }
 }
diff --git a/app-thunderbird/src/debug/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt b/app-thunderbird/src/debug/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
index 4bd162712e8..1521fdf83ef 100644
--- a/app-thunderbird/src/debug/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
+++ b/app-thunderbird/src/debug/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
@@ -15,6 +15,7 @@ class TbFeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = true),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = true),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = true),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = true),
         )
     }
 }
diff --git a/app-thunderbird/src/release/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt b/app-thunderbird/src/release/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
index f34f32cd962..79b280a9bac 100644
--- a/app-thunderbird/src/release/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
+++ b/app-thunderbird/src/release/kotlin/net/thunderbird/android/featureflag/TbFeatureFlagFactory.kt
@@ -15,6 +15,7 @@ class TbFeatureFlagFactory : FeatureFlagFactory {
             FeatureFlag("disable_font_size_config".toFeatureFlagKey(), enabled = false),
             FeatureFlag("email_notification_default".toFeatureFlagKey(), enabled = false),
             FeatureFlag("enable_dropdown_drawer".toFeatureFlagKey(), enabled = false),
+            FeatureFlag("enable_dropdown_drawer_ui".toFeatureFlagKey(), enabled = false),
         )
     }
 }
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/DropDownDrawer.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/DropDownDrawer.kt
index 27747178a8f..d68cf3781c4 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/DropDownDrawer.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/DropDownDrawer.kt
@@ -7,6 +7,7 @@ import androidx.drawerlayout.widget.DrawerLayout
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.update
+import net.thunderbird.core.featureflag.FeatureFlagProvider
 import net.thunderbird.core.ui.theme.api.FeatureThemeProvider
 import net.thunderbird.feature.navigation.drawer.api.NavigationDrawer
 import net.thunderbird.feature.navigation.drawer.api.R
@@ -32,6 +33,7 @@ class DropDownDrawer(
 ) : NavigationDrawer, KoinComponent {

     private val themeProvider: FeatureThemeProvider by inject()
+    private val featureFlagProvider: FeatureFlagProvider by inject()

     private val drawer: DrawerLayout = parent.findViewById(R.id.navigation_drawer_layout)
     private val drawerContent: ComposeView = parent.findViewById(R.id.navigation_drawer_content)
@@ -52,6 +54,7 @@ class DropDownDrawer(
                     openUnifiedFolder = openUnifiedFolder,
                     openManageFolders = openManageFolders,
                     openSettings = openSettings,
+                    featureFlagProvider = featureFlagProvider,
                     closeDrawer = { close() },
                 )
             }
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerContent.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerContent.kt
index 95c6cb886b9..a27ce49850d 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerContent.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerContent.kt
@@ -4,7 +4,6 @@ import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.WindowInsets
-import androidx.compose.foundation.layout.displayCutout
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.statusBars
@@ -12,25 +11,18 @@ import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.layout.windowInsetsPadding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.unit.Dp
-import androidx.compose.ui.unit.LayoutDirection
-import androidx.compose.ui.unit.dp
 import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
 import app.k9mail.core.ui.compose.designsystem.atom.Surface
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.Event
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.State
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.account.AccountList
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.account.AccountView
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.common.DRAWER_WIDTH
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.common.getAdditionalWidth
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.folder.FolderList
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.setting.SettingList

-// As long as we use DrawerLayout, we don't have to worry about screens narrower than DRAWER_WIDTH. DrawerLayout will
-// automatically limit the width of the content view so there's still room for a scrim with minimum tap width.
-private val DRAWER_WIDTH = 360.dp
-
 @Composable
 internal fun DrawerContent(
     state: State,
@@ -94,19 +86,3 @@ internal fun DrawerContent(
         }
     }
 }
-
-@Composable
-fun getAdditionalWidth(): Dp {
-    val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
-
-    return if (isRtl) {
-        WindowInsets.displayCutout.getRight(density = density, layoutDirection = layoutDirection)
-    } else {
-        WindowInsets.displayCutout.getLeft(density = density, layoutDirection = layoutDirection)
-    }.pxToDp()
-}
-
-@Composable
-fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt
index c96220207cf..913dd8c6059 100644
--- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerView.kt
@@ -2,14 +2,19 @@ package net.thunderbird.feature.navigation.drawer.dropdown.ui

 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
 import app.k9mail.core.ui.compose.common.mvi.observe
 import app.k9mail.core.ui.compose.designsystem.molecule.PullToRefreshBox
+import net.thunderbird.core.featureflag.FeatureFlagKey
+import net.thunderbird.core.featureflag.FeatureFlagProvider
+import net.thunderbird.core.featureflag.FeatureFlagResult
 import net.thunderbird.feature.navigation.drawer.dropdown.FolderDrawerState
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.Effect
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.Event
 import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.ViewModel
 import org.koin.androidx.compose.koinViewModel

+@Suppress("LongParameterList")
 @Composable
 internal fun DrawerView(
     drawerState: FolderDrawerState,
@@ -19,6 +24,7 @@ internal fun DrawerView(
     openManageFolders: () -> Unit,
     openSettings: () -> Unit,
     closeDrawer: () -> Unit,
+    featureFlagProvider: FeatureFlagProvider,
     viewModel: ViewModel = koinViewModel<DrawerViewModel>(),
 ) {
     val (state, dispatch) = viewModel.observe { effect ->
@@ -28,6 +34,7 @@ internal fun DrawerView(
                 effect.accountId,
                 effect.folderId,
             )
+
             Effect.OpenUnifiedFolder -> openUnifiedFolder()
             is Effect.OpenManageFolders -> openManageFolders()
             is Effect.OpenSettings -> openSettings()
@@ -35,6 +42,10 @@ internal fun DrawerView(
         }
     }

+    val isDropdownDrawerEnabled = remember {
+        featureFlagProvider.provide(FeatureFlagKey("enable_dropdown_drawer_ui")) == FeatureFlagResult.Enabled
+    }
+
     LaunchedEffect(drawerState.selectedAccountUuid) {
         dispatch(Event.SelectAccount(drawerState.selectedAccountUuid))
     }
@@ -47,9 +58,16 @@ internal fun DrawerView(
         isRefreshing = state.value.isLoading,
         onRefresh = { dispatch(Event.OnSyncAccount) },
     ) {
-        DrawerContent(
-            state = state.value,
-            onEvent = { dispatch(it) },
-        )
+        if (isDropdownDrawerEnabled) {
+            DrawerContent(
+                state = state.value,
+                onEvent = { dispatch(it) },
+            )
+        } else {
+            SideRailDrawerContent(
+                state = state.value,
+                onEvent = { dispatch(it) },
+            )
+        }
     }
 }
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/SideRailDrawerContent.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/SideRailDrawerContent.kt
new file mode 100644
index 00000000000..a2cb7c35b14
--- /dev/null
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/SideRailDrawerContent.kt
@@ -0,0 +1,88 @@
+package net.thunderbird.feature.navigation.drawer.dropdown.ui
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.statusBars
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import app.k9mail.core.ui.compose.designsystem.atom.DividerHorizontal
+import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.Event
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.DrawerContract.State
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.account.AccountList
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.account.AccountView
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.common.DRAWER_WIDTH
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.common.getAdditionalWidth
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.folder.FolderList
+import net.thunderbird.feature.navigation.drawer.dropdown.ui.setting.SettingList
+
+@Composable
+internal fun SideRailDrawerContent(
+    state: State,
+    onEvent: (Event) -> Unit,
+    modifier: Modifier = Modifier,
+) {
+    val additionalWidth = getAdditionalWidth()
+
+    Surface(
+        modifier = modifier
+            .windowInsetsPadding(WindowInsets.statusBars)
+            .width(DRAWER_WIDTH + additionalWidth)
+            .fillMaxHeight()
+            .testTag("DrawerContent"),
+    ) {
+        val selectedAccount = state.accounts.firstOrNull { it.id == state.selectedAccountId }
+        Column {
+            selectedAccount?.let {
+                AccountView(
+                    account = selectedAccount,
+                    onClick = { onEvent(Event.OnAccountViewClick(selectedAccount)) },
+                    showAvatar = state.config.showAccountSelector,
+                )
+
+                DividerHorizontal()
+            }
+            Row {
+                AnimatedVisibility(
+                    visible = state.config.showAccountSelector,
+                ) {
+                    AccountList(
+                        accounts = state.accounts,
+                        selectedAccount = selectedAccount,
+                        onAccountClick = { onEvent(Event.OnAccountClick(it)) },
+                        onSyncAllAccountsClick = { onEvent(Event.OnSyncAllAccounts) },
+                        onSettingsClick = { onEvent(Event.OnSettingsClick) },
+                    )
+                }
+                Column(
+                    modifier = Modifier
+                        .weight(1f)
+                        .fillMaxSize(),
+                ) {
+                    FolderList(
+                        rootFolder = state.rootFolder,
+                        selectedFolder = state.folders.firstOrNull { it.id == state.selectedFolderId },
+                        onFolderClick = { folder ->
+                            onEvent(Event.OnFolderClick(folder))
+                        },
+                        showStarredCount = state.config.showStarredCount,
+                        modifier = Modifier.weight(1f),
+                    )
+                    DividerHorizontal()
+                    SettingList(
+                        onAccountSelectorClick = { onEvent(Event.OnAccountSelectorClick) },
+                        onManageFoldersClick = { onEvent(Event.OnManageFoldersClick) },
+                        showAccountSelector = state.config.showAccountSelector,
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/common/DrawerUtil.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/common/DrawerUtil.kt
new file mode 100644
index 00000000000..daf662e07e4
--- /dev/null
+++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/common/DrawerUtil.kt
@@ -0,0 +1,30 @@
+package net.thunderbird.feature.navigation.drawer.dropdown.ui.common
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+// As long as we use DrawerLayout, we don't have to worry about screens narrower than DRAWER_WIDTH. DrawerLayout will
+// automatically limit the width of the content view so there's still room for a scrim with minimum tap width.
+internal val DRAWER_WIDTH = 360.dp
+
+@Composable
+internal fun getAdditionalWidth(): Dp {
+    val density = LocalDensity.current
+    val layoutDirection = LocalLayoutDirection.current
+    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl
+
+    return if (isRtl) {
+        WindowInsets.displayCutout.getRight(density = density, layoutDirection = layoutDirection)
+    } else {
+        WindowInsets.displayCutout.getLeft(density = density, layoutDirection = layoutDirection)
+    }.pxToDp()
+}
+
+@Composable
+private fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }

diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewKtTest.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewKtTest.kt
index ec9150c67d1..2f31ce54a99 100644
--- a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewKtTest.kt
+++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/DrawerViewKtTest.kt
@@ -35,6 +35,7 @@ internal class DrawerViewKtTest : ComposeTest() {
                 openManageFolders = { counter.openManageFoldersCount++ },
                 openSettings = { counter.openSettingsCount++ },
                 closeDrawer = { counter.closeDrawerCount++ },
+                featureFlagProvider = FakeFeatureFlagProvider(isEnabled = true),
                 viewModel = viewModel,
             )
         }
@@ -85,6 +86,7 @@ internal class DrawerViewKtTest : ComposeTest() {
                 openManageFolders = { },
                 openSettings = { },
                 closeDrawer = { },
+                featureFlagProvider = FakeFeatureFlagProvider(isEnabled = true),
                 viewModel = viewModel,
             )
         }
@@ -122,6 +124,7 @@ internal class DrawerViewKtTest : ComposeTest() {
                 openManageFolders = {},
                 openSettings = {},
                 closeDrawer = {},
+                featureFlagProvider = FakeFeatureFlagProvider(isEnabled = true),
                 viewModel = viewModel,
             )
         }
diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/FakeFeatureFlagProvider.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/FakeFeatureFlagProvider.kt
new file mode 100644
index 00000000000..258fe4da616
--- /dev/null
+++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/ui/FakeFeatureFlagProvider.kt
@@ -0,0 +1,17 @@
+package net.thunderbird.feature.navigation.drawer.dropdown.ui
+
+import net.thunderbird.core.featureflag.FeatureFlagKey
+import net.thunderbird.core.featureflag.FeatureFlagProvider
+import net.thunderbird.core.featureflag.FeatureFlagResult
+
+class FakeFeatureFlagProvider(
+    private val isEnabled: Boolean = false,
+) : FeatureFlagProvider {
+    override fun provide(key: FeatureFlagKey): FeatureFlagResult {
+        return if (isEnabled) {
+            FeatureFlagResult.Enabled
+        } else {
+            FeatureFlagResult.Disabled
+        }
+    }
+}