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

Issue:
Improve drawer sync mechanism
During review of #8143 it was stated that the `SyncMail` use case could be used wrongly and needs improvement. 

As the `SyncMail` implementation is basically a copy paste from the existing code, it does not distinguish between syncing a single account vs all accounts and at what point in time either of them is appropriate to use. 

It would be good to improve the api to minimize misunderstandings in the future. Take note that the underlying `MessagingController.checkMail()` could benefit from such an improvement too.

See [comment](https://github.com/thunderbird/thunderbird-android/pull/8143/files/29bada814b06f528cbc9c4964af8b2503ca9a5af#r1761876643) for more details.


Discussion hints:
nan

Pull request: Change drawer sync calls
Resolves #8146

Add a syn all accounts button to the side rail.

Depends on #8165 

Test command: ./gradlew :feature:navigation:drawer:testDebugUnitTest --tests "app.k9mail.feature.navigation.drawer.domain.usecase.SyncAccountTest" --tests "app.k9mail.feature.navigation.drawer.domain.usecase.SyncMailTest" --tests "app.k9mail.feature.navigation.drawer.ui.DrawerViewModelTest" --tests "app.k9mail.feature.navigation.drawer.domain.usecase.SyncAllAccountsTest"

Fix patch:
diff --git a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/icon/Icons.kt b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/icon/Icons.kt
index 5e4a5861902..7be1b9d56c2 100644
--- a/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/icon/Icons.kt
+++ b/core/ui/compose/designsystem/src/main/kotlin/app/k9mail/core/ui/compose/designsystem/atom/icon/Icons.kt
@@ -25,6 +25,7 @@ import androidx.compose.material.icons.outlined.Menu
 import androidx.compose.material.icons.outlined.Report
 import androidx.compose.material.icons.outlined.Security
 import androidx.compose.material.icons.outlined.Settings
+import androidx.compose.material.icons.outlined.Sync
 import androidx.compose.material.icons.outlined.Visibility
 import androidx.compose.ui.graphics.vector.ImageVector
 import app.k9mail.core.ui.compose.designsystem.atom.icon.filled.Dot
@@ -111,6 +112,9 @@ object Icons {
         val Settings: ImageVector
             get() = MaterialIcons.Outlined.Settings
 
+        val Sync: ImageVector
+            get() = MaterialIcons.Outlined.Sync
+
         val Report: ImageVector
             get() = MaterialIcons.Outlined.Report
 
diff --git a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountListPreview.kt b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountListPreview.kt
index 81c688f2dbf..e8f083e7139 100644
--- a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountListPreview.kt
+++ b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountListPreview.kt
@@ -17,6 +17,7 @@ internal fun AccountListPreview() {
             selectedAccount = null,
             onAccountClick = { },
             onSettingsClick = { },
+            onSyncAllAccountsClick = { },
         )
     }
 }
@@ -32,6 +33,7 @@ internal fun AccountListWithSelectedPreview() {
             selectedAccount = DISPLAY_ACCOUNT,
             onAccountClick = { },
             onSettingsClick = { },
+            onSyncAllAccountsClick = { },
         )
     }
 }
diff --git a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItemPreview.kt b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItemPreview.kt
index cfd63708628..70bbd16d92f 100644
--- a/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItemPreview.kt
+++ b/feature/navigation/drawer/src/debug/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItemPreview.kt
@@ -3,12 +3,14 @@ package app.k9mail.feature.navigation.drawer.ui.setting
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.tooling.preview.Preview
 import app.k9mail.core.ui.compose.designsystem.PreviewWithThemes
+import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
 
 @Composable
 @Preview(showBackground = true)
 internal fun SettingItemPreview() {
     PreviewWithThemes {
         SettingItem(
+            icon = Icons.Outlined.Settings,
             label = "Setting",
             onClick = {},
         )
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 a69724638f3..35e8fd2bf0a 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
@@ -4,7 +4,8 @@ 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
 import app.k9mail.feature.navigation.drawer.domain.usecase.GetDrawerConfig
-import app.k9mail.feature.navigation.drawer.domain.usecase.SyncMail
+import app.k9mail.feature.navigation.drawer.domain.usecase.SyncAccount
+import app.k9mail.feature.navigation.drawer.domain.usecase.SyncAllAccounts
 import app.k9mail.feature.navigation.drawer.legacy.AccountsViewModel
 import app.k9mail.feature.navigation.drawer.legacy.FoldersViewModel
 import app.k9mail.feature.navigation.drawer.ui.DrawerViewModel
@@ -37,8 +38,14 @@ val navigationDrawerModule: Module = module {
         )
     }
 
-    single<UseCase.SyncMail> {
-        SyncMail(
+    single<UseCase.SyncAccount> {
+        SyncAccount(
+            messagingController = get(),
+        )
+    }
+
+    single<UseCase.SyncAllAccounts> {
+        SyncAllAccounts(
             messagingController = get(),
         )
     }
@@ -66,7 +73,8 @@ val navigationDrawerModule: Module = module {
             getDrawerConfig = get(),
             getDisplayAccounts = get(),
             getDisplayFoldersForAccount = get(),
-            syncMail = get(),
+            syncAccount = get(),
+            syncAllAccounts = get(),
         )
     }
 }
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 6bdc3542657..f8ab0118074 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
@@ -22,12 +22,17 @@ internal interface DomainContract {
         }
 
         /**
-         * Synchronize mail for the given account.
-         *
-         * Account can be null to synchronize unified inbox or account list.
+         * Synchronize the given account.
          */
-        fun interface SyncMail {
-            operator fun invoke(account: Account?): Flow<Result<Unit>>
+        fun interface SyncAccount {
+            operator fun invoke(account: Account): Flow<Result<Unit>>
+        }
+
+        /**
+         * Synchronize all accounts.
+         */
+        fun interface SyncAllAccounts {
+            operator fun invoke(): Flow<Result<Unit>>
         }
     }
 }
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMail.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccount.kt
similarity index 90%
rename from feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMail.kt
rename to feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccount.kt
index a8906e4a18d..90609176660 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMail.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccount.kt
@@ -12,11 +12,11 @@ import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.flowOn
 
-internal class SyncMail(
+internal class SyncAccount(
     private val messagingController: MessagingControllerMailChecker,
     private val coroutineContext: CoroutineContext = Dispatchers.IO,
-) : UseCase.SyncMail {
-    override fun invoke(account: Account?): Flow<Result<Unit>> = callbackFlow {
+) : UseCase.SyncAccount {
+    override fun invoke(account: Account): Flow<Result<Unit>> = callbackFlow {
         val listener = object : SimpleMessagingListener() {
             override fun checkMailFinished(context: Context?, account: Account?) {
                 trySend(Result.success(Unit))
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccounts.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccounts.kt
new file mode 100644
index 00000000000..b89996cf3c7
--- /dev/null
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccounts.kt
@@ -0,0 +1,37 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import android.content.Context
+import app.k9mail.feature.navigation.drawer.domain.DomainContract.UseCase
+import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
+import app.k9mail.legacy.message.controller.SimpleMessagingListener
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.flowOn
+
+class SyncAllAccounts(
+    private val messagingController: MessagingControllerMailChecker,
+    private val coroutineContext: CoroutineContext = Dispatchers.IO,
+) : UseCase.SyncAllAccounts {
+    override fun invoke(): Flow<Result<Unit>> = callbackFlow {
+        val listener = object : SimpleMessagingListener() {
+            override fun checkMailFinished(context: Context?, account: Account?) {
+                trySend(Result.success(Unit))
+                close()
+            }
+        }
+
+        messagingController.checkMail(
+            account = null,
+            ignoreLastCheckedTime = true,
+            useManualWakeLock = true,
+            notify = true,
+            listener = listener,
+        )
+
+        awaitClose()
+    }.flowOn(coroutineContext)
+}
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt
index e1fd3ef419b..809214bb98a 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContent.kt
@@ -45,6 +45,7 @@ internal fun DrawerContent(
                         accounts = state.accounts,
                         selectedAccount = state.selectedAccount,
                         onAccountClick = { onEvent(Event.OnAccountClick(it)) },
+                        onSyncAllAccountsClick = { onEvent(Event.OnSyncAllAccounts) },
                         onSettingsClick = { onEvent(Event.OnSettingsClick) },
                     )
                 }
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt
index 435500dc758..657e4d1d9c0 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerContract.kt
@@ -34,7 +34,8 @@ internal interface DrawerContract {
         data object OnAccountSelectorClick : Event
         data object OnManageFoldersClick : Event
         data object OnSettingsClick : Event
-        data object OnRefresh : Event
+        data object OnSyncAccount : Event
+        data object OnSyncAllAccounts : Event
     }
 
     sealed interface Effect {
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt
index ff52de5d257..33f493b0ffb 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerView.kt
@@ -32,7 +32,7 @@ internal fun DrawerView(
 
     PullToRefreshBox(
         isRefreshing = state.value.isLoading,
-        onRefresh = { dispatch(Event.OnRefresh) },
+        onRefresh = { dispatch(Event.OnSyncAccount) },
     ) {
         DrawerContent(
             state = state.value,
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 b48b4ee6953..8e659d7ec59 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
@@ -28,7 +28,8 @@ internal class DrawerViewModel(
     private val getDrawerConfig: UseCase.GetDrawerConfig,
     private val getDisplayAccounts: UseCase.GetDisplayAccounts,
     private val getDisplayFoldersForAccount: UseCase.GetDisplayFoldersForAccount,
-    private val syncMail: UseCase.SyncMail,
+    private val syncAccount: UseCase.SyncAccount,
+    private val syncAllAccounts: UseCase.SyncAllAccounts,
     initialState: State = State(),
 ) : BaseViewModel<State, Event, Effect>(
     initialState = initialState,
@@ -101,7 +102,6 @@ internal class DrawerViewModel(
 
     override fun event(event: Event) {
         when (event) {
-            Event.OnRefresh -> refresh()
             is Event.OnAccountClick -> selectAccount(event.account)
             is Event.OnFolderClick -> selectFolder(event.folder)
             is Event.OnAccountViewClick -> {
@@ -113,6 +113,8 @@ internal class DrawerViewModel(
             Event.OnAccountSelectorClick -> updateState { it.copy(showAccountSelector = it.showAccountSelector.not()) }
             Event.OnManageFoldersClick -> emitEffect(Effect.OpenManageFolders)
             Event.OnSettingsClick -> emitEffect(Effect.OpenSettings)
+            Event.OnSyncAccount -> onSyncAccount()
+            Event.OnSyncAllAccounts -> onSyncAllAccounts()
         }
     }
 
@@ -156,13 +158,31 @@ internal class DrawerViewModel(
         }
     }
 
-    private fun refresh() {
+    private fun onSyncAccount() {
+        if (state.value.isLoading || state.value.selectedAccount == null) return
+
+        viewModelScope.launch {
+            updateState {
+                it.copy(isLoading = true)
+            }
+
+            state.value.selectedAccount?.account?.let { syncAccount(it).collect() }
+
+            updateState {
+                it.copy(isLoading = false)
+            }
+        }
+    }
+
+    private fun onSyncAllAccounts() {
+        if (state.value.isLoading) return
+
         viewModelScope.launch {
             updateState {
                 it.copy(isLoading = true)
             }
 
-            syncMail(state.value.selectedAccount?.account).collect()
+            syncAllAccounts().collect()
 
             updateState {
                 it.copy(isLoading = false)
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountList.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountList.kt
index bab2b57bc73..3e857bd66e6 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountList.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/account/AccountList.kt
@@ -2,14 +2,19 @@ package app.k9mail.feature.navigation.drawer.ui.account
 
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
 import app.k9mail.core.ui.compose.designsystem.atom.Surface
+import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
 import app.k9mail.core.ui.compose.theme2.MainTheme
 import app.k9mail.feature.navigation.drawer.R
 import app.k9mail.feature.navigation.drawer.domain.entity.DisplayAccount
@@ -21,6 +26,7 @@ internal fun AccountList(
     accounts: ImmutableList<DisplayAccount>,
     selectedAccount: DisplayAccount?,
     onAccountClick: (DisplayAccount) -> Unit,
+    onSyncAllAccountsClick: () -> Unit,
     onSettingsClick: () -> Unit,
     modifier: Modifier = Modifier,
 ) {
@@ -50,10 +56,22 @@ internal fun AccountList(
                     )
                 }
             }
-            SettingItem(
-                label = stringResource(id = R.string.navigation_drawer_action_settings),
-                onClick = onSettingsClick,
-            )
+            Column(
+                modifier = Modifier.padding(vertical = MainTheme.spacings.oneHalf),
+            ) {
+                SettingItem(
+                    icon = Icons.Outlined.Sync,
+                    label = stringResource(id = R.string.navigation_drawer_action_sync_all_accounts),
+                    onClick = onSyncAllAccountsClick,
+                )
+                // Hack to compensate the column placement at an uneven coordinate, caused by the 1.dp divider.
+                Spacer(modifier = Modifier.height(7.dp))
+                SettingItem(
+                    icon = Icons.Outlined.Settings,
+                    label = stringResource(id = R.string.navigation_drawer_action_settings),
+                    onClick = onSettingsClick,
+                )
+            }
         }
     }
 }
diff --git a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItem.kt b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItem.kt
index 88f84a1a616..f2f2f1e8c0b 100644
--- a/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItem.kt
+++ b/feature/navigation/drawer/src/main/kotlin/app/k9mail/feature/navigation/drawer/ui/setting/SettingItem.kt
@@ -8,13 +8,14 @@ import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.vector.ImageVector
 import app.k9mail.core.ui.compose.designsystem.atom.Surface
 import app.k9mail.core.ui.compose.designsystem.atom.icon.Icon
-import app.k9mail.core.ui.compose.designsystem.atom.icon.Icons
 import app.k9mail.core.ui.compose.theme2.MainTheme
 
 @Composable
 internal fun SettingItem(
+    icon: ImageVector,
     label: String,
     onClick: () -> Unit,
     modifier: Modifier = Modifier,
@@ -24,12 +25,11 @@ internal fun SettingItem(
         contentAlignment = Alignment.Center,
     ) {
         Surface(
-            modifier = Modifier.padding(vertical = MainTheme.spacings.oneHalf),
             color = MainTheme.colors.surfaceContainer,
             shape = CircleShape,
         ) {
             Icon(
-                imageVector = Icons.Outlined.Settings,
+                imageVector = icon,
                 contentDescription = label,
                 modifier = Modifier
                     .clickable(onClick = onClick)
diff --git a/feature/navigation/drawer/src/main/res/values/strings.xml b/feature/navigation/drawer/src/main/res/values/strings.xml
index 8fd48000956..3324776b02a 100644
--- a/feature/navigation/drawer/src/main/res/values/strings.xml
+++ b/feature/navigation/drawer/src/main/res/values/strings.xml
@@ -2,6 +2,7 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="navigation_drawer_action_settings">Settings</string>
     <string name="navigation_drawer_action_manage_folders">Manage folders</string>
+    <string name="navigation_drawer_action_sync_all_accounts">Sync all accounts</string>
     <string name="navigation_drawer_action_show_accounts">Show accounts</string>
     <string name="navigation_drawer_action_hide_accounts">Hide accounts</string>
     <string name="navigation_drawer_unified_inbox_title">Unified Inbox</string>


Test patch:
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeMessagingControllerMailChecker.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeMessagingControllerMailChecker.kt
new file mode 100644
index 00000000000..487ff72ff25
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/FakeMessagingControllerMailChecker.kt
@@ -0,0 +1,19 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.k9mail.legacy.account.Account
+import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
+import app.k9mail.legacy.message.controller.MessagingListener
+
+internal class FakeMessagingControllerMailChecker(
+    private val listenerExecutor: (MessagingListener?) -> Unit = {},
+) : MessagingControllerMailChecker {
+    override fun checkMail(
+        account: Account?,
+        ignoreLastCheckedTime: Boolean,
+        useManualWakeLock: Boolean,
+        notify: Boolean,
+        listener: MessagingListener?,
+    ) {
+        listenerExecutor(listener)
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccountTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccountTest.kt
new file mode 100644
index 00000000000..526d09c66be
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAccountTest.kt
@@ -0,0 +1,27 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.k9mail.feature.navigation.drawer.ui.FakeData
+import app.k9mail.legacy.message.controller.MessagingListener
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+
+class SyncAccountTest {
+
+    @Test
+    fun `should sync mail with account`() = runTest {
+        val listenerExecutor: (MessagingListener?) -> Unit = { listener ->
+            listener?.checkMailFinished(null, null)
+        }
+        val testSubject = SyncAccount(
+            messagingController = FakeMessagingControllerMailChecker(
+                listenerExecutor = listenerExecutor,
+            ),
+        )
+
+        val result = testSubject(FakeData.ACCOUNT).first()
+
+        assertk.assertThat(result.isSuccess).isEqualTo(true)
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccountsTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccountsTest.kt
new file mode 100644
index 00000000000..26849c0cfde
--- /dev/null
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncAllAccountsTest.kt
@@ -0,0 +1,26 @@
+package app.k9mail.feature.navigation.drawer.domain.usecase
+
+import app.k9mail.legacy.message.controller.MessagingListener
+import assertk.assertions.isEqualTo
+import kotlin.test.Test
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+
+class SyncAllAccountsTest {
+
+    @Test
+    fun `should sync mail`() = runTest {
+        val listenerExecutor: (MessagingListener?) -> Unit = { listener ->
+            listener?.checkMailFinished(null, null)
+        }
+        val testSubject = SyncAllAccounts(
+            messagingController = FakeMessagingControllerMailChecker(
+                listenerExecutor = listenerExecutor,
+            ),
+        )
+
+        val result = testSubject().first()
+
+        assertk.assertThat(result.isSuccess).isEqualTo(true)
+    }
+}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMailTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMailTest.kt
deleted file mode 100644
index 71acfcdbe4a..00000000000
--- a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/domain/usecase/SyncMailTest.kt
+++ /dev/null
@@ -1,60 +0,0 @@
-package app.k9mail.feature.navigation.drawer.domain.usecase
-
-import app.k9mail.feature.navigation.drawer.ui.FakeData
-import app.k9mail.legacy.account.Account
-import app.k9mail.legacy.message.controller.MessagingControllerMailChecker
-import app.k9mail.legacy.message.controller.MessagingListener
-import assertk.assertThat
-import assertk.assertions.isEqualTo
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.test.runTest
-import org.junit.Test
-
-class SyncMailTest {
-
-    @Test
-    fun `should sync mail`() = runTest {
-        val listenerExecutor: (MessagingListener?) -> Unit = { listener ->
-            listener?.checkMailFinished(null, null)
-        }
-        val testSubject = SyncMail(
-            messagingController = FakeMessagingControllerMailChecker(
-                listenerExecutor = listenerExecutor,
-            ),
-        )
-
-        val result = testSubject(null).first()
-
-        assertThat(result.isSuccess).isEqualTo(true)
-    }
-
-    @Test
-    fun `should sync mail with account`() = runTest {
-        val listenerExecutor: (MessagingListener?) -> Unit = { listener ->
-            listener?.checkMailFinished(null, null)
-        }
-        val testSubject = SyncMail(
-            messagingController = FakeMessagingControllerMailChecker(
-                listenerExecutor = listenerExecutor,
-            ),
-        )
-
-        val result = testSubject(FakeData.ACCOUNT).first()
-
-        assertThat(result.isSuccess).isEqualTo(true)
-    }
-
-    private class FakeMessagingControllerMailChecker(
-        private val listenerExecutor: (MessagingListener?) -> Unit = {},
-    ) : MessagingControllerMailChecker {
-        override fun checkMail(
-            account: Account?,
-            ignoreLastCheckedTime: Boolean,
-            useManualWakeLock: Boolean,
-            notify: Boolean,
-            listener: MessagingListener?,
-        ) {
-            listenerExecutor(listener)
-        }
-    }
-}
diff --git a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt
index 7fa7e455274..5a8b2290cde 100644
--- a/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt
+++ b/feature/navigation/drawer/src/test/kotlin/app/k9mail/feature/navigation/drawer/ui/DrawerViewModelTest.kt
@@ -16,6 +16,7 @@ import app.k9mail.feature.navigation.drawer.domain.entity.DisplayUnifiedFolderTy
 import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Effect
 import app.k9mail.feature.navigation.drawer.ui.DrawerContract.Event
 import app.k9mail.feature.navigation.drawer.ui.DrawerContract.State
+import app.k9mail.feature.navigation.drawer.ui.FakeData.DISPLAY_ACCOUNT
 import app.k9mail.legacy.account.Account
 import app.k9mail.legacy.account.Identity
 import assertk.assertThat
@@ -60,9 +61,87 @@ class DrawerViewModelTest {
     }
 
     @Test
-    fun `should change loading state when OnRefresh event is received`() = runTest {
+    fun `should change loading state when OnSyncAccount event is received`() = runTest {
+        val initialState = State(
+            accounts = listOf(DISPLAY_ACCOUNT).toImmutableList(),
+            selectedAccount = DISPLAY_ACCOUNT,
+        )
         val testSubject = createTestSubject(
-            syncMailFlow = flow {
+            initialState = initialState,
+            displayAccountsFlow = flow { emit(listOf(DISPLAY_ACCOUNT)) },
+            syncAccountFlow = flow {
+                delay(25)
+                emit(Result.success(Unit))
+            },
+        )
+
+        eventStateTest(
+            viewModel = testSubject,
+            initialState = initialState,
+            event = Event.OnSyncAccount,
+            expectedState = initialState.copy(isLoading = true),
+            coroutineScope = backgroundScope,
+        )
+
+        advanceUntilIdle()
+
+        assertThat(testSubject.state.value.isLoading).isEqualTo(false)
+    }
+
+    @Test
+    fun `should skip loading when no account is selected and OnSyncAccount event is received`() = runTest {
+        val initialState = State(selectedAccount = null)
+        var counter = 0
+        val testSubject = createTestSubject(
+            initialState = initialState,
+            syncAccountFlow = flow {
+                delay(25)
+                emit(Result.success(Unit))
+            },
+        )
+
+        val turbines = turbinesWithInitialStateCheck(testSubject, initialState)
+
+        testSubject.event(Event.OnSyncAccount)
+
+        advanceUntilIdle()
+
+        assertThat(testSubject.state.value.isLoading).isEqualTo(false)
+        assertThat(counter).isEqualTo(0)
+
+        turbines.stateTurbine.ensureAllEventsConsumed()
+        turbines.effectTurbine.ensureAllEventsConsumed()
+    }
+
+    @Test
+    fun `should skip loading when already loading and OnSyncAccount event received`() = runTest {
+        val initialState = State(isLoading = true)
+        var counter = 0
+        val testSubject = createTestSubject(
+            initialState = initialState,
+            syncAccountFlow = flow {
+                counter++
+                delay(25)
+                emit(Result.success(Unit))
+            },
+        )
+        val turbines = turbinesWithInitialStateCheck(testSubject, initialState)
+
+        testSubject.event(Event.OnSyncAccount)
+
+        advanceUntilIdle()
+
+        assertThat(testSubject.state.value.isLoading).isEqualTo(true)
+        assertThat(counter).isEqualTo(0)
+
+        turbines.stateTurbine.ensureAllEventsConsumed()
+        turbines.effectTurbine.ensureAllEventsConsumed()
+    }
+
+    @Test
+    fun `should change loading state when OnSyncAllAccounts event is received`() = runTest {
+        val testSubject = createTestSubject(
+            syncAllAccounts = flow {
                 delay(25)
                 emit(Result.success(Unit))
             },
@@ -71,7 +150,7 @@ class DrawerViewModelTest {
         eventStateTest(
             viewModel = testSubject,
             initialState = State(isLoading = false),
-            event = Event.OnRefresh,
+            event = Event.OnSyncAllAccounts,
             expectedState = State(isLoading = true),
             coroutineScope = backgroundScope,
         )
@@ -81,6 +160,31 @@ class DrawerViewModelTest {
         assertThat(testSubject.state.value.isLoading).isEqualTo(false)
     }
 
+    @Test
+    fun `should skip loading when already loading and OnSyncAllAccounts event received`() = runTest {
+        val initialState = State(isLoading = true)
+        var counter = 0
+        val testSubject = createTestSubject(
+            initialState = initialState,
+            syncAccountFlow = flow {
+                counter++
+                delay(25)
+                emit(Result.success(Unit))
+            },
+        )
+        val turbines = turbinesWithInitialStateCheck(testSubject, initialState)
+
+        testSubject.event(Event.OnSyncAllAccounts)
+
+        advanceUntilIdle()
+
+        assertThat(testSubject.state.value.isLoading).isEqualTo(true)
+        assertThat(counter).isEqualTo(0)
+
+        turbines.stateTurbine.ensureAllEventsConsumed()
+        turbines.effectTurbine.ensureAllEventsConsumed()
+    }
+
     @Test
     fun `should collect display accounts when created and select first as selected`() = runTest {
         val displayAccounts = createDisplayAccountList(2)
@@ -314,18 +418,22 @@ class DrawerViewModelTest {
     }
 
     private fun createTestSubject(
+        initialState: State = State(),
         drawerConfigFlow: Flow<DrawerConfig> = flow { emit(createDrawerConfig()) },
         displayAccountsFlow: Flow<List<DisplayAccount>> = flow { emit(emptyList()) },
         displayFoldersFlow: Flow<Map<String, List<DisplayFolder>>> = flow { emit(emptyMap()) },
-        syncMailFlow: Flow<Result<Unit>> = flow { emit(Result.success(Unit)) },
+        syncAccountFlow: Flow<Result<Unit>> = flow { emit(Result.success(Unit)) },
+        syncAllAccounts: Flow<Result<Unit>> = flow { emit(Result.success(Unit)) },
     ): DrawerViewModel {
         return DrawerViewModel(
+            initialState = initialState,
             getDrawerConfig = { drawerConfigFlow },
             getDisplayAccounts = { displayAccountsFlow },
             getDisplayFoldersForAccount = { accountUuid, _ ->
                 displayFoldersFlow.map { it[accountUuid] ?: emptyList() }
             },
-            syncMail = { syncMailFlow },
+            syncAccount = { syncAccountFlow },
+            syncAllAccounts = { syncAllAccounts },
         )
     }