|
|
|
@ -3,25 +3,26 @@ package org.koitharu.kotatsu.settings.sources
|
|
|
|
import androidx.core.os.LocaleListCompat
|
|
|
|
import androidx.core.os.LocaleListCompat
|
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
import kotlinx.coroutines.CoroutineScope
|
|
|
|
|
|
|
|
import kotlinx.coroutines.CoroutineStart
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.Dispatchers
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
import kotlinx.coroutines.runInterruptible
|
|
|
|
|
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
|
|
import kotlinx.coroutines.sync.Mutex
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
import kotlinx.coroutines.sync.withLock
|
|
|
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.R
|
|
|
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
|
|
|
import org.koitharu.kotatsu.core.model.getLocaleTitle
|
|
|
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
|
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
|
|
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|
|
|
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
|
|
|
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
|
|
|
import org.koitharu.kotatsu.core.ui.util.ReversibleAction
|
|
|
|
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
|
|
|
|
import org.koitharu.kotatsu.core.util.AlphanumComparator
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.MutableEventFlow
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.call
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.call
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.map
|
|
|
|
import org.koitharu.kotatsu.core.util.ext.map
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.explore.data.MangaSourcesRepository
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
|
|
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
|
|
|
import org.koitharu.kotatsu.parsers.util.mapToSet
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.move
|
|
|
|
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
|
|
|
import org.koitharu.kotatsu.parsers.util.toTitleCase
|
|
|
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
|
|
|
import org.koitharu.kotatsu.settings.sources.model.SourceConfigItem
|
|
|
|
|
|
|
|
import java.util.EnumSet
|
|
|
|
import java.util.Locale
|
|
|
|
import java.util.Locale
|
|
|
|
import java.util.TreeMap
|
|
|
|
import java.util.TreeMap
|
|
|
|
import javax.inject.Inject
|
|
|
|
import javax.inject.Inject
|
|
|
|
@ -34,6 +35,7 @@ private const val TIP_REORDER = "src_reorder"
|
|
|
|
@HiltViewModel
|
|
|
|
@HiltViewModel
|
|
|
|
class SourcesListViewModel @Inject constructor(
|
|
|
|
class SourcesListViewModel @Inject constructor(
|
|
|
|
private val settings: AppSettings,
|
|
|
|
private val settings: AppSettings,
|
|
|
|
|
|
|
|
private val repository: MangaSourcesRepository,
|
|
|
|
) : BaseViewModel() {
|
|
|
|
) : BaseViewModel() {
|
|
|
|
|
|
|
|
|
|
|
|
val items = MutableStateFlow<List<SourceConfigItem>>(emptyList())
|
|
|
|
val items = MutableStateFlow<List<SourceConfigItem>>(emptyList())
|
|
|
|
@ -51,13 +53,19 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
|
|
|
|
|
|
|
|
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
|
|
|
fun reorderSources(oldPos: Int, newPos: Int): Boolean {
|
|
|
|
val snapshot = items.value.toMutableList()
|
|
|
|
val snapshot = items.value.toMutableList()
|
|
|
|
if ((snapshot[oldPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
|
|
|
val item = (snapshot[oldPos] as? SourceConfigItem.SourceItem) ?: return false
|
|
|
|
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isEnabled != true) return false
|
|
|
|
if ((snapshot[newPos] as? SourceConfigItem.SourceItem)?.isDraggable != true) return false
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
snapshot.move(oldPos, newPos)
|
|
|
|
var targetPosition = 0
|
|
|
|
settings.sourcesOrder = snapshot.mapNotNull {
|
|
|
|
for ((i, x) in snapshot.withIndex()) {
|
|
|
|
(it as? SourceConfigItem.SourceItem)?.source?.name
|
|
|
|
if (i == newPos) {
|
|
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (x is SourceConfigItem.SourceItem) {
|
|
|
|
|
|
|
|
targetPosition++
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
repository.setPosition(item.source, targetPosition)
|
|
|
|
buildList()
|
|
|
|
buildList()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
return true
|
|
|
|
@ -71,17 +79,8 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
|
|
|
|
|
|
|
|
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
|
|
|
fun setEnabled(source: MangaSource, isEnabled: Boolean) {
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
settings.hiddenSources = if (isEnabled) {
|
|
|
|
val rollback = repository.setSourceEnabled(source, isEnabled)
|
|
|
|
settings.hiddenSources - source.name
|
|
|
|
if (!isEnabled) {
|
|
|
|
} else {
|
|
|
|
|
|
|
|
settings.hiddenSources + source.name
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isEnabled) {
|
|
|
|
|
|
|
|
settings.markKnownSources(setOf(source))
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
val rollback = ReversibleHandle {
|
|
|
|
|
|
|
|
setEnabled(source, true)
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
|
|
|
onActionDone.call(ReversibleAction(R.string.source_disabled, rollback))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
buildList()
|
|
|
|
buildList()
|
|
|
|
@ -90,9 +89,7 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
|
|
|
|
|
|
|
|
fun disableAll() {
|
|
|
|
fun disableAll() {
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
launchAtomicJob(Dispatchers.Default) {
|
|
|
|
settings.hiddenSources = settings.getMangaSources(includeHidden = true).mapToSet {
|
|
|
|
repository.disableAllSources()
|
|
|
|
it.name
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
buildList()
|
|
|
|
buildList()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
@ -122,36 +119,37 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private suspend fun buildList() = runInterruptible(Dispatchers.Default) {
|
|
|
|
private suspend fun buildList() = withContext(Dispatchers.Default) {
|
|
|
|
val sources = settings.getMangaSources(includeHidden = true)
|
|
|
|
val allSources = repository.allMangaSources
|
|
|
|
val hiddenSources = settings.hiddenSources
|
|
|
|
val enabledSources = repository.getEnabledSources()
|
|
|
|
|
|
|
|
val enabledSet = EnumSet.copyOf(enabledSources)
|
|
|
|
val query = searchQuery
|
|
|
|
val query = searchQuery
|
|
|
|
if (!query.isNullOrEmpty()) {
|
|
|
|
if (!query.isNullOrEmpty()) {
|
|
|
|
items.value = sources.mapNotNull {
|
|
|
|
items.value = allSources.mapNotNull {
|
|
|
|
if (!it.title.contains(query, ignoreCase = true)) {
|
|
|
|
if (!it.title.contains(query, ignoreCase = true)) {
|
|
|
|
return@mapNotNull null
|
|
|
|
return@mapNotNull null
|
|
|
|
}
|
|
|
|
}
|
|
|
|
SourceConfigItem.SourceItem(
|
|
|
|
SourceConfigItem.SourceItem(
|
|
|
|
source = it,
|
|
|
|
source = it,
|
|
|
|
summary = it.getLocaleTitle(),
|
|
|
|
summary = it.getLocaleTitle(),
|
|
|
|
isEnabled = it.name !in hiddenSources,
|
|
|
|
isEnabled = it in enabledSet,
|
|
|
|
isDraggable = false,
|
|
|
|
isDraggable = false,
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}.ifEmpty {
|
|
|
|
}.ifEmpty {
|
|
|
|
listOf(SourceConfigItem.EmptySearchResult)
|
|
|
|
listOf(SourceConfigItem.EmptySearchResult)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return@runInterruptible
|
|
|
|
return@withContext
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val map = sources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
|
|
|
val map = allSources.groupByTo(TreeMap(LocaleKeyComparator())) {
|
|
|
|
if (it.name !in hiddenSources) {
|
|
|
|
if (it in enabledSet) {
|
|
|
|
KEY_ENABLED
|
|
|
|
KEY_ENABLED
|
|
|
|
} else {
|
|
|
|
} else {
|
|
|
|
it.locale
|
|
|
|
it.locale
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
val result = ArrayList<SourceConfigItem>(sources.size + map.size + 2)
|
|
|
|
map.remove(KEY_ENABLED)
|
|
|
|
val enabledSources = map.remove(KEY_ENABLED)
|
|
|
|
val result = ArrayList<SourceConfigItem>(allSources.size + map.size + 2)
|
|
|
|
if (!enabledSources.isNullOrEmpty()) {
|
|
|
|
if (enabledSources.isNotEmpty()) {
|
|
|
|
result += SourceConfigItem.Header(R.string.enabled_sources)
|
|
|
|
result += SourceConfigItem.Header(R.string.enabled_sources)
|
|
|
|
if (settings.isTipEnabled(TIP_REORDER)) {
|
|
|
|
if (settings.isTipEnabled(TIP_REORDER)) {
|
|
|
|
result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip)
|
|
|
|
result += SourceConfigItem.Tip(TIP_REORDER, R.drawable.ic_tap_reorder, R.string.sources_reorder_tip)
|
|
|
|
@ -165,10 +163,11 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (enabledSources?.size != sources.size) {
|
|
|
|
if (enabledSources.size != allSources.size) {
|
|
|
|
result += SourceConfigItem.Header(R.string.available_sources)
|
|
|
|
result += SourceConfigItem.Header(R.string.available_sources)
|
|
|
|
|
|
|
|
val comparator = compareBy<MangaSource, String>(AlphanumComparator()) { it.name }
|
|
|
|
for ((key, list) in map) {
|
|
|
|
for ((key, list) in map) {
|
|
|
|
list.sortBy { it.ordinal }
|
|
|
|
list.sortWith(comparator)
|
|
|
|
val isExpanded = key in expandedGroups
|
|
|
|
val isExpanded = key in expandedGroups
|
|
|
|
result += SourceConfigItem.LocaleGroup(
|
|
|
|
result += SourceConfigItem.LocaleGroup(
|
|
|
|
localeId = key,
|
|
|
|
localeId = key,
|
|
|
|
@ -195,12 +194,12 @@ class SourcesListViewModel @Inject constructor(
|
|
|
|
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
|
|
|
return locale.getDisplayLanguage(locale).toTitleCase(locale)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private inline fun launchAtomicJob(
|
|
|
|
private fun launchAtomicJob(
|
|
|
|
context: CoroutineContext = EmptyCoroutineContext,
|
|
|
|
context: CoroutineContext = EmptyCoroutineContext,
|
|
|
|
crossinline block: suspend CoroutineScope.() -> Unit
|
|
|
|
block: suspend CoroutineScope.() -> Unit
|
|
|
|
) = launchJob(context) {
|
|
|
|
) = launchJob(start = CoroutineStart.ATOMIC) {
|
|
|
|
mutex.withLock {
|
|
|
|
mutex.withLock {
|
|
|
|
block()
|
|
|
|
withContext(context, block)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|