Merge pull request #4114 from k9mail/drawer_unread_count

Display unread message count in drawer
This commit is contained in:
cketti 2019-07-24 17:20:26 +02:00 committed by GitHub
commit d4d1280a9c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 174 additions and 60 deletions

View file

@ -1,5 +1,6 @@
package com.fsck.k9.mailstore
import android.database.sqlite.SQLiteDatabase
import com.fsck.k9.Account
import com.fsck.k9.Account.FolderMode
import com.fsck.k9.mail.Folder.FolderClass
@ -10,11 +11,12 @@ class FolderRepository(
private val specialFolderSelectionStrategy: SpecialFolderSelectionStrategy,
private val account: Account
) {
private val sortForDisplay = compareByDescending<LocalFolder> { it.serverId == account.inboxFolder }
.thenByDescending { it.serverId == account.outboxFolder }
.thenByDescending { account.isSpecialFolder(it.serverId) }
private val sortForDisplay =
compareByDescending<DisplayFolder> { it.folder.serverId == account.inboxFolder }
.thenByDescending { it.folder.serverId == account.outboxFolder }
.thenByDescending { account.isSpecialFolder(it.folder.serverId) }
.thenByDescending { it.isInTopGroup }
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name }
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.folder.name }
fun getRemoteFolderInfo(): RemoteFolderInfo {
@ -38,33 +40,72 @@ class FolderRepository(
.map { Folder(it.databaseId, it.serverId, it.name, it.type.toFolderType()) }
}
fun getDisplayFolders(): List<Folder> {
val folders = localStoreProvider.getInstance(account).getPersonalNamespaces(false)
return folders
.filter(::shouldDisplayFolder)
.sortedWith(sortForDisplay)
.map(::createFolderFromLocalFolder)
fun getDisplayFolders(): List<DisplayFolder> {
val database = localStoreProvider.getInstance(account).database
val displayFolders = database.execute(false) { db -> getDisplayFolders(db) }
return displayFolders.sortedWith(sortForDisplay)
}
private fun shouldDisplayFolder(localFolder: LocalFolder): Boolean {
val displayMode = account.folderDisplayMode
val displayClass = localFolder.displayClass
return when (displayMode) {
FolderMode.ALL -> true
FolderMode.FIRST_CLASS -> displayClass == FolderClass.FIRST_CLASS
FolderMode.FIRST_AND_SECOND_CLASS -> {
displayClass == FolderClass.FIRST_CLASS || displayClass == FolderClass.SECOND_CLASS
private fun getDisplayFolders(db: SQLiteDatabase): List<DisplayFolder> {
val queryBuilder = StringBuilder("""
SELECT f.id, f.server_id, f.name, f.top_group, (
SELECT COUNT(m.id)
FROM messages m
WHERE m.folder_id = f.id AND m.empty = 0 AND m.deleted = 0 AND m.read = 0
)
FROM folders f
""".trimIndent()
)
addDisplayClassSelection(queryBuilder)
val query = queryBuilder.toString()
db.rawQuery(query, null).use { cursor ->
val displayFolders = mutableListOf<DisplayFolder>()
while (cursor.moveToNext()) {
val id = cursor.getLong(0)
val serverId = cursor.getString(1)
val name = cursor.getString(2)
val type = folderTypeOf(serverId)
val isInTopGroup = cursor.getInt(3) == 1
val unreadCount = cursor.getInt(4)
val folder = Folder(id, serverId, name, type)
displayFolders.add(DisplayFolder(folder, isInTopGroup, unreadCount))
}
FolderMode.NOT_SECOND_CLASS -> displayClass != FolderClass.SECOND_CLASS
else -> throw AssertionError("Invalid folder display mode: $displayMode")
return displayFolders
}
}
private fun createFolderFromLocalFolder(localFolder: LocalFolder): Folder {
return Folder(localFolder.databaseId, localFolder.serverId, localFolder.name, folderTypeOf(localFolder))
private fun addDisplayClassSelection(query: StringBuilder) {
when (val displayMode = account.folderDisplayMode) {
FolderMode.ALL -> Unit // Return all folders
FolderMode.FIRST_CLASS -> {
query.append(" WHERE f.display_class = '")
.append(FolderClass.FIRST_CLASS.name)
.append("'")
}
FolderMode.FIRST_AND_SECOND_CLASS -> {
query.append(" WHERE f.display_class IN ('")
.append(FolderClass.FIRST_CLASS.name)
.append("', '")
.append(FolderClass.SECOND_CLASS.name)
.append("')")
}
FolderMode.NOT_SECOND_CLASS -> {
query.append(" WHERE f.display_class != '")
.append(FolderClass.SECOND_CLASS.name)
.append("'")
}
FolderMode.NONE -> throw AssertionError("Invalid folder display mode: $displayMode")
}
}
private fun folderTypeOf(folder: LocalFolder) = when (folder.serverId) {
private fun folderTypeOf(serverId: String) = when (serverId) {
account.inboxFolder -> FolderType.INBOX
account.outboxFolder -> FolderType.OUTBOX
account.sentFolder -> FolderType.SENT
@ -89,6 +130,12 @@ class FolderRepository(
data class Folder(val id: Long, val serverId: String, val name: String, val type: FolderType)
data class DisplayFolder(
val folder: Folder,
val isInTopGroup: Boolean,
val unreadCount: Int
)
data class RemoteFolderInfo(val folders: List<Folder>, val automaticSpecialFolders: Map<FolderType, Folder?>)
enum class FolderType {

View file

@ -15,9 +15,11 @@ import com.fsck.k9.K9
import com.fsck.k9.Preferences
import com.fsck.k9.activity.MessageList
import com.fsck.k9.helper.Contacts
import com.fsck.k9.mailstore.DisplayFolder
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderType
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.folders.FoldersLiveData
import com.fsck.k9.ui.messagelist.MessageListViewModel
import com.fsck.k9.ui.messagelist.MessageListViewModelFactory
import com.fsck.k9.ui.settings.SettingsActivity
@ -56,6 +58,11 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) {
private var unifiedInboxSelected: Boolean = false
private var openedFolderServerId: String? = null
private var foldersLiveData: FoldersLiveData? = null
private val foldersObserver = Observer<List<DisplayFolder>> { folders ->
setUserFolders(folders)
}
val layout: DrawerLayout
get() = drawer.drawerLayout
@ -201,9 +208,12 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) {
accountHeader.headerBackgroundView.setColorFilter(account.chipColor, PorterDuff.Mode.MULTIPLY)
val viewModelProvider = ViewModelProviders.of(parent, MessageListViewModelFactory())
val viewModel = viewModelProvider.get(MessageListViewModel::class.java)
viewModel.getFolders(account).observe(parent, Observer {
folders -> setUserFolders(folders)
})
foldersLiveData?.removeObserver(foldersObserver)
foldersLiveData = viewModel.getFolders(account).apply {
observe(parent, foldersObserver)
}
updateFolderSettingsItem()
}
}
@ -228,7 +238,7 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) {
}
}
private fun setUserFolders(folders: List<Folder>?) {
private fun setUserFolders(folders: List<DisplayFolder>?) {
clearUserFolders()
if (folders == null) {
@ -237,14 +247,22 @@ class K9Drawer(private val parent: MessageList, savedInstanceState: Bundle?) {
var openedFolderDrawerId: Long = -1
for (i in folders.indices.reversed()) {
val folder = folders[i]
val displayFolder = folders[i]
val folder = displayFolder.folder
val drawerId = folder.id shl DRAWER_FOLDER_SHIFT
drawer.addItemAtPosition(PrimaryDrawerItem()
val drawerItem = PrimaryDrawerItem()
.withIcon(getFolderIcon(folder))
.withIdentifier(drawerId)
.withTag(folder)
.withName(getFolderDisplayName(folder)),
headerItemCount)
.withName(getFolderDisplayName(folder))
val unreadCount = displayFolder.unreadCount
if (unreadCount > 0) {
drawerItem.withBadge(unreadCount.toString())
}
drawer.addItemAtPosition(drawerItem, headerItemCount)
userFolderDrawerIds.add(drawerId)

View file

@ -1,6 +1,7 @@
package com.fsck.k9.ui
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.folders.FoldersLiveDataFactory
import com.fsck.k9.ui.helper.DisplayHtmlUiFactory
import com.fsck.k9.ui.helper.HtmlSettingsProvider
import com.fsck.k9.ui.helper.HtmlToSpanned
@ -12,4 +13,5 @@ val uiModule = applicationContext {
bean { ThemeManager(get()) }
bean { HtmlSettingsProvider(get()) }
bean { DisplayHtmlUiFactory(get()) }
bean { FoldersLiveDataFactory(get(), get()) }
}

View file

@ -0,0 +1,48 @@
package com.fsck.k9.ui.folders
import androidx.lifecycle.LiveData
import com.fsck.k9.Account
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.controller.SimpleMessagingListener
import com.fsck.k9.mailstore.DisplayFolder
import com.fsck.k9.mailstore.FolderRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
class FoldersLiveData(
private val folderRepository: FolderRepository,
private val messagingController: MessagingController,
val accountUuid: String
) : LiveData<List<DisplayFolder>>() {
private val listener = object : SimpleMessagingListener() {
override fun folderStatusChanged(
account: Account?,
folderServerId: String?,
unreadMessageCount: Int
) {
if (account?.uuid == accountUuid) {
loadFoldersAsync()
}
}
}
private fun loadFoldersAsync() {
GlobalScope.launch(Dispatchers.Main) {
value = withContext(Dispatchers.IO) { folderRepository.getDisplayFolders() }
}
}
override fun onActive() {
super.onActive()
messagingController.addListener(listener)
loadFoldersAsync()
}
override fun onInactive() {
super.onInactive()
messagingController.removeListener(listener)
}
}

View file

@ -0,0 +1,15 @@
package com.fsck.k9.ui.folders
import com.fsck.k9.Account
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.mailstore.FolderRepositoryManager
class FoldersLiveDataFactory(
private val folderRepositoryManager: FolderRepositoryManager,
private val messagingController: MessagingController
) {
fun create(account: Account): FoldersLiveData {
val folderRepository = folderRepositoryManager.getFolderRepository(account)
return FoldersLiveData(folderRepository, messagingController, account.uuid)
}
}

View file

@ -1,38 +1,22 @@
package com.fsck.k9.ui.messagelist
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fsck.k9.Account
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.FolderRepositoryManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.launch
import com.fsck.k9.ui.folders.FoldersLiveData
import com.fsck.k9.ui.folders.FoldersLiveDataFactory
class MessageListViewModel(private val folderRepositoryManager: FolderRepositoryManager) : ViewModel() {
private val foldersLiveData = MutableLiveData<List<Folder>>()
private var account: Account? = null
class MessageListViewModel(private val foldersLiveDataFactory: FoldersLiveDataFactory) : ViewModel() {
private var foldersLiveData: FoldersLiveData? = null
fun getFolders(account: Account): LiveData<List<Folder>> {
if (foldersLiveData.value == null || this.account != account) {
this.account = account
loadFolders(account)
fun getFolders(account: Account): FoldersLiveData {
val liveData = foldersLiveData
if (liveData != null && liveData.accountUuid == account.uuid) {
return liveData
}
return foldersLiveData
}
private fun loadFolders(account: Account) {
GlobalScope.launch(Dispatchers.Main) {
val folders = async {
val folderRepository = folderRepositoryManager.getFolderRepository(account)
folderRepository.getDisplayFolders()
}.await()
foldersLiveData.value = folders
return foldersLiveDataFactory.create(account).also {
foldersLiveData = it
}
}
}

View file

@ -2,15 +2,15 @@ package com.fsck.k9.ui.messagelist
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.fsck.k9.mailstore.FolderRepositoryManager
import com.fsck.k9.ui.folders.FoldersLiveDataFactory
import org.koin.standalone.KoinComponent
import org.koin.standalone.inject
class MessageListViewModelFactory : ViewModelProvider.Factory, KoinComponent {
private val folderRepositoryManager: FolderRepositoryManager by inject()
private val foldersLiveDataFactory: FoldersLiveDataFactory by inject()
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return MessageListViewModel(folderRepositoryManager) as T
return MessageListViewModel(foldersLiveDataFactory) as T
}
}