Option to move manga source to top
parent
95547a8d03
commit
c4ff37350c
@ -0,0 +1,186 @@
|
|||||||
|
package org.koitharu.kotatsu.settings.sources
|
||||||
|
|
||||||
|
import androidx.core.os.LocaleListCompat
|
||||||
|
import androidx.room.InvalidationTracker
|
||||||
|
import dagger.hilt.android.ViewModelLifecycle
|
||||||
|
import dagger.hilt.android.scopes.ViewModelScoped
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.filter
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koitharu.kotatsu.R
|
||||||
|
import org.koitharu.kotatsu.core.db.TABLE_SOURCES
|
||||||
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
||||||
|
import org.koitharu.kotatsu.core.model.isNsfw
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.lifecycleScope
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.map
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.toEnumSet
|
||||||
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
||||||
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.TreeMap
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ViewModelScoped
|
||||||
|
class SourcesListProducer @Inject constructor(
|
||||||
|
lifecycle: ViewModelLifecycle,
|
||||||
|
private val repository: MangaSourcesRepository,
|
||||||
|
private val settings: AppSettings,
|
||||||
|
) : InvalidationTracker.Observer(TABLE_SOURCES) {
|
||||||
|
|
||||||
|
private val scope = lifecycle.lifecycleScope
|
||||||
|
private var query: String = ""
|
||||||
|
private val expanded = HashSet<String?>()
|
||||||
|
val list = MutableStateFlow(emptyList<SourceConfigItem>())
|
||||||
|
|
||||||
|
private var job = scope.launch(Dispatchers.Default) {
|
||||||
|
list.value = buildList()
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
settings.observe()
|
||||||
|
.filter { it == AppSettings.KEY_TIPS_CLOSED || it == AppSettings.KEY_DISABLE_NSFW }
|
||||||
|
.flowOn(Dispatchers.Default)
|
||||||
|
.onEach { onInvalidated(emptySet()) }
|
||||||
|
.launchIn(scope)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onInvalidated(tables: Set<String>) {
|
||||||
|
val prevJob = job
|
||||||
|
job = scope.launch(Dispatchers.Default) {
|
||||||
|
prevJob.cancelAndJoin()
|
||||||
|
list.update { buildList() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setQuery(value: String) {
|
||||||
|
this.query = value
|
||||||
|
onInvalidated(emptySet())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun expandCollapse(group: String?) {
|
||||||
|
if (!expanded.remove(group)) {
|
||||||
|
expanded.add(group)
|
||||||
|
}
|
||||||
|
onInvalidated(emptySet())
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun buildList(): List<SourceConfigItem> {
|
||||||
|
val allSources = repository.allMangaSources
|
||||||
|
val enabledSources = repository.getEnabledSources()
|
||||||
|
val isNsfwDisabled = settings.isNsfwContentDisabled
|
||||||
|
val withTip = settings.isTipEnabled(TIP_REORDER)
|
||||||
|
val enabledSet = enabledSources.toEnumSet()
|
||||||
|
if (query.isNotEmpty()) {
|
||||||
|
return allSources.mapNotNull {
|
||||||
|
if (!it.title.contains(query, ignoreCase = true)) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
SourceConfigItem.SourceItem(
|
||||||
|
source = it,
|
||||||
|
summary = it.getLocaleTitle(),
|
||||||
|
isEnabled = it in enabledSet,
|
||||||
|
isDraggable = false,
|
||||||
|
isAvailable = !isNsfwDisabled || !it.isNsfw(),
|
||||||
|
)
|
||||||
|
}.ifEmpty {
|
||||||
|
listOf(SourceConfigItem.EmptySearchResult)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val map = allSources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
||||||
|
if (it in enabledSet) {
|
||||||
|
KEY_ENABLED
|
||||||
|
} else {
|
||||||
|
it.locale
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map.remove(KEY_ENABLED)
|
||||||
|
val result = ArrayList<SourceConfigItem>(allSources.size + map.size + 2)
|
||||||
|
if (enabledSources.isNotEmpty()) {
|
||||||
|
result += SourceConfigItem.Header(R.string.enabled_sources)
|
||||||
|
if (withTip) {
|
||||||
|
result += SourceConfigItem.Tip(
|
||||||
|
TIP_REORDER,
|
||||||
|
R.drawable.ic_tap_reorder,
|
||||||
|
R.string.sources_reorder_tip,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
enabledSources.mapTo(result) {
|
||||||
|
SourceConfigItem.SourceItem(
|
||||||
|
source = it,
|
||||||
|
summary = it.getLocaleTitle(),
|
||||||
|
isEnabled = true,
|
||||||
|
isDraggable = true,
|
||||||
|
isAvailable = false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (enabledSources.size != allSources.size) {
|
||||||
|
result += SourceConfigItem.Header(R.string.available_sources)
|
||||||
|
val comparator = compareBy<MangaSource, String>(AlphanumComparator()) { it.name }
|
||||||
|
for ((key, list) in map) {
|
||||||
|
list.sortWith(comparator)
|
||||||
|
val isExpanded = key in expanded
|
||||||
|
result += SourceConfigItem.LocaleGroup(
|
||||||
|
localeId = key,
|
||||||
|
title = getLocaleTitle(key),
|
||||||
|
isExpanded = isExpanded,
|
||||||
|
)
|
||||||
|
if (isExpanded) {
|
||||||
|
list.mapTo(result) {
|
||||||
|
SourceConfigItem.SourceItem(
|
||||||
|
source = it,
|
||||||
|
summary = null,
|
||||||
|
isEnabled = false,
|
||||||
|
isDraggable = false,
|
||||||
|
isAvailable = !isNsfwDisabled || !it.isNsfw(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private class LocaleKeyComparator : Comparator<String?> {
|
||||||
|
|
||||||
|
private val deviceLocales = LocaleListCompat.getAdjustedDefault()
|
||||||
|
.map { it.language }
|
||||||
|
|
||||||
|
override fun compare(a: String?, b: String?): Int {
|
||||||
|
when {
|
||||||
|
a == b -> return 0
|
||||||
|
a == null -> return 1
|
||||||
|
b == null -> return -1
|
||||||
|
}
|
||||||
|
val ai = deviceLocales.indexOf(a!!)
|
||||||
|
val bi = deviceLocales.indexOf(b!!)
|
||||||
|
return when {
|
||||||
|
ai < 0 && bi < 0 -> a.compareTo(b)
|
||||||
|
ai < 0 -> 1
|
||||||
|
bi < 0 -> -1
|
||||||
|
else -> ai.compareTo(bi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private fun getLocaleTitle(localeKey: String?): String? {
|
||||||
|
val locale = Locale(localeKey ?: return null)
|
||||||
|
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val KEY_ENABLED = "!"
|
||||||
|
const val TIP_REORDER = "src_reorder"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_lift"
|
||||||
|
android:title="@string/to_top" />
|
||||||
|
|
||||||
|
<item
|
||||||
|
android:id="@+id/action_settings"
|
||||||
|
android:title="@string/settings" />
|
||||||
|
|
||||||
|
</menu>
|
||||||
Loading…
Reference in New Issue