Sort global search source results properly

Fixes #8741
This commit is contained in:
arkon 2022-12-14 23:20:51 -05:00
parent 90db3acefd
commit bc6a12a4f7
5 changed files with 51 additions and 55 deletions

View File

@ -19,8 +19,8 @@ import eu.kanade.presentation.components.Scaffold
import eu.kanade.presentation.util.padding import eu.kanade.presentation.util.padding
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
@ -60,7 +60,7 @@ fun GlobalSearchScreen(
@Composable @Composable
fun GlobalSearchContent( fun GlobalSearchContent(
items: Map<CatalogueSource, GlobalSearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
@ -78,13 +78,13 @@ fun GlobalSearchContent(
onClick = { onClickSource(source) }, onClick = { onClickSource(source) },
) { ) {
when (result) { when (result) {
is GlobalSearchItemResult.Error -> { is SearchItemResult.Error -> {
GlobalSearchErrorResultItem(message = result.throwable.message) GlobalSearchErrorResultItem(message = result.throwable.message)
} }
GlobalSearchItemResult.Loading -> { SearchItemResult.Loading -> {
GlobalSearchLoadingResultItem() GlobalSearchLoadingResultItem()
} }
is GlobalSearchItemResult.Success -> { is SearchItemResult.Success -> {
if (result.isEmpty) { if (result.isEmpty) {
Text( Text(
text = stringResource(R.string.no_results_found), text = stringResource(R.string.no_results_found),

View File

@ -14,7 +14,7 @@ import eu.kanade.presentation.components.LazyColumn
import eu.kanade.presentation.components.Scaffold import eu.kanade.presentation.components.Scaffold
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState import eu.kanade.tachiyomi.ui.browse.migration.search.MigrateSearchState
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.util.system.LocaleHelper import eu.kanade.tachiyomi.util.system.LocaleHelper
@Composable @Composable
@ -56,7 +56,7 @@ fun MigrateSearchScreen(
@Composable @Composable
fun MigrateSearchContent( fun MigrateSearchContent(
sourceId: Long, sourceId: Long,
items: Map<CatalogueSource, GlobalSearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (CatalogueSource, Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
@ -74,13 +74,13 @@ fun MigrateSearchContent(
onClick = { onClickSource(source) }, onClick = { onClickSource(source) },
) { ) {
when (result) { when (result) {
is GlobalSearchItemResult.Error -> { is SearchItemResult.Error -> {
GlobalSearchErrorResultItem(message = result.throwable.message) GlobalSearchErrorResultItem(message = result.throwable.message)
} }
GlobalSearchItemResult.Loading -> { SearchItemResult.Loading -> {
GlobalSearchLoadingResultItem() GlobalSearchLoadingResultItem()
} }
is GlobalSearchItemResult.Success -> { is SearchItemResult.Success -> {
if (result.isEmpty) { if (result.isEmpty) {
GlobalSearchEmptyResultItem() GlobalSearchEmptyResultItem()
return@GlobalSearchResultItem return@GlobalSearchResultItem

View File

@ -8,7 +8,7 @@ import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -58,13 +58,13 @@ class MigrateSearchScreenModel(
} }
} }
override fun updateItems(items: Map<CatalogueSource, GlobalSearchItemResult>) { override fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
mutableState.update { mutableState.update {
it.copy(items = items) it.copy(items = items)
} }
} }
override fun getItems(): Map<CatalogueSource, GlobalSearchItemResult> { override fun getItems(): Map<CatalogueSource, SearchItemResult> {
return mutableState.value.items return mutableState.value.items
} }
@ -83,11 +83,11 @@ sealed class MigrateSearchDialog {
data class MigrateSearchState( data class MigrateSearchState(
val manga: Manga? = null, val manga: Manga? = null,
val searchQuery: String? = null, val searchQuery: String? = null,
val items: Map<CatalogueSource, GlobalSearchItemResult> = emptyMap(), val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
val dialog: MigrateSearchDialog? = null, val dialog: MigrateSearchDialog? = null,
) { ) {
val progress: Int = items.count { it.value !is GlobalSearchItemResult.Loading } val progress: Int = items.count { it.value !is SearchItemResult.Loading }
val total: Int = items.size val total: Int = items.size
} }

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import eu.kanade.domain.base.BasePreferences import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
@ -45,39 +44,24 @@ class GlobalSearchScreenModel(
} }
} }
override fun updateItems(items: Map<CatalogueSource, GlobalSearchItemResult>) { override fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
mutableState.update { mutableState.update {
it.copy(items = items) it.copy(items = items)
} }
} }
override fun getItems(): Map<CatalogueSource, GlobalSearchItemResult> { override fun getItems(): Map<CatalogueSource, SearchItemResult> {
return mutableState.value.items return mutableState.value.items
} }
} }
sealed class GlobalSearchItemResult {
object Loading : GlobalSearchItemResult()
data class Error(
val throwable: Throwable,
) : GlobalSearchItemResult()
data class Success(
val result: List<Manga>,
) : GlobalSearchItemResult() {
val isEmpty: Boolean
get() = result.isEmpty()
}
}
@Immutable @Immutable
data class GlobalSearchState( data class GlobalSearchState(
val searchQuery: String? = null, val searchQuery: String? = null,
val items: Map<CatalogueSource, GlobalSearchItemResult> = emptyMap(), val items: Map<CatalogueSource, SearchItemResult> = emptyMap(),
) { ) {
val progress: Int = items.count { it.value !is GlobalSearchItemResult.Loading } val progress: Int = items.count { it.value !is SearchItemResult.Loading }
val total: Int = items.size val total: Int = items.size
} }

View File

@ -42,6 +42,15 @@ abstract class SearchScreenModel<T>(
protected lateinit var extensionFilter: String protected lateinit var extensionFilter: String
private val sources by lazy { getSelectedSources() } private val sources by lazy { getSelectedSources() }
private val pinnedSources by lazy { sourcePreferences.pinnedSources().get() }
private val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
compareBy<CatalogueSource>(
{ (map[it] as? SearchItemResult.Success)?.isEmpty ?: true },
{ "${it.id}" !in pinnedSources },
{ "${it.name.lowercase()} (${it.lang})" },
)
}
@Composable @Composable
fun getManga(source: CatalogueSource, initialManga: Manga): State<Manga> { fun getManga(source: CatalogueSource, initialManga: Manga): State<Manga> {
@ -107,11 +116,11 @@ abstract class SearchScreenModel<T>(
abstract fun updateSearchQuery(query: String?) abstract fun updateSearchQuery(query: String?)
abstract fun updateItems(items: Map<CatalogueSource, GlobalSearchItemResult>) abstract fun updateItems(items: Map<CatalogueSource, SearchItemResult>)
abstract fun getItems(): Map<CatalogueSource, GlobalSearchItemResult> abstract fun getItems(): Map<CatalogueSource, SearchItemResult>
fun getAndUpdateItems(function: (Map<CatalogueSource, GlobalSearchItemResult>) -> Map<CatalogueSource, GlobalSearchItemResult>) { fun getAndUpdateItems(function: (Map<CatalogueSource, SearchItemResult>) -> Map<CatalogueSource, SearchItemResult>) {
updateItems(function(getItems())) updateItems(function(getItems()))
} }
@ -120,19 +129,9 @@ abstract class SearchScreenModel<T>(
this.query = query this.query = query
val initialItems = getSelectedSources().associateWith { GlobalSearchItemResult.Loading } val initialItems = getSelectedSources().associateWith { SearchItemResult.Loading }
updateItems(initialItems) updateItems(initialItems)
val pinnedSources = sourcePreferences.pinnedSources().get()
val comparator = { mutableMap: MutableMap<CatalogueSource, GlobalSearchItemResult> ->
compareBy<CatalogueSource>(
{ mutableMap[it] is GlobalSearchItemResult.Success },
{ "${it.id}" in pinnedSources },
{ "${it.name.lowercase()} (${it.lang})" },
)
}
coroutineScope.launch { coroutineScope.launch {
sources.forEach { source -> sources.forEach { source ->
val page = try { val page = try {
@ -142,9 +141,8 @@ abstract class SearchScreenModel<T>(
} catch (e: Exception) { } catch (e: Exception) {
getAndUpdateItems { items -> getAndUpdateItems { items ->
val mutableMap = items.toMutableMap() val mutableMap = items.toMutableMap()
mutableMap[source] = GlobalSearchItemResult.Error(throwable = e) mutableMap[source] = SearchItemResult.Error(throwable = e)
mutableMap.toSortedMap(comparator(mutableMap)) mutableMap.toSortedMap(sortComparator(mutableMap))
mutableMap.toMap()
} }
return@forEach return@forEach
} }
@ -157,11 +155,25 @@ abstract class SearchScreenModel<T>(
getAndUpdateItems { items -> getAndUpdateItems { items ->
val mutableMap = items.toMutableMap() val mutableMap = items.toMutableMap()
mutableMap[source] = GlobalSearchItemResult.Success(titles) mutableMap[source] = SearchItemResult.Success(titles)
mutableMap.toSortedMap(comparator(mutableMap)) mutableMap.toSortedMap(sortComparator(mutableMap))
mutableMap.toMap()
} }
} }
} }
} }
} }
sealed class SearchItemResult {
object Loading : SearchItemResult()
data class Error(
val throwable: Throwable,
) : SearchItemResult()
data class Success(
val result: List<Manga>,
) : SearchItemResult() {
val isEmpty: Boolean
get() = result.isEmpty()
}
}