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