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