diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/NavigationDrawerModule.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/NavigationDrawerModule.kt index 4c942014b4a..cfbbe28c598 100644 --- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/NavigationDrawerModule.kt +++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/NavigationDrawerModule.kt @@ -42,6 +42,7 @@ val navigationDropDownDrawerModule: Module = module { notificationStream = get(), featureFlagProvider = get(), avatarMapper = get(), + unifiedFolderRepository = get(), ) } diff --git a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccounts.kt b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccounts.kt index 4f087bde846..e05501644fb 100644 --- a/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccounts.kt +++ b/feature/navigation/drawer/dropdown/src/main/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccounts.kt @@ -21,10 +21,13 @@ import net.thunderbird.core.android.account.LegacyAccountDtoManager import net.thunderbird.core.featureflag.FeatureFlagKey import net.thunderbird.core.featureflag.FeatureFlagProvider import net.thunderbird.feature.account.storage.mapper.AvatarDataMapper +import net.thunderbird.feature.navigation.drawer.dropdown.domain.DomainContract.UnifiedFolderRepository import net.thunderbird.feature.navigation.drawer.dropdown.domain.DomainContract.UseCase import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.DisplayAccount import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.MailDisplayAccount import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayAccount +import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayFolder +import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayFolderType import net.thunderbird.feature.notification.api.content.AuthenticationErrorNotification import net.thunderbird.feature.notification.api.receiver.InAppNotificationStream @@ -35,6 +38,7 @@ internal class GetDisplayAccounts( private val notificationStream: InAppNotificationStream, private val featureFlagProvider: FeatureFlagProvider, private val avatarMapper: AvatarDataMapper, + private val unifiedFolderRepository: UnifiedFolderRepository, private val coroutineContext: CoroutineContext = Dispatchers.IO, ) : UseCase.GetDisplayAccounts { @@ -49,7 +53,7 @@ internal class GetDisplayAccounts( getMessageCountsFlow(account) } - combine(messageCountsFlows) { messageCountsList -> + val displayAccountsFlow = combine(messageCountsFlows) { messageCountsList -> val displayAccounts = messageCountsList.mapIndexed { index, messageCounts -> val account = accounts[index] MailDisplayAccount( @@ -65,11 +69,18 @@ internal class GetDisplayAccounts( ) } - if (showUnifiedAccount) { - withUnifiedAccount(displayAccounts) - } else { - displayAccounts + displayAccounts + } + + if (showUnifiedAccount) { + combine( + displayAccountsFlow, + unifiedFolderRepository.getUnifiedDisplayFolderFlow(UnifiedDisplayFolderType.INBOX), + ) { displayAccounts, unifiedInbox -> + withUnifiedAccount(displayAccounts, unifiedInbox) } + } else { + displayAccountsFlow } } } @@ -92,10 +103,13 @@ internal class GetDisplayAccounts( } } - private fun withUnifiedAccount(accounts: List): List { + private fun withUnifiedAccount( + accounts: List, + unifiedInbox: UnifiedDisplayFolder, + ): List { val unified = UnifiedDisplayAccount( - unreadMessageCount = accounts.sumOf { it.unreadMessageCount }, - starredMessageCount = accounts.sumOf { it.starredMessageCount }, + unreadMessageCount = unifiedInbox.unreadMessageCount, + starredMessageCount = unifiedInbox.starredMessageCount, hasError = accounts.any { it.hasError }, ) diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/data/FakeMessageCountsProvider.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/data/FakeMessageCountsProvider.kt index 5118d6a7fd1..4942db5798b 100644 --- a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/data/FakeMessageCountsProvider.kt +++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/data/FakeMessageCountsProvider.kt @@ -10,12 +10,13 @@ import net.thunderbird.feature.search.legacy.SearchAccount internal class FakeMessageCountsProvider( private val messageCounts: MessageCounts, + private val accountMessageCounts: Map = emptyMap(), ) : MessageCountsProvider { var recordedSearch: LocalMessageSearch = LocalMessageSearch() override fun getMessageCounts(account: LegacyAccountDto): MessageCounts { - TODO("Not yet implemented") + return checkNotNull(accountMessageCounts[account.uuid]) } override fun getMessageCounts(searchAccount: SearchAccount): MessageCounts { diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/FakeLegacyAccountDtoManager.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/FakeLegacyAccountDtoManager.kt index 3e05680e32e..e5737a47a18 100644 --- a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/FakeLegacyAccountDtoManager.kt +++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/FakeLegacyAccountDtoManager.kt @@ -1,6 +1,7 @@ package net.thunderbird.feature.navigation.drawer.dropdown.domain.usecase import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf import net.thunderbird.core.android.account.AccountRemovedListener import net.thunderbird.core.android.account.AccountsChangeListener import net.thunderbird.core.android.account.LegacyAccountDto @@ -15,7 +16,7 @@ internal class FakeLegacyAccountDtoManager( } override fun getAccountsFlow(): Flow> { - TODO("Not yet implemented") + return flowOf(accounts) } override fun getAccount(accountUuid: String): LegacyAccountDto? { diff --git a/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccountsTest.kt b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccountsTest.kt new file mode 100644 index 00000000000..fc7680602ea --- /dev/null +++ b/feature/navigation/drawer/dropdown/src/test/kotlin/net/thunderbird/feature/navigation/drawer/dropdown/domain/usecase/GetDisplayAccountsTest.kt @@ -0,0 +1,129 @@ +package net.thunderbird.feature.navigation.drawer.dropdown.domain.usecase + +import app.k9mail.legacy.mailstore.MessageListChangedListener +import app.k9mail.legacy.mailstore.MessageListRepository +import app.k9mail.legacy.mailstore.MessageMapper +import app.k9mail.legacy.message.controller.MessageCounts +import assertk.assertThat +import assertk.assertions.isEqualTo +import kotlin.test.Test +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.test.runTest +import net.thunderbird.core.android.account.Identity +import net.thunderbird.core.android.account.LegacyAccountDto +import net.thunderbird.feature.account.avatar.Avatar +import net.thunderbird.feature.account.storage.mapper.AvatarDataMapper +import net.thunderbird.feature.account.storage.profile.AvatarDto +import net.thunderbird.feature.account.storage.profile.AvatarTypeDto +import net.thunderbird.feature.navigation.drawer.dropdown.data.FakeMessageCountsProvider +import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayAccount +import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayFolder +import net.thunderbird.feature.navigation.drawer.dropdown.domain.entity.UnifiedDisplayFolderType +import net.thunderbird.feature.navigation.drawer.dropdown.ui.FakeFeatureFlagProvider +import net.thunderbird.feature.notification.api.content.InAppNotification +import net.thunderbird.feature.notification.api.receiver.InAppNotificationStream + +internal class GetDisplayAccountsTest { + + @Test + fun `should use filtered unified inbox counts for unified account`() = runTest { + // Arrange + val includedAccount = createAccount("00000000-0000-0000-0000-000000000001") + val excludedAccount = createAccount("00000000-0000-0000-0000-000000000002") + val testSubject = GetDisplayAccounts( + accountManager = FakeLegacyAccountDtoManager(accounts = listOf(includedAccount, excludedAccount)), + messageCountsProvider = FakeMessageCountsProvider( + messageCounts = MessageCounts(unread = 1, starred = 2), + accountMessageCounts = mapOf( + includedAccount.uuid to MessageCounts(unread = 1, starred = 2), + excludedAccount.uuid to MessageCounts(unread = 5, starred = 7), + ), + ), + messageListRepository = FakeMessageListRepository(), + notificationStream = FakeInAppNotificationStream(), + featureFlagProvider = FakeFeatureFlagProvider(), + avatarMapper = FakeAvatarDataMapper, + unifiedFolderRepository = FakeUnifiedFolderRepository( + flowOf( + UnifiedDisplayFolder( + id = "unified_inbox", + unifiedType = UnifiedDisplayFolderType.INBOX, + unreadMessageCount = 1, + starredMessageCount = 2, + ), + ), + ), + ) + + // Act + val result = testSubject(showUnifiedAccount = true).first() + + // Assert + assertThat(result.first()).isEqualTo( + UnifiedDisplayAccount( + unreadMessageCount = 1, + starredMessageCount = 2, + hasError = false, + ), + ) + } + + private fun createAccount(uuid: String): LegacyAccountDto { + return LegacyAccountDto(uuid).apply { + identities = mutableListOf(Identity(email = "$uuid@example.com")) + } + } + + private class FakeMessageListRepository : MessageListRepository { + override fun addListener(listener: MessageListChangedListener) = Unit + + override fun addListener(accountUuid: String, listener: MessageListChangedListener) = Unit + + override fun removeListener(listener: MessageListChangedListener) = Unit + + override fun notifyMessageListChanged(accountUuid: String) = Unit + + override fun getMessages( + accountUuid: String, + selection: String, + selectionArgs: Array, + sortOrder: String, + messageMapper: MessageMapper, + ): List = emptyList() + + override fun getThreadedMessages( + accountUuid: String, + selection: String, + selectionArgs: Array, + sortOrder: String, + messageMapper: MessageMapper, + ): List = emptyList() + + override fun getThread( + accountUuid: String, + threadId: Long, + sortOrder: String, + messageMapper: MessageMapper, + ): List = emptyList() + } + + private class FakeInAppNotificationStream : InAppNotificationStream { + override val notifications: StateFlow> = MutableStateFlow(emptySet()) + } + + private object FakeAvatarDataMapper : AvatarDataMapper { + override fun toDomain(dto: AvatarDto): Avatar = Avatar.Monogram("?") + + override fun toDto(domain: Avatar): AvatarDto { + return AvatarDto( + avatarType = AvatarTypeDto.MONOGRAM, + avatarMonogram = "?", + avatarImageUri = null, + avatarIconName = null, + ) + } + } +}