Do sources configuration ops in background

This commit is contained in:
Koitharu
2023-03-31 19:26:03 +03:00
parent e1780b71ae
commit f42e3d7912
4 changed files with 84 additions and 51 deletions

View File

@@ -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()

View File

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

View File

@@ -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<List<SourceConfigItem>>(emptyList())
val onActionDone = SingleLiveEvent<ReversibleAction>()
private val mutex = Mutex()
private val expandedGroups = HashSet<String?>()
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<String?> {
private val deviceLocales = LocaleListCompat.getAdjustedDefault()

View File

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