Add support for SpecialFolderSelection to FolderListPreference
This commit is contained in:
parent
a519c91b64
commit
5e00abfaac
9 changed files with 158 additions and 55 deletions
|
@ -3,20 +3,37 @@ package com.fsck.k9.mailstore
|
|||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.FolderMode
|
||||
import com.fsck.k9.mail.Folder.FolderClass
|
||||
import com.fsck.k9.mail.Folder.FolderType as RemoteFolderType
|
||||
|
||||
class FolderRepository(private val account: Account) {
|
||||
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 { it.isInTopGroup }
|
||||
.thenBy(String.CASE_INSENSITIVE_ORDER) { it.name }
|
||||
|
||||
|
||||
fun getRemoteFolders(): List<Folder> {
|
||||
fun getRemoteFolderInfo(): RemoteFolderInfo {
|
||||
val folders = getRemoteFolders()
|
||||
val automaticSpecialFolders = mapOf(
|
||||
FolderType.ARCHIVE to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.ARCHIVE),
|
||||
FolderType.DRAFTS to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.DRAFTS),
|
||||
FolderType.SENT to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.SENT),
|
||||
FolderType.SPAM to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.SPAM),
|
||||
FolderType.TRASH to specialFolderSelectionStrategy.selectSpecialFolder(folders, FolderType.TRASH)
|
||||
)
|
||||
|
||||
return RemoteFolderInfo(folders, automaticSpecialFolders)
|
||||
}
|
||||
|
||||
private fun getRemoteFolders(): List<Folder> {
|
||||
val folders = account.localStore.getPersonalNamespaces(false)
|
||||
|
||||
return folders
|
||||
.filterNot { it.isLocalOnly }
|
||||
.map(::createFolderFromLocalFolder)
|
||||
.map { Folder(it.databaseId, it.serverId, it.name, it.type.toFolderType()) }
|
||||
}
|
||||
|
||||
fun getDisplayFolders(): List<Folder> {
|
||||
|
@ -55,10 +72,23 @@ class FolderRepository(private val account: Account) {
|
|||
account.spamFolder -> FolderType.SPAM
|
||||
else -> FolderType.REGULAR
|
||||
}
|
||||
|
||||
private fun RemoteFolderType.toFolderType(): FolderType = when (this) {
|
||||
RemoteFolderType.REGULAR -> FolderType.REGULAR
|
||||
RemoteFolderType.INBOX -> FolderType.INBOX
|
||||
RemoteFolderType.OUTBOX -> FolderType.REGULAR // We currently don't support remote Outbox folders
|
||||
RemoteFolderType.DRAFTS -> FolderType.DRAFTS
|
||||
RemoteFolderType.SENT -> FolderType.SENT
|
||||
RemoteFolderType.TRASH -> FolderType.TRASH
|
||||
RemoteFolderType.SPAM -> FolderType.SPAM
|
||||
RemoteFolderType.ARCHIVE -> FolderType.ARCHIVE
|
||||
}
|
||||
}
|
||||
|
||||
data class Folder(val id: Long, val serverId: String, val name: String, val type: FolderType)
|
||||
|
||||
data class RemoteFolderInfo(val folders: List<Folder>, val automaticSpecialFolders: Map<FolderType, Folder?>)
|
||||
|
||||
enum class FolderType {
|
||||
REGULAR,
|
||||
INBOX,
|
||||
|
|
|
@ -2,6 +2,6 @@ package com.fsck.k9.mailstore
|
|||
|
||||
import com.fsck.k9.Account
|
||||
|
||||
class FolderRepositoryManager {
|
||||
fun getFolderRepository(account: Account) = FolderRepository(account)
|
||||
class FolderRepositoryManager(private val specialFolderSelectionStrategy: SpecialFolderSelectionStrategy) {
|
||||
fun getFolderRepository(account: Account) = FolderRepository(specialFolderSelectionStrategy, account)
|
||||
}
|
||||
|
|
|
@ -3,8 +3,9 @@ package com.fsck.k9.mailstore
|
|||
import org.koin.dsl.module.applicationContext
|
||||
|
||||
val mailStoreModule = applicationContext {
|
||||
bean { FolderRepositoryManager() }
|
||||
bean { FolderRepositoryManager(get()) }
|
||||
bean { MessageViewInfoExtractor(get(), get(), get()) }
|
||||
bean { StorageManager.getInstance(get()) }
|
||||
bean { SearchStatusManager() }
|
||||
bean { SpecialFolderSelectionStrategy() }
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
package com.fsck.k9.mailstore
|
||||
|
||||
/**
|
||||
* Implements the automatic special folder selection strategy.
|
||||
*/
|
||||
class SpecialFolderSelectionStrategy {
|
||||
fun selectSpecialFolder(folders: List<Folder>, type: FolderType): Folder? {
|
||||
return folders.firstOrNull { folder -> folder.type == type }
|
||||
}
|
||||
}
|
|
@ -3,9 +3,9 @@ package com.fsck.k9.ui.settings.account
|
|||
import android.content.Context
|
||||
import android.support.v7.preference.PreferenceDataStore
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.SpecialFolderSelection
|
||||
import com.fsck.k9.Preferences
|
||||
import com.fsck.k9.service.MailService
|
||||
import com.fsck.k9.ui.settings.account.FolderListPreference.Companion.NO_FOLDER_SELECTED_VALUE
|
||||
import java.util.concurrent.ExecutorService
|
||||
|
||||
class AccountSettingsDataStore(
|
||||
|
@ -127,15 +127,17 @@ class AccountSettingsDataStore(
|
|||
"message_format" -> account.messageFormat.name
|
||||
"quote_style" -> account.quoteStyle.name
|
||||
"account_quote_prefix" -> account.quotePrefix
|
||||
"account_setup_auto_expand_folder" -> account.autoExpandFolder.toFolderPreferenceValue()
|
||||
"account_setup_auto_expand_folder" -> {
|
||||
loadSpecialFolder(account.autoExpandFolder, SpecialFolderSelection.MANUAL)
|
||||
}
|
||||
"folder_display_mode" -> account.folderDisplayMode.name
|
||||
"folder_target_mode" -> account.folderTargetMode.name
|
||||
"searchable_folders" -> account.searchableFolders.name
|
||||
"archive_folder" -> account.archiveFolder.toFolderPreferenceValue()
|
||||
"drafts_folder" -> account.draftsFolder.toFolderPreferenceValue()
|
||||
"sent_folder" -> account.sentFolder.toFolderPreferenceValue()
|
||||
"spam_folder" -> account.spamFolder.toFolderPreferenceValue()
|
||||
"trash_folder" -> account.trashFolder.toFolderPreferenceValue()
|
||||
"archive_folder" -> loadSpecialFolder(account.archiveFolder, account.archiveFolderSelection)
|
||||
"drafts_folder" -> loadSpecialFolder(account.draftsFolder, account.draftsFolderSelection)
|
||||
"sent_folder" -> loadSpecialFolder(account.sentFolder, account.sentFolderSelection)
|
||||
"spam_folder" -> loadSpecialFolder(account.spamFolder, account.spamFolderSelection)
|
||||
"trash_folder" -> loadSpecialFolder(account.trashFolder, account.trashFolderSelection)
|
||||
"folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode.name
|
||||
"account_vibrate_pattern" -> account.notificationSetting.vibratePattern.toString()
|
||||
"account_vibrate_times" -> account.notificationSetting.vibrateTimes.toString()
|
||||
|
@ -177,15 +179,15 @@ class AccountSettingsDataStore(
|
|||
"message_format" -> account.messageFormat = Account.MessageFormat.valueOf(value)
|
||||
"quote_style" -> account.quoteStyle = Account.QuoteStyle.valueOf(value)
|
||||
"account_quote_prefix" -> account.quotePrefix = value
|
||||
"account_setup_auto_expand_folder" -> account.autoExpandFolder = value.toFolderStorageValue()
|
||||
"account_setup_auto_expand_folder" -> account.autoExpandFolder = extractFolderName(value)
|
||||
"folder_display_mode" -> account.folderDisplayMode = Account.FolderMode.valueOf(value)
|
||||
"folder_target_mode" -> account.folderTargetMode = Account.FolderMode.valueOf(value)
|
||||
"searchable_folders" -> account.searchableFolders = Account.Searchable.valueOf(value)
|
||||
"archive_folder" -> account.archiveFolder = value.toFolderStorageValue()
|
||||
"drafts_folder" -> account.draftsFolder = value.toFolderStorageValue()
|
||||
"sent_folder" -> account.sentFolder = value.toFolderStorageValue()
|
||||
"spam_folder" -> account.spamFolder = value.toFolderStorageValue()
|
||||
"trash_folder" -> account.trashFolder = value.toFolderStorageValue()
|
||||
"archive_folder" -> saveSpecialFolderSelection(value, account::setArchiveFolder)
|
||||
"drafts_folder" -> saveSpecialFolderSelection(value, account::setDraftsFolder)
|
||||
"sent_folder" -> saveSpecialFolderSelection(value, account::setSentFolder)
|
||||
"spam_folder" -> saveSpecialFolderSelection(value, account::setSpamFolder)
|
||||
"trash_folder" -> saveSpecialFolderSelection(value, account::setTrashFolder)
|
||||
"folder_notify_new_mail_mode" -> account.folderNotifyNewMailMode = Account.FolderMode.valueOf(value)
|
||||
"account_vibrate_pattern" -> account.notificationSetting.vibratePattern = value.toInt()
|
||||
"account_vibrate_times" -> account.notificationSetting.vibrateTimes = value.toInt()
|
||||
|
@ -225,7 +227,32 @@ class AccountSettingsDataStore(
|
|||
MailService.actionRestartPushers(context, null)
|
||||
}
|
||||
|
||||
private fun String.toFolderStorageValue() = if (this == NO_FOLDER_SELECTED_VALUE) null else this
|
||||
private fun extractFolderName(preferenceValue: String): String? {
|
||||
val folderValue = preferenceValue.substringAfter(FolderListPreference.FOLDER_VALUE_DELIMITER)
|
||||
return if (folderValue == FolderListPreference.NO_FOLDER_VALUE) null else folderValue
|
||||
}
|
||||
|
||||
private fun String?.toFolderPreferenceValue() = this ?: NO_FOLDER_SELECTED_VALUE
|
||||
private fun saveSpecialFolderSelection(
|
||||
preferenceValue: String,
|
||||
specialFolderSetter: (String?, SpecialFolderSelection) -> Unit
|
||||
) {
|
||||
val specialFolder = extractFolderName(preferenceValue)
|
||||
|
||||
val specialFolderSelection = if (preferenceValue.startsWith(FolderListPreference.AUTOMATIC_PREFIX)) {
|
||||
SpecialFolderSelection.AUTOMATIC
|
||||
} else {
|
||||
SpecialFolderSelection.MANUAL
|
||||
}
|
||||
|
||||
specialFolderSetter(specialFolder, specialFolderSelection)
|
||||
}
|
||||
|
||||
private fun loadSpecialFolder(specialFolder: String?, specialFolderSelection: SpecialFolderSelection): String {
|
||||
val prefix = when (specialFolderSelection) {
|
||||
SpecialFolderSelection.AUTOMATIC -> FolderListPreference.AUTOMATIC_PREFIX
|
||||
SpecialFolderSelection.MANUAL -> FolderListPreference.MANUAL_PREFIX
|
||||
}
|
||||
|
||||
return prefix + (specialFolder ?: FolderListPreference.NO_FOLDER_VALUE)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,9 @@ import com.fsck.k9.activity.setup.AccountSetupIncoming
|
|||
import com.fsck.k9.activity.setup.AccountSetupOutgoing
|
||||
import com.fsck.k9.controller.MessagingController
|
||||
import com.fsck.k9.crypto.OpenPgpApiHelper
|
||||
import com.fsck.k9.mailstore.Folder
|
||||
import com.fsck.k9.mailstore.FolderType
|
||||
import com.fsck.k9.mailstore.RemoteFolderInfo
|
||||
import com.fsck.k9.mailstore.StorageManager
|
||||
import com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity
|
||||
import com.fsck.k9.ui.settings.onClick
|
||||
|
@ -241,15 +244,30 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
private fun loadFolders(account: Account) {
|
||||
viewModel.getFolders(account).observe(this@AccountSettingsFragment) { folders ->
|
||||
if (folders != null) {
|
||||
FOLDER_LIST_PREFERENCES.forEach {
|
||||
(findPreference(it) as? FolderListPreference)?.folders = folders
|
||||
}
|
||||
viewModel.getFolders(account).observe(this@AccountSettingsFragment) { remoteFolderInfo ->
|
||||
if (remoteFolderInfo != null) {
|
||||
setFolders(PREFERENCE_AUTO_EXPAND_FOLDER, remoteFolderInfo.folders)
|
||||
setFolders(PREFERENCE_ARCHIVE_FOLDER, remoteFolderInfo, FolderType.ARCHIVE)
|
||||
setFolders(PREFERENCE_DRAFTS_FOLDER, remoteFolderInfo, FolderType.DRAFTS)
|
||||
setFolders(PREFERENCE_SENT_FOLDER, remoteFolderInfo, FolderType.SENT)
|
||||
setFolders(PREFERENCE_SPAM_FOLDER, remoteFolderInfo, FolderType.SPAM)
|
||||
setFolders(PREFERENCE_TRASH_FOLDER, remoteFolderInfo, FolderType.TRASH)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setFolders(preferenceKey: String, folders: List<Folder>) {
|
||||
val folderListPreference = findPreference(preferenceKey) as? FolderListPreference ?: return
|
||||
folderListPreference.setFolders(folders)
|
||||
}
|
||||
|
||||
private fun setFolders(preferenceKey: String, remoteFolderInfo: RemoteFolderInfo, type: FolderType?) {
|
||||
val folderListPreference = findPreference(preferenceKey) as? FolderListPreference ?: return
|
||||
|
||||
val automaticFolder = remoteFolderInfo.automaticSpecialFolders[type]
|
||||
folderListPreference.setFolders(remoteFolderInfo.folders, automaticFolder)
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
val openPgpKeyPreference = findPreference(PREFERENCE_OPENPGP_KEY) as? OpenPgpKeyPreference
|
||||
if (openPgpKeyPreference?.handleOnActivityResult(requestCode, resultCode, data) == true) {
|
||||
|
@ -291,15 +309,6 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
|||
private const val PREFERENCE_TRASH_FOLDER = "trash_folder"
|
||||
private const val DELETE_POLICY_MARK_AS_READ = "MARK_AS_READ"
|
||||
|
||||
private val FOLDER_LIST_PREFERENCES = listOf(
|
||||
PREFERENCE_AUTO_EXPAND_FOLDER,
|
||||
PREFERENCE_ARCHIVE_FOLDER,
|
||||
PREFERENCE_DRAFTS_FOLDER,
|
||||
PREFERENCE_SENT_FOLDER,
|
||||
PREFERENCE_SPAM_FOLDER,
|
||||
PREFERENCE_TRASH_FOLDER
|
||||
)
|
||||
|
||||
fun create(accountUuid: String, rootKey: String?) = AccountSettingsFragment().withArguments(
|
||||
ARG_ACCOUNT_UUID to accountUuid,
|
||||
PreferenceFragmentCompat.ARG_PREFERENCE_ROOT to rootKey)
|
||||
|
|
|
@ -5,8 +5,8 @@ import android.arch.lifecycle.MutableLiveData
|
|||
import android.arch.lifecycle.ViewModel
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Preferences
|
||||
import com.fsck.k9.mailstore.Folder
|
||||
import com.fsck.k9.mailstore.FolderRepositoryManager
|
||||
import com.fsck.k9.mailstore.RemoteFolderInfo
|
||||
import kotlinx.coroutines.experimental.android.UI
|
||||
import kotlinx.coroutines.experimental.launch
|
||||
import org.jetbrains.anko.coroutines.experimental.bg
|
||||
|
@ -16,7 +16,7 @@ class AccountSettingsViewModel(
|
|||
private val folderRepositoryManager: FolderRepositoryManager
|
||||
) : ViewModel() {
|
||||
private val accountLiveData = MutableLiveData<Account>()
|
||||
private val foldersLiveData = MutableLiveData<List<Folder>>()
|
||||
private val foldersLiveData = MutableLiveData<RemoteFolderInfo>()
|
||||
|
||||
fun getAccount(accountUuid: String): LiveData<Account> {
|
||||
if (accountLiveData.value == null) {
|
||||
|
@ -44,7 +44,7 @@ class AccountSettingsViewModel(
|
|||
|
||||
private fun loadAccount(accountUuid: String) = preferences.getAccount(accountUuid)
|
||||
|
||||
fun getFolders(account: Account): LiveData<List<Folder>> {
|
||||
fun getFolders(account: Account): LiveData<RemoteFolderInfo> {
|
||||
if (foldersLiveData.value == null) {
|
||||
loadFolders(account)
|
||||
}
|
||||
|
@ -55,11 +55,11 @@ class AccountSettingsViewModel(
|
|||
private fun loadFolders(account: Account) {
|
||||
val folderRepository = folderRepositoryManager.getFolderRepository(account)
|
||||
launch(UI) {
|
||||
val folders = bg {
|
||||
folderRepository.getRemoteFolders()
|
||||
val remoteFolderInfo = bg {
|
||||
folderRepository.getRemoteFolderInfo()
|
||||
}.await()
|
||||
|
||||
foldersLiveData.value = folders
|
||||
foldersLiveData.value = remoteFolderInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,19 +28,8 @@ constructor(
|
|||
defStyleRes: Int = 0
|
||||
) : ListPreference(context, attrs, defStyleAttr, defStyleRes), KoinComponent {
|
||||
private val folderNameFormatter: FolderNameFormatter by inject()
|
||||
private val noFolderSelectedName = SpannableString(context.getString(R.string.account_settings_no_folder_selected))
|
||||
.apply { setSpan(StyleSpan(Typeface.ITALIC), 0, this.length, 0) }
|
||||
|
||||
|
||||
var folders: List<Folder>
|
||||
get() = throw UnsupportedOperationException()
|
||||
set(folders) {
|
||||
entries = (listOf(noFolderSelectedName) + folders.map { folderNameFormatter.displayName(it) }).toTypedArray()
|
||||
entryValues = (listOf(NO_FOLDER_SELECTED_VALUE) + folders.map { it.serverId }).toTypedArray()
|
||||
|
||||
isEnabled = true
|
||||
}
|
||||
|
||||
private val noFolderSelectedName = context.getString(R.string.account_settings_no_folder_selected).italicize()
|
||||
private lateinit var automaticFolderOption: CharSequence
|
||||
|
||||
init {
|
||||
entries = emptyArray()
|
||||
|
@ -48,6 +37,30 @@ constructor(
|
|||
isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
fun setFolders(folders: List<Folder>) {
|
||||
entries = (listOf(noFolderSelectedName) + getFolderDisplayNames(folders)).toTypedArray()
|
||||
entryValues = (listOf(NO_FOLDER_SELECTED_VALUE) + getFolderValues(folders)).toTypedArray()
|
||||
isEnabled = true
|
||||
}
|
||||
|
||||
fun setFolders(folders: List<Folder>, automaticFolder: Folder?) {
|
||||
val automaticFolderName = if (automaticFolder != null) {
|
||||
folderNameFormatter.displayName(automaticFolder)
|
||||
} else {
|
||||
context.getString(R.string.account_settings_no_folder_selected)
|
||||
}
|
||||
val automaticFolderValue = AUTOMATIC_PREFIX + (automaticFolder?.serverId ?: NO_FOLDER_VALUE)
|
||||
|
||||
automaticFolderOption = context.getString(R.string.account_settings_automatic_special_folder,
|
||||
automaticFolderName).italicize()
|
||||
|
||||
entries = (listOf(automaticFolderOption) + noFolderSelectedName + getFolderDisplayNames(folders)).toTypedArray()
|
||||
entryValues = (listOf(automaticFolderValue) + NO_FOLDER_SELECTED_VALUE + getFolderValues(folders)).toTypedArray()
|
||||
|
||||
isEnabled = true
|
||||
}
|
||||
|
||||
override fun getSummary(): CharSequence {
|
||||
// While folders are being loaded the summary returned by ListPreference will be empty. This leads to the
|
||||
// summary view being hidden. Once folders are loaded the summary updates and the list height changes. This
|
||||
|
@ -55,13 +68,25 @@ constructor(
|
|||
return when {
|
||||
entries.isEmpty() -> PLACEHOLDER_SUMMARY
|
||||
value == NO_FOLDER_SELECTED_VALUE -> noFolderSelectedName
|
||||
value.startsWith(AUTOMATIC_PREFIX) -> automaticFolderOption
|
||||
else -> super.getSummary()
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFolderDisplayNames(folders: List<Folder>) = folders.map { folderNameFormatter.displayName(it) }
|
||||
|
||||
private fun getFolderValues(folders: List<Folder>) = folders.map { MANUAL_PREFIX + it.serverId }
|
||||
|
||||
private fun String.italicize(): CharSequence {
|
||||
return SpannableString(this).apply { setSpan(StyleSpan(Typeface.ITALIC), 0, this.length, 0) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val NO_FOLDER_SELECTED_VALUE = ""
|
||||
const val FOLDER_VALUE_DELIMITER = "|"
|
||||
const val AUTOMATIC_PREFIX = "AUTOMATIC|"
|
||||
const val MANUAL_PREFIX = "MANUAL|"
|
||||
const val NO_FOLDER_VALUE = ""
|
||||
private const val NO_FOLDER_SELECTED_VALUE = MANUAL_PREFIX + NO_FOLDER_VALUE
|
||||
private const val PLACEHOLDER_SUMMARY = " "
|
||||
}
|
||||
}
|
||||
|
|
|
@ -903,6 +903,7 @@ Please submit bug reports, contribute new features and ask questions at
|
|||
<string name="account_settings_searchable_none">None</string>
|
||||
|
||||
<string name="account_settings_no_folder_selected">None</string>
|
||||
<string name="account_settings_automatic_special_folder">Automatic (%s)</string>
|
||||
|
||||
<string name="font_size_settings_title">Font size</string>
|
||||
<string name="font_size_settings_description">Configure font size</string>
|
||||
|
|
Loading…
Reference in a new issue