From 3310784faf3bf817c0dee7c94ba68f8da8f4918c Mon Sep 17 00:00:00 2001 From: Cuong-Tran Date: Tue, 13 May 2025 23:18:01 +0700 Subject: [PATCH] chore(network-local-manga): Reintroduce (in-memory) fast browsing (#909) * measure time * Revert "Revert "Fast browsing (#612)"" This reverts commit d23130b317ed05473c3b1ebf0b3d94f1baad0bb8. * Fix networkToLocal for bulk-selection selectAll/reverseSelection * fix comments --- .../presentation/browse/GlobalSearchScreen.kt | 18 +++++- .../browse/MigrateSearchScreen.kt | 18 +++++- .../presentation/browse/SourceFeedScreen.kt | 18 +++++- .../RelatedMangasComfortableGrid.kt | 2 +- .../components/RelatedMangasCompactGrid.kt | 2 +- .../browse/components/RelatedMangasList.kt | 2 +- .../presentation/components/TabbedScreen.kt | 16 ++++- .../manga/components/RelatedMangasRow.kt | 2 +- .../ui/browse/BulkFavoriteScreenModel.kt | 5 ++ .../ui/browse/feed/FeedScreenModel.kt | 9 ++- .../tachiyomi/ui/browse/feed/FeedTab.kt | 31 +++++---- .../migration/search/MigrateSearchScreen.kt | 43 ++++++++----- .../migration/search/SourceSearchScreen.kt | 42 ++++++++++--- .../source/browse/BrowseSourceScreen.kt | 63 ++++++++++++------- .../source/browse/BrowseSourceScreenModel.kt | 4 ++ .../ui/browse/source/feed/SourceFeedScreen.kt | 31 +++++---- .../source/feed/SourceFeedScreenModel.kt | 9 ++- .../source/globalsearch/GlobalSearchScreen.kt | 40 ++++++++---- .../source/globalsearch/SearchScreenModel.kt | 11 ++-- .../kanade/tachiyomi/ui/manga/MangaScreen.kt | 14 ++++- .../tachiyomi/ui/manga/MangaScreenModel.kt | 14 +++-- .../tachiyomi/ui/manga/RelatedMangasScreen.kt | 42 +++++++++---- .../exh/md/follows/MangaDexFollowsScreen.kt | 43 ++++++++----- .../java/exh/recs/BrowseRecommendsScreen.kt | 52 +++++++++++---- .../main/java/exh/recs/RecommendsScreen.kt | 36 +++++++---- .../java/exh/recs/RecommendsScreenModel.kt | 12 ++-- .../data/source/EHentaiPagingSource.kt | 2 +- .../data/source/SourcePagingSource.kt | 2 +- .../manga/interactor/NetworkToLocalManga.kt | 18 ++++++ 29 files changed, 431 insertions(+), 170 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt index 54ce83c29..1d4f904df 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/GlobalSearchScreen.kt @@ -6,6 +6,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import eu.kanade.domain.source.model.installedExtension import eu.kanade.presentation.browse.components.GlobalSearchCardRow @@ -21,6 +22,7 @@ import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter import eu.kanade.tachiyomi.util.system.LocaleHelper import kotlinx.collections.immutable.ImmutableMap +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.domain.source.model.Source as DomainSource @@ -43,6 +45,7 @@ fun GlobalSearchScreen( // KMK <-- ) { // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() // KMK <-- @@ -59,13 +62,24 @@ fun GlobalSearchScreen( state.filteredItems.values .filterIsInstance() .flatMap { it.result } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { state.filteredItems.values .filterIsInstance() .flatMap { it.result } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { diff --git a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt index 831c2afb0..61dfa4081 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/MigrateSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import eu.kanade.presentation.browse.components.GlobalSearchToolbar import eu.kanade.presentation.components.BulkSelectionToolbar import eu.kanade.tachiyomi.source.CatalogueSource @@ -11,6 +12,7 @@ import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.components.material.Scaffold @@ -33,6 +35,7 @@ fun MigrateSearchScreen( // KMK <-- ) { // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() // KMK <-- @@ -49,13 +52,24 @@ fun MigrateSearchScreen( state.filteredItems.values .filterIsInstance() .flatMap { it.result } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { state.filteredItems.values .filterIsInstance() .flatMap { it.result } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { diff --git a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt index d805f3b43..f0c5e842d 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/SourceFeedScreen.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Modifier import dev.icerock.moko.resources.StringResource import eu.kanade.presentation.browse.components.BrowseSourceFloatingActionButton @@ -26,6 +27,7 @@ import eu.kanade.presentation.components.SearchToolbar import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.model.FeedSavedSearch import tachiyomi.domain.source.model.SavedSearch @@ -112,6 +114,7 @@ fun SourceFeedScreen( // KMK <-- ) { // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() // KMK <-- @@ -127,12 +130,23 @@ fun SourceFeedScreen( onSelectAll = { items.mapNotNull { it.results } .flatten() - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { items.mapNotNull { it.results } .flatten() - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt index 4b910b642..52cac4641 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasComfortableGrid.kt @@ -80,7 +80,7 @@ fun RelatedMangasComfortableGrid( ) } items( - key = { "related-comfort-${relatedManga.mangaList[it].id}" }, + key = { "related-comfort-${relatedManga.mangaList[it].url.hashCode()}" }, count = relatedManga.mangaList.size, ) { index -> val manga by getManga(relatedManga.mangaList[index]) diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt index 054f556b9..c4158b471 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasCompactGrid.kt @@ -79,7 +79,7 @@ fun RelatedMangasCompactGrid( ) } items( - key = { "related-compact-${relatedManga.mangaList[it].id}" }, + key = { "related-compact-${relatedManga.mangaList[it].url.hashCode()}" }, count = relatedManga.mangaList.size, ) { index -> val manga by getManga(relatedManga.mangaList[index]) diff --git a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt index a7b639e3e..d0b05e667 100644 --- a/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt +++ b/app/src/main/java/eu/kanade/presentation/browse/components/RelatedMangasList.kt @@ -82,7 +82,7 @@ fun RelatedMangasList( ) } items( - key = { "related-list-${relatedManga.mangaList[it].id}" }, + key = { "related-list-${relatedManga.mangaList[it].url.hashCode()}" }, count = relatedManga.mangaList.size, ) { index -> val manga by getManga(relatedManga.mangaList[index]) diff --git a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt index 5799bcf96..398bbaf87 100644 --- a/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/components/TabbedScreen.kt @@ -29,6 +29,7 @@ import eu.kanade.tachiyomi.ui.browse.feed.FeedScreenModel import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.launch +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.TabText import tachiyomi.presentation.core.i18n.stringResource @@ -68,14 +69,25 @@ fun TabbedScreen( feedState.items?.let { result -> result.mapNotNull { it.results } .flatten() - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } } }, onReverseSelection = { feedState.items?.let { result -> result.mapNotNull { it.results } .flatten() - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } } }, ) diff --git a/app/src/main/java/eu/kanade/presentation/manga/components/RelatedMangasRow.kt b/app/src/main/java/eu/kanade/presentation/manga/components/RelatedMangasRow.kt index 428a8ef2c..7baad53ae 100644 --- a/app/src/main/java/eu/kanade/presentation/manga/components/RelatedMangasRow.kt +++ b/app/src/main/java/eu/kanade/presentation/manga/components/RelatedMangasRow.kt @@ -66,7 +66,7 @@ fun RelatedMangaCardRow( contentPadding = PaddingValues(MaterialTheme.padding.small), horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), ) { - items(mangas, key = { "related-row-${it.id}" }) { + items(mangas, key = { "related-row-${it.url.hashCode()}" }) { val manga by getManga(it) MangaItem( title = manga.title, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt index bb9d2f8cb..848a99130 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/BulkFavoriteScreenModel.kt @@ -34,6 +34,7 @@ import tachiyomi.domain.category.model.Category import tachiyomi.domain.chapter.interactor.SetMangaDefaultChapterFlags import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga +import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.toMangaUpdate @@ -53,6 +54,10 @@ class BulkFavoriteScreenModel( private val coverCache: CoverCache = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val addTracks: AddTracks = Injekt.get(), + + // KMK --> + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + // KMK <-- ) : StateScreenModel(initialState) { fun backHandler() { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt index dfc9d3191..40ac6669a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedScreenModel.kt @@ -60,7 +60,7 @@ open class FeedScreenModel( val sourceManager: SourceManager = Injekt.get(), val sourcePreferences: SourcePreferences = Injekt.get(), private val getManga: GetManga = Injekt.get(), - private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(), private val countFeedSavedSearchGlobal: CountFeedSavedSearchGlobal = Injekt.get(), @@ -304,8 +304,8 @@ open class FeedScreenModel( results = page .map { it.toDomainManga(itemUI.source!!.id) } .distinctBy { it.url } - .let { networkToLocalManga(it) } // KMK --> + // .let { networkToLocalManga(it) } .filter { !hideInLibraryFeedItems.get() || !it.favorite }, // KMK <-- ) @@ -343,8 +343,11 @@ open class FeedScreenModel( return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) .collectLatest { manga -> - if (manga == null) return@collectLatest + /* KMK --> if (manga == null) return@collectLatest KMK <-- */ value = manga + // KMK --> + ?: initialManga + // KMK <-- } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt index aa575335b..bcce2ac1f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/feed/FeedTab.kt @@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.screen.Screen @@ -36,6 +37,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.source.interactor.GetRemoteManga import tachiyomi.i18n.MR import tachiyomi.i18n.kmk.KMR @@ -53,6 +55,7 @@ fun Screen.feedTab( val state by screenModel.state.collectAsState() // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val showingFeedOrderScreen = rememberSaveable { mutableStateOf(false) } @@ -165,21 +168,27 @@ fun Screen.feedTab( // KMK --> onLongClickFeed = screenModel::openActionsDialog, // KMK <-- - onClickManga = { manga -> + onClickManga = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - navigator.push(MangaScreen(manga.id, true)) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + } } }, // KMK --> - onLongClickManga = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) + onLongClickManga = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } } }, selection = bulkFavoriteState.selection, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt index 2024aea6d..454c4df20 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/MigrateSearchScreen.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -13,6 +14,7 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.core.common.util.lang.launchIO /** * Manual search [validSources] for manga to migrate to. @@ -30,6 +32,7 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L val dialogState by dialogScreenModel.state.collectAsState() // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() @@ -52,23 +55,33 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L navigator.push(SourceSearchScreen(dialogState.manga!!, it.id, state.searchQuery)) // SY <-- }, - onClickItem = { manga -> + onClickItem = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else - // KMK <-- - { - // SY --> - navigator.items - .filterIsInstance() - .last() - .newSelectedItem = mangaId to manga.id - navigator.popUntil { it is MigrationListScreen } - // SY <-- - } + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else + // KMK <-- + { + // SY --> + navigator.items + .filterIsInstance() + .last() + .newSelectedItem = mangaId to manga.id + navigator.popUntil { it is MigrationListScreen } + // SY <-- + } + } + }, + onLongClickItem = { + // KMK --> + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + } }, - onLongClickItem = { navigator.push(MangaScreen(it.id, true)) }, // KMK --> bulkFavoriteScreenModel = bulkFavoriteScreenModel, hasPinnedSources = screenModel.hasPinnedSources(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt index f8a8fd34b..a23a54f95 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SourceSearchScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalUriHandler @@ -33,6 +34,7 @@ import eu.kanade.tachiyomi.util.system.toast import kotlinx.collections.immutable.persistentListOf import mihon.presentation.core.util.collectAsLazyPagingItems import tachiyomi.core.common.Constants +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.i18n.sy.SYMR import tachiyomi.presentation.core.components.material.Scaffold @@ -66,6 +68,7 @@ data class SourceSearchScreen( // KMK --> val context = LocalContext.current + val scope = rememberCoroutineScope() val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() @@ -89,12 +92,23 @@ data class SourceSearchScreen( onSelectAll = { mangaList.itemSnapshotList.items .map { it.value.first } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { mangaList.itemSnapshotList.items .map { it.value.first } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { @@ -161,16 +175,26 @@ data class SourceSearchScreen( }, onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }, - onMangaClick = { manga -> + onMangaClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - openMigrateDialog(manga) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + openMigrateDialog(manga) + } + } + }, + onMangaLongClick = { + // KMK --> + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) } }, - onMangaLongClick = { navigator.push(MangaScreen(it.id, true)) }, // KMK --> selection = bulkFavoriteState.selection, // KMK <-- diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt index 4200dd961..85774d0cf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreen.kt @@ -202,12 +202,23 @@ data class BrowseSourceScreen( onSelectAll = { mangaList.itemSnapshotList.items .map { it.value.first } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { mangaList.itemSnapshotList.items .map { it.value.first } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { @@ -356,32 +367,36 @@ data class BrowseSourceScreen( onWebViewClick = onWebViewClick, onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onLocalSourceHelpClick = onHelpClick, - onMangaClick = { manga -> + onMangaClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - navigator.push( - MangaScreen( - mangaId = manga.id, - // KMK --> - // Finding the entry to be merged to, so we don't want to expand description - // so that user can see the `Merge to another` button - fromSource = smartSearchConfig == null, - // KMK <-- - smartSearchConfig = smartSearchConfig, - ), - ) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + navigator.push( + MangaScreen( + mangaId = manga.id, + // KMK --> + // Finding the entry to be merged to, so we don't want to expand description + // so that user can see the `Merge to another` button + fromSource = smartSearchConfig == null, + // KMK <-- + smartSearchConfig = smartSearchConfig, + ), + ) + } } }, - onMangaLongClick = { manga -> + onMangaLongClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - navigator.push(MangaScreen(manga.id, true)) - } else { - // KMK <-- - scope.launchIO { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + navigator.push(MangaScreen(manga.id, true)) + } else { + // KMK <-- val duplicates = screenModel.getDuplicateLibraryManga(manga) when { manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt index 32176b79b..f32b44999 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt @@ -70,6 +70,7 @@ import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.manga.interactor.GetDuplicateLibraryManga import tachiyomi.domain.manga.interactor.GetFlatMetadataById import tachiyomi.domain.manga.interactor.GetManga +import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.MangaWithChapterCount import tachiyomi.domain.manga.model.toMangaUpdate @@ -114,6 +115,9 @@ open class BrowseSourceScreenModel( private val extensionManager: ExtensionManager = Injekt.get(), // KMK <-- + // KMK --> + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + // KMK <-- // SY --> unsortedPreferences: UnsortedPreferences = Injekt.get(), uiPreferences: UiPreferences = Injekt.get(), diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt index fa352b258..9d5116ef6 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback @@ -39,6 +40,7 @@ import exh.md.follows.MangaDexFollowsScreen import exh.source.anyIs import exh.source.isEhBasedSource import exh.util.nullIfBlank +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.source.interactor.GetRemoteManga @@ -65,6 +67,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { val context = LocalContext.current // KMK --> + val scope = rememberCoroutineScope() screenModel.source.let { if (it is StubSource) { MissingSourceScreen( @@ -123,13 +126,16 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { // onClickDelete = screenModel::openDeleteFeed, onLongClickFeed = screenModel::openActionsDialog, // KMK <-- - onClickManga = { manga -> + onClickManga = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - onMangaClick(navigator, manga) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + onMangaClick(navigator, manga) + } } }, onClickSearch = { onSearchClick(navigator, screenModel.source, it) }, @@ -164,11 +170,14 @@ class SourceFeedScreen(val sourceId: Long) : Screen() { .filterIsInstance() .isNotEmpty() }, - onLongClickManga = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) + onLongClickManga = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } } }, bulkFavoriteScreenModel = bulkFavoriteScreenModel, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt index 6da0c0bdb..598e495f2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/feed/SourceFeedScreenModel.kt @@ -73,7 +73,7 @@ open class SourceFeedScreenModel( uiPreferences: UiPreferences = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), private val getManga: GetManga = Injekt.get(), - private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(), private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(), private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(), @@ -250,7 +250,7 @@ open class SourceFeedScreenModel( val titles = withIOContext { page.map { it.toDomainManga(source.id) } .distinctBy { it.url } - .let { networkToLocalManga(it) } + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ } mutableState.update { state -> @@ -284,8 +284,11 @@ open class SourceFeedScreenModel( return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) .collectLatest { manga -> - if (manga == null) return@collectLatest + /* KMK --> if (manga == null) return@collectLatest KMK <-- */ value = manga + // KMK --> + ?: initialManga + // KMK <-- } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt index b7d073347..80245dec7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.model.rememberScreenModel @@ -19,6 +20,7 @@ import eu.kanade.presentation.util.Screen import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.presentation.core.screens.LoadingScreen class GlobalSearchScreen( @@ -47,6 +49,7 @@ class GlobalSearchScreen( } // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() @@ -66,7 +69,12 @@ class GlobalSearchScreen( is SearchItemResult.Success -> { val manga = result.result.singleOrNull() if (manga != null) { - navigator.replace(MangaScreen(manga.id, true)) + // KMK --> + scope.launchIO { + val localManga = screenModel.networkToLocalManga.getLocal(manga) + // KMK <-- + navigator.replace(MangaScreen(localManga.id, true)) + } } else { // Backoff to result screen showSingleLoadingScreen = false @@ -87,22 +95,28 @@ class GlobalSearchScreen( onClickSource = { navigator.push(BrowseSourceScreen(it.id, state.searchQuery)) }, - onClickItem = { manga -> + onClickItem = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - navigator.push(MangaScreen(manga.id, true)) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + } } }, - onLongClickItem = { manga -> + onLongClickItem = { // KMK --> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - // KMK <-- - navigator.push(MangaScreen(manga.id, true)) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + } } }, // KMK --> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt index 1e702decc..39694034a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/SearchScreenModel.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.flow.filterNotNull +import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.update import kotlinx.coroutines.isActive import kotlinx.coroutines.launch @@ -39,7 +39,7 @@ abstract class SearchScreenModel( sourcePreferences: SourcePreferences = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), private val extensionManager: ExtensionManager = Injekt.get(), - private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val getManga: GetManga = Injekt.get(), private val preferences: SourcePreferences = Injekt.get(), ) : StateScreenModel(initialState) { @@ -83,9 +83,12 @@ abstract class SearchScreenModel( fun getManga(initialManga: Manga): androidx.compose.runtime.State { return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) - .filterNotNull() + /* KMK --> .filterNotNull() KMK <-- */ .collectLatest { manga -> value = manga + // KMK --> + ?: initialManga + // KMK <-- } } } @@ -189,7 +192,7 @@ abstract class SearchScreenModel( val titles = page.mangas .map { it.toDomainManga(source.id) } .distinctBy { it.url } - .let { networkToLocalManga(it) } + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ if (isActive) { updateItem(source, SearchItemResult.Success(titles)) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt index 7772ca063..51679b2b8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreen.kt @@ -389,8 +389,18 @@ class MangaScreen( } showRelatedMangasScreen() }, - onRelatedMangaClick = { navigator.push(MangaScreen(it.id, true)) }, - onRelatedMangaLongClick = { bulkFavoriteScreenModel.addRemoveManga(it, haptic) }, + onRelatedMangaClick = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + navigator.push(MangaScreen(manga.id, true)) + } + }, + onRelatedMangaLongClick = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } + }, onSourceClick = { if (successState.source !is StubSource) { val screen = when { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt index 499414468..dad689a3f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt @@ -94,7 +94,6 @@ import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.map @@ -195,7 +194,7 @@ class MangaScreenModel( private val smartSearchMerge: SmartSearchMerge = Injekt.get(), // KMK <-- private val updateMergedSettings: UpdateMergedSettings = Injekt.get(), - private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), private val deleteMergeById: DeleteMergeById = Injekt.get(), private val getFlatMetadata: GetFlatMetadataById = Injekt.get(), private val getPagePreviews: GetPagePreviews = Injekt.get(), @@ -705,10 +704,13 @@ class MangaScreenModel( fun getManga(initialManga: Manga): RuntimeState { return produceState(initialValue = initialManga) { getManga.subscribe(initialManga.url, initialManga.source) - .filterNotNull() + /* KMK --> .filterNotNull() KMK <-- */ .flowWithLifecycle(lifecycle) .collectLatest { manga -> value = manga + // KMK --> + ?: initialManga + // KMK <-- } } } @@ -1155,7 +1157,7 @@ class MangaScreenModel( mangaList .map { it.toDomainManga(state.source.id) } .distinctBy { it.url } - .let { networkToLocalManga(it) } + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ } updateSuccessState { successState -> @@ -2008,14 +2010,14 @@ sealed interface RelatedManga { } internal fun List.removeDuplicates(manga: Manga): List { - val mangaIds = HashSet().apply { add(manga.id) } + val mangaHashes = HashSet().apply { add(manga.url.hashCode()) } return map { relatedManga -> if (relatedManga is Success) { Success( relatedManga.keyword, relatedManga.mangaList - .filter { mangaIds.add(it.id) }, + .filter { mangaHashes.add(it.url.hashCode()) }, ) } else { relatedManga diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt index bd5747131..f42706b8d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/RelatedMangasScreen.kt @@ -22,6 +22,7 @@ import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen import kotlinx.coroutines.CoroutineScope +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.presentation.core.components.material.Scaffold import uy.kohesive.injekt.Injekt @@ -57,14 +58,25 @@ fun RelatedMangasScreen( successState.relatedMangasSorted?.let { result -> result.map { it as RelatedManga.Success } .flatMap { it.mangaList } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } } }, onReverseSelection = { successState.relatedMangasSorted?.let { result -> result.map { it as RelatedManga.Success } .flatMap { it.mangaList } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } } }, ) @@ -88,18 +100,24 @@ fun RelatedMangasScreen( columns = getColumnsPreference(LocalConfiguration.current.orientation), displayMode = displayMode, contentPadding = paddingValues, - onMangaClick = { manga -> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - navigator.push(MangaScreen(manga.id, true)) + onMangaClick = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + navigator.push(MangaScreen(manga.id, true)) + } } }, - onMangaLongClick = { manga -> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) - } else { - navigator.push(MangaScreen(manga.id, true)) + onMangaLongClick = { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + navigator.push(MangaScreen(manga.id, true)) + } } }, onKeywordClick = { query -> diff --git a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt index d2cefb18f..d29378d49 100644 --- a/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt +++ b/app/src/main/java/exh/md/follows/MangaDexFollowsScreen.kt @@ -77,12 +77,23 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { onSelectAll = { mangaList.itemSnapshotList.items .map { it.value.first } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { mangaList.itemSnapshotList.items .map { it.value.first } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { @@ -117,22 +128,26 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() { onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = { manga -> + onMangaClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) - } else { - // KMK <-- - navigator.push(MangaScreen(manga.id, true)) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + navigator.push(MangaScreen(manga.id, true)) + } } }, - onMangaLongClick = { manga -> + onMangaLongClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - navigator.push(MangaScreen(manga.id, true)) - } else { - // KMK <-- - scope.launchIO { + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + navigator.push(MangaScreen(manga.id, true)) + } else { + // KMK <-- val duplicates = screenModel.getDuplicateLibraryManga(manga) when { manga.favorite -> screenModel.setDialog(BrowseSourceScreenModel.Dialog.RemoveManga(manga)) diff --git a/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt b/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt index c2e99aced..aa3e7e078 100644 --- a/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt +++ b/app/src/main/java/exh/recs/BrowseRecommendsScreen.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback @@ -24,6 +25,7 @@ import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewActivity import mihon.presentation.core.util.collectAsLazyPagingItems +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.screens.LoadingScreen @@ -50,6 +52,7 @@ class BrowseRecommendsScreen( } // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() @@ -94,12 +97,23 @@ class BrowseRecommendsScreen( onSelectAll = { mangaList.itemSnapshotList.items .map { it.value.first } - .forEach { bulkFavoriteScreenModel.select(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it) + .forEach { bulkFavoriteScreenModel.select(it) } + } + } }, onReverseSelection = { mangaList.itemSnapshotList.items .map { it.value.first } - .let { bulkFavoriteScreenModel.reverseSelection(it) } + .let { + scope.launchIO { + bulkFavoriteScreenModel.reverseSelection( + bulkFavoriteScreenModel.networkToLocalManga.getLocal(it), + ) + } + } }, ) } else { @@ -139,23 +153,37 @@ class BrowseRecommendsScreen( onWebViewClick = null, onHelpClick = null, onLocalSourceHelpClick = null, - onMangaClick = { manga -> + onMangaClick = { // KMK --> - if (bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.toggleSelection(manga) + if (isExternalSource) { + onClickItem(it) } else { - // KMK <-- - onClickItem(manga) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.toggleSelection(manga) + } else { + // KMK <-- + onClickItem(manga) + } + } } }, - onMangaLongClick = { manga -> + onMangaLongClick = { // KMK --> - if (!bulkFavoriteState.selectionMode) { - bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + if (isExternalSource) { + onLongClickItem(it) } else { - onLongClickItem(manga) + scope.launchIO { + val manga = screenModel.networkToLocalManga.getLocal(it) + if (!bulkFavoriteState.selectionMode) { + bulkFavoriteScreenModel.addRemoveManga(manga, haptic) + } else { + onLongClickItem(manga) + } + } + // KMK <-- } - // KMK <-- }, // KMK --> selection = bulkFavoriteState.selection, diff --git a/app/src/main/java/exh/recs/RecommendsScreen.kt b/app/src/main/java/exh/recs/RecommendsScreen.kt index caa60822e..1afc8a439 100644 --- a/app/src/main/java/exh/recs/RecommendsScreen.kt +++ b/app/src/main/java/exh/recs/RecommendsScreen.kt @@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalHapticFeedback import cafe.adriel.voyager.core.model.rememberScreenModel @@ -17,6 +18,7 @@ import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.webview.WebViewActivity import exh.recs.components.RecommendsScreen +import tachiyomi.core.common.util.lang.launchIO import tachiyomi.domain.manga.model.Manga import tachiyomi.presentation.core.screens.LoadingScreen @@ -38,6 +40,7 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { val state by screenModel.state.collectAsState() // KMK --> + val scope = rememberCoroutineScope() val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() @@ -49,12 +52,21 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { // KMK <-- val onClickItem = { manga: Manga -> - navigator.push( - when (manga.source) { - RECOMMENDS_SOURCE -> SourcesScreen(SourcesScreen.SmartSearchConfig(manga.ogTitle)) - else -> MangaScreen(manga.id, true) - }, - ) + when (manga.source) { + RECOMMENDS_SOURCE -> navigator.push( + SourcesScreen(SourcesScreen.SmartSearchConfig(manga.ogTitle)), + ) + else -> { + // KMK --> + scope.launchIO { + val localManga = screenModel.networkToLocalManga.getLocal(manga) + navigator.push( + // KMK <-- + MangaScreen(localManga.id, true), + ) + } + } + } } val onLongClickItem = { manga: Manga -> @@ -62,11 +74,13 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() { RECOMMENDS_SOURCE -> WebViewActivity.newIntent(context, manga.url, title = manga.title).let(context::startActivity) else -> { // KMK --> - // Add to favorite - bulkFavoriteScreenModel.addRemoveManga( - manga, - haptic, - ) + scope.launchIO { + val localManga = screenModel.networkToLocalManga.getLocal(manga) + bulkFavoriteScreenModel.addRemoveManga( + localManga, + haptic, + ) + } // KMK <-- } } diff --git a/app/src/main/java/exh/recs/RecommendsScreenModel.kt b/app/src/main/java/exh/recs/RecommendsScreenModel.kt index c63ed52bf..a404cb83c 100644 --- a/app/src/main/java/exh/recs/RecommendsScreenModel.kt +++ b/app/src/main/java/exh/recs/RecommendsScreenModel.kt @@ -32,7 +32,7 @@ open class RecommendsScreenModel( val mangaId: Long, val sourceId: Long, private val getManga: GetManga = Injekt.get(), - protected val networkToLocalManga: NetworkToLocalManga = Injekt.get(), + val networkToLocalManga: NetworkToLocalManga = Injekt.get(), ) : StateScreenModel(State()) { // KMK --> @@ -78,14 +78,14 @@ open class RecommendsScreenModel( } val recSourceId = recSource.associatedSourceId - val titles = if (recSourceId != null) { + // KMK --> + val titles = page.mangas.map { // If the recommendation is associated with a source, resolve it - page.mangas.map { it.toDomainManga(recSourceId) } - .let { networkToLocalManga(it) } - } else { // Otherwise, skip this step. The user will be prompted to choose a source via SmartSearch - page.mangas.map { it.toDomainManga(RECOMMENDS_SOURCE) } + it.toDomainManga(recSourceId ?: RECOMMENDS_SOURCE) + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ } + // KMK <-- .distinctBy { it.url } if (isActive) { diff --git a/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt b/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt index 35117f1b5..933e504fc 100644 --- a/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/EHentaiPagingSource.kt @@ -20,7 +20,7 @@ abstract class EHentaiPagingSource(override val source: CatalogueSource) : BaseS val manga = mangasPage.mangas .map { it.toDomainManga(source.id) } .filter { seenManga.add(it.url) } - .let { networkToLocalManga(it) } + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ // SY --> .mapIndexed { index, manga -> manga to metadata.getOrNull(index) } // SY <-- diff --git a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt index 9f9ba9405..13a8c7406 100644 --- a/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt +++ b/data/src/main/java/tachiyomi/data/source/SourcePagingSource.kt @@ -92,7 +92,7 @@ abstract class BaseSourcePagingSource( val manga = mangasPage.mangas .map { it.toDomainManga(source!!.id) } .filter { seenManga.add(it.url) } - .let { networkToLocalManga(it) } + /* KMK --> .let { networkToLocalManga(it) } KMK <-- */ // SY --> .mapIndexed { index, manga -> manga to metadata.getOrNull(index) } // SY <-- diff --git a/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt b/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt index 69137d9e9..1e4674b50 100644 --- a/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt +++ b/domain/src/main/java/tachiyomi/domain/manga/interactor/NetworkToLocalManga.kt @@ -14,4 +14,22 @@ class NetworkToLocalManga( suspend operator fun invoke(manga: List): List { return mangaRepository.insertNetworkManga(manga) } + + // KMK --> + suspend fun getLocal(manga: Manga): Manga = if (manga.id <= 0) { + invoke(manga) + } else { + manga + } + + suspend fun getLocal(mangas: List): List { + return mangas.map { manga -> + if (manga.id <= 0) { + invoke(manga) + } else { + manga + } + } + } + // KMK <-- }