Merge pull request #4589 from k9mail/refactor_manage_folders

Refactor 'manage folders' and 'folder settings' screens
This commit is contained in:
cketti 2020-03-06 15:44:13 +01:00 committed by GitHub
commit 231f54955d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 424 additions and 434 deletions

View file

@ -145,11 +145,6 @@
android:configChanges="keyboardHidden|orientation|locale"
android:label="@string/account_setup_check_settings_title"/>
<activity
android:name="com.fsck.k9.activity.setup.FolderSettings"
android:configChanges="locale"
android:label="@string/folder_settings_title"/>
<activity
android:name="com.fsck.k9.ui.endtoend.AutocryptKeyTransferActivity"
android:configChanges="locale"

View file

@ -171,11 +171,6 @@
android:configChanges="keyboardHidden|orientation|locale"
android:label="@string/account_setup_check_settings_title"/>
<activity
android:name=".activity.setup.FolderSettings"
android:configChanges="locale"
android:label="@string/folder_settings_title"/>
<activity
android:name=".ui.endtoend.AutocryptKeyTransferActivity"
android:configChanges="locale"

View file

@ -23,6 +23,7 @@ dependencies {
implementation "com.takisoft.preferencex:preferencex-ringtone:${versions.preferencesFix}"
implementation "androidx.recyclerview:recyclerview:${versions.androidxRecyclerView}"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}"
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}"
implementation "androidx.navigation:navigation-fragment-ktx:${versions.androidxNavigation}"
implementation "androidx.navigation:navigation-ui-ktx:${versions.androidxNavigation}"
implementation "androidx.constraintlayout:constraintlayout:${versions.androidxConstraintLayout}"

View file

@ -1,198 +0,0 @@
package com.fsck.k9.activity.setup;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.ListPreference;
import android.preference.Preference;
import com.fsck.k9.Account;
import com.fsck.k9.DI;
import com.fsck.k9.Preferences;
import com.fsck.k9.mailstore.Folder;
import com.fsck.k9.mailstore.FolderType;
import com.fsck.k9.mailstore.LocalStoreProvider;
import com.fsck.k9.ui.R;
import com.fsck.k9.activity.FolderInfoHolder;
import com.fsck.k9.activity.K9PreferenceActivity;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.job.K9JobManager;
import com.fsck.k9.mail.FolderClass;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mailstore.LocalFolder;
import com.fsck.k9.mailstore.LocalStore;
import com.fsck.k9.ui.folders.FolderNameFormatter;
import com.fsck.k9.ui.folders.FolderNameFormatterFactory;
import timber.log.Timber;
public class FolderSettings extends K9PreferenceActivity {
private static final String EXTRA_FOLDER_NAME = "com.fsck.k9.folderName";
private static final String EXTRA_ACCOUNT = "com.fsck.k9.account";
private static final String PREFERENCE_TOP_CATERGORY = "folder_settings";
private static final String PREFERENCE_DISPLAY_CLASS = "folder_settings_folder_display_mode";
private static final String PREFERENCE_SYNC_CLASS = "folder_settings_folder_sync_mode";
private static final String PREFERENCE_PUSH_CLASS = "folder_settings_folder_push_mode";
private static final String PREFERENCE_NOTIFY_CLASS = "folder_settings_folder_notify_mode";
private static final String PREFERENCE_IN_TOP_GROUP = "folder_settings_in_top_group";
private static final String PREFERENCE_INTEGRATE = "folder_settings_include_in_integrated_inbox";
private final MessagingController messagingController = DI.get(MessagingController.class);
private final K9JobManager jobManager = DI.get(K9JobManager.class);
private final FolderNameFormatterFactory folderNameFormatterFactory = DI.get(FolderNameFormatterFactory.class);
private FolderNameFormatter folderNameFormatter;
private LocalFolder mFolder;
private CheckBoxPreference mInTopGroup;
private CheckBoxPreference mIntegrate;
private ListPreference mDisplayClass;
private ListPreference mSyncClass;
private ListPreference mPushClass;
private ListPreference mNotifyClass;
public static void actionSettings(Context context, Account account, String folderServerId) {
Intent i = new Intent(context, FolderSettings.class);
i.putExtra(EXTRA_FOLDER_NAME, folderServerId);
i.putExtra(EXTRA_ACCOUNT, account.getUuid());
context.startActivity(i);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
folderNameFormatter = folderNameFormatterFactory.create(this);
String folderServerId = (String)getIntent().getSerializableExtra(EXTRA_FOLDER_NAME);
String accountUuid = getIntent().getStringExtra(EXTRA_ACCOUNT);
Account mAccount = Preferences.getPreferences(this).getAccount(accountUuid);
try {
LocalStore localStore = DI.get(LocalStoreProvider.class).getInstance(mAccount);
mFolder = localStore.getFolder(folderServerId);
mFolder.open();
} catch (MessagingException me) {
Timber.e(me, "Unable to edit folder %s preferences", folderServerId);
return;
}
boolean isPushCapable = messagingController.isPushCapable(mAccount);
addPreferencesFromResource(R.xml.folder_settings_preferences);
String folderName = mFolder.getName();
String displayName = getDisplayName(mAccount, folderServerId, folderName);
Preference category = findPreference(PREFERENCE_TOP_CATERGORY);
category.setTitle(displayName);
mInTopGroup = (CheckBoxPreference)findPreference(PREFERENCE_IN_TOP_GROUP);
mInTopGroup.setChecked(mFolder.isInTopGroup());
mIntegrate = (CheckBoxPreference)findPreference(PREFERENCE_INTEGRATE);
mIntegrate.setChecked(mFolder.isIntegrate());
mDisplayClass = (ListPreference) findPreference(PREFERENCE_DISPLAY_CLASS);
mDisplayClass.setValue(mFolder.getDisplayClass().name());
mDisplayClass.setSummary(mDisplayClass.getEntry());
mDisplayClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mDisplayClass.findIndexOfValue(summary);
mDisplayClass.setSummary(mDisplayClass.getEntries()[index]);
mDisplayClass.setValue(summary);
return false;
}
});
mSyncClass = (ListPreference) findPreference(PREFERENCE_SYNC_CLASS);
mSyncClass.setValue(mFolder.getRawSyncClass().name());
mSyncClass.setSummary(mSyncClass.getEntry());
mSyncClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mSyncClass.findIndexOfValue(summary);
mSyncClass.setSummary(mSyncClass.getEntries()[index]);
mSyncClass.setValue(summary);
return false;
}
});
/* Temporarily disabled. See GH-4253
mPushClass = (ListPreference) findPreference(PREFERENCE_PUSH_CLASS);
mPushClass.setEnabled(isPushCapable);
mPushClass.setValue(mFolder.getRawPushClass().name());
mPushClass.setSummary(mPushClass.getEntry());
mPushClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mPushClass.findIndexOfValue(summary);
mPushClass.setSummary(mPushClass.getEntries()[index]);
mPushClass.setValue(summary);
return false;
}
});
*/
mNotifyClass = (ListPreference) findPreference(PREFERENCE_NOTIFY_CLASS);
mNotifyClass.setValue(mFolder.getRawNotifyClass().name());
mNotifyClass.setSummary(mNotifyClass.getEntry());
mNotifyClass.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
public boolean onPreferenceChange(Preference preference, Object newValue) {
final String summary = newValue.toString();
int index = mNotifyClass.findIndexOfValue(summary);
mNotifyClass.setSummary(mNotifyClass.getEntries()[index]);
mNotifyClass.setValue(summary);
return false;
}
});
}
private void saveSettings() throws MessagingException {
mFolder.setInTopGroup(mInTopGroup.isChecked());
mFolder.setIntegrate(mIntegrate.isChecked());
// We call getPushClass() because display class changes can affect push class when push class is set to inherit
FolderClass oldPushClass = mFolder.getPushClass();
FolderClass oldDisplayClass = mFolder.getDisplayClass();
mFolder.setDisplayClass(FolderClass.valueOf(mDisplayClass.getValue()));
mFolder.setSyncClass(FolderClass.valueOf(mSyncClass.getValue()));
/* Temporarily disabled. See GH-4253
mFolder.setPushClass(FolderClass.valueOf(mPushClass.getValue()));
*/
mFolder.setNotifyClass(FolderClass.valueOf(mNotifyClass.getValue()));
mFolder.save();
FolderClass newPushClass = mFolder.getPushClass();
FolderClass newDisplayClass = mFolder.getDisplayClass();
if (oldPushClass != newPushClass
|| (newPushClass != FolderClass.NO_CLASS && oldDisplayClass != newDisplayClass)) {
jobManager.schedulePusherRefresh();
}
}
@Override
public void onPause() {
try {
saveSettings();
} catch (MessagingException e) {
Timber.e(e, "Saving folder settings failed");
}
super.onPause();
}
public String getDisplayName(Account account, String serverId, String name) {
FolderType folderType = FolderInfoHolder.getFolderType(account, serverId);
Folder folder = new Folder(-1, serverId, name, folderType);
return folderNameFormatter.displayName(folder);
}
}

View file

@ -0,0 +1,56 @@
package com.fsck.k9.ui.managefolders
import androidx.preference.PreferenceDataStore
import com.fsck.k9.mail.FolderClass
import com.fsck.k9.mailstore.LocalFolder
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class FolderSettingsDataStore(private val folder: LocalFolder) : PreferenceDataStore() {
private val saveScope = CoroutineScope(GlobalScope.coroutineContext + Dispatchers.IO)
override fun getBoolean(key: String?, defValue: Boolean): Boolean {
return when (key) {
"folder_settings_in_top_group" -> folder.isInTopGroup
"folder_settings_include_in_integrated_inbox" -> folder.isIntegrate
else -> error("Unknown key: $key")
}
}
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 }
else -> error("Unknown key: $key")
}
}
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
else -> error("Unknown key: $key")
}
}
override fun putString(key: String?, value: String?) {
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) }
else -> error("Unknown key: $key")
}
}
private fun updateFolder(block: LocalFolder.() -> Unit) {
saveScope.launch {
block(folder)
folder.save()
}
}
}

View file

@ -0,0 +1,51 @@
package com.fsck.k9.ui.managefolders
import android.os.Bundle
import android.view.View
import androidx.preference.Preference
import com.fsck.k9.ui.R
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.observeNotNull
import com.takisoft.preferencex.PreferenceFragmentCompat
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class FolderSettingsFragment : PreferenceFragmentCompat() {
private val viewModel: FolderSettingsViewModel by viewModel()
private val folderNameFormatter: FolderNameFormatter by inject { parametersOf(requireActivity()) }
override fun onCreatePreferencesFix(savedInstanceState: Bundle?, rootKey: String?) {
// Set empty preferences resource while data is being loaded
setPreferencesFromResource(R.xml.empty_preferences, null)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
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'")
viewModel.getFolderSettingsLiveData(accountUuid, folderServerId)
.observeNotNull(viewLifecycleOwner) { folderSettings ->
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
}
companion object {
const val EXTRA_ACCOUNT = "account"
const val EXTRA_FOLDER_SERVER_ID = "folderServerId"
private const val PREFERENCE_TOP_CATEGORY = "folder_settings"
}
}

View file

@ -0,0 +1,65 @@
package com.fsck.k9.ui.managefolders
import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.liveData
import androidx.lifecycle.viewModelScope
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 kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
class FolderSettingsViewModel(
private val preferences: Preferences,
private val localStoreProvider: LocalStoreProvider
) : ViewModel() {
private var folderSettingsLiveData: LiveData<FolderSettingsData>? = null
fun getFolderSettingsLiveData(accountUuid: String, folderServerId: String): LiveData<FolderSettingsData> {
return folderSettingsLiveData ?: createFolderSettingsLiveData(accountUuid, folderServerId).also {
folderSettingsLiveData = it
}
}
private fun createFolderSettingsLiveData(
accountUuid: String,
folderServerId: String
): LiveData<FolderSettingsData> {
return liveData(context = viewModelScope.coroutineContext) {
val account = loadAccount(accountUuid)
val localFolder = loadLocalFolder(account, folderServerId)
val folderSettingsData = FolderSettingsData(
folder = createFolderObject(account, folderServerId, localFolder),
dataStore = FolderSettingsDataStore(localFolder)
)
emit(folderSettingsData)
}
}
private suspend fun loadAccount(accountUuid: String): Account {
return withContext(Dispatchers.IO) {
preferences.getAccount(accountUuid) ?: error("Missing account: $accountUuid")
}
}
private suspend fun loadLocalFolder(account: Account, folderServerId: String): LocalFolder {
return withContext(Dispatchers.IO) {
val localStore = localStoreProvider.getInstance(account)
val folder = localStore.getFolder(folderServerId)
folder.open()
folder
}
}
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)
}
}
data class FolderSettingsData(val folder: Folder, val dataStore: FolderSettingsDataStore)

View file

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

View file

@ -1,230 +1,59 @@
package com.fsck.k9.ui.managefolders
import android.content.Context
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
import android.widget.Toast
import androidx.appcompat.app.ActionBar
import androidx.appcompat.widget.SearchView
import androidx.appcompat.widget.SearchView.OnQueryTextListener
import androidx.recyclerview.widget.RecyclerView
import androidx.core.os.bundleOf
import androidx.navigation.NavController
import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController
import com.fsck.k9.Account
import com.fsck.k9.Account.FolderMode
import com.fsck.k9.Preferences
import com.fsck.k9.activity.K9Activity
import com.fsck.k9.activity.setup.FolderSettings
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.controller.SimpleMessagingListener
import com.fsck.k9.mailstore.DisplayFolder
import com.fsck.k9.ui.R
import com.fsck.k9.ui.folders.FolderIconProvider
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.helper.SizeFormatter
import com.fsck.k9.ui.observeNotNull
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import java.util.Locale
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
import com.fsck.k9.ui.findNavController
class ManageFoldersActivity : K9Activity() {
private val viewModel: ManageFoldersViewModel by viewModel()
private val folderNameFormatter: FolderNameFormatter by inject { parametersOf(this) }
private val messagingController: MessagingController by inject()
private val preferences: Preferences by inject()
private val folderIconProvider by lazy { FolderIconProvider(theme) }
private val sizeFormatter: SizeFormatter by inject { parametersOf(this) }
private lateinit var account: Account
private lateinit var actionBar: ActionBar
private lateinit var itemAdapter: ItemAdapter<FolderListItem>
private val messagingListener = object : SimpleMessagingListener() {
override fun accountSizeChanged(account: Account, oldSize: Long, newSize: Long) {
if (account == this@ManageFoldersActivity.account) {
runOnUiThread {
val toastText = getString(
R.string.account_size_changed,
account.description,
sizeFormatter.formatSize(oldSize),
sizeFormatter.formatSize(newSize)
)
val toast = Toast.makeText(application, toastText, Toast.LENGTH_LONG)
toast.show()
}
}
}
}
private lateinit var navController: NavController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setLayout(R.layout.folder_list)
setLayout(R.layout.activity_manage_folders)
if (!decodeArguments()) {
finish()
return
}
val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) ?: error("Missing Intent extra '$EXTRA_ACCOUNT'")
initializeActionBar()
initializeFolderList()
viewModel.getFolders(account).observeNotNull(this) { folders ->
updateFolderList(folders)
}
initializeNavController(accountUuid)
}
private fun decodeArguments(): Boolean {
val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) ?: return false
account = preferences.getAccount(accountUuid) ?: return false
private fun initializeNavController(accountUuid: String) {
navController = findNavController(R.id.nav_host_fragment)
val fragmentArguments = bundleOf(
ManageFoldersFragment.EXTRA_ACCOUNT to accountUuid
)
navController.setGraph(R.navigation.navigation_manage_folders, fragmentArguments)
val appBarConfiguration = AppBarConfiguration(topLevelDestinationIds = emptySet())
setupActionBarWithNavController(navController, appBarConfiguration)
}
override fun onSupportNavigateUp(): Boolean {
return navController.navigateUp() || super.onSupportNavigateUp() || navigateUpBySimulatedBackButtonPress()
}
private fun navigateUpBySimulatedBackButtonPress(): Boolean {
onBackPressed()
return true
}
private fun initializeActionBar() {
actionBar = supportActionBar!!
actionBar.setDisplayHomeAsUpEnabled(true)
}
private fun initializeFolderList() {
itemAdapter = ItemAdapter()
itemAdapter.itemFilter.filterPredicate = ::folderListFilter
val folderListAdapter = FastAdapter.with(itemAdapter).apply {
setHasStableIds(true)
onClickListener = { _, _, item: FolderListItem, _ ->
openFolderSettings(item.serverId)
true
}
}
val recyclerView = findViewById<RecyclerView>(R.id.folderList)
recyclerView.adapter = folderListAdapter
}
private fun updateFolderList(displayFolders: List<DisplayFolder>) {
val folderListItems = displayFolders.map { displayFolder ->
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)
}
itemAdapter.set(folderListItems)
}
private fun openFolderSettings(folderServerId: String) {
FolderSettings.actionSettings(this, account, folderServerId)
}
public override fun onPause() {
messagingController.removeListener(messagingListener)
super.onPause()
}
public override fun onResume() {
super.onResume()
messagingController.addListener(messagingListener)
}
override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_H -> displayHelpText()
KeyEvent.KEYCODE_1 -> setDisplayMode(FolderMode.FIRST_CLASS)
KeyEvent.KEYCODE_2 -> setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS)
KeyEvent.KEYCODE_3 -> setDisplayMode(FolderMode.NOT_SECOND_CLASS)
KeyEvent.KEYCODE_4 -> setDisplayMode(FolderMode.ALL)
else -> return super.onKeyDown(keyCode, event)
}
return true
}
private fun displayHelpText() {
val toast = Toast.makeText(this, R.string.folder_list_help_key, Toast.LENGTH_LONG)
toast.show()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
super.onCreateOptionsMenu(menu)
menuInflater.inflate(R.menu.folder_list_option, menu)
configureFolderSearchView(menu)
return true
}
private fun configureFolderSearchView(menu: Menu) {
val folderMenuItem = menu.findItem(R.id.filter_folders)
val folderSearchView = folderMenuItem.actionView as SearchView
folderSearchView.queryHint = getString(R.string.folder_list_filter_hint)
folderSearchView.setOnQueryTextListener(object : OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
itemAdapter.filter(query)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
itemAdapter.filter(newText)
return true
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
android.R.id.home -> onBackPressed()
R.id.list_folders -> refreshFolderList()
R.id.compact -> compactAccount()
R.id.display_1st_class -> setDisplayMode(FolderMode.FIRST_CLASS)
R.id.display_1st_and_2nd_class -> setDisplayMode(FolderMode.FIRST_AND_SECOND_CLASS)
R.id.display_not_second_class -> setDisplayMode(FolderMode.NOT_SECOND_CLASS)
R.id.display_all -> setDisplayMode(FolderMode.ALL)
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun refreshFolderList() {
messagingController.refreshFolderList(account)
}
private fun compactAccount() {
val toastText = getString(R.string.compacting_account, account.description)
val toast = Toast.makeText(application, toastText, Toast.LENGTH_SHORT)
toast.show()
messagingController.compact(account, null)
}
private fun setDisplayMode(newMode: FolderMode) {
account.folderDisplayMode = newMode
preferences.saveAccount(account)
itemAdapter.filter(null)
}
private fun folderListFilter(item: FolderListItem, constraint: CharSequence?): Boolean {
if (constraint.isNullOrEmpty()) return true
val locale = Locale.getDefault()
val displayName = item.displayName.toLowerCase(locale)
return constraint.splitToSequence(" ")
.map { it.toLowerCase(locale) }
.any { it in displayName }
}
companion object {
private const val EXTRA_ACCOUNT = "account"
@JvmStatic
fun launch(context: Context, account: Account) {
val intent = Intent(context, ManageFoldersActivity::class.java)
intent.putExtra(EXTRA_ACCOUNT, account.uuid)
context.startActivity(intent)
fun launch(activity: Activity, account: Account) {
val intent = Intent(activity, ManageFoldersActivity::class.java).apply {
putExtra(EXTRA_ACCOUNT, account.uuid)
}
activity.startActivity(intent)
}
}
}

View file

@ -0,0 +1,157 @@
package com.fsck.k9.ui.managefolders
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.fsck.k9.Account
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.mailstore.DisplayFolder
import com.fsck.k9.ui.R
import com.fsck.k9.ui.folders.FolderIconProvider
import com.fsck.k9.ui.folders.FolderNameFormatter
import com.fsck.k9.ui.observeNotNull
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.adapters.ItemAdapter
import java.util.Locale
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class ManageFoldersFragment : Fragment() {
private val viewModel: ManageFoldersViewModel by viewModel()
private val folderNameFormatter: FolderNameFormatter by inject { parametersOf(requireActivity()) }
private val messagingController: MessagingController by inject()
private val preferences: Preferences by inject()
private val folderIconProvider by lazy { FolderIconProvider(requireActivity().theme) }
private lateinit var account: Account
private lateinit var itemAdapter: ItemAdapter<FolderListItem>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setHasOptionsMenu(true)
val arguments = arguments ?: error("Missing arguments")
val accountUuid = arguments.getString(EXTRA_ACCOUNT) ?: error("Missing argument '$EXTRA_ACCOUNT'")
account = preferences.getAccount(accountUuid) ?: error("Missing account: $accountUuid")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_manage_folders, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
initializeFolderList()
viewModel.getFolders(account).observeNotNull(this) { folders ->
updateFolderList(folders)
}
}
private fun initializeFolderList() {
itemAdapter = ItemAdapter()
itemAdapter.itemFilter.filterPredicate = ::folderListFilter
val folderListAdapter = FastAdapter.with(itemAdapter).apply {
setHasStableIds(true)
onClickListener = { _, _, item: FolderListItem, _ ->
openFolderSettings(item.serverId)
true
}
}
val recyclerView = requireView().findViewById<RecyclerView>(R.id.folderList)
recyclerView.adapter = folderListAdapter
}
private fun updateFolderList(displayFolders: List<DisplayFolder>) {
val folderListItems = displayFolders.map { displayFolder ->
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)
}
itemAdapter.set(folderListItems)
}
private fun openFolderSettings(folderServerId: String) {
val folderSettingsArguments = bundleOf(
FolderSettingsFragment.EXTRA_ACCOUNT to account.uuid,
FolderSettingsFragment.EXTRA_FOLDER_SERVER_ID to folderServerId
)
findNavController().navigate(R.id.action_manageFoldersScreen_to_folderSettingsScreen, folderSettingsArguments)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.folder_list_option, menu)
configureFolderSearchView(menu)
}
private fun configureFolderSearchView(menu: Menu) {
val folderMenuItem = menu.findItem(R.id.filter_folders)
val folderSearchView = folderMenuItem.actionView as SearchView
folderSearchView.queryHint = getString(R.string.folder_list_filter_hint)
folderSearchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
itemAdapter.filter(query)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
itemAdapter.filter(newText)
return true
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.list_folders -> refreshFolderList()
R.id.display_1st_class -> setDisplayMode(Account.FolderMode.FIRST_CLASS)
R.id.display_1st_and_2nd_class -> setDisplayMode(Account.FolderMode.FIRST_AND_SECOND_CLASS)
R.id.display_not_second_class -> setDisplayMode(Account.FolderMode.NOT_SECOND_CLASS)
R.id.display_all -> setDisplayMode(Account.FolderMode.ALL)
else -> return super.onOptionsItemSelected(item)
}
return true
}
private fun refreshFolderList() {
messagingController.refreshFolderList(account)
}
private fun setDisplayMode(newMode: Account.FolderMode) {
account.folderDisplayMode = newMode
preferences.saveAccount(account)
itemAdapter.filter(null)
}
private fun folderListFilter(item: FolderListItem, constraint: CharSequence?): Boolean {
if (constraint.isNullOrEmpty()) return true
val locale = Locale.getDefault()
val displayName = item.displayName.toLowerCase(locale)
return constraint.splitToSequence(" ")
.map { it.toLowerCase(locale) }
.any { it in displayName }
}
companion object {
const val EXTRA_ACCOUNT = "account"
}
}

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="@layout/toolbar" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:defaultNavHost="true" />
</LinearLayout>

View file

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/folderList"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/folder_list_item" />

View file

@ -25,10 +25,6 @@
android:title="@string/folder_list_display_mode_not_second_class"/>
</menu>
</item>
<item
android:id="@+id/compact"
app:showAsAction="never"
android:title="@string/compact_action"/>
<item
android:id="@+id/list_folders"
android:icon="?attr/iconActionRefresh"

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/navigation_onboarding"
app:startDestination="@id/manageFoldersScreen">
<fragment
android:id="@+id/manageFoldersScreen"
android:name="com.fsck.k9.ui.managefolders.ManageFoldersFragment"
android:label="@string/folders_action"
tools:layout="@layout/fragment_manage_folders">
<action
android:id="@+id/action_manageFoldersScreen_to_folderSettingsScreen"
app:destination="@id/folderSettingsScreen" />
</fragment>
<fragment
android:id="@+id/folderSettingsScreen"
android:name="com.fsck.k9.ui.managefolders.FolderSettingsFragment"
android:label="@string/folder_settings_title"/>
</navigation>

View file

@ -756,8 +756,6 @@ Please submit bug reports, contribute new features and ask questions at
<string name="message_view_help_key">Del (or D) - Delete\nR - Reply\nA - Reply All\nC - Compose\nF - Forward\nM - Move\nV - Archive\nY - Copy\nZ - Mark (Un)read\nG - Star\nO - Sort type\nI - Sort order\nQ - Return to Folders\nS - Select/deselect\nJ or P - Previous Message\nK or N - Next Message</string>
<string name="message_list_help_key">Del (or D) - Delete\nC - Compose\nM - Move\nV - Archive\nY - Copy\nZ - Mark (Un)read\nG - Star\nO - Sort type\nI - Sort order\nQ - Return to Folders\nS - Select/deselect</string>
<string name="folder_list_help_key">1 - Display only 1st Class folders\n2 - Display 1st and 2nd Class folders\n3 - Display all except 2nd Class folders\n4 - Display all folders\nQ - Return to Accounts\nS - Edit Account Settings</string>
<string name="folder_list_filter_hint">Folder name contains</string>
<string name="folder_list_display_mode_label">Show folders…</string>

View file

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen />

View file

@ -14,62 +14,51 @@
limitations under the License.
-->
<!--
Make sure to add android:persistent="false" to all preferences to disable saving
the preference values to SharedPreferences. We use our own storage mechanism for
the preferences. See com.fsck.k9.preferences.Storage.
Also note that every sub-PreferenceScreen needs an "android:key" parameter so the correct screen
can be displayed after the device has been rotated.
-->
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:key="folder_settings">
<CheckBoxPreference
android:persistent="false"
android:key="folder_settings_in_top_group"
android:title="@string/folder_settings_in_top_group_label"
android:summary="@string/folder_settings_in_top_group_summary" />
<ListPreference
android:persistent="false"
android:key="folder_settings_folder_display_mode"
android:title="@string/folder_settings_folder_display_mode_label"
android:summary="%s"
android:entries="@array/folder_settings_folder_display_mode_entries"
android:entryValues="@array/folder_settings_folder_display_mode_values"
android:dialogTitle="@string/folder_settings_folder_display_mode_label" />
<ListPreference
android:persistent="false"
android:key="folder_settings_folder_sync_mode"
android:title="@string/folder_settings_folder_sync_mode_label"
android:summary="%s"
android:entries="@array/folder_settings_folder_sync_mode_entries"
android:entryValues="@array/folder_settings_folder_sync_mode_values"
android:dialogTitle="@string/folder_settings_folder_sync_mode_label" />
<!-- Temporarily disabled. See GH-4253
<ListPreference
android:persistent="false"
android:key="folder_settings_folder_push_mode"
android:title="@string/folder_settings_folder_push_mode_label"
android:summary="%s"
android:entries="@array/folder_settings_folder_push_mode_entries"
android:entryValues="@array/folder_settings_folder_push_mode_values"
android:dialogTitle="@string/folder_settings_folder_push_mode_label" />
-->
<ListPreference
android:persistent="false"
android:key="folder_settings_folder_notify_mode"
android:title="@string/folder_settings_folder_notify_mode_label"
android:summary="%s"
android:entries="@array/folder_settings_folder_notify_mode_entries"
android:entryValues="@array/folder_settings_folder_notify_mode_values"
android:dialogTitle="@string/folder_settings_folder_notify_mode_label" />
<CheckBoxPreference
android:persistent="false"
android:key="folder_settings_include_in_integrated_inbox"
android:title="@string/folder_settings_include_in_integrated_inbox_label"
android:summary="@string/folder_settings_include_in_integrated_inbox_summary" />