Tweak library selection (#8513)
* Tweak library selection Also use the new `fast*` extensions functions in other places of library presenter * Cleanup (cherry picked from commit 3f34fa1f588d3b9a0562415d907e4ca6e01f7715) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryItem.kt # app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt
This commit is contained in:
parent
d12e0954b6
commit
2321e6b0d8
@ -1,6 +1,9 @@
|
|||||||
package eu.kanade.core.util
|
package eu.kanade.core.util
|
||||||
|
|
||||||
|
import androidx.compose.ui.util.fastForEach
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
|
import kotlin.contracts.ExperimentalContracts
|
||||||
|
import kotlin.contracts.contract
|
||||||
|
|
||||||
fun <T : R, R : Any> List<T>.insertSeparators(
|
fun <T : R, R : Any> List<T>.insertSeparators(
|
||||||
generator: (T?, T?) -> R?,
|
generator: (T?, T?) -> R?,
|
||||||
@ -33,3 +36,79 @@ fun <E> HashSet<E>.addOrRemove(value: E, shouldAdd: Boolean) {
|
|||||||
remove(value)
|
remove(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only elements matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@Suppress("BanInlineOptIn")
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilter(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing all elements not matching the given [predicate].
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@Suppress("BanInlineOptIn")
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastFilterNot(predicate: (T) -> Boolean): List<T> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val destination = ArrayList<T>()
|
||||||
|
fastForEach { if (!predicate(it)) destination.add(it) }
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list containing only the non-null results of applying the
|
||||||
|
* given [transform] function to each element in the original collection.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@Suppress("BanInlineOptIn")
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T, R> List<T>.fastMapNotNull(transform: (T) -> R?): List<R> {
|
||||||
|
contract { callsInPlace(transform) }
|
||||||
|
val destination = ArrayList<R>()
|
||||||
|
fastForEach { element ->
|
||||||
|
transform(element)?.let { destination.add(it) }
|
||||||
|
}
|
||||||
|
return destination
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Splits the original collection into pair of lists,
|
||||||
|
* where *first* list contains elements for which [predicate] yielded `true`,
|
||||||
|
* while *second* list contains elements for which [predicate] yielded `false`.
|
||||||
|
*
|
||||||
|
* **Do not use for collections that come from public APIs**, since they may not support random
|
||||||
|
* access in an efficient way, and this method may actually be a lot slower. Only use for
|
||||||
|
* collections that are created by code we control and are known to support random access.
|
||||||
|
*/
|
||||||
|
@Suppress("BanInlineOptIn")
|
||||||
|
@OptIn(ExperimentalContracts::class)
|
||||||
|
inline fun <T> List<T>.fastPartition(predicate: (T) -> Boolean): Pair<List<T>, List<T>> {
|
||||||
|
contract { callsInPlace(predicate) }
|
||||||
|
val first = ArrayList<T>()
|
||||||
|
val second = ArrayList<T>()
|
||||||
|
fastForEach {
|
||||||
|
if (predicate(it)) {
|
||||||
|
first.add(it)
|
||||||
|
} else {
|
||||||
|
second.add(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Pair(first, second)
|
||||||
|
}
|
||||||
|
@ -18,12 +18,12 @@ class LibraryItem(
|
|||||||
var sourceLanguage = ""
|
var sourceLanguage = ""
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters a manga depending on a query.
|
* Checks if a query matches the manga
|
||||||
*
|
*
|
||||||
* @param constraint the query to apply.
|
* @param constraint the query to check.
|
||||||
* @return true if the manga should be included, false otherwise.
|
* @return true if the manga matches the query, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun filter(constraint: String): Boolean {
|
fun matches(constraint: String): Boolean {
|
||||||
val sourceName by lazy { sourceManager.getOrStub(libraryManga.manga.source).getNameForMangaInfo(null) }
|
val sourceName by lazy { sourceManager.getOrStub(libraryManga.manga.source).getNameForMangaInfo(null) }
|
||||||
val genres by lazy { libraryManga.manga.genre }
|
val genres by lazy { libraryManga.manga.genre }
|
||||||
return libraryManga.manga.title.contains(constraint, true) ||
|
return libraryManga.manga.title.contains(constraint, true) ||
|
||||||
|
@ -11,10 +11,15 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.util.fastAll
|
||||||
import androidx.compose.ui.util.fastAny
|
import androidx.compose.ui.util.fastAny
|
||||||
import androidx.compose.ui.util.fastMap
|
import androidx.compose.ui.util.fastMap
|
||||||
import eu.kanade.core.prefs.CheckboxState
|
import eu.kanade.core.prefs.CheckboxState
|
||||||
import eu.kanade.core.prefs.PreferenceMutableState
|
import eu.kanade.core.prefs.PreferenceMutableState
|
||||||
|
import eu.kanade.core.util.fastFilter
|
||||||
|
import eu.kanade.core.util.fastFilterNot
|
||||||
|
import eu.kanade.core.util.fastMapNotNull
|
||||||
|
import eu.kanade.core.util.fastPartition
|
||||||
import eu.kanade.domain.UnsortedPreferences
|
import eu.kanade.domain.UnsortedPreferences
|
||||||
import eu.kanade.domain.base.BasePreferences
|
import eu.kanade.domain.base.BasePreferences
|
||||||
import eu.kanade.domain.category.interactor.GetCategories
|
import eu.kanade.domain.category.interactor.GetCategories
|
||||||
@ -245,7 +250,7 @@ class LibraryPresenter(
|
|||||||
val filterBookmarked = libraryPreferences.filterBookmarked().get()
|
val filterBookmarked = libraryPreferences.filterBookmarked().get()
|
||||||
val filterCompleted = libraryPreferences.filterCompleted().get()
|
val filterCompleted = libraryPreferences.filterCompleted().get()
|
||||||
|
|
||||||
val loggedInTrackServices = trackManager.services.filter { trackService -> trackService.isLogged }
|
val loggedInTrackServices = trackManager.services.fastFilter { trackService -> trackService.isLogged }
|
||||||
.associate { trackService ->
|
.associate { trackService ->
|
||||||
trackService.id to libraryPreferences.filterTracking(trackService.id.toInt()).get()
|
trackService.id to libraryPreferences.filterTracking(trackService.id.toInt()).get()
|
||||||
}
|
}
|
||||||
@ -324,8 +329,8 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
val mangaTracks = trackMap[item.libraryManga.id].orEmpty()
|
val mangaTracks = trackMap[item.libraryManga.id].orEmpty()
|
||||||
|
|
||||||
val exclude = mangaTracks.filter { it in excludedTracks }
|
val exclude = mangaTracks.fastFilter { it in excludedTracks }
|
||||||
val include = mangaTracks.filter { it in includedTracks }
|
val include = mangaTracks.fastFilter { it in includedTracks }
|
||||||
|
|
||||||
// TODO: Simplify the filter logic
|
// TODO: Simplify the filter logic
|
||||||
if (includedTracks.isNotEmpty() && excludedTracks.isNotEmpty()) {
|
if (includedTracks.isNotEmpty() && excludedTracks.isNotEmpty()) {
|
||||||
@ -366,7 +371,7 @@ class LibraryPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.mapValues { entry -> entry.value.filter(filterFn) }
|
return this.mapValues { entry -> entry.value.fastFilter(filterFn) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -514,7 +519,7 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
return combine(getCategories.subscribe(), libraryMangasFlow) { categories, libraryManga ->
|
return combine(getCategories.subscribe(), libraryMangasFlow) { categories, libraryManga ->
|
||||||
val displayCategories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
|
val displayCategories = if (libraryManga.isNotEmpty() && libraryManga.containsKey(0).not()) {
|
||||||
categories.filterNot { it.isSystemCategory }
|
categories.fastFilterNot { it.isSystemCategory }
|
||||||
} else {
|
} else {
|
||||||
categories
|
categories
|
||||||
}
|
}
|
||||||
@ -613,8 +618,8 @@ class LibraryPresenter(
|
|||||||
.groupBy { it.mangaId }
|
.groupBy { it.mangaId }
|
||||||
.forEach ab@{ (mangaId, chapters) ->
|
.forEach ab@{ (mangaId, chapters) ->
|
||||||
val mergedManga = mergedMangas[mangaId] ?: return@ab
|
val mergedManga = mergedMangas[mangaId] ?: return@ab
|
||||||
val downloadChapters = chapters.filterNot { chapter ->
|
val downloadChapters = chapters.fastFilterNot { chapter ->
|
||||||
downloadManager.queue.any { chapter.id == it.chapter.id } ||
|
downloadManager.queue.fastAny { chapter.id == it.chapter.id } ||
|
||||||
downloadManager.isChapterDownloaded(
|
downloadManager.isChapterDownloaded(
|
||||||
chapter.name,
|
chapter.name,
|
||||||
chapter.scanlator,
|
chapter.scanlator,
|
||||||
@ -631,8 +636,8 @@ class LibraryPresenter(
|
|||||||
|
|
||||||
// SY <--
|
// SY <--
|
||||||
val chapters = getNextChapters.await(manga.id)
|
val chapters = getNextChapters.await(manga.id)
|
||||||
.filterNot { chapter ->
|
.fastFilterNot { chapter ->
|
||||||
downloadManager.queue.any { chapter.id == it.chapter.id } ||
|
downloadManager.queue.fastAny { chapter.id == it.chapter.id } ||
|
||||||
downloadManager.isChapterDownloaded(
|
downloadManager.isChapterDownloaded(
|
||||||
chapter.name,
|
chapter.name,
|
||||||
chapter.scanlator,
|
chapter.scanlator,
|
||||||
@ -839,21 +844,23 @@ class LibraryPresenter(
|
|||||||
// SY -->
|
// SY -->
|
||||||
@Composable
|
@Composable
|
||||||
fun getMangaForCategory(page: Int): List<LibraryItem> {
|
fun getMangaForCategory(page: Int): List<LibraryItem> {
|
||||||
val unfiltered = remember(categories, loadedManga, page) {
|
val categoryId = remember(categories, page) {
|
||||||
val categoryId = categories.getOrNull(page)?.id ?: -1
|
categories.getOrNull(page)?.id ?: -1
|
||||||
|
}
|
||||||
|
val unfiltered = remember(loadedManga, categoryId) {
|
||||||
loadedManga[categoryId] ?: emptyList()
|
loadedManga[categoryId] ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
val items = produceState(initialValue = unfiltered, unfiltered, searchQuery) {
|
val items = produceState(initialValue = unfiltered, unfiltered, searchQuery, categoryId) {
|
||||||
value = withIOContext {
|
value = withIOContext {
|
||||||
filterLibrary(unfiltered, searchQuery)
|
filterLibrary(unfiltered, searchQuery, categoryId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return items.value
|
return items.value
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun filterLibrary(unfiltered: List<LibraryItem>, query: String?): List<LibraryItem> {
|
suspend fun filterLibrary(unfiltered: List<LibraryItem>, query: String?, categoryId: Long): List<LibraryItem> {
|
||||||
return if (unfiltered.isNotEmpty() && !query.isNullOrBlank()) {
|
return if (unfiltered.isNotEmpty() && !query.isNullOrBlank()) {
|
||||||
// Prepare filter object
|
// Prepare filter object
|
||||||
val parsedQuery = searchEngine.parseQuery(query)
|
val parsedQuery = searchEngine.parseQuery(query)
|
||||||
@ -865,7 +872,7 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
val sources = unfiltered
|
val sources = unfiltered
|
||||||
.distinctBy { it.libraryManga.manga.source }
|
.distinctBy { it.libraryManga.manga.source }
|
||||||
.mapNotNull { sourceManager.get(it.libraryManga.manga.source) }
|
.fastMapNotNull { sourceManager.get(it.libraryManga.manga.source) }
|
||||||
.associateBy { it.id }
|
.associateBy { it.id }
|
||||||
unfiltered.asFlow().cancellable().filter { item ->
|
unfiltered.asFlow().cancellable().filter { item ->
|
||||||
val mangaId = item.libraryManga.manga.id
|
val mangaId = item.libraryManga.manga.id
|
||||||
@ -900,8 +907,11 @@ class LibraryPresenter(
|
|||||||
source = sources[sourceId],
|
source = sources[sourceId],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.toList()
|
}.toList().also { queriedMangaMap[categoryId] = it }
|
||||||
} else {
|
} else {
|
||||||
|
if (query.isNullOrBlank()) {
|
||||||
|
queriedMangaMap.clear()
|
||||||
|
}
|
||||||
unfiltered
|
unfiltered
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -930,12 +940,12 @@ class LibraryPresenter(
|
|||||||
(source?.name?.contains(query, true) == true) ||
|
(source?.name?.contains(query, true) == true) ||
|
||||||
(sourceIdString != null && sourceIdString == query) ||
|
(sourceIdString != null && sourceIdString == query) ||
|
||||||
(loggedServices.isNotEmpty() && tracks != null && filterTracks(query, tracks)) ||
|
(loggedServices.isNotEmpty() && tracks != null && filterTracks(query, tracks)) ||
|
||||||
(genre.any { it.contains(query, true) }) ||
|
(genre.fastAny { it.contains(query, true) }) ||
|
||||||
(searchTags?.any { it.name.contains(query, true) } == true) ||
|
(searchTags?.fastAny { it.name.contains(query, true) } == true) ||
|
||||||
(searchTitles?.any { it.title.contains(query, true) } == true)
|
(searchTitles?.fastAny { it.title.contains(query, true) } == true)
|
||||||
}
|
}
|
||||||
is Namespace -> {
|
is Namespace -> {
|
||||||
searchTags != null && searchTags.any {
|
searchTags != null && searchTags.fastAny {
|
||||||
val tag = queryComponent.tag
|
val tag = queryComponent.tag
|
||||||
(it.namespace.equals(queryComponent.namespace, true) && tag?.run { it.name.contains(tag.asQuery(), true) } == true) ||
|
(it.namespace.equals(queryComponent.namespace, true) && tag?.run { it.name.contains(tag.asQuery(), true) } == true) ||
|
||||||
(tag == null && it.namespace.equals(queryComponent.namespace, true))
|
(tag == null && it.namespace.equals(queryComponent.namespace, true))
|
||||||
@ -954,14 +964,14 @@ class LibraryPresenter(
|
|||||||
(source?.name?.contains(query, true) != true) &&
|
(source?.name?.contains(query, true) != true) &&
|
||||||
(sourceIdString != null && sourceIdString != query) &&
|
(sourceIdString != null && sourceIdString != query) &&
|
||||||
(loggedServices.isEmpty() || loggedServices.isNotEmpty() && tracks == null || tracks != null && !filterTracks(query, tracks)) &&
|
(loggedServices.isEmpty() || loggedServices.isNotEmpty() && tracks == null || tracks != null && !filterTracks(query, tracks)) &&
|
||||||
(genre.none { it.contains(query, true) }) &&
|
(!genre.fastAny { it.contains(query, true) }) &&
|
||||||
(searchTags?.any { it.name.contains(query, true) } != true) &&
|
(searchTags?.fastAny { it.name.contains(query, true) } != true) &&
|
||||||
(searchTitles?.any { it.title.contains(query, true) } != true)
|
(searchTitles?.fastAny { it.title.contains(query, true) } != true)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is Namespace -> {
|
is Namespace -> {
|
||||||
val searchedTag = queryComponent.tag?.asQuery()
|
val searchedTag = queryComponent.tag?.asQuery()
|
||||||
searchTags == null || (queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) || searchTags.all { mangaTag ->
|
searchTags == null || (queryComponent.namespace.isBlank() && searchedTag.isNullOrBlank()) || searchTags.fastAll { mangaTag ->
|
||||||
if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) {
|
if (queryComponent.namespace.isBlank() && !searchedTag.isNullOrBlank()) {
|
||||||
!mangaTag.name.contains(searchedTag, true)
|
!mangaTag.name.contains(searchedTag, true)
|
||||||
} else if (searchedTag.isNullOrBlank()) {
|
} else if (searchedTag.isNullOrBlank()) {
|
||||||
@ -980,7 +990,7 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun filterTracks(constraint: String, tracks: List<Track>): Boolean {
|
private fun filterTracks(constraint: String, tracks: List<Track>): Boolean {
|
||||||
return tracks.any {
|
return tracks.fastAny {
|
||||||
val trackService = trackManager.getService(it.syncId)
|
val trackService = trackManager.getService(it.syncId)
|
||||||
if (trackService != null) {
|
if (trackService != null) {
|
||||||
val status = trackService.getStatus(it.status.toInt())
|
val status = trackService.getStatus(it.status.toInt())
|
||||||
@ -1023,6 +1033,20 @@ class LibraryPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Map is cleared out via [getMangaForCategory] when [searchQuery] is null or blank
|
||||||
|
*/
|
||||||
|
private val queriedMangaMap: MutableMap<Long, List<LibraryItem>> = mutableMapOf()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used by select all, inverse and range selection.
|
||||||
|
*
|
||||||
|
* If current query is empty then we get manga list from [loadedManga] otherwise from [queriedMangaMap]
|
||||||
|
*/
|
||||||
|
private fun getMangaForCategoryWithQuery(categoryId: Long, query: String?): List<LibraryItem> {
|
||||||
|
return if (query.isNullOrBlank()) loadedManga[categoryId].orEmpty() else queriedMangaMap[categoryId].orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Selects all mangas between and including the given manga and the last pressed manga from the
|
* Selects all mangas between and including the given manga and the last pressed manga from the
|
||||||
* same category as the given manga
|
* same category as the given manga
|
||||||
@ -1035,16 +1059,21 @@ class LibraryPresenter(
|
|||||||
add(manga)
|
add(manga)
|
||||||
return@apply
|
return@apply
|
||||||
}
|
}
|
||||||
val items = loadedManga[manga.category].orEmpty().run {
|
val items = getMangaForCategoryWithQuery(manga.category, searchQuery)
|
||||||
filterLibrary(this, searchQuery)
|
.fastMap { it.libraryManga }
|
||||||
}.fastMap { it.libraryManga }
|
|
||||||
val lastMangaIndex = items.indexOf(lastSelected)
|
val lastMangaIndex = items.indexOf(lastSelected)
|
||||||
val curMangaIndex = items.indexOf(manga)
|
val curMangaIndex = items.indexOf(manga)
|
||||||
|
|
||||||
val selectedIds = fastMap { it.id }
|
val selectedIds = fastMap { it.id }
|
||||||
val newSelections = when (lastMangaIndex >= curMangaIndex + 1) {
|
val selectionRange = when {
|
||||||
true -> items.subList(curMangaIndex, lastMangaIndex)
|
lastMangaIndex < curMangaIndex -> IntRange(lastMangaIndex, curMangaIndex)
|
||||||
false -> items.subList(lastMangaIndex, curMangaIndex + 1)
|
curMangaIndex < lastMangaIndex -> IntRange(curMangaIndex, lastMangaIndex)
|
||||||
}.filterNot { it.id in selectedIds }
|
// We shouldn't reach this point
|
||||||
|
else -> return@apply
|
||||||
|
}
|
||||||
|
val newSelections = selectionRange.mapNotNull { index ->
|
||||||
|
items[index].takeUnless { it.id in selectedIds }
|
||||||
|
}
|
||||||
addAll(newSelections)
|
addAll(newSelections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1054,11 +1083,12 @@ class LibraryPresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
state.selection = state.selection.toMutableList().apply {
|
state.selection = state.selection.toMutableList().apply {
|
||||||
val categoryId = categories.getOrNull(index)?.id ?: -1
|
val categoryId = categories.getOrNull(index)?.id ?: -1
|
||||||
val items = loadedManga[categoryId].orEmpty().run {
|
|
||||||
filterLibrary(this, searchQuery)
|
|
||||||
}.fastMap { it.libraryManga }
|
|
||||||
val selectedIds = fastMap { it.id }
|
val selectedIds = fastMap { it.id }
|
||||||
val newSelections = items.filterNot { it.id in selectedIds }
|
val newSelections = getMangaForCategoryWithQuery(categoryId, searchQuery)
|
||||||
|
.fastMapNotNull { item ->
|
||||||
|
item.libraryManga.takeUnless { it.id in selectedIds }
|
||||||
|
}
|
||||||
|
|
||||||
addAll(newSelections)
|
addAll(newSelections)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1068,11 +1098,9 @@ class LibraryPresenter(
|
|||||||
presenterScope.launchIO {
|
presenterScope.launchIO {
|
||||||
state.selection = selection.toMutableList().apply {
|
state.selection = selection.toMutableList().apply {
|
||||||
val categoryId = categories[index].id
|
val categoryId = categories[index].id
|
||||||
val items = loadedManga[categoryId].orEmpty().run {
|
val items = getMangaForCategoryWithQuery(categoryId, searchQuery).fastMap { it.libraryManga }
|
||||||
filterLibrary(this, searchQuery)
|
|
||||||
}.fastMap { it.libraryManga }
|
|
||||||
val selectedIds = fastMap { it.id }
|
val selectedIds = fastMap { it.id }
|
||||||
val (toRemove, toAdd) = items.partition { it.id in selectedIds }
|
val (toRemove, toAdd) = items.fastPartition { it.id in selectedIds }
|
||||||
val toRemoveIds = toRemove.fastMap { it.id }
|
val toRemoveIds = toRemove.fastMap { it.id }
|
||||||
removeAll { it.id in toRemoveIds }
|
removeAll { it.id in toRemoveIds }
|
||||||
addAll(toAdd)
|
addAll(toAdd)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user