Do sources configuration ops in background

pull/311/head
Koitharu 3 years ago
parent e1780b71ae
commit f42e3d7912
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -24,7 +24,7 @@ import org.koitharu.kotatsu.databinding.ActivitySettingsBinding
import org.koitharu.kotatsu.main.ui.owners.AppBarOwner import org.koitharu.kotatsu.main.ui.owners.AppBarOwner
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
import org.koitharu.kotatsu.settings.about.AboutSettingsFragment 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.settings.tracker.TrackerSettingsFragment
import org.koitharu.kotatsu.utils.ext.getSerializableExtraCompat import org.koitharu.kotatsu.utils.ext.getSerializableExtraCompat
import org.koitharu.kotatsu.utils.ext.isScrolledToTop import org.koitharu.kotatsu.utils.ext.isScrolledToTop
@ -133,7 +133,7 @@ class SettingsActivity :
intent.getSerializableExtraCompat(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL, intent.getSerializableExtraCompat(EXTRA_SOURCE) as? MangaSource ?: MangaSource.LOCAL,
) )
ACTION_MANAGE_SOURCES -> SourcesSettingsFragment() ACTION_MANAGE_SOURCES -> SourcesListFragment()
Intent.ACTION_VIEW -> { Intent.ACTION_VIEW -> {
when (intent.data?.host) { when (intent.data?.host) {
HOST_ABOUT -> AboutSettingsFragment() HOST_ABOUT -> AboutSettingsFragment()

@ -33,7 +33,7 @@ import org.koitharu.kotatsu.utils.ext.getItem
import javax.inject.Inject import javax.inject.Inject
@AndroidEntryPoint @AndroidEntryPoint
class SourcesSettingsFragment : class SourcesListFragment :
BaseFragment<FragmentSettingsSourcesBinding>(), BaseFragment<FragmentSettingsSourcesBinding>(),
SourceConfigListener, SourceConfigListener,
RecyclerViewOwner { RecyclerViewOwner {
@ -42,7 +42,7 @@ class SourcesSettingsFragment :
lateinit var coil: ImageLoader lateinit var coil: ImageLoader
private var reorderHelper: ItemTouchHelper? = null private var reorderHelper: ItemTouchHelper? = null
private val viewModel by viewModels<SourcesSettingsViewModel>() private val viewModel by viewModels<SourcesListViewModel>()
override val recyclerView: RecyclerView override val recyclerView: RecyclerView
get() = binding.recyclerView get() = binding.recyclerView

@ -3,6 +3,11 @@ package org.koitharu.kotatsu.settings.sources
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import dagger.hilt.android.lifecycle.HiltViewModel 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.R
import org.koitharu.kotatsu.base.domain.ReversibleHandle import org.koitharu.kotatsu.base.domain.ReversibleHandle
import org.koitharu.kotatsu.base.ui.BaseViewModel 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.Locale
import java.util.TreeMap import java.util.TreeMap
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
private const val KEY_ENABLED = "!" private const val KEY_ENABLED = "!"
private const val TIP_REORDER = "src_reorder" private const val TIP_REORDER = "src_reorder"
@HiltViewModel @HiltViewModel
class SourcesSettingsViewModel @Inject constructor( class SourcesListViewModel @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
) : BaseViewModel() { ) : BaseViewModel() {
val items = MutableLiveData<List<SourceConfigItem>>(emptyList()) val items = MutableLiveData<List<SourceConfigItem>>(emptyList())
val onActionDone = SingleLiveEvent<ReversibleAction>() val onActionDone = SingleLiveEvent<ReversibleAction>()
private val mutex = Mutex()
private val expandedGroups = HashSet<String?>() private val expandedGroups = HashSet<String?>()
private var searchQuery: String? = null private var searchQuery: String? = null
init { init {
buildList() launchAtomicJob(Dispatchers.Default) {
buildList()
}
} }
fun reorderSources(oldPos: Int, newPos: Int): Boolean { fun reorderSources(oldPos: Int, newPos: Int): Boolean {
val snapshot = items.value?.toMutableList() ?: return false val snapshot = items.value?.toMutableList() ?: return false
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
snapshot.move(oldPos, newPos) launchAtomicJob(Dispatchers.Default) {
settings.sourcesOrder = snapshot.mapNotNull { snapshot.move(oldPos, newPos)
(it as? SourceConfigItem.SourceItem)?.source?.name settings.sourcesOrder = snapshot.mapNotNull {
(it as? SourceConfigItem.SourceItem)?.source?.name
}
buildList()
} }
buildList()
return true return true
} }
@ -58,67 +70,79 @@ class SourcesSettingsViewModel @Inject constructor(
} }
fun setEnabled(source: MangaSource, isEnabled: Boolean) { fun setEnabled(source: MangaSource, isEnabled: Boolean) {
settings.hiddenSources = if (isEnabled) { launchAtomicJob(Dispatchers.Default) {
settings.hiddenSources - source.name settings.hiddenSources = if (isEnabled) {
} else { settings.hiddenSources - source.name
settings.hiddenSources + source.name } else {
} settings.hiddenSources + source.name
if (isEnabled) {
settings.markKnownSources(setOf(source))
} else {
val rollback = ReversibleHandle {
setEnabled(source, true)
} }
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() { fun disableAll() {
settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet { launchAtomicJob(Dispatchers.Default) {
it.name settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet {
it.name
}
buildList()
} }
buildList()
} }
fun expandOrCollapse(headerId: String?) { fun expandOrCollapse(headerId: String?) {
if (headerId in expandedGroups) { launchAtomicJob {
expandedGroups.remove(headerId) if (headerId in expandedGroups) {
} else { expandedGroups.remove(headerId)
expandedGroups.add(headerId) } else {
expandedGroups.add(headerId)
}
buildList()
} }
buildList()
} }
fun performSearch(query: String?) { fun performSearch(query: String?) {
searchQuery = query?.trim() launchAtomicJob {
buildList() searchQuery = query?.trim()
buildList()
}
} }
fun onTipClosed(item: SourceConfigItem.Tip) { fun onTipClosed(item: SourceConfigItem.Tip) {
settings.closeTip(item.key) launchAtomicJob(Dispatchers.Default) {
buildList() settings.closeTip(item.key)
buildList()
}
} }
private fun buildList() { private suspend fun buildList() = runInterruptible(Dispatchers.Default) {
val sources = settings.getMangaSources(includeHidden = true) val sources = settings.getMangaSources(includeHidden = true)
val hiddenSources = settings.hiddenSources val hiddenSources = settings.hiddenSources
val query = searchQuery val query = searchQuery
if (!query.isNullOrEmpty()) { if (!query.isNullOrEmpty()) {
items.value = sources.mapNotNull { items.postValue(
if (!it.title.contains(query, ignoreCase = true)) { sources.mapNotNull {
return@mapNotNull null if (!it.title.contains(query, ignoreCase = true)) {
} return@mapNotNull null
SourceConfigItem.SourceItem( }
source = it, SourceConfigItem.SourceItem(
summary = it.getLocaleTitle(), source = it,
isEnabled = it.name !in hiddenSources, summary = it.getLocaleTitle(),
isDraggable = false, isEnabled = it.name !in hiddenSources,
) isDraggable = false,
}.ifEmpty { )
listOf(SourceConfigItem.EmptySearchResult) }.ifEmpty {
} listOf(SourceConfigItem.EmptySearchResult)
return },
)
return@runInterruptible
} }
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) { val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
if (it.name !in hiddenSources) { 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? { private fun getLocaleTitle(localeKey: String?): String? {
@ -173,6 +197,15 @@ class SourcesSettingsViewModel @Inject constructor(
return locale.getDisplayLanguage(locale).toTitleCase(locale) 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<String?> { private class LocaleKeyComparator : Comparator<String?> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault() private val deviceLocales = LocaleListCompat.getAdjustedDefault()

@ -5,7 +5,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<PreferenceScreen <PreferenceScreen
android:fragment="org.koitharu.kotatsu.settings.sources.SourcesSettingsFragment" android:fragment="org.koitharu.kotatsu.settings.sources.SourcesListFragment"
android:key="remote_sources" android:key="remote_sources"
android:title="@string/remote_sources" /> android:title="@string/remote_sources" />

Loading…
Cancel
Save