From b601b07586a30e28f84b60fd9297c4dbf02336d4 Mon Sep 17 00:00:00 2001 From: Koitharu Date: Fri, 6 Sep 2024 11:35:23 +0300 Subject: [PATCH] Optimize the Downloaded quick filter --- .../ui/list/FavouritesListViewModel.kt | 12 ++++++---- .../history/ui/HistoryListViewModel.kt | 18 +++++++------- .../local/domain/LocalObserveMapper.kt | 24 ++++++++++++------- 3 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt index 3e784a114..18863085e 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/favourites/ui/list/FavouritesListViewModel.kt @@ -9,8 +9,10 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.plus import org.koitharu.kotatsu.R @@ -55,7 +57,7 @@ class FavouritesListViewModel @Inject constructor( val categoryId: Long = savedStateHandle[ARG_CATEGORY_ID] ?: NO_ID private val refreshTrigger = MutableStateFlow(Any()) 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 } .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.favoritesListMode) @@ -76,7 +78,9 @@ class FavouritesListViewModel @Inject constructor( observeListModeWithTriggers(), refreshTrigger, ) { list, filters, mode, _ -> - list.mapList(mode, filters).also { isReady.set(true) } + list.mapList(mode, filters) + }.distinctUntilChanged().onEach { + isPaginationReady.set(true) }.catch { emit(listOf(it.toErrorState(canRetry = false))) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) @@ -118,7 +122,7 @@ class FavouritesListViewModel @Inject constructor( } fun requestMoreItems() { - if (isReady.compareAndSet(true, false)) { + if (isPaginationReady.compareAndSet(true, false)) { limit.value += PAGE_SIZE } } @@ -143,7 +147,7 @@ class FavouritesListViewModel @Inject constructor( quickFilter.appliedOptions.combineWithSettings(), limit, ) { order, filters, limit -> - isReady.set(false) + isPaginationReady.set(false) repository.observeAll(order, filters, limit) }.flattenLatest() } else { diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt index aa099e398..b12c906f4 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/history/ui/HistoryListViewModel.kt @@ -8,7 +8,8 @@ import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.catch 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.plus 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.call 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.history.data.HistoryRepository import org.koitharu.kotatsu.history.domain.HistoryListQuickFilter @@ -75,7 +75,7 @@ class HistoryListViewModel @Inject constructor( } private val limit = MutableStateFlow(PAGE_SIZE) - private val isReady = AtomicBoolean(false) + private val isPaginationReady = AtomicBoolean(false) val isStatsEnabled = settings.observeAsStateFlow( scope = viewModelScope + Dispatchers.Default, @@ -90,11 +90,9 @@ class HistoryListViewModel @Inject constructor( observeListModeWithTriggers(), settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled }, ) { filters, list, grouped, mode, incognito -> - mapList(list, grouped, mode, filters, incognito).also { isReady.set(true) } - }.onStart { - loadingCounter.increment() - }.onFirst { - loadingCounter.decrement() + mapList(list, grouped, mode, filters, incognito) + }.distinctUntilChanged().onEach { + isPaginationReady.set(true) }.catch { e -> emit(listOf(e.toErrorState(canRetry = false))) }.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, listOf(LoadingState)) @@ -133,7 +131,7 @@ class HistoryListViewModel @Inject constructor( } fun requestMoreItems() { - if (isReady.compareAndSet(true, false)) { + if (isPaginationReady.compareAndSet(true, false)) { limit.value += PAGE_SIZE } } @@ -143,7 +141,7 @@ class HistoryListViewModel @Inject constructor( quickFilter.appliedOptions.combineWithSettings(), limit, ) { order, filters, limit -> - isReady.set(false) + isPaginationReady.set(false) repository.observeAllWithHistory(order, filters, limit) }.flattenLatest() diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/LocalObserveMapper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/LocalObserveMapper.kt index 03e77b252..0e94293ea 100644 --- a/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/LocalObserveMapper.kt +++ b/app/src/main/kotlin/org/koitharu/kotatsu/local/domain/LocalObserveMapper.kt @@ -1,5 +1,6 @@ package org.koitharu.kotatsu.local.domain +import kotlinx.coroutines.CompletableDeferred import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll @@ -12,39 +13,44 @@ import kotlinx.coroutines.flow.transformLatest import org.koitharu.kotatsu.core.model.isLocal import org.koitharu.kotatsu.local.data.LocalMangaRepository import org.koitharu.kotatsu.parsers.model.Manga +import java.util.Collections +import java.util.WeakHashMap -abstract class LocalObserveMapper( +abstract class LocalObserveMapper( private val localMangaRepository: LocalMangaRepository, private val limitStep: Int, ) { + private val cache = Collections.synchronizedMap(WeakHashMap()) + protected fun observe(limit: Int, observer: (limit: Int) -> Flow>): Flow> { val floatingLimit = MutableStateFlow(limit) return floatingLimit.flatMapLatest { l -> observer(l) .transformLatest { fullList -> - val mapped = fullList.mapToLocal() + val mapped = fullList.mapToLocal(cache) if (mapped.size < limit && fullList.size == l) { floatingLimit.value += limitStep } else { emit(mapped.take(limit)) } }.distinctUntilChanged() - } } - private suspend fun List.mapToLocal(): List = coroutineScope { - val dispatcher = Dispatchers.IO.limitedParallelism(6) - map { - async(dispatcher) { - val m = toManga(it) + private suspend fun List.mapToLocal(cache: MutableMap): List = coroutineScope { + val dispatcher = Dispatchers.IO.limitedParallelism(8) + map { item -> + val m = toManga(item) + if (cache.contains(m)) { + CompletableDeferred(cache[m]) + } else async(dispatcher) { val mapped = if (m.isLocal) { m } else { localMangaRepository.findSavedManga(m)?.manga } - mapped?.let { mm -> toResult(it, mm) } + mapped?.let { mm -> toResult(item, mm) }.also { cache[m] = it } } }.awaitAll().filterNotNull() }