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

Issue:
Unread message count doesn't change unless refreshed
### 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

8.2

### Where did you get the app from?

Google Play

### Android version

14

### Device model

_No response_

### Steps to reproduce

1. Have some unread emails.
2. Check in the sidebar how many emails are unread.
3. Mark one email read.
4. Now check the number in the sidebar again.

You will find that the number didn't decrease.


### Expected behavior

When I mark emails as read, the unread message count should decrease in the side bar.

### Actual behavior

The unread count doesn't change after marking emails as read. It only charges after refreshing the inbox which is a bit annoying.

### Logs

_No response_


Discussion hints:
This is not fixed with #8813, seems like there is a delay in propagating the changes to the UI.
We are able to reproduce this behavior. Working on a fix.

Pull request: Fix drawer message count updates
This pull request fixes the update of unread and stared messages counts in the drawer.

It introduces a new `UnifiedFolderRepository` to the navigation drawer feature, along with several related changes to support updating the unread count reliably.

Fixes #8800, #7865


Test command: ./gradlew :feature:widget:unread:testDebugUnitTest --tests "app.k9mail.feature.widget.unread.UnreadWidgetDataProviderTest" :feature:navigation:drawer:testDebugUnitTest --tests "app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayFoldersForAccountTest" --tests "app.k9mail.feature.navigation.drawer.data.UnifiedFolderRepositoryTest" :legacy:core:testDebugUnitTest --tests "com.fsck.k9.controller.DefaultMessageCountsProviderTest"

Fix patch:
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/NavigationDrawerModule.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/NavigationDrawerModule.kt
index 78ce21ee6e4..899ebadb8c4 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/NavigationDrawerModule.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/NavigationDrawerModule.kt
@@ -1,5 +1,7 @@
 package app.k9mail.feature.navigation.drawer
 
+import app.k9mail.feature.navigation.drawer.data.UnifiedFolderRepository
+import app.k9mail.feature.navigation.drawer.domain.DomainContract
 import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
 import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayAccounts
 import app.k9mail.feature.navigation.drawer.domain.usecase.GetDisplayFoldersForAccount
@@ -14,6 +16,12 @@ import org.koin.dsl.module
 
 val navigationDrawerModule: Module = module {
 
+    single<DomainContract.UnifiedFolderRepository> {
+        UnifiedFolderRepository(
+            messageCountsProvider = get(),
+        )
+    }
+
     single<UseCase.GetDrawerConfig> {
         GetDrawerConfig(
             configLoader = get(),
@@ -35,8 +43,8 @@ val navigationDrawerModule: Module = module {
 
     single<UseCase.GetDisplayFoldersForAccount> {
         GetDisplayFoldersForAccount(
-            repository = get(),
-            messageCountsProvider = get(),
+            displayFolderRepository = get(),
+            unifiedFolderRepository = get(),
         )
     }
 
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepository.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepository.kt
new file mode 100644
index 00000000000..2042b3a768c
--- /dev/null
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepository.kt
@@ -0,0 +1,44 @@
+package app.k9mail.feature.navigation.drawer.data
+
+import app.k9mail.feature.navigation.drawer.domain.DomainContract
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
+import app.k9mail.legacy.message.controller.MessageCountsProvider
+import app.k9mail.legacy.search.LocalSearch
+import app.k9mail.legacy.search.api.SearchAttribute
+import app.k9mail.legacy.search.api.SearchField
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+internal class UnifiedFolderRepository(
+    private val messageCountsProvider: MessageCountsProvider,
+) : DomainContract.UnifiedFolderRepository {
+
+    override fun getDisplayUnifiedFolderFlow(unifiedFolderType: DisplayUnifiedFolderType): Flow<DisplayUnifiedFolder> {
+        return messageCountsProvider.getMessageCountsFlow(createUnifiedFolderSearch(unifiedFolderType)).map {
+            DisplayUnifiedFolder(
+                id = UNIFIED_INBOX_ID,
+                unifiedType = DisplayUnifiedFolderType.INBOX,
+                unreadMessageCount = it.unread,
+                starredMessageCount = it.starred,
+            )
+        }
+    }
+
+    private fun createUnifiedFolderSearch(unifiedFolderType: DisplayUnifiedFolderType): LocalSearch {
+        return when (unifiedFolderType) {
+            DisplayUnifiedFolderType.INBOX -> return createUnifiedInboxSearch()
+        }
+    }
+
+    private fun createUnifiedInboxSearch(): LocalSearch {
+        return LocalSearch().apply {
+            id = UNIFIED_INBOX_ID
+            and(SearchField.INTEGRATE, "1", SearchAttribute.EQUALS)
+        }
+    }
+
+    companion object {
+        const val UNIFIED_INBOX_ID = "unified_inbox"
+    }
+}
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/DomainContract.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/DomainContract.kt
index f0164ac9e2d..335abfb40ed 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/DomainContract.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/DomainContract.kt
@@ -3,6 +3,8 @@ package app.k9mail.feature.navigation.drawer.domain
 import app.k9mail.feature.navigation.drawer.NavigationDrawerExternalContract.DrawerConfig
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
 import kotlinx.coroutines.flow.Flow
 
 internal interface DomainContract {
@@ -38,4 +40,8 @@ internal interface DomainContract {
             operator fun invoke(): Flow<Result<Unit>>
         }
     }
+
+    interface UnifiedFolderRepository {
+        fun getDisplayUnifiedFolderFlow(unifiedFolderType: DisplayUnifiedFolderType): Flow<DisplayUnifiedFolder>
+    }
 }
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccount.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccount.kt
index 0b57abe58ff..a18b6c2bba6 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccount.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccount.kt
@@ -1,68 +1,49 @@
 package app.k9mail.feature.navigation.drawer.domain.usecase
 
+import app.k9mail.feature.navigation.drawer.domain.DomainContract.UnifiedFolderRepository
 import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayFolder
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
-import app.k9mail.legacy.message.controller.MessageCountsProvider
-import app.k9mail.legacy.search.LocalSearch
-import app.k9mail.legacy.search.api.SearchAttribute
-import app.k9mail.legacy.search.api.SearchField
 import app.k9mail.legacy.ui.folder.DisplayFolderRepository
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 
 internal class GetDisplayFoldersForAccount(
-    private val repository: DisplayFolderRepository,
-    private val messageCountsProvider: MessageCountsProvider,
+    private val displayFolderRepository: DisplayFolderRepository,
+    private val unifiedFolderRepository: UnifiedFolderRepository,
 ) : UseCase.GetDisplayFoldersForAccount {
     override fun invoke(accountId: String, includeUnifiedFolders: Boolean): Flow<List<DisplayFolder>> {
-        return repository.getDisplayFoldersFlow(accountId).map { displayFolders ->
-            displayFolders.map { displayFolder ->
-                DisplayAccountFolder(
-                    accountId = accountId,
-                    folder = displayFolder.folder,
-                    isInTopGroup = displayFolder.isInTopGroup,
-                    unreadMessageCount = displayFolder.unreadMessageCount,
-                    starredMessageCount = displayFolder.starredMessageCount,
-                )
+        val accountFoldersFlow: Flow<List<DisplayFolder>> =
+            displayFolderRepository.getDisplayFoldersFlow(accountId).map { displayFolders ->
+                displayFolders.map { displayFolder ->
+                    DisplayAccountFolder(
+                        accountId = accountId,
+                        folder = displayFolder.folder,
+                        isInTopGroup = displayFolder.isInTopGroup,
+                        unreadMessageCount = displayFolder.unreadMessageCount,
+                        starredMessageCount = displayFolder.starredMessageCount,
+                    )
+                }
             }
-        }.map { displayFolders ->
-            if (includeUnifiedFolders) {
-                createDisplayUnifiedFolders() + displayFolders
-            } else {
-                displayFolders
-            }
-        }
-    }
-
-    private fun createDisplayUnifiedFolders(): List<DisplayUnifiedFolder> {
-        return listOf(
-            createUnifiedInboxFolder(),
-        )
-    }
 
-    private fun createUnifiedInboxFolder(): DisplayUnifiedFolder {
-        val search = getUnifiedInboxSearch()
-        val messageCounts = messageCountsProvider.getMessageCounts(search)
-
-        return DisplayUnifiedFolder(
-            id = UNIFIED_INBOX_ID,
-            unifiedType = DisplayUnifiedFolderType.INBOX,
-            unreadMessageCount = messageCounts.unread,
-            starredMessageCount = messageCounts.starred,
-        )
-    }
-
-    private fun getUnifiedInboxSearch(): LocalSearch {
-        return LocalSearch().apply {
-            id = UNIFIED_INBOX_ID
-            and(SearchField.INTEGRATE, "1", SearchAttribute.EQUALS)
+        val unifiedFoldersFlow: Flow<List<DisplayFolder>> = if (includeUnifiedFolders) {
+            unifiedFolderRepository.getDisplayUnifiedFolderFlow(DisplayUnifiedFolderType.INBOX)
+                .map { displayUnifiedFolder ->
+                    listOf(displayUnifiedFolder)
+                }
+        } else {
+            flowOf(emptyList<DisplayUnifiedFolder>())
         }
-    }
 
-    companion object {
-        private const val UNIFIED_INBOX_ID = "unified_inbox"
+        return combine(
+            accountFoldersFlow,
+            unifiedFoldersFlow,
+        ) { accountFolders, unifiedFolders ->
+            unifiedFolders + accountFolders
+        }
     }
 }
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt
index ba49d366a1f..990d4c955bf 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModel.kt
@@ -84,7 +84,7 @@ internal class DrawerViewModel(
             .distinctUntilChanged()
             .flatMapLatest { (accountId, showUnifiedInbox) ->
                 getDisplayFoldersForAccount(accountId, showUnifiedInbox)
-            }.collectLatest { folders ->
+            }.collect { folders ->
                 updateFolders(folders)
             }
     }
diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/DefaultMessageCountsProvider.kt b/legacy/core/src/main/java/com/fsck/k9/controller/DefaultMessageCountsProvider.kt
index 0eec16e3445..7c4a630617f 100644
--- a/legacy/core/src/main/java/com/fsck/k9/controller/DefaultMessageCountsProvider.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/controller/DefaultMessageCountsProvider.kt
@@ -5,17 +5,31 @@ import app.k9mail.legacy.account.AccountManager
 import app.k9mail.legacy.mailstore.MessageStoreManager
 import app.k9mail.legacy.message.controller.MessageCounts
 import app.k9mail.legacy.message.controller.MessageCountsProvider
+import app.k9mail.legacy.message.controller.MessagingControllerRegistry
+import app.k9mail.legacy.message.controller.SimpleMessagingListener
 import app.k9mail.legacy.search.ConditionsTreeNode
 import app.k9mail.legacy.search.LocalSearch
 import app.k9mail.legacy.search.SearchAccount
 import com.fsck.k9.search.excludeSpecialFolders
 import com.fsck.k9.search.getAccounts
 import com.fsck.k9.search.limitToDisplayableFolders
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.trySendBlocking
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import timber.log.Timber
 
 internal class DefaultMessageCountsProvider(
     private val accountManager: AccountManager,
     private val messageStoreManager: MessageStoreManager,
+    private val messagingControllerRegistry: MessagingControllerRegistry,
+    private val coroutineContext: CoroutineContext = Dispatchers.IO,
 ) : MessageCountsProvider {
     override fun getMessageCounts(account: Account): MessageCounts {
         val search = LocalSearch().apply {
@@ -59,6 +73,25 @@ internal class DefaultMessageCountsProvider(
         }
     }
 
+    override fun getMessageCountsFlow(search: LocalSearch): Flow<MessageCounts> {
+        return callbackFlow {
+            send(getMessageCounts(search))
+
+            val folderStatusChangedListener = object : SimpleMessagingListener() {
+                override fun folderStatusChanged(account: Account, folderId: Long) {
+                    trySendBlocking(getMessageCounts(search))
+                }
+            }
+            messagingControllerRegistry.addListener(folderStatusChangedListener)
+
+            awaitClose {
+                messagingControllerRegistry.removeListener(folderStatusChangedListener)
+            }
+        }.buffer(capacity = Channel.CONFLATED)
+            .distinctUntilChanged()
+            .flowOn(coroutineContext)
+    }
+
     @Suppress("TooGenericExceptionCaught")
     private fun getMessageCounts(account: Account, conditions: ConditionsTreeNode?): MessageCounts {
         return try {
diff --git a/legacy/core/src/main/java/com/fsck/k9/controller/KoinModule.kt b/legacy/core/src/main/java/com/fsck/k9/controller/KoinModule.kt
index 565e7925f17..b4f46e5d905 100644
--- a/legacy/core/src/main/java/com/fsck/k9/controller/KoinModule.kt
+++ b/legacy/core/src/main/java/com/fsck/k9/controller/KoinModule.kt
@@ -39,6 +39,7 @@ val controllerModule = module {
         DefaultMessageCountsProvider(
             accountManager = get(),
             messageStoreManager = get(),
+            messagingControllerRegistry = get(),
         )
     }
 
diff --git a/legacy/message/src/main/java/app/k9mail/legacy/message/controller/MessageCountsProvider.kt b/legacy/message/src/main/java/app/k9mail/legacy/message/controller/MessageCountsProvider.kt
index 0c27cc4b3e5..19cbd7e0791 100644
--- a/legacy/message/src/main/java/app/k9mail/legacy/message/controller/MessageCountsProvider.kt
+++ b/legacy/message/src/main/java/app/k9mail/legacy/message/controller/MessageCountsProvider.kt
@@ -3,11 +3,13 @@ package app.k9mail.legacy.message.controller
 import app.k9mail.legacy.account.Account
 import app.k9mail.legacy.search.LocalSearch
 import app.k9mail.legacy.search.SearchAccount
+import kotlinx.coroutines.flow.Flow
 
 interface MessageCountsProvider {
     fun getMessageCounts(account: Account): MessageCounts
     fun getMessageCounts(searchAccount: SearchAccount): MessageCounts
     fun getMessageCounts(search: LocalSearch): MessageCounts
+    fun getMessageCountsFlow(search: LocalSearch): Flow<MessageCounts>
     fun getUnreadMessageCount(account: Account, folderId: Long): Int
 }
 
diff --git a/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DefaultDisplayFolderRepository.kt b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DefaultDisplayFolderRepository.kt
new file mode 100644
index 00000000000..a22021a3c67
--- /dev/null
+++ b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DefaultDisplayFolderRepository.kt
@@ -0,0 +1,89 @@
+package app.k9mail.legacy.ui.folder
+
+import app.k9mail.core.mail.folder.api.Folder
+import app.k9mail.core.mail.folder.api.FolderType
+import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.account.AccountManager
+import app.k9mail.legacy.mailstore.FolderSettingsChangedListener
+import app.k9mail.legacy.mailstore.FolderTypeMapper
+import app.k9mail.legacy.mailstore.MessageStoreManager
+import app.k9mail.legacy.message.controller.MessagingControllerRegistry
+import app.k9mail.legacy.message.controller.SimpleMessagingListener
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.trySendBlocking
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.buffer
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+
+class DefaultDisplayFolderRepository(
+    private val accountManager: AccountManager,
+    private val messagingController: MessagingControllerRegistry,
+    private val messageStoreManager: MessageStoreManager,
+    private val coroutineContext: CoroutineContext = Dispatchers.IO,
+) : DisplayFolderRepository {
+    private val sortForDisplay =
+        compareByDescending<DisplayFolder> { it.folder.type == FolderType.INBOX }
+            .thenByDescending { it.folder.type == FolderType.OUTBOX }
+            .thenByDescending { it.folder.type != FolderType.REGULAR }
+            .thenByDescending { it.isInTopGroup }
+            .thenBy(String.CASE_INSENSITIVE_ORDER) { it.folder.name }
+
+    private fun getDisplayFolders(account: Account, includeHiddenFolders: Boolean): List<DisplayFolder> {
+        val messageStore = messageStoreManager.getMessageStore(account.uuid)
+        return messageStore.getDisplayFolders(
+            includeHiddenFolders = includeHiddenFolders,
+            outboxFolderId = account.outboxFolderId,
+        ) { folder ->
+            DisplayFolder(
+                folder = Folder(
+                    id = folder.id,
+                    name = folder.name,
+                    type = FolderTypeMapper.folderTypeOf(account, folder.id),
+                    isLocalOnly = folder.isLocalOnly,
+                ),
+                isInTopGroup = folder.isInTopGroup,
+                unreadMessageCount = folder.unreadMessageCount,
+                starredMessageCount = folder.starredMessageCount,
+            )
+        }.sortedWith(sortForDisplay)
+    }
+
+    override fun getDisplayFoldersFlow(account: Account, includeHiddenFolders: Boolean): Flow<List<DisplayFolder>> {
+        val messageStore = messageStoreManager.getMessageStore(account.uuid)
+
+        return callbackFlow {
+            send(getDisplayFolders(account, includeHiddenFolders))
+
+            val folderStatusChangedListener = object : SimpleMessagingListener() {
+                override fun folderStatusChanged(statusChangedAccount: Account, folderId: Long) {
+                    if (statusChangedAccount.uuid == account.uuid) {
+                        trySendBlocking(getDisplayFolders(account, includeHiddenFolders))
+                    }
+                }
+            }
+            messagingController.addListener(folderStatusChangedListener)
+
+            val folderSettingsChangedListener = FolderSettingsChangedListener {
+                trySendBlocking(getDisplayFolders(account, includeHiddenFolders))
+            }
+            messageStore.addFolderSettingsChangedListener(folderSettingsChangedListener)
+
+            awaitClose {
+                messagingController.removeListener(folderStatusChangedListener)
+                messageStore.removeFolderSettingsChangedListener(folderSettingsChangedListener)
+            }
+        }.buffer(capacity = Channel.CONFLATED)
+            .distinctUntilChanged()
+            .flowOn(coroutineContext)
+    }
+
+    override fun getDisplayFoldersFlow(accountUuid: String): Flow<List<DisplayFolder>> {
+        val account = accountManager.getAccount(accountUuid) ?: error("Account not found: $accountUuid")
+        return getDisplayFoldersFlow(account, includeHiddenFolders = false)
+    }
+}
diff --git a/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DisplayFolderRepository.kt b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DisplayFolderRepository.kt
index c413c9308df..a03f3ca6b5c 100644
--- a/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DisplayFolderRepository.kt
+++ b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/DisplayFolderRepository.kt
@@ -1,89 +1,11 @@
 package app.k9mail.legacy.ui.folder
 
-import app.k9mail.core.mail.folder.api.Folder
-import app.k9mail.core.mail.folder.api.FolderType
 import app.k9mail.legacy.account.Account
-import app.k9mail.legacy.account.AccountManager
-import app.k9mail.legacy.mailstore.FolderSettingsChangedListener
-import app.k9mail.legacy.mailstore.FolderTypeMapper
-import app.k9mail.legacy.mailstore.MessageStoreManager
-import app.k9mail.legacy.message.controller.MessagingControllerRegistry
-import app.k9mail.legacy.message.controller.SimpleMessagingListener
-import kotlin.coroutines.CoroutineContext
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.channels.trySendBlocking
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.buffer
-import kotlinx.coroutines.flow.callbackFlow
-import kotlinx.coroutines.flow.distinctUntilChanged
-import kotlinx.coroutines.flow.flowOn
 
-class DisplayFolderRepository(
-    private val accountManager: AccountManager,
-    private val messagingController: MessagingControllerRegistry,
-    private val messageStoreManager: MessageStoreManager,
-    private val coroutineContext: CoroutineContext = Dispatchers.IO,
-) {
-    private val sortForDisplay =
-        compareByDescending<DisplayFolder> { it.folder.type == FolderType.INBOX }
-            .thenByDescending { it.folder.type == FolderType.OUTBOX }
-            .thenByDescending { it.folder.type != FolderType.REGULAR }
-            .thenByDescending { it.isInTopGroup }
-            .thenBy(String.CASE_INSENSITIVE_ORDER) { it.folder.name }
+interface DisplayFolderRepository {
 
-    private fun getDisplayFolders(account: Account, includeHiddenFolders: Boolean): List<DisplayFolder> {
-        val messageStore = messageStoreManager.getMessageStore(account.uuid)
-        return messageStore.getDisplayFolders(
-            includeHiddenFolders = includeHiddenFolders,
-            outboxFolderId = account.outboxFolderId,
-        ) { folder ->
-            DisplayFolder(
-                folder = Folder(
-                    id = folder.id,
-                    name = folder.name,
-                    type = FolderTypeMapper.folderTypeOf(account, folder.id),
-                    isLocalOnly = folder.isLocalOnly,
-                ),
-                isInTopGroup = folder.isInTopGroup,
-                unreadMessageCount = folder.unreadMessageCount,
-                starredMessageCount = folder.starredMessageCount,
-            )
-        }.sortedWith(sortForDisplay)
-    }
+    fun getDisplayFoldersFlow(account: Account, includeHiddenFolders: Boolean): Flow<List<DisplayFolder>>
 
-    fun getDisplayFoldersFlow(account: Account, includeHiddenFolders: Boolean): Flow<List<DisplayFolder>> {
-        val messageStore = messageStoreManager.getMessageStore(account.uuid)
-
-        return callbackFlow {
-            send(getDisplayFolders(account, includeHiddenFolders))
-
-            val folderStatusChangedListener = object : SimpleMessagingListener() {
-                override fun folderStatusChanged(statusChangedAccount: Account, folderId: Long) {
-                    if (statusChangedAccount.uuid == account.uuid) {
-                        trySendBlocking(getDisplayFolders(account, includeHiddenFolders))
-                    }
-                }
-            }
-            messagingController.addListener(folderStatusChangedListener)
-
-            val folderSettingsChangedListener = FolderSettingsChangedListener {
-                trySendBlocking(getDisplayFolders(account, includeHiddenFolders))
-            }
-            messageStore.addFolderSettingsChangedListener(folderSettingsChangedListener)
-
-            awaitClose {
-                messagingController.removeListener(folderStatusChangedListener)
-                messageStore.removeFolderSettingsChangedListener(folderSettingsChangedListener)
-            }
-        }.buffer(capacity = Channel.CONFLATED)
-            .distinctUntilChanged()
-            .flowOn(coroutineContext)
-    }
-
-    fun getDisplayFoldersFlow(accountUuid: String): Flow<List<DisplayFolder>> {
-        val account = accountManager.getAccount(accountUuid) ?: error("Account not found: $accountUuid")
-        return getDisplayFoldersFlow(account, includeHiddenFolders = false)
-    }
+    fun getDisplayFoldersFlow(accountUuid: String): Flow<List<DisplayFolder>>
 }
diff --git a/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/KoinModule.kt b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/KoinModule.kt
index 70a212104dd..06229fd4a3f 100644
--- a/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/KoinModule.kt
+++ b/legacy/ui/folder/src/main/java/app/k9mail/legacy/ui/folder/KoinModule.kt
@@ -3,8 +3,8 @@ package app.k9mail.legacy.ui.folder
 import org.koin.dsl.module
 
 val uiFolderModule = module {
-    single {
-        DisplayFolderRepository(
+    single<DisplayFolderRepository> {
+        DefaultDisplayFolderRepository(
             accountManager = get(),
             messagingController = get(),
             messageStoreManager = get(),


Test patch:
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/FakeMessageCountsProvider.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/FakeMessageCountsProvider.kt
new file mode 100644
index 00000000000..5cd94e1e891
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/FakeMessageCountsProvider.kt
@@ -0,0 +1,36 @@
+package app.k9mail.feature.navigation.drawer.data
+
+import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.message.controller.MessageCounts
+import app.k9mail.legacy.message.controller.MessageCountsProvider
+import app.k9mail.legacy.search.LocalSearch
+import app.k9mail.legacy.search.SearchAccount
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+
+internal class FakeMessageCountsProvider(
+    private val messageCounts: MessageCounts,
+) : MessageCountsProvider {
+    var recordedSearch: LocalSearch = LocalSearch()
+
+    override fun getMessageCounts(account: Account): MessageCounts {
+        TODO("Not yet implemented")
+    }
+
+    override fun getMessageCounts(searchAccount: SearchAccount): MessageCounts {
+        TODO("Not yet implemented")
+    }
+
+    override fun getMessageCounts(search: LocalSearch): MessageCounts {
+        TODO("Not yet implemented")
+    }
+
+    override fun getMessageCountsFlow(search: LocalSearch): Flow<MessageCounts> {
+        recordedSearch = search
+        return flowOf(messageCounts)
+    }
+
+    override fun getUnreadMessageCount(account: Account, folderId: Long): Int {
+        TODO("Not yet implemented")
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepositoryTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepositoryTest.kt
new file mode 100644
index 00000000000..29205048f41
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/data/UnifiedFolderRepositoryTest.kt
@@ -0,0 +1,47 @@
+package app.k9mail.feature.navigation.drawer.data
+
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
+import app.k9mail.legacy.message.controller.MessageCounts
+import app.k9mail.legacy.search.api.SearchAttribute
+import app.k9mail.legacy.search.api.SearchField
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+
+internal class UnifiedFolderRepositoryTest {
+
+    @Test
+    fun `should return DisplayUnifiedFolder for unified inbox`() = runTest {
+        val messageCountsProvider = FakeMessageCountsProvider(
+            messageCounts = MessageCounts(
+                unread = 2,
+                starred = 2,
+            ),
+        )
+        val testSubject = UnifiedFolderRepository(
+            messageCountsProvider = messageCountsProvider,
+        )
+        val folderType = DisplayUnifiedFolderType.INBOX
+
+        val result = testSubject.getDisplayUnifiedFolderFlow(folderType).first()
+
+        assertThat(result).isEqualTo(
+            DisplayUnifiedFolder(
+                id = "unified_inbox",
+                unifiedType = folderType,
+                unreadMessageCount = 2,
+                starredMessageCount = 2,
+            ),
+        )
+
+        val search = messageCountsProvider.recordedSearch
+        assertThat(search.id).isEqualTo("unified_inbox")
+        val condition = search.conditions.condition
+        assertThat(condition.value).isEqualTo("1")
+        assertThat(condition.attribute).isEqualTo(SearchAttribute.EQUALS)
+        assertThat(condition.field).isEqualTo(SearchField.INTEGRATE)
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeDisplayFolderRepository.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeDisplayFolderRepository.kt
new file mode 100644
index 00000000000..71e8632070f
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeDisplayFolderRepository.kt
@@ -0,0 +1,21 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.ui.folder.DisplayFolder
+import app.k9mail.legacy.ui.folder.DisplayFolderRepository
+import kotlinx.coroutines.flow.Flow
+
+internal class FakeDisplayFolderRepository(
+    private val foldersFlow: Flow<List<DisplayFolder>>,
+) : DisplayFolderRepository {
+    override fun getDisplayFoldersFlow(
+        account: Account,
+        includeHiddenFolders: Boolean,
+    ): Flow<List<DisplayFolder>> {
+        TODO("Not yet implemented")
+    }
+
+    override fun getDisplayFoldersFlow(accountUuid: String): Flow<List<DisplayFolder>> {
+        return foldersFlow
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeUnifiedFolderRepository.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeUnifiedFolderRepository.kt
new file mode 100644
index 00000000000..bb27588a9c8
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeUnifiedFolderRepository.kt
@@ -0,0 +1,14 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.k9mail.feature.navigation.drawer.domain.DomainContract.UnifiedFolderRepository
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
+import kotlinx.coroutines.flow.Flow
+
+internal class FakeUnifiedFolderRepository(
+    private val displayUnifiedFolderFlow: Flow<DisplayUnifiedFolder>,
+) : UnifiedFolderRepository {
+    override fun getDisplayUnifiedFolderFlow(unifiedFolderType: DisplayUnifiedFolderType): Flow<DisplayUnifiedFolder> {
+        return displayUnifiedFolderFlow
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccountTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccountTest.kt
new file mode 100644
index 00000000000..3668ef3fbe9
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/GetDisplayFoldersForAccountTest.kt
@@ -0,0 +1,154 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.cash.turbine.test
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccountFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolder
+import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderType
+import app.k9mail.feature.navigation.drawer.ui.FakeData
+import assertk.assertThat
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import app.k9mail.legacy.ui.folder.DisplayFolder as LegacyDisplayFolder
+
+internal class GetDisplayFoldersForAccountTest {
+
+    @Test
+    fun `should return only account folders when includeUnifiedFolders is false`() = runTest {
+        val accountId = "account_id"
+        val legacyDisplayFolderFlow = MutableStateFlow(LEGACY_DISPLAY_FOLDERS)
+        val displayFolderRepository = FakeDisplayFolderRepository(legacyDisplayFolderFlow)
+        val unifiedFolderFlow = MutableStateFlow(DISPLAY_UNIFIED_FOLDER)
+        val unifiedFolderRepository = FakeUnifiedFolderRepository(unifiedFolderFlow)
+        val testSubject = GetDisplayFoldersForAccount(
+            displayFolderRepository = displayFolderRepository,
+            unifiedFolderRepository = unifiedFolderRepository,
+        )
+
+        val result = testSubject(accountId, includeUnifiedFolders = false).first()
+
+        assertThat(result).isEqualTo(DISPLAY_ACCOUNT_FOLDERS)
+    }
+
+    @Test
+    fun `should return account folders and unified folders when includeUnifiedFolders is true`() = runTest {
+        val accountId = "account_id"
+        val legacyDisplayFolderFlow = MutableStateFlow(LEGACY_DISPLAY_FOLDERS)
+        val displayFolderRepository = FakeDisplayFolderRepository(legacyDisplayFolderFlow)
+        val unifiedFolderFlow = MutableStateFlow(DISPLAY_UNIFIED_FOLDER)
+        val unifiedFolderRepository = FakeUnifiedFolderRepository(unifiedFolderFlow)
+        val testSubject = GetDisplayFoldersForAccount(
+            displayFolderRepository = displayFolderRepository,
+            unifiedFolderRepository = unifiedFolderRepository,
+        )
+
+        val result = testSubject(accountId, includeUnifiedFolders = true).first()
+
+        assertThat(result).isEqualTo(DISPLAY_UNIFIED_FOLDERS + DISPLAY_ACCOUNT_FOLDERS)
+    }
+
+    @Test
+    fun `should emit new list when account folders or unified folders emit new items`() = runTest {
+        val accountId = "account_id"
+        val legacyDisplayFolderFlow = MutableStateFlow(LEGACY_DISPLAY_FOLDERS)
+        val displayFolderRepository = FakeDisplayFolderRepository(legacyDisplayFolderFlow)
+        val unifiedFolderFlow = MutableStateFlow(DISPLAY_UNIFIED_FOLDER)
+        val unifiedFolderRepository = FakeUnifiedFolderRepository(unifiedFolderFlow)
+        val testSubject = GetDisplayFoldersForAccount(
+            displayFolderRepository = displayFolderRepository,
+            unifiedFolderRepository = unifiedFolderRepository,
+        )
+
+        testSubject(accountId, includeUnifiedFolders = true).test {
+            assertThat(awaitItem()).isEqualTo(DISPLAY_UNIFIED_FOLDERS + DISPLAY_ACCOUNT_FOLDERS)
+
+            legacyDisplayFolderFlow.emit(LEGACY_DISPLAY_FOLDERS_2)
+
+            assertThat(awaitItem()).isEqualTo(DISPLAY_UNIFIED_FOLDERS + DISPLAY_ACCOUNT_FOLDERS_2)
+
+            unifiedFolderFlow.emit(DISPLAY_UNIFIED_FOLDER_2)
+
+            assertThat(awaitItem()).isEqualTo(listOf(DISPLAY_UNIFIED_FOLDER_2) + DISPLAY_ACCOUNT_FOLDERS_2)
+        }
+    }
+
+    private companion object {
+        val LEGACY_DISPLAY_FOLDERS = listOf(
+            LegacyDisplayFolder(
+                folder = FakeData.FOLDER,
+                isInTopGroup = false,
+                unreadMessageCount = 0,
+                starredMessageCount = 0,
+            ),
+            LegacyDisplayFolder(
+                folder = FakeData.FOLDER.copy(
+                    id = 2,
+                    name = "Folder 2",
+                ),
+                isInTopGroup = false,
+                unreadMessageCount = 1,
+                starredMessageCount = 0,
+            ),
+        )
+
+        val LEGACY_DISPLAY_FOLDERS_2 = LEGACY_DISPLAY_FOLDERS + LegacyDisplayFolder(
+            folder = FakeData.FOLDER.copy(
+                id = 3,
+                name = "Folder 3",
+            ),
+            isInTopGroup = false,
+            unreadMessageCount = 0,
+            starredMessageCount = 0,
+        )
+
+        val DISPLAY_UNIFIED_FOLDER = DisplayUnifiedFolder(
+            id = "unified_inbox",
+            unifiedType = DisplayUnifiedFolderType.INBOX,
+            unreadMessageCount = 2,
+            starredMessageCount = 2,
+        )
+
+        val DISPLAY_UNIFIED_FOLDER_2 = DisplayUnifiedFolder(
+            id = "unified_inbox",
+            unifiedType = DisplayUnifiedFolderType.INBOX,
+            unreadMessageCount = 3,
+            starredMessageCount = 3,
+        )
+
+        val DISPLAY_UNIFIED_FOLDERS = listOf(DISPLAY_UNIFIED_FOLDER)
+
+        val DISPLAY_ACCOUNT_FOLDERS = listOf<DisplayFolder>(
+            DisplayAccountFolder(
+                accountId = "account_id",
+                folder = FakeData.FOLDER,
+                isInTopGroup = false,
+                unreadMessageCount = 0,
+                starredMessageCount = 0,
+            ),
+            DisplayAccountFolder(
+                accountId = "account_id",
+                folder = FakeData.FOLDER.copy(
+                    id = 2,
+                    name = "Folder 2",
+                ),
+                isInTopGroup = false,
+                unreadMessageCount = 1,
+                starredMessageCount = 0,
+            ),
+        )
+
+        val DISPLAY_ACCOUNT_FOLDERS_2 = DISPLAY_ACCOUNT_FOLDERS + DisplayAccountFolder(
+            accountId = "account_id",
+            folder = FakeData.FOLDER.copy(
+                id = 3,
+                name = "Folder 3",
+            ),
+            isInTopGroup = false,
+            unreadMessageCount = 0,
+            starredMessageCount = 0,
+        )
+    }
+}
diff --git a/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt b/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt
index d770889aaab..f7d2c0ccf1c 100644
--- a/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt
+++ b/feature/widget/unread/src/test/kotlin/app/k9mail/feature/widget/unread/UnreadWidgetDataProviderTest.kt
@@ -16,6 +16,7 @@ import assertk.assertions.isNull
 import com.fsck.k9.CoreResourceProvider
 import com.fsck.k9.Preferences
 import com.fsck.k9.ui.messagelist.DefaultFolderProvider
+import kotlinx.coroutines.flow.Flow
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -141,6 +142,10 @@ class UnreadWidgetDataProviderTest : AutoCloseKoinTest() {
             throw UnsupportedOperationException()
         }
 
+        override fun getMessageCountsFlow(search: LocalSearch): Flow<MessageCounts> {
+            throw UnsupportedOperationException()
+        }
+
         override fun getUnreadMessageCount(account: Account, folderId: Long): Int {
             return FOLDER_UNREAD_COUNT
         }
diff --git a/legacy/core/src/test/java/com/fsck/k9/controller/DefaultMessageCountsProviderTest.kt b/legacy/core/src/test/java/com/fsck/k9/controller/DefaultMessageCountsProviderTest.kt
index 0860a046148..001d097992e 100644
--- a/legacy/core/src/test/java/com/fsck/k9/controller/DefaultMessageCountsProviderTest.kt
+++ b/legacy/core/src/test/java/com/fsck/k9/controller/DefaultMessageCountsProviderTest.kt
@@ -1,14 +1,22 @@
 package com.fsck.k9.controller
 
+import app.cash.turbine.test
 import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.account.AccountManager
 import app.k9mail.legacy.mailstore.ListenableMessageStore
 import app.k9mail.legacy.mailstore.MessageStoreManager
+import app.k9mail.legacy.message.controller.MessageCounts
+import app.k9mail.legacy.message.controller.MessagingControllerRegistry
+import app.k9mail.legacy.message.controller.MessagingListener
+import app.k9mail.legacy.message.controller.SimpleMessagingListener
 import app.k9mail.legacy.search.ConditionsTreeNode
+import app.k9mail.legacy.search.LocalSearch
 import assertk.assertThat
 import assertk.assertions.isEqualTo
-import com.fsck.k9.Preferences
+import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.doAnswer
 import org.mockito.kotlin.doReturn
 import org.mockito.kotlin.mock
 
@@ -17,8 +25,11 @@ private const val UNREAD_COUNT = 2
 private const val STARRED_COUNT = 3
 
 class DefaultMessageCountsProviderTest {
-    private val preferences = mock<Preferences>()
+
     private val account = Account(ACCOUNT_UUID)
+    private val accountManager = mock<AccountManager> {
+        on { getAccounts() } doReturn listOf(account)
+    }
     private val messageStore = mock<ListenableMessageStore> {
         on { getUnreadMessageCount(anyOrNull<ConditionsTreeNode>()) } doReturn UNREAD_COUNT
         on { getStarredMessageCount(anyOrNull()) } doReturn STARRED_COUNT
@@ -27,7 +38,13 @@ class DefaultMessageCountsProviderTest {
         on { getMessageStore(account) } doReturn messageStore
     }
 
-    private val messageCountsProvider = DefaultMessageCountsProvider(preferences, messageStoreManager)
+    private val messagingControllerRegistry = mock<MessagingControllerRegistry> {}
+
+    private val messageCountsProvider = DefaultMessageCountsProvider(
+        accountManager = accountManager,
+        messageStoreManager = messageStoreManager,
+        messagingControllerRegistry = messagingControllerRegistry,
+    )
 
     @Test
     fun `getMessageCounts() without any special folders`() {
@@ -43,4 +60,44 @@ class DefaultMessageCountsProviderTest {
         assertThat(messageCounts.unread).isEqualTo(UNREAD_COUNT)
         assertThat(messageCounts.starred).isEqualTo(STARRED_COUNT)
     }
+
+    @Test
+    fun `getMessageCountsFlow should emit for every change`() = runTest {
+        var currentListener: SimpleMessagingListener? = null
+        val registry = object : MessagingControllerRegistry {
+            override fun addListener(listener: MessagingListener) {
+                currentListener = listener as SimpleMessagingListener
+            }
+
+            override fun removeListener(listener: MessagingListener) {
+                currentListener = null
+            }
+        }
+        var currentCount = 0
+        val messageStore = mock<ListenableMessageStore> {
+            on { getUnreadMessageCount(anyOrNull<ConditionsTreeNode>()) } doAnswer { currentCount }
+            on { getStarredMessageCount(anyOrNull()) } doAnswer { currentCount }
+        }
+        val messageStoreManager = mock<MessageStoreManager> {
+            on { getMessageStore(account) } doReturn messageStore
+        }
+        val testSubject = DefaultMessageCountsProvider(
+            accountManager = accountManager,
+            messageStoreManager = messageStoreManager,
+            messagingControllerRegistry = registry,
+        )
+        val search = LocalSearch().apply {
+            addAccountUuid(account.uuid)
+        }
+
+        testSubject.getMessageCountsFlow(search).test {
+            assertThat(awaitItem()).isEqualTo(MessageCounts(0, 0))
+            currentCount = 1
+            currentListener?.folderStatusChanged(account, 0)
+            assertThat(awaitItem()).isEqualTo(MessageCounts(1, 1))
+            currentCount = 2
+            currentListener?.folderStatusChanged(account, 0)
+            assertThat(awaitItem()).isEqualTo(MessageCounts(2, 2))
+        }
+    }
 }