From 886a5a06101d3a1e6017d7213582d80e2cb6c32b Mon Sep 17 00:00:00 2001 From: Art O Cathain Date: Sat, 5 Jun 2021 15:27:36 +1000 Subject: [PATCH] Show account unread count on account list in drawer --- app/ui/base/src/main/res/drawable/empty.xml | 2 + app/ui/legacy/build.gradle | 2 +- .../src/main/java/com/fsck/k9/ui/K9Drawer.kt | 15 +++- .../fsck/k9/ui/account/AccountsViewModel.kt | 82 ++++++++++++++++++- .../com/fsck/k9/ui/account/DisplayAccount.kt | 5 ++ .../java/com/fsck/k9/ui/account/KoinModule.kt | 2 +- 6 files changed, 101 insertions(+), 7 deletions(-) create mode 100644 app/ui/base/src/main/res/drawable/empty.xml create mode 100644 app/ui/legacy/src/main/java/com/fsck/k9/ui/account/DisplayAccount.kt diff --git a/app/ui/base/src/main/res/drawable/empty.xml b/app/ui/base/src/main/res/drawable/empty.xml new file mode 100644 index 000000000..a764c47cb --- /dev/null +++ b/app/ui/base/src/main/res/drawable/empty.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/app/ui/legacy/build.gradle b/app/ui/legacy/build.gradle index fbcc9ed00..0342660c7 100644 --- a/app/ui/legacy/build.gradle +++ b/app/ui/legacy/build.gradle @@ -33,7 +33,7 @@ dependencies { implementation "de.cketti.library.changelog:ckchangelog-core:2.0.0-beta01" implementation "com.splitwise:tokenautocomplete:4.0.0-beta01" implementation "de.cketti.safecontentresolver:safe-content-resolver-v21:1.0.0" - implementation 'com.mikepenz:materialdrawer:8.4.0' + implementation 'com.mikepenz:materialdrawer:8.4.1' implementation 'com.mikepenz:materialdrawer-iconics:8.3.3' implementation 'com.mikepenz:fontawesome-typeface:5.9.0.0-kotlin@aar' implementation 'com.github.ByteHamster:SearchPreference:v2.0.0' diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt index 176c3dc47..f2f39a3b8 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/K9Drawer.kt @@ -21,6 +21,7 @@ import com.fsck.k9.mailstore.DisplayFolder import com.fsck.k9.mailstore.Folder import com.fsck.k9.ui.account.AccountImageLoader import com.fsck.k9.ui.account.AccountsViewModel +import com.fsck.k9.ui.account.DisplayAccount import com.fsck.k9.ui.base.Theme import com.fsck.k9.ui.base.ThemeManager import com.fsck.k9.ui.folders.FolderIconProvider @@ -68,6 +69,7 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) : K private val headerView: AccountHeaderView = AccountHeaderView(parent).apply { attachToSliderView(this@K9Drawer.sliderView) dividerBelowHeader = false + displayBadgesOnCurrentProfileImage = false } private val folderIconProvider: FolderIconProvider = FolderIconProvider(parent.theme) private val swipeRefreshLayout: SwipeRefreshLayout @@ -120,7 +122,7 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) : K addFooterItems() - accountsViewModel.accountsLiveData.observeNotNull(parent) { accounts -> + accountsViewModel.displayAccountsLiveData.observeNotNull(parent) { accounts -> setAccounts(accounts) } @@ -159,11 +161,12 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) : K } } - private fun setAccounts(accounts: List) { + private fun setAccounts(displayAccounts: List) { val oldSelectedBackgroundColor = selectedBackgroundColor var newActiveProfile: IProfile? = null - val accountItems = accounts.map { account -> + val accountItems = displayAccounts.map { accountAndUnread -> + val account = accountAndUnread.account val drawerId = (account.accountNumber + 1 shl DRAWER_ACCOUNT_SHIFT).toLong() val drawerColors = getDrawerColorsForAccount(account) @@ -179,6 +182,12 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) : K descriptionTextColor = selectedTextColor selectedColorInt = drawerColors.selectedColor icon = ImageHolder(createAccountImageUri(account)) + accountAndUnread.unreadCount.takeIf { it > 0 }?.let { unreadCount -> + badgeText = unreadCount.toString() + badgeStyle = BadgeStyle().apply { + textColorStateList = selectedTextColor + } + } } if (account.uuid == openedAccountUuid) { diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/AccountsViewModel.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/AccountsViewModel.kt index 9d41f4f07..1752fa35b 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/AccountsViewModel.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/AccountsViewModel.kt @@ -1,8 +1,86 @@ package com.fsck.k9.ui.account +import android.content.ContentResolver +import android.database.ContentObserver +import android.os.Handler +import android.os.Looper +import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.asLiveData +import com.fsck.k9.Account +import com.fsck.k9.AccountsChangeListener import com.fsck.k9.Preferences +import com.fsck.k9.controller.UnreadMessageCountProvider +import com.fsck.k9.provider.EmailProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.channels.awaitClose +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.launch -class AccountsViewModel(preferences: Preferences) : ViewModel() { - val accountsLiveData = AccountsLiveData(preferences) +@OptIn(ExperimentalCoroutinesApi::class) +class AccountsViewModel( + val preferences: Preferences, + val unreadMessageCountProvider: UnreadMessageCountProvider, + private val contentResolver: ContentResolver +) : + ViewModel() { + + private val accountsFlow: Flow> = + callbackFlow { + send(preferences.accounts) + + val accountsChangeListener = AccountsChangeListener { + launch { + send(preferences.accounts) + } + } + preferences.addOnAccountsChangeListener(accountsChangeListener) + awaitClose { + preferences.removeOnAccountsChangeListener(accountsChangeListener) + } + }.flowOn(Dispatchers.IO) + + private val displayAccountFlow: Flow> = accountsFlow + .flatMapLatest { accounts -> + + val unreadCountFlows: List> = accounts.map { account -> + getUnreadCountFlow(account) + } + + combine(unreadCountFlows) { unreadCounts -> + unreadCounts.mapIndexed { index, unreadCount -> + DisplayAccount(account = accounts[index], unreadCount) + } + } + } + + private fun getUnreadCountFlow(account: Account): Flow { + return callbackFlow { + + val notificationUri = EmailProvider.getNotificationUri(account.uuid) + + send(unreadMessageCountProvider.getUnreadMessageCount(account)) + + val contentObserver = object : ContentObserver(Handler(Looper.getMainLooper())) { + override fun onChange(selfChange: Boolean) { + launch { + send(unreadMessageCountProvider.getUnreadMessageCount(account)) + } + } + } + + contentResolver.registerContentObserver(notificationUri, false, contentObserver) + + awaitClose { + contentResolver.unregisterContentObserver(contentObserver) + } + }.flowOn(Dispatchers.IO) + } + + val displayAccountsLiveData: LiveData> = displayAccountFlow.asLiveData() } diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/DisplayAccount.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/DisplayAccount.kt new file mode 100644 index 000000000..530919e86 --- /dev/null +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/DisplayAccount.kt @@ -0,0 +1,5 @@ +package com.fsck.k9.ui.account + +import com.fsck.k9.Account + +data class DisplayAccount(val account: Account, val unreadCount: Int) diff --git a/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/KoinModule.kt b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/KoinModule.kt index d17582410..42f073779 100644 --- a/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/KoinModule.kt +++ b/app/ui/legacy/src/main/java/com/fsck/k9/ui/account/KoinModule.kt @@ -4,7 +4,7 @@ import org.koin.androidx.viewmodel.dsl.viewModel import org.koin.dsl.module val accountUiModule = module { - viewModel { AccountsViewModel(preferences = get()) } + viewModel { AccountsViewModel(preferences = get(), unreadMessageCountProvider = get(), contentResolver = get()) } factory { AccountImageLoader(accountFallbackImageProvider = get()) } factory { AccountFallbackImageProvider(context = get()) } factory { AccountImageModelLoaderFactory(contactPhotoLoader = get(), accountFallbackImageProvider = get()) }