mirror of
https://github.com/Suwayomi/TachideskJUI.git
synced 2026-01-30 07:24:08 +01:00
Backend sorting code
This commit is contained in:
@@ -9,6 +9,7 @@ package ca.gosyer.data.library
|
||||
import ca.gosyer.core.prefs.Preference
|
||||
import ca.gosyer.core.prefs.PreferenceStore
|
||||
import ca.gosyer.data.library.model.DisplayMode
|
||||
import ca.gosyer.data.library.model.Sort
|
||||
|
||||
class LibraryPreferences(private val preferenceStore: PreferenceStore) {
|
||||
|
||||
@@ -16,6 +17,14 @@ class LibraryPreferences(private val preferenceStore: PreferenceStore) {
|
||||
return preferenceStore.getJsonObject("display_mode", DisplayMode.CompactGrid, DisplayMode.serializer())
|
||||
}
|
||||
|
||||
fun sortMode(): Preference<Sort> {
|
||||
return preferenceStore.getJsonObject("sort_mode", Sort.ALPHABETICAL, Sort.serializer())
|
||||
}
|
||||
|
||||
fun sortAscending(): Preference<Boolean> {
|
||||
return preferenceStore.getBoolean("sort_ascending", true)
|
||||
}
|
||||
|
||||
fun gridColumns(): Preference<Int> {
|
||||
return preferenceStore.getInt("grid_columns", 0)
|
||||
}
|
||||
|
||||
21
data/src/jvmMain/kotlin/ca/gosyer/data/library/model/Sort.kt
Normal file
21
data/src/jvmMain/kotlin/ca/gosyer/data/library/model/Sort.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ca.gosyer.data.library.model
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
enum class Sort {
|
||||
ALPHABETICAL,
|
||||
// LAST_READ,
|
||||
// LAST_CHECKED,
|
||||
UNREAD,
|
||||
// TOTAL_CHAPTERS,
|
||||
// LATEST_CHAPTER,
|
||||
// DATE_FETCHED,
|
||||
DATE_ADDED;
|
||||
}
|
||||
@@ -9,6 +9,7 @@ package ca.gosyer.ui.library
|
||||
import ca.gosyer.core.lang.withDefaultContext
|
||||
import ca.gosyer.core.logging.CKLogger
|
||||
import ca.gosyer.data.library.LibraryPreferences
|
||||
import ca.gosyer.data.library.model.Sort
|
||||
import ca.gosyer.data.models.Category
|
||||
import ca.gosyer.data.models.Manga
|
||||
import ca.gosyer.data.server.interactions.CategoryInteractionHandler
|
||||
@@ -19,55 +20,36 @@ import ca.gosyer.uicore.vm.ViewModel
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.buffer
|
||||
import kotlinx.coroutines.flow.cancellable
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.flow.single
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import me.tatarka.inject.annotations.Inject
|
||||
import java.text.Collator
|
||||
import java.util.Collections
|
||||
import java.util.Locale
|
||||
|
||||
private typealias CategoryItems = Pair<MutableStateFlow<List<Manga>>, MutableStateFlow<List<Manga>>>
|
||||
private typealias CategoryItems = Pair<StateFlow<List<Manga>>, MutableStateFlow<List<Manga>>>
|
||||
private typealias LibraryMap = MutableMap<Long, CategoryItems>
|
||||
private data class Library(val categories: MutableStateFlow<List<Category>>, val mangaMap: LibraryMap)
|
||||
|
||||
private fun LibraryMap.getManga(id: Long) =
|
||||
getOrPut(id) { MutableStateFlow(emptyList<Manga>()) to MutableStateFlow(emptyList()) }
|
||||
private suspend fun LibraryMap.setManga(query: String?, id: Long, manga: List<Manga>) {
|
||||
getManga(id).let { (items, unfilteredItems) ->
|
||||
items.value = filterManga(query, manga)
|
||||
unfilteredItems.value = manga
|
||||
private fun LibraryMap.getManga(id: Long, getItemsFlow: (StateFlow<List<Manga>>) -> StateFlow<List<Manga>>) =
|
||||
getOrPut(id) {
|
||||
val unfilteredItems = MutableStateFlow<List<Manga>>(emptyList())
|
||||
getItemsFlow(unfilteredItems) to unfilteredItems
|
||||
}
|
||||
}
|
||||
private suspend fun LibraryMap.updateMangaFilter(query: String?) {
|
||||
values.forEach { (items, unfilteredItems) ->
|
||||
items.value = filterManga(query, unfilteredItems.value)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun filterManga(query: String?, mangaList: List<Manga>): List<Manga> {
|
||||
if (query.isNullOrBlank()) return mangaList
|
||||
val queries = query.split(" ")
|
||||
return mangaList.asFlow()
|
||||
.filter { manga ->
|
||||
queries.all { query ->
|
||||
manga.title.contains(query, true) ||
|
||||
manga.author.orEmpty().contains(query, true) ||
|
||||
manga.artist.orEmpty().contains(query, true) ||
|
||||
manga.genre.any { it.contains(query, true) } ||
|
||||
manga.description.orEmpty().contains(query, true) ||
|
||||
manga.status.name.contains(query, true)
|
||||
}
|
||||
}
|
||||
.cancellable()
|
||||
.buffer()
|
||||
.toList()
|
||||
private fun LibraryMap.setManga(id: Long, manga: List<Manga>, getItemsFlow: (StateFlow<List<Manga>>) -> StateFlow<List<Manga>>) {
|
||||
getManga(id, getItemsFlow).second.value = manga
|
||||
}
|
||||
|
||||
class LibraryScreenViewModel @Inject constructor(
|
||||
@@ -87,6 +69,9 @@ class LibraryScreenViewModel @Inject constructor(
|
||||
val gridColumns = libraryPreferences.gridColumns().stateIn(scope)
|
||||
val gridSize = libraryPreferences.gridSize().stateIn(scope)
|
||||
|
||||
private val sortMode = libraryPreferences.sortMode().stateIn(scope)
|
||||
private val sortAscending = libraryPreferences.sortAscending().stateIn(scope)
|
||||
|
||||
private val _isLoading = MutableStateFlow(true)
|
||||
val isLoading = _isLoading.asStateFlow()
|
||||
|
||||
@@ -96,12 +81,12 @@ class LibraryScreenViewModel @Inject constructor(
|
||||
private val _query = MutableStateFlow("")
|
||||
val query = _query.asStateFlow()
|
||||
|
||||
private val comparator = combine(sortMode, sortAscending) { sortMode, sortAscending ->
|
||||
getComparator(sortMode, sortAscending)
|
||||
}.stateIn(scope, SharingStarted.Eagerly, compareBy { it.title })
|
||||
|
||||
init {
|
||||
getLibrary()
|
||||
|
||||
_query.mapLatest {
|
||||
library.mangaMap.updateMangaFilter(it)
|
||||
}.launchIn(scope)
|
||||
}
|
||||
|
||||
private fun getLibrary() {
|
||||
@@ -127,8 +112,64 @@ class LibraryScreenViewModel @Inject constructor(
|
||||
_selectedCategoryIndex.value = page
|
||||
}
|
||||
|
||||
private fun getComparator(sortMode: Sort, ascending: Boolean): Comparator<Manga> {
|
||||
val sortFn = when (sortMode) {
|
||||
Sort.ALPHABETICAL -> {
|
||||
val locale = Locale.getDefault()
|
||||
val collator = Collator.getInstance(locale).apply {
|
||||
strength = Collator.PRIMARY
|
||||
};
|
||||
|
||||
{ a: Manga, b: Manga ->
|
||||
collator.compare(a.title.lowercase(locale), b.title.lowercase(locale))
|
||||
}
|
||||
}
|
||||
Sort.UNREAD -> {
|
||||
{ a: Manga, b: Manga ->
|
||||
(a.unreadCount ?: 0).compareTo(b.unreadCount ?: 0)
|
||||
}
|
||||
}
|
||||
Sort.DATE_ADDED -> {
|
||||
{ a: Manga, b: Manga ->
|
||||
a.inLibraryAt.compareTo(b.inLibraryAt)
|
||||
}
|
||||
}
|
||||
}
|
||||
return if (ascending) {
|
||||
Comparator(sortFn)
|
||||
} else {
|
||||
Collections.reverseOrder(sortFn)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun filterManga(query: String, mangaList: List<Manga>): List<Manga> {
|
||||
if (query.isBlank()) return mangaList
|
||||
val queries = query.split(" ")
|
||||
return mangaList.asFlow()
|
||||
.filter { manga ->
|
||||
queries.all { query ->
|
||||
manga.title.contains(query, true) ||
|
||||
manga.author.orEmpty().contains(query, true) ||
|
||||
manga.artist.orEmpty().contains(query, true) ||
|
||||
manga.genre.any { it.contains(query, true) } ||
|
||||
manga.description.orEmpty().contains(query, true) ||
|
||||
manga.status.name.contains(query, true)
|
||||
}
|
||||
}
|
||||
.cancellable()
|
||||
.buffer()
|
||||
.toList()
|
||||
}
|
||||
|
||||
private fun getMangaItemsFlow(unfilteredItemsFlow: StateFlow<List<Manga>>): StateFlow<List<Manga>> {
|
||||
return combine(unfilteredItemsFlow, query, comparator) { unfilteredItems, query, comparator ->
|
||||
filterManga(query, unfilteredItems)
|
||||
.sortedWith(comparator)
|
||||
}.stateIn(scope, SharingStarted.Eagerly, emptyList())
|
||||
}
|
||||
|
||||
fun getLibraryForCategoryId(id: Long): StateFlow<List<Manga>> {
|
||||
return library.mangaMap.getManga(id).first.asStateFlow()
|
||||
return library.mangaMap.getManga(id, ::getMangaItemsFlow).first
|
||||
}
|
||||
|
||||
private suspend fun updateCategories(categories: List<Category>) {
|
||||
@@ -136,14 +177,14 @@ class LibraryScreenViewModel @Inject constructor(
|
||||
categories.map { category ->
|
||||
async {
|
||||
library.mangaMap.setManga(
|
||||
query.value,
|
||||
category.id,
|
||||
categoryHandler.getMangaFromCategory(category)
|
||||
id = category.id,
|
||||
manga = categoryHandler.getMangaFromCategory(category)
|
||||
.catch {
|
||||
info { "Error getting manga for category $category" }
|
||||
emit(emptyList())
|
||||
}
|
||||
.single()
|
||||
.single(),
|
||||
getItemsFlow = ::getMangaItemsFlow
|
||||
)
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
Reference in New Issue
Block a user