Merge pull request #4649 from k9mail/folder_database_id

Make "manage folders" screens use database ID to refer to folders
This commit is contained in:
cketti 2020-04-08 18:33:00 +02:00 committed by GitHub
commit 308917e6fd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 206 additions and 53 deletions

View file

@ -1,13 +1,17 @@
package com.fsck.k9.mailstore
import android.database.sqlite.SQLiteDatabase
import androidx.core.content.contentValuesOf
import androidx.core.database.getStringOrNull
import com.fsck.k9.Account
import com.fsck.k9.Account.FolderMode
import com.fsck.k9.Preferences
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mail.FolderType as RemoteFolderType
class FolderRepository(
private val localStoreProvider: LocalStoreProvider,
private val preferences: Preferences,
private val account: Account
) {
private val sortForDisplay =
@ -35,6 +39,105 @@ class FolderRepository(
return displayFolders.sortedWith(sortForDisplay)
}
fun getFolderDetails(folderId: Long): FolderDetails? {
val database = localStoreProvider.getInstance(account).database
return database.execute(false) { db ->
db.query(
"folders",
arrayOf(
"server_id",
"name",
"top_group",
"integrate",
"poll_class",
"display_class",
"notify_class",
"push_class"
),
"id = ?",
arrayOf(folderId.toString()),
null,
null,
null
).use { cursor ->
if (cursor.moveToFirst()) {
val serverId = cursor.getString(0)
FolderDetails(
folder = Folder(
id = folderId,
serverId = serverId,
name = cursor.getString(1),
type = folderTypeOf(serverId)
),
isInTopGroup = cursor.getInt(2) == 1,
isIntegrate = cursor.getInt(3) == 1,
syncClass = cursor.getStringOrNull(4).toFolderClass(),
displayClass = cursor.getStringOrNull(5).toFolderClass(),
notifyClass = cursor.getStringOrNull(6).toFolderClass(),
pushClass = cursor.getStringOrNull(7).toFolderClass()
)
} else {
null
}
}
}
}
fun updateFolderDetails(folderDetails: FolderDetails) {
val database = localStoreProvider.getInstance(account).database
database.execute(false) { db ->
val contentValues = contentValuesOf(
"top_group" to folderDetails.isInTopGroup,
"integrate" to folderDetails.isIntegrate,
"poll_class" to folderDetails.syncClass.name,
"display_class" to folderDetails.displayClass.name,
"notify_class" to folderDetails.notifyClass.name,
"push_class" to folderDetails.pushClass.name
)
db.update("folders", contentValues, "id = ?", arrayOf(folderDetails.folder.id.toString()))
}
saveFolderDetailsToPreferences(folderDetails)
}
private fun saveFolderDetailsToPreferences(folderDetails: FolderDetails) {
val folder = folderDetails.folder
val editor = preferences.createStorageEditor()
val id = "${account.uuid}:${folderDetails.folder.serverId}"
// There can be a lot of folders. For the defaults, let's not save prefs, saving space, except for INBOX.
val inboxServerId = account.inboxFolder
if (folderDetails.displayClass == FolderClass.NO_CLASS && folder.serverId != inboxServerId) {
editor.remove("$id.displayMode")
} else {
editor.putString("$id.displayMode", folderDetails.displayClass.name)
}
if (folderDetails.syncClass == FolderClass.INHERITED && folder.serverId != inboxServerId) {
editor.remove("$id.syncMode")
} else {
editor.putString("$id.syncMode", folderDetails.syncClass.name)
}
if (folderDetails.notifyClass == FolderClass.INHERITED && folder.serverId != inboxServerId) {
editor.remove("$id.notifyMode")
} else {
editor.putString("$id.notifyMode", folderDetails.notifyClass.name)
}
if (folderDetails.pushClass == FolderClass.SECOND_CLASS && folder.serverId != inboxServerId) {
editor.remove("$id.pushMode")
} else {
editor.putString("$id.pushMode", folderDetails.pushClass.name)
}
editor.putBoolean("$id.inTopGroup", folderDetails.isInTopGroup)
editor.putBoolean("$id.integrate", folderDetails.isIntegrate)
editor.commit()
}
private fun getDisplayFolders(db: SQLiteDatabase, displayMode: FolderMode): List<DisplayFolder> {
val queryBuilder = StringBuilder("""
SELECT f.id, f.server_id, f.name, f.top_group, (
@ -114,6 +217,10 @@ class FolderRepository(
RemoteFolderType.ARCHIVE -> FolderType.ARCHIVE
}
private fun String?.toFolderClass(): FolderClass {
return this?.let { FolderClass.valueOf(this) } ?: FolderClass.NO_CLASS
}
fun setIncludeInUnifiedInbox(serverId: String, includeInUnifiedInbox: Boolean) {
val localStore = localStoreProvider.getInstance(account)
val folder = localStore.getFolder(serverId)
@ -141,6 +248,16 @@ class FolderRepository(
data class Folder(val id: Long, val serverId: String, val name: String, val type: FolderType)
data class FolderDetails(
val folder: Folder,
val isInTopGroup: Boolean,
val isIntegrate: Boolean,
val syncClass: FolderClass,
val displayClass: FolderClass,
val notifyClass: FolderClass,
val pushClass: FolderClass
)
data class DisplayFolder(
val folder: Folder,
val isInTopGroup: Boolean,

View file

@ -1,10 +1,11 @@
package com.fsck.k9.mailstore
import com.fsck.k9.Account
import com.fsck.k9.Preferences
class FolderRepositoryManager(
private val localStoreProvider: LocalStoreProvider,
private val specialFolderSelectionStrategy: SpecialFolderSelectionStrategy
private val preferences: Preferences
) {
fun getFolderRepository(account: Account) = FolderRepository(localStoreProvider, account)
fun getFolderRepository(account: Account) = FolderRepository(localStoreProvider, preferences, account)
}

View file

@ -376,6 +376,10 @@ public class LocalStore {
return new LocalFolder(this, serverId);
}
public LocalFolder getFolder(long folderId) {
return new LocalFolder(this, folderId);
}
public LocalFolder getFolder(String serverId, String name, FolderType type) {
return new LocalFolder(this, serverId, name, type);
}

View file

@ -8,12 +8,11 @@ import com.fsck.k9.ui.R
import com.mikepenz.fastadapter.items.AbstractItem
class FolderListItem(
override var identifier: Long,
val folderId: Long,
private val folderIconResource: Int,
val displayName: String,
val serverId: String
val displayName: String
) : AbstractItem<FolderListViewHolder>() {
override var identifier: Long = folderId
override val layoutRes = R.layout.folder_list_item
override val type: Int = 1

View file

@ -2,13 +2,17 @@ package com.fsck.k9.ui.managefolders
import androidx.preference.PreferenceDataStore
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mailstore.LocalFolder
import com.fsck.k9.mailstore.FolderDetails
import com.fsck.k9.mailstore.FolderRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class FolderSettingsDataStore(private val folder: LocalFolder) : PreferenceDataStore() {
class FolderSettingsDataStore(
private val folderRepository: FolderRepository,
private var folder: FolderDetails
) : PreferenceDataStore() {
private val saveScope = CoroutineScope(GlobalScope.coroutineContext + Dispatchers.IO)
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
@ -21,8 +25,8 @@ class FolderSettingsDataStore(private val folder: LocalFolder) : PreferenceDataS
override fun putBoolean(key: String?, value: Boolean) {
return when (key) {
"folder_settings_in_top_group" -> updateFolder { isInTopGroup = value }
"folder_settings_include_in_integrated_inbox" -> updateFolder { isIntegrate = value }
"folder_settings_in_top_group" -> updateFolder(folder.copy(isInTopGroup = value))
"folder_settings_include_in_integrated_inbox" -> updateFolder(folder.copy(isIntegrate = value))
else -> error("Unknown key: $key")
}
}
@ -30,8 +34,8 @@ class FolderSettingsDataStore(private val folder: LocalFolder) : PreferenceDataS
override fun getString(key: String?, defValue: String?): String? {
return when (key) {
"folder_settings_folder_display_mode" -> folder.displayClass.name
"folder_settings_folder_sync_mode" -> folder.rawSyncClass.name
"folder_settings_folder_notify_mode" -> folder.rawNotifyClass.name
"folder_settings_folder_sync_mode" -> folder.syncClass.name
"folder_settings_folder_notify_mode" -> folder.notifyClass.name
else -> error("Unknown key: $key")
}
}
@ -40,17 +44,23 @@ class FolderSettingsDataStore(private val folder: LocalFolder) : PreferenceDataS
val newValue = requireNotNull(value) { "'value' can't be null" }
when (key) {
"folder_settings_folder_display_mode" -> updateFolder { displayClass = FolderClass.valueOf(newValue) }
"folder_settings_folder_sync_mode" -> updateFolder { syncClass = FolderClass.valueOf(newValue) }
"folder_settings_folder_notify_mode" -> updateFolder { notifyClass = FolderClass.valueOf(newValue) }
"folder_settings_folder_display_mode" -> {
updateFolder(folder.copy(displayClass = FolderClass.valueOf(newValue)))
}
"folder_settings_folder_sync_mode" -> {
updateFolder(folder.copy(syncClass = FolderClass.valueOf(newValue)))
}
"folder_settings_folder_notify_mode" -> {
updateFolder(folder.copy(notifyClass = FolderClass.valueOf(newValue)))
}
else -> error("Unknown key: $key")
}
}
private fun updateFolder(block: LocalFolder.() -> Unit) {
private fun updateFolder(newFolder: FolderDetails) {
folder = newFolder
saveScope.launch {
block(folder)
folder.save()
folderRepository.updateFolderDetails(newFolder)
}
}
}

View file

@ -2,6 +2,7 @@ package com.fsck.k9.ui.managefolders
import android.os.Bundle
import android.view.View
import androidx.navigation.fragment.findNavController
import androidx.preference.Preference
import com.fsck.k9.ui.R
import com.fsck.k9.ui.folders.FolderNameFormatter
@ -25,18 +26,28 @@ class FolderSettingsFragment : PreferenceFragmentCompat() {
val arguments = arguments ?: error("Arguments missing")
val accountUuid = arguments.getString(EXTRA_ACCOUNT) ?: error("Missing argument '$EXTRA_ACCOUNT'")
val folderServerId = arguments.getString(EXTRA_FOLDER_SERVER_ID)
?: error("Missing argument '$EXTRA_FOLDER_SERVER_ID'")
val folderId = arguments.getLong(EXTRA_FOLDER_ID)
viewModel.getFolderSettingsLiveData(accountUuid, folderServerId)
.observeNotNull(viewLifecycleOwner) { folderSettings ->
preferenceManager.preferenceDataStore = folderSettings.dataStore
setPreferencesFromResource(R.xml.folder_settings_preferences, null)
setCategoryTitle(folderSettings)
viewModel.getFolderSettingsLiveData(accountUuid, folderId)
.observeNotNull(viewLifecycleOwner) { folderSettingsResult ->
when (folderSettingsResult) {
is FolderNotFound -> navigateBack()
is FolderSettingsData -> initPreferences(folderSettingsResult)
}
}
}
private fun navigateBack() {
findNavController().popBackStack()
}
private fun initPreferences(folderSettings: FolderSettingsData) {
preferenceManager.preferenceDataStore = folderSettings.dataStore
setPreferencesFromResource(R.xml.folder_settings_preferences, null)
setCategoryTitle(folderSettings)
}
private fun setCategoryTitle(folderSettings: FolderSettingsData) {
val folderDisplayName = folderNameFormatter.displayName(folderSettings.folder)
findPreference<Preference>(PREFERENCE_TOP_CATEGORY)!!.title = folderDisplayName
@ -44,7 +55,7 @@ class FolderSettingsFragment : PreferenceFragmentCompat() {
companion object {
const val EXTRA_ACCOUNT = "account"
const val EXTRA_FOLDER_SERVER_ID = "folderServerId"
const val EXTRA_FOLDER_ID = "folderId"
private const val PREFERENCE_TOP_CATEGORY = "folder_settings"
}

View file

@ -8,34 +8,42 @@ import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.activity.FolderInfoHolder
import com.fsck.k9.mailstore.Folder
import com.fsck.k9.mailstore.LocalFolder
import com.fsck.k9.mailstore.LocalStoreProvider
import com.fsck.k9.mailstore.FolderDetails
import com.fsck.k9.mailstore.FolderRepository
import com.fsck.k9.mailstore.FolderRepositoryManager
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
class FolderSettingsViewModel(
private val preferences: Preferences,
private val localStoreProvider: LocalStoreProvider
private val folderRepositoryManager: FolderRepositoryManager
) : ViewModel() {
private var folderSettingsLiveData: LiveData<FolderSettingsData>? = null
private var folderSettingsLiveData: LiveData<FolderSettingsResult>? = null
fun getFolderSettingsLiveData(accountUuid: String, folderServerId: String): LiveData<FolderSettingsData> {
return folderSettingsLiveData ?: createFolderSettingsLiveData(accountUuid, folderServerId).also {
fun getFolderSettingsLiveData(accountUuid: String, folderId: Long): LiveData<FolderSettingsResult> {
return folderSettingsLiveData ?: createFolderSettingsLiveData(accountUuid, folderId).also {
folderSettingsLiveData = it
}
}
private fun createFolderSettingsLiveData(
accountUuid: String,
folderServerId: String
): LiveData<FolderSettingsData> {
folderId: Long
): LiveData<FolderSettingsResult> {
return liveData(context = viewModelScope.coroutineContext) {
val account = loadAccount(accountUuid)
val localFolder = loadLocalFolder(account, folderServerId)
val folderRepository = folderRepositoryManager.getFolderRepository(account)
val folderDetails = folderRepository.loadFolderDetails(folderId)
if (folderDetails == null) {
Timber.w("Folder with ID $folderId not found")
emit(FolderNotFound)
return@liveData
}
val folderSettingsData = FolderSettingsData(
folder = createFolderObject(account, folderServerId, localFolder),
dataStore = FolderSettingsDataStore(localFolder)
folder = createFolderObject(account, folderDetails.folder),
dataStore = FolderSettingsDataStore(folderRepository, folderDetails)
)
emit(folderSettingsData)
}
@ -47,19 +55,23 @@ class FolderSettingsViewModel(
}
}
private suspend fun loadLocalFolder(account: Account, folderServerId: String): LocalFolder {
private suspend fun FolderRepository.loadFolderDetails(folderId: Long): FolderDetails? {
return withContext(Dispatchers.IO) {
val localStore = localStoreProvider.getInstance(account)
val folder = localStore.getFolder(folderServerId)
folder.open()
folder
getFolderDetails(folderId)
}
}
private fun createFolderObject(account: Account, folderServerId: String, localFolder: LocalFolder): Folder {
val folderType = FolderInfoHolder.getFolderType(account, folderServerId)
return Folder(id = -1, serverId = folderServerId, name = localFolder.name, type = folderType)
private fun createFolderObject(account: Account, folder: Folder): Folder {
val folderType = FolderInfoHolder.getFolderType(account, folder.serverId)
return Folder(
id = folder.id,
serverId = folder.serverId,
name = folder.name,
type = folderType
)
}
}
data class FolderSettingsData(val folder: Folder, val dataStore: FolderSettingsDataStore)
sealed class FolderSettingsResult
object FolderNotFound : FolderSettingsResult()
data class FolderSettingsData(val folder: Folder, val dataStore: FolderSettingsDataStore) : FolderSettingsResult()

View file

@ -5,5 +5,5 @@ import org.koin.dsl.module
val manageFoldersUiModule = module {
viewModel { ManageFoldersViewModel(foldersLiveDataFactory = get()) }
viewModel { FolderSettingsViewModel(preferences = get(), localStoreProvider = get()) }
viewModel { FolderSettingsViewModel(preferences = get(), folderRepositoryManager = get()) }
}

View file

@ -65,7 +65,7 @@ class ManageFoldersFragment : Fragment() {
val folderListAdapter = FastAdapter.with(itemAdapter).apply {
setHasStableIds(true)
onClickListener = { _, _, item: FolderListItem, _ ->
openFolderSettings(item.serverId)
openFolderSettings(item.folderId)
true
}
}
@ -79,18 +79,17 @@ class ManageFoldersFragment : Fragment() {
val databaseId = displayFolder.folder.id
val folderIconResource = folderIconProvider.getFolderIcon(displayFolder.folder.type)
val displayName = folderNameFormatter.displayName(displayFolder.folder)
val serverId = displayFolder.folder.serverId
FolderListItem(databaseId, folderIconResource, displayName, serverId)
FolderListItem(databaseId, folderIconResource, displayName)
}
itemAdapter.set(folderListItems)
}
private fun openFolderSettings(folderServerId: String) {
private fun openFolderSettings(folderId: Long) {
val folderSettingsArguments = bundleOf(
FolderSettingsFragment.EXTRA_ACCOUNT to account.uuid,
FolderSettingsFragment.EXTRA_FOLDER_SERVER_ID to folderServerId
FolderSettingsFragment.EXTRA_FOLDER_ID to folderId
)
findNavController().navigate(R.id.action_manageFoldersScreen_to_folderSettingsScreen, folderSettingsArguments)
}