From f42e3d7912315b2daa8b1c88356246de62ccbab5 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 31 Mar 2023 19:26:03 +0300 Subject: [PATCH] Do sources configuration ops in background --- .../kotatsu/settings/SettingsActivity.kt | 4 +- ...ingsFragment.kt => SourcesListFragment.kt} | 4 +- ...gsViewModel.kt => SourcesListViewModel.kt} | 125 +++++++++++------- app/src/main/res/xml/pref_content.xml | 2 +- 4 files changed, 84 insertions(+), 51 deletions(-) rename app/src/main/java/org/koitharu/kotatsu/settings/sources/{SourcesSettingsFragment.kt => SourcesListFragment.kt} (98%) rename app/src/main/java/org/koitharu/kotatsu/settings/sources/{SourcesSettingsViewModel.kt => SourcesListViewModel.kt} (65%) diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt index 31dce19a6..ad800fcd4 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/SettingsActivity.kt @@ -24,7 +24,7 @@ import org.koitharu.kotatsu.databinding.ActivitySettingsBinding import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.settings.about.AboutSettingsFragment -import org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment +import org.koitharu.kotatsu.settings.sources.SourcesListFragment import org.koitharu.kotatsu.settings.tracker.TrackerSettingsFragment import org.koitharu.kotatsu.utils.ext.getSerializableExtraCompat import org.koitharu.kotatsu.utils.ext.isScrolledToTop @@ -133,7 +133,7 @@ class SettingsActivity : intent.getSerializableExtraCompat(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL, ) - ACTION_MANAGE_SOURCES -> SourcesSettingsFragment() + ACTION_MANAGE_SOURCES -> SourcesListFragment() Intent.ACTION_VIEW -> { when (intent.data?.host) { HOST_ABOUT -> AboutSettingsFragment() diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListFragment.kt similarity index 98% rename from app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListFragment.kt index a33d95486..2fdd8b92f 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsFragment.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListFragment.kt @@ -33,7 +33,7 @@ import org.koitharu.kotatsu.utils.ext.getItem import javax.inject.Inject @AndroidEntryPoint -class SourcesSettingsFragment : +class SourcesListFragment : BaseFragment(), SourceConfigListener, RecyclerViewOwner { @@ -42,7 +42,7 @@ class SourcesSettingsFragment : lateinit var coil: ImageLoader private var reorderHelper: ItemTouchHelper? = null - private val viewModel by viewModels() + private val viewModel by viewModels() override val recyclerView: RecyclerView get() = binding.recyclerView diff --git a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListViewModel.kt similarity index 65% rename from app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt rename to app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListViewModel.kt index 1641e37dc..22b7a6c79 100644 --- a/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesSettingsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/settings/sources/SourcesListViewModel.kt @@ -3,6 +3,11 @@ package org.koitharu.kotatsu.settings.sources import androidx.core.os.LocaleListCompat import androidx.lifecycle.MutableLiveData import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.runInterruptible +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import org.koitharu.kotatsu.R import org.koitharu.kotatsu.base.domain.ReversibleHandle import org.koitharu.kotatsu.base.ui.BaseViewModel @@ -19,34 +24,41 @@ import org.koitharu.kotatsu.utils.ext.move import java.util.Locale import java.util.TreeMap import javax.inject.Inject +import kotlin.coroutines.CoroutineContext +import kotlin.coroutines.EmptyCoroutineContext private const val KEY_ENABLED = "!" private const val TIP_REORDER = "src_reorder" @HiltViewModel -class SourcesSettingsViewModel @Inject constructor( +class SourcesListViewModel @Inject constructor( private val settings: AppSettings, ) : BaseViewModel() { val items = MutableLiveData>(emptyList()) val onActionDone = SingleLiveEvent() + private val mutex = Mutex() private val expandedGroups = HashSet() private var searchQuery: String? = null init { - buildList() + launchAtomicJob(Dispatchers.Default) { + buildList() + } } fun reorderSources(oldPos: Int, newPos: Int): Boolean { val snapshot = items.value?.toMutableList() ?: return false if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false - snapshot.move(oldPos, newPos) - settings.sourcesOrder = snapshot.mapNotNull { - (it as? SourceConfigItem.SourceItem)?.source?.name + launchAtomicJob(Dispatchers.Default) { + snapshot.move(oldPos, newPos) + settings.sourcesOrder = snapshot.mapNotNull { + (it as? SourceConfigItem.SourceItem)?.source?.name + } + buildList() } - buildList() return true } @@ -58,67 +70,79 @@ class SourcesSettingsViewModel @Inject constructor( } fun setEnabled(source: MangaSource, isEnabled: Boolean) { - settings.hiddenSources = if (isEnabled) { - settings.hiddenSources - source.name - } else { - settings.hiddenSources + source.name - } - if (isEnabled) { - settings.markKnownSources(setOf(source)) - } else { - val rollback = ReversibleHandle { - setEnabled(source, true) + launchAtomicJob(Dispatchers.Default) { + settings.hiddenSources = if (isEnabled) { + settings.hiddenSources - source.name + } else { + settings.hiddenSources + source.name } - onActionDone.postCall(ReversibleAction(R.string.source_disabled, rollback)) + if (isEnabled) { + settings.markKnownSources(setOf(source)) + } else { + val rollback = ReversibleHandle { + setEnabled(source, true) + } + onActionDone.postCall(ReversibleAction(R.string.source_disabled, rollback)) + } + buildList() } - buildList() } fun disableAll() { - settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet { - it.name + launchAtomicJob(Dispatchers.Default) { + settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet { + it.name + } + buildList() } - buildList() } fun expandOrCollapse(headerId: String?) { - if (headerId in expandedGroups) { - expandedGroups.remove(headerId) - } else { - expandedGroups.add(headerId) + launchAtomicJob { + if (headerId in expandedGroups) { + expandedGroups.remove(headerId) + } else { + expandedGroups.add(headerId) + } + buildList() } - buildList() } fun performSearch(query: String?) { - searchQuery = query?.trim() - buildList() + launchAtomicJob { + searchQuery = query?.trim() + buildList() + } } fun onTipClosed(item: SourceConfigItem.Tip) { - settings.closeTip(item.key) - buildList() + launchAtomicJob(Dispatchers.Default) { + settings.closeTip(item.key) + buildList() + } } - private fun buildList() { + private suspend fun buildList() = runInterruptible(Dispatchers.Default) { val sources = settings.getMangaSources(includeHidden = true) val hiddenSources = settings.hiddenSources val query = searchQuery if (!query.isNullOrEmpty()) { - items.value = sources.mapNotNull { - if (!it.title.contains(query, ignoreCase = true)) { - return@mapNotNull null - } - SourceConfigItem.SourceItem( - source = it, - summary = it.getLocaleTitle(), - isEnabled = it.name !in hiddenSources, - isDraggable = false, - ) - }.ifEmpty { - listOf(SourceConfigItem.EmptySearchResult) - } - return + items.postValue( + sources.mapNotNull { + if (!it.title.contains(query, ignoreCase = true)) { + return@mapNotNull null + } + SourceConfigItem.SourceItem( + source = it, + summary = it.getLocaleTitle(), + isEnabled = it.name !in hiddenSources, + isDraggable = false, + ) + }.ifEmpty { + listOf(SourceConfigItem.EmptySearchResult) + }, + ) + return@runInterruptible } val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) { if (it.name !in hiddenSources) { @@ -165,7 +189,7 @@ class SourcesSettingsViewModel @Inject constructor( } } } - items.value = result + items.postValue(result) } private fun getLocaleTitle(localeKey: String?): String? { @@ -173,6 +197,15 @@ class SourcesSettingsViewModel @Inject constructor( return locale.getDisplayLanguage(locale).toTitleCase(locale) } + private inline fun launchAtomicJob( + context: CoroutineContext = EmptyCoroutineContext, + crossinline block: suspend CoroutineScope.() -> Unit + ) = launchJob(context) { + mutex.withLock { + block() + } + } + private class LocaleKeyComparator : Comparator { private val deviceLocales = LocaleListCompat.getAdjustedDefault() diff --git a/app/src/main/res/xml/pref_content.xml b/app/src/main/res/xml/pref_content.xml index cc679251b..fd971b20c 100644 --- a/app/src/main/res/xml/pref_content.xml +++ b/app/src/main/res/xml/pref_content.xml @@ -5,7 +5,7 @@ xmlns:tools="http://schemas.android.com/tools">