Fix Downloaded quick filter (close #1076, close #1079)

master
Koitharu 2 years ago
parent c1ac207809
commit 8a74faa4f0
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -9,6 +9,7 @@ import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.onCompletion
@ -129,3 +130,5 @@ fun <T1, T2, T3, T4, T5, T6, R> combine(
suspend fun <T : Any> Flow<T?>.firstNotNull(): T = checkNotNull(first { x -> x != null }) suspend fun <T : Any> Flow<T?>.firstNotNull(): T = checkNotNull(first { x -> x != null })
suspend fun <T : Any> Flow<T?>.firstNotNullOrNull(): T? = firstOrNull { x -> x != null } suspend fun <T : Any> Flow<T?>.firstNotNullOrNull(): T? = firstOrNull { x -> x != null }
fun <T> Flow<Flow<T>>.flattenLatest() = flatMapLatest { it }

@ -27,6 +27,7 @@ import org.koitharu.kotatsu.core.parser.external.ExternalMangaSource
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.util.ReversibleHandle import org.koitharu.kotatsu.core.ui.util.ReversibleHandle
import org.koitharu.kotatsu.core.util.ext.flattenLatest
import org.koitharu.kotatsu.parsers.model.ContentType import org.koitharu.kotatsu.parsers.model.ContentType
import org.koitharu.kotatsu.parsers.model.MangaParserSource import org.koitharu.kotatsu.parsers.model.MangaParserSource
import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.model.MangaSource
@ -168,7 +169,7 @@ class MangaSourcesRepository @Inject constructor(
dao.observeEnabled(order).map { dao.observeEnabled(order).map {
it.toSources(skipNsfw, order) it.toSources(skipNsfw, order)
} }
}.flatMapLatest { it } }.flattenLatest()
.onStart { assimilateNewSources() } .onStart { assimilateNewSources() }
.combine(observeExternalSources()) { enabled, external -> .combine(observeExternalSources()) { enabled, external ->
val list = ArrayList<MangaSourceInfo>(enabled.size + external.size) val list = ArrayList<MangaSourceInfo>(enabled.size + external.size)

@ -27,6 +27,7 @@ import javax.inject.Inject
@Reusable @Reusable
class FavouritesRepository @Inject constructor( class FavouritesRepository @Inject constructor(
private val db: MangaDatabase, private val db: MangaDatabase,
private val localObserver: LocalFavoritesObserver,
) { ) {
suspend fun getAllManga(): List<Manga> { suspend fun getAllManga(): List<Manga> {
@ -40,6 +41,9 @@ class FavouritesRepository @Inject constructor(
} }
fun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> { fun observeAll(order: ListSortOrder, filterOptions: Set<ListFilterOption>, limit: Int): Flow<List<Manga>> {
if (ListFilterOption.Downloaded in filterOptions) {
return localObserver.observeAll(order, filterOptions - ListFilterOption.Downloaded, limit)
}
return db.getFavouritesDao().observeAll(order, filterOptions, limit) return db.getFavouritesDao().observeAll(order, filterOptions, limit)
.mapItems { it.toManga() } .mapItems { it.toManga() }
} }
@ -55,6 +59,9 @@ class FavouritesRepository @Inject constructor(
filterOptions: Set<ListFilterOption>, filterOptions: Set<ListFilterOption>,
limit: Int limit: Int
): Flow<List<Manga>> { ): Flow<List<Manga>> {
if (ListFilterOption.Downloaded in filterOptions) {
return localObserver.observeAll(categoryId, order, filterOptions - ListFilterOption.Downloaded, limit)
}
return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit) return db.getFavouritesDao().observeAll(categoryId, order, filterOptions, limit)
.mapItems { it.toManga() } .mapItems { it.toManga() }
} }

@ -0,0 +1,42 @@
package org.koitharu.kotatsu.favourites.domain
import dagger.Reusable
import kotlinx.coroutines.flow.Flow
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.favourites.data.FavouriteManga
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.domain.LocalObserveMapper
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject
@Reusable
class LocalFavoritesObserver @Inject constructor(
localMangaRepository: LocalMangaRepository,
private val db: MangaDatabase,
) : LocalObserveMapper<FavouriteManga, Manga>(localMangaRepository, limitStep = 10) {
fun observeAll(
order: ListSortOrder,
filterOptions: Set<ListFilterOption>,
limit: Int
): Flow<List<Manga>> = observe(limit) { newLimit ->
db.getFavouritesDao().observeAll(order, filterOptions, newLimit)
}
fun observeAll(
categoryId: Long,
order: ListSortOrder,
filterOptions: Set<ListFilterOption>,
limit: Int
): Flow<List<Manga>> = observe(limit) { newLimit ->
db.getFavouritesDao().observeAll(categoryId, order, filterOptions, newLimit)
}
override fun toManga(e: FavouriteManga) = e.manga.toManga(e.tags.toMangaTags())
override fun toResult(e: FavouriteManga, manga: Manga) = manga
}

@ -4,26 +4,22 @@ import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted 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.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
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
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
import org.koitharu.kotatsu.core.ui.util.ReversibleAction import org.koitharu.kotatsu.core.ui.util.ReversibleAction
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.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter import org.koitharu.kotatsu.favourites.domain.FavoritesListQuickFilter
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
@ -39,12 +35,11 @@ import org.koitharu.kotatsu.list.ui.model.EmptyState
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
private const val PAGE_SIZE = 20 private const val PAGE_SIZE = 16
@HiltViewModel @HiltViewModel
class FavouritesListViewModel @Inject constructor( class FavouritesListViewModel @Inject constructor(
@ -53,7 +48,6 @@ class FavouritesListViewModel @Inject constructor(
private val mangaListMapper: MangaListMapper, private val mangaListMapper: MangaListMapper,
private val markAsReadUseCase: MarkAsReadUseCase, private val markAsReadUseCase: MarkAsReadUseCase,
private val quickFilter: FavoritesListQuickFilter, private val quickFilter: FavoritesListQuickFilter,
private val localMangaRepository: LocalMangaRepository,
settings: AppSettings, settings: AppSettings,
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter { ) : MangaListViewModel(settings, downloadScheduler), QuickFilterListener by quickFilter {
@ -130,30 +124,32 @@ class FavouritesListViewModel @Inject constructor(
} }
private suspend fun List<Manga>.mapList(mode: ListMode, filters: Set<ListFilterOption>): List<ListModel> { private suspend fun List<Manga>.mapList(mode: ListMode, filters: Set<ListFilterOption>): List<ListModel> {
val list = if (ListFilterOption.Downloaded in filters) { if (isEmpty()) {
mapToLocal()
} else {
this
}
if (list.isEmpty()) {
return if (filters.isEmpty()) { return if (filters.isEmpty()) {
listOf(getEmptyState(hasFilters = false)) listOf(getEmptyState(hasFilters = false))
} else { } else {
listOfNotNull(quickFilter.filterItem(filters), getEmptyState(hasFilters = true)) listOfNotNull(quickFilter.filterItem(filters), getEmptyState(hasFilters = true))
} }
} }
val result = ArrayList<ListModel>(list.size + 1) val result = ArrayList<ListModel>(size + 1)
quickFilter.filterItem(filters)?.let(result::add) quickFilter.filterItem(filters)?.let(result::add)
mangaListMapper.toListModelList(result, list, mode) mangaListMapper.toListModelList(result, this, mode)
return result return result
} }
private fun observeFavorites() = if (categoryId == NO_ID) { private fun observeFavorites() = if (categoryId == NO_ID) {
combine(sortOrder.filterNotNull(), quickFilter.appliedOptions.combineWithSettings(), limit, ::Triple) combine(
.flatMapLatest { repository.observeAll(it.first, it.second - ListFilterOption.Downloaded, it.third) } sortOrder.filterNotNull(),
quickFilter.appliedOptions.combineWithSettings(),
limit,
) { order, filters, limit ->
isReady.set(false)
repository.observeAll(order, filters, limit)
}.flattenLatest()
} else { } else {
combine(quickFilter.appliedOptions, limit, ::Pair) combine(quickFilter.appliedOptions.combineWithSettings(), limit) { filters, limit ->
.flatMapLatest { repository.observeAll(categoryId, it.first - ListFilterOption.Downloaded, it.second) } repository.observeAll(categoryId, filters, limit)
}.flattenLatest()
} }
private fun getEmptyState(hasFilters: Boolean) = if (hasFilters) { private fun getEmptyState(hasFilters: Boolean) = if (hasFilters) {
@ -175,16 +171,4 @@ class FavouritesListViewModel @Inject constructor(
actionStringRes = 0, actionStringRes = 0,
) )
} }
private suspend fun List<Manga>.mapToLocal(): List<Manga> = coroutineScope {
map {
async {
if (it.isLocal) {
it
} else {
localMangaRepository.findSavedManga(it)?.manga
}
}
}.awaitAll().filterNotNull()
}
} }

@ -0,0 +1,35 @@
package org.koitharu.kotatsu.history.data
import dagger.Reusable
import org.koitharu.kotatsu.core.db.MangaDatabase
import org.koitharu.kotatsu.core.db.entity.toManga
import org.koitharu.kotatsu.core.db.entity.toMangaTags
import org.koitharu.kotatsu.history.domain.model.MangaWithHistory
import org.koitharu.kotatsu.list.domain.ListFilterOption
import org.koitharu.kotatsu.list.domain.ListSortOrder
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.local.domain.LocalObserveMapper
import org.koitharu.kotatsu.parsers.model.Manga
import javax.inject.Inject
@Reusable
class HistoryLocalObserver @Inject constructor(
localMangaRepository: LocalMangaRepository,
private val db: MangaDatabase,
) : LocalObserveMapper<HistoryWithManga, MangaWithHistory>(localMangaRepository, limitStep = 10) {
fun observeAll(
order: ListSortOrder,
filterOptions: Set<ListFilterOption>,
limit: Int
) = observe(limit) { newLimit ->
db.getHistoryDao().observeAll(order, filterOptions, newLimit)
}
override fun toManga(e: HistoryWithManga) = e.manga.toManga(e.tags.toMangaTags())
override fun toResult(e: HistoryWithManga, manga: Manga) = MangaWithHistory(
manga = manga,
history = e.history.toMangaHistory(),
)
}

@ -39,6 +39,7 @@ class HistoryRepository @Inject constructor(
private val settings: AppSettings, private val settings: AppSettings,
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>, private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
private val mangaRepository: MangaDataRepository, private val mangaRepository: MangaDataRepository,
private val localObserver: HistoryLocalObserver,
private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>, private val newChaptersUseCaseProvider: Provider<CheckNewChaptersUseCase>,
) { ) {
@ -80,6 +81,9 @@ class HistoryRepository @Inject constructor(
filterOptions: Set<ListFilterOption>, filterOptions: Set<ListFilterOption>,
limit: Int limit: Int
): Flow<List<MangaWithHistory>> { ): Flow<List<MangaWithHistory>> {
if (ListFilterOption.Downloaded in filterOptions) {
return localObserver.observeAll(order, filterOptions - ListFilterOption.Downloaded, limit)
}
return db.getHistoryDao().observeAll(order, filterOptions, limit).mapItems { return db.getHistoryDao().observeAll(order, filterOptions, limit).mapItems {
MangaWithHistory( MangaWithHistory(
it.manga.toManga(it.tags.toMangaTags()), it.manga.toManga(it.tags.toMangaTags()),

@ -3,21 +3,16 @@ package org.koitharu.kotatsu.history.ui
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted 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.flatMapLatest
import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.onStart
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
import org.koitharu.kotatsu.core.model.MangaHistory import org.koitharu.kotatsu.core.model.MangaHistory
import org.koitharu.kotatsu.core.model.isLocal
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ListMode import org.koitharu.kotatsu.core.prefs.ListMode
import org.koitharu.kotatsu.core.prefs.observeAsFlow import org.koitharu.kotatsu.core.prefs.observeAsFlow
@ -25,6 +20,7 @@ import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
import org.koitharu.kotatsu.core.ui.util.ReversibleAction 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.onFirst 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
@ -42,20 +38,18 @@ import org.koitharu.kotatsu.list.ui.model.ListHeader
import org.koitharu.kotatsu.list.ui.model.ListModel import org.koitharu.kotatsu.list.ui.model.ListModel
import org.koitharu.kotatsu.list.ui.model.LoadingState import org.koitharu.kotatsu.list.ui.model.LoadingState
import org.koitharu.kotatsu.list.ui.model.toErrorState import org.koitharu.kotatsu.list.ui.model.toErrorState
import org.koitharu.kotatsu.local.data.LocalMangaRepository
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import java.time.Instant import java.time.Instant
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Inject import javax.inject.Inject
private const val PAGE_SIZE = 20 private const val PAGE_SIZE = 16
@HiltViewModel @HiltViewModel
class HistoryListViewModel @Inject constructor( class HistoryListViewModel @Inject constructor(
private val repository: HistoryRepository, private val repository: HistoryRepository,
settings: AppSettings, settings: AppSettings,
private val mangaListMapper: MangaListMapper, private val mangaListMapper: MangaListMapper,
private val localMangaRepository: LocalMangaRepository,
private val markAsReadUseCase: MarkAsReadUseCase, private val markAsReadUseCase: MarkAsReadUseCase,
private val quickFilter: HistoryListQuickFilter, private val quickFilter: HistoryListQuickFilter,
downloadScheduler: DownloadWorker.Scheduler, downloadScheduler: DownloadWorker.Scheduler,
@ -144,21 +138,22 @@ class HistoryListViewModel @Inject constructor(
} }
} }
private fun observeHistory() = combine(sortOrder, quickFilter.appliedOptions.combineWithSettings(), limit, ::Triple) private fun observeHistory() = combine(
.flatMapLatest { repository.observeAllWithHistory(it.first, it.second - ListFilterOption.Downloaded, it.third) } sortOrder,
quickFilter.appliedOptions.combineWithSettings(),
limit,
) { order, filters, limit ->
isReady.set(false)
repository.observeAllWithHistory(order, filters, limit)
}.flattenLatest()
private suspend fun mapList( private suspend fun mapList(
historyList: List<MangaWithHistory>, list: List<MangaWithHistory>,
grouped: Boolean, grouped: Boolean,
mode: ListMode, mode: ListMode,
filters: Set<ListFilterOption>, filters: Set<ListFilterOption>,
isIncognito: Boolean, isIncognito: Boolean,
): List<ListModel> { ): List<ListModel> {
val list = if (ListFilterOption.Downloaded in filters) {
historyList.mapToLocal()
} else {
historyList
}
if (list.isEmpty()) { if (list.isEmpty()) {
return if (filters.isEmpty()) { return if (filters.isEmpty()) {
listOf(getEmptyState(hasFilters = false)) listOf(getEmptyState(hasFilters = false))
@ -198,20 +193,6 @@ class HistoryListViewModel @Inject constructor(
return result return result
} }
private suspend fun List<MangaWithHistory>.mapToLocal() = coroutineScope {
map {
async {
if (it.manga.isLocal) {
it
} else {
localMangaRepository.findSavedManga(it.manga)?.let { localManga ->
MangaWithHistory(localManga.manga, it.history)
}
}
}
}.awaitAll().filterNotNull()
}
private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) { private fun MangaHistory.header(order: ListSortOrder): ListHeader? = when (order) {
ListSortOrder.LAST_READ, ListSortOrder.LAST_READ,
ListSortOrder.LONG_AGO_READ -> ListHeader(calculateTimeAgo(updatedAt)) ListSortOrder.LONG_AGO_READ -> ListHeader(calculateTimeAgo(updatedAt))

@ -0,0 +1,55 @@
package org.koitharu.kotatsu.local.domain
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.flatMapLatest
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
abstract class LocalObserveMapper<E, R>(
private val localMangaRepository: LocalMangaRepository,
private val limitStep: Int,
) {
protected fun observe(limit: Int, observer: (limit: Int) -> Flow<List<E>>): Flow<List<R>> {
val floatingLimit = MutableStateFlow(limit)
return floatingLimit.flatMapLatest { l ->
observer(l)
.transformLatest { fullList ->
val mapped = fullList.mapToLocal()
if (mapped.size < limit && fullList.size == l) {
floatingLimit.value += limitStep
} else {
emit(mapped.take(limit))
}
}.distinctUntilChanged()
}
}
private suspend fun List<E>.mapToLocal(): List<R> = coroutineScope {
val dispatcher = Dispatchers.IO.limitedParallelism(6)
map {
async(dispatcher) {
val m = toManga(it)
val mapped = if (m.isLocal) {
m
} else {
localMangaRepository.findSavedManga(m)?.manga
}
mapped?.let { mm -> toResult(it, mm) }
}
}.awaitAll().filterNotNull()
}
protected abstract fun toManga(e: E): Manga
protected abstract fun toResult(e: E, manga: Manga): R
}
Loading…
Cancel
Save