Optimize the Downloaded quick filter

master
Koitharu 2 years ago
parent 73cea59691
commit b601b07586
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -9,8 +9,10 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -55,7 +57,7 @@ class FavouritesListViewModel @Inject constructor(
val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID
private val refreshTrigger = MutableStateFlow(Any()) private val refreshTrigger = MutableStateFlow(Any())
private val limit = MutableStateFlow(PAGE_SIZE) private val limit = MutableStateFlow(PAGE_SIZE)
private val isReady = AtomicBoolean(false) private val isPaginationReady = AtomicBoolean(false)
override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode } override val listMode = settings.observeAsFlow(AppSettings.KEY_LIST_MODE_FAVORITES) { favoritesListMode }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode)
@ -76,7 +78,9 @@ class FavouritesListViewModel @Inject constructor(
observeListModeWithTriggers(), observeListModeWithTriggers(),
refreshTrigger, refreshTrigger,
) { list, filters, mode, _ -> ) { list, filters, mode, _ ->
list.mapList(mode, filters).also { isReady.set(true) } list.mapList(mode, filters)
}.distinctUntilChanged().onEach {
isPaginationReady.set(true)
}.catch { }.catch {
emit(listOf(it.toErrorState(canRetry = false))) emit(listOf(it.toErrorState(canRetry = false)))
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
@ -118,7 +122,7 @@ class FavouritesListViewModel @Inject constructor(
} }
fun requestMoreItems() { fun requestMoreItems() {
if (isReady.compareAndSet(true, false)) { if (isPaginationReady.compareAndSet(true, false)) {
limit.value += PAGE_SIZE limit.value += PAGE_SIZE
} }
} }
@ -143,7 +147,7 @@ class FavouritesListViewModel @Inject constructor(
quickFilter.appliedOptions.combineWithSettings(), quickFilter.appliedOptions.combineWithSettings(),
limit, limit,
) { order, filters, limit -> ) { order, filters, limit ->
isReady.set(false) isPaginationReady.set(false)
repository.observeAll(order, filters, limit) repository.observeAll(order, filters, limit)
}.flattenLatest() }.flattenLatest()
} else { } else {

@ -8,7 +8,8 @@ import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.plus import kotlinx.coroutines.plus
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@ -21,7 +22,6 @@ import org.koitharu.kotatsu.core.ui.util.ReversibleAction
import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo import org.koitharu.kotatsu.core.util.ext.calculateTimeAgo
import org.koitharu.kotatsu.core.util.ext.call import org.koitharu.kotatsu.core.util.ext.call
import org.koitharu.kotatsu.core.util.ext.flattenLatest import org.koitharu.kotatsu.core.util.ext.flattenLatest
import org.koitharu.kotatsu.core.util.ext.onFirst
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository import org.koitharu.kotatsu.history.data.HistoryRepository
import org.koitharu.kotatsu.history.domain.HistoryListQuickFilter import org.koitharu.kotatsu.history.domain.HistoryListQuickFilter
@ -75,7 +75,7 @@ class HistoryListViewModel @Inject constructor(
} }
private val limit = MutableStateFlow(PAGE_SIZE) private val limit = MutableStateFlow(PAGE_SIZE)
private val isReady = AtomicBoolean(false) private val isPaginationReady = AtomicBoolean(false)
val isStatsEnabled = settings.observeAsStateFlow( val isStatsEnabled = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default, scope = viewModelScope + Dispatchers.Default,
@ -90,11 +90,9 @@ class HistoryListViewModel @Inject constructor(
observeListModeWithTriggers(), observeListModeWithTriggers(),
settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled }, settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled },
) { filters, list, grouped, mode, incognito -> ) { filters, list, grouped, mode, incognito ->
mapList(list, grouped, mode, filters, incognito).also { isReady.set(true) } mapList(list, grouped, mode, filters, incognito)
}.onStart { }.distinctUntilChanged().onEach {
loadingCounter.increment() isPaginationReady.set(true)
}.onFirst {
loadingCounter.decrement()
}.catch { e -> }.catch { e ->
emit(listOf(e.toErrorState(canRetry = false))) emit(listOf(e.toErrorState(canRetry = false)))
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState))
@ -133,7 +131,7 @@ class HistoryListViewModel @Inject constructor(
} }
fun requestMoreItems() { fun requestMoreItems() {
if (isReady.compareAndSet(true, false)) { if (isPaginationReady.compareAndSet(true, false)) {
limit.value += PAGE_SIZE limit.value += PAGE_SIZE
} }
} }
@ -143,7 +141,7 @@ class HistoryListViewModel @Inject constructor(
quickFilter.appliedOptions.combineWithSettings(), quickFilter.appliedOptions.combineWithSettings(),
limit, limit,
) { order, filters, limit -> ) { order, filters, limit ->
isReady.set(false) isPaginationReady.set(false)
repository.observeAllWithHistory(order, filters, limit) repository.observeAllWithHistory(order, filters, limit)
}.flattenLatest() }.flattenLatest()

@ -1,5 +1,6 @@
package org.koitharu.kotatsu.local.domain package org.koitharu.kotatsu.local.domain
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
@ -12,39 +13,44 @@ import kotlinx.coroutines.flow.transformLatest
import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.local.data.LocalMangaRepository import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import java.util.Collections
import java.util.WeakHashMap
abstract class LocalObserveMapper<E, R>( abstract class LocalObserveMapper<E : Any, R : Any>(
private val localMangaRepository: LocalMangaRepository, private val localMangaRepository: LocalMangaRepository,
private val limitStep: Int, private val limitStep: Int,
) { ) {
private val cache = Collections.synchronizedMap(WeakHashMap<Manga, R?>())
protected fun observe(limit: Int, observer: (limit: Int) -> Flow<List<E>>): Flow<List<R>> { protected fun observe(limit: Int, observer: (limit: Int) -> Flow<List<E>>): Flow<List<R>> {
val floatingLimit = MutableStateFlow(limit) val floatingLimit = MutableStateFlow(limit)
return floatingLimit.flatMapLatest { l -> return floatingLimit.flatMapLatest { l ->
observer(l) observer(l)
.transformLatest { fullList -> .transformLatest { fullList ->
val mapped = fullList.mapToLocal() val mapped = fullList.mapToLocal(cache)
if (mapped.size < limit && fullList.size == l) { if (mapped.size < limit && fullList.size == l) {
floatingLimit.value += limitStep floatingLimit.value += limitStep
} else { } else {
emit(mapped.take(limit)) emit(mapped.take(limit))
} }
}.distinctUntilChanged() }.distinctUntilChanged()
} }
} }
private suspend fun List<E>.mapToLocal(): List<R> = coroutineScope { private suspend fun List<E>.mapToLocal(cache: MutableMap<Manga, R?>): List<R> = coroutineScope {
val dispatcher = Dispatchers.IO.limitedParallelism(6) val dispatcher = Dispatchers.IO.limitedParallelism(8)
map { map { item ->
async(dispatcher) { val m = toManga(item)
val m = toManga(it) if (cache.contains(m)) {
CompletableDeferred(cache[m])
} else async(dispatcher) {
val mapped = if (m.isLocal) { val mapped = if (m.isLocal) {
m m
} else { } else {
localMangaRepository.findSavedManga(m)?.manga localMangaRepository.findSavedManga(m)?.manga
} }
mapped?.let { mm -> toResult(it, mm) } mapped?.let { mm -> toResult(item, mm) }.also { cache[m] = it }
} }
}.awaitAll().filterNotNull() }.awaitAll().filterNotNull()
} }

Loading…
Cancel
Save