Optimize the Downloaded quick filter
This commit is contained in:
@@ -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()
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user