Fast browsing (#612)

* BrowseTab: serialized data object must have readResolve

* simplify usage of bulk-selection select-all/reverse

* hide bulk-selection select-all/reverse

* fast browsing by avoid insert every entries into DB
This commit is contained in:
Cuong-Tran 2024-12-31 11:52:04 +07:00 committed by GitHub
parent 1f3d2cbd3c
commit 5fc9e202e8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 338 additions and 201 deletions

View file

@ -12,7 +12,6 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.SnackbarResult import androidx.compose.material3.SnackbarResult
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.paging.LoadState import androidx.paging.LoadState
@ -24,7 +23,6 @@ import eu.kanade.presentation.browse.components.BrowseSourceList
import eu.kanade.presentation.components.AppBar import eu.kanade.presentation.components.AppBar
import eu.kanade.presentation.util.formattedMessage import eu.kanade.presentation.util.formattedMessage
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel
import exh.metadata.metadata.RaisedSearchMetadata import exh.metadata.metadata.RaisedSearchMetadata
import exh.source.isEhBasedSource import exh.source.isEhBasedSource
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
@ -62,7 +60,6 @@ fun BrowseSourceContent(
onMangaLongClick: (Manga) -> Unit, onMangaLongClick: (Manga) -> Unit,
// KMK --> // KMK -->
selection: List<Manga>, selection: List<Manga>,
browseSourceState: BrowseSourceScreenModel.State,
// KMK <-- // KMK <--
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -138,22 +135,9 @@ fun BrowseSourceContent(
LoadingScreen( LoadingScreen(
modifier = Modifier.padding(contentPadding), modifier = Modifier.padding(contentPadding),
) )
// KMK -->
browseSourceState.mangaDisplayingList.clear()
// KMK <--
return return
} }
// KMK -->
for (idx in browseSourceState.mangaDisplayingList.size..<mangaList.itemCount) {
mangaList[idx]?.collectAsState()?.value?.first?.let { manga ->
if (!browseSourceState.mangaDisplayingList.map { it.id }.contains(manga.id)) {
browseSourceState.mangaDisplayingList.add(manga)
}
}
}
// KMK <--
// SY --> // SY -->
if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) { if (source?.isEhBasedSource() == true && ehentaiBrowseDisplayMode) {
BrowseSourceEHentaiList( BrowseSourceEHentaiList(

View file

@ -80,7 +80,7 @@ fun RelatedMangasComfortableGrid(
) )
} }
items( items(
key = { "related-comfort-${relatedManga.mangaList[it].id}" }, key = { "related-comfort-${relatedManga.mangaList[it].url.hashCode()}" },
count = relatedManga.mangaList.size, count = relatedManga.mangaList.size,
) { index -> ) { index ->
val manga by getManga(relatedManga.mangaList[index]) val manga by getManga(relatedManga.mangaList[index])

View file

@ -79,7 +79,7 @@ fun RelatedMangasCompactGrid(
) )
} }
items( items(
key = { "related-compact-${relatedManga.mangaList[it].id}" }, key = { "related-compact-${relatedManga.mangaList[it].url.hashCode()}" },
count = relatedManga.mangaList.size, count = relatedManga.mangaList.size,
) { index -> ) { index ->
val manga by getManga(relatedManga.mangaList[index]) val manga by getManga(relatedManga.mangaList[index])

View file

@ -82,7 +82,7 @@ fun RelatedMangasList(
) )
} }
items( items(
key = { "related-list-${relatedManga.mangaList[it].id}" }, key = { "related-list-${relatedManga.mangaList[it].url.hashCode()}" },
count = relatedManga.mangaList.size, count = relatedManga.mangaList.size,
) { index -> ) { index ->
val manga by getManga(relatedManga.mangaList[index]) val manga by getManga(relatedManga.mangaList[index])

View file

@ -4,8 +4,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.Favorite
import androidx.compose.material.icons.filled.SelectAll
import androidx.compose.material.icons.outlined.FlipToBack
import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -33,6 +31,7 @@ fun BulkSelectionToolbar(
actions = persistentListOf<AppBar.AppBarAction>() actions = persistentListOf<AppBar.AppBarAction>()
.builder() .builder()
.apply { .apply {
/*
if (onSelectAll != null) { if (onSelectAll != null) {
add( add(
AppBar.Action( AppBar.Action(
@ -51,6 +50,7 @@ fun BulkSelectionToolbar(
), ),
) )
} }
*/
if (isRunning) { if (isRunning) {
add( add(
AppBar.ActionCompose( AppBar.ActionCompose(

View file

@ -66,7 +66,7 @@ fun RelatedMangaCardRow(
contentPadding = PaddingValues(MaterialTheme.padding.small), contentPadding = PaddingValues(MaterialTheme.padding.small),
horizontalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall), 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) val manga by getManga(it)
MangaItem( MangaItem(
title = manga.title, title = manga.title,

View file

@ -39,6 +39,7 @@ import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
data object BrowseTab : Tab { data object BrowseTab : Tab {
private fun readResolve(): Any = BrowseTab
override val options: TabOptions override val options: TabOptions
@Composable @Composable

View file

@ -10,6 +10,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.hapticfeedback.HapticFeedback import androidx.compose.ui.hapticfeedback.HapticFeedback
import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.util.fastAny import androidx.compose.ui.util.fastAny
import androidx.compose.ui.util.fastDistinctBy
import androidx.compose.ui.util.fastForEach import androidx.compose.ui.util.fastForEach
import androidx.compose.ui.util.fastForEachIndexed import androidx.compose.ui.util.fastForEachIndexed
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
@ -113,7 +114,9 @@ class BulkFavoriteScreenModel(
mutableState.update { state -> mutableState.update { state ->
val newSelection = mangas.filterNot { manga -> val newSelection = mangas.filterNot { manga ->
state.selection.contains(manga) state.selection.contains(manga)
}.toPersistentList() }
.fastDistinctBy { it.id }
.toPersistentList()
state.copy( state.copy(
selection = newSelection, selection = newSelection,
selectionMode = newSelection.isNotEmpty(), selectionMode = newSelection.isNotEmpty(),

View file

@ -23,6 +23,7 @@ import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
@ -58,7 +59,7 @@ open class FeedScreenModel(
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val sourcePreferences: SourcePreferences = Injekt.get(), val sourcePreferences: SourcePreferences = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(), getFeedSavedSearchGlobal: GetFeedSavedSearchGlobal = Injekt.get(),
private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(), private val getSavedSearchGlobalFeed: GetSavedSearchGlobalFeed = Injekt.get(),
private val countFeedSavedSearchGlobal: CountFeedSavedSearchGlobal = Injekt.get(), private val countFeedSavedSearchGlobal: CountFeedSavedSearchGlobal = Injekt.get(),
@ -318,9 +319,9 @@ open class FeedScreenModel(
val result = withIOContext { val result = withIOContext {
itemUI.copy( itemUI.copy(
results = page.map { results = page.map {
networkToLocalManga.await(it.toDomainManga(itemUI.source!!.id))
}
// KMK --> // KMK -->
it.toDomainManga(itemUI.source!!.id)
}
.filter { !hideInLibraryFeedItems.get() || !it.favorite }, .filter { !hideInLibraryFeedItems.get() || !it.favorite },
// KMK <-- // KMK <--
) )
@ -355,8 +356,10 @@ open class FeedScreenModel(
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga -> .collectLatest { manga ->
if (manga == null) return@collectLatest
value = manga value = manga
// KMK -->
?: initialManga
// KMK <--
} }
} }
} }

View file

@ -13,6 +13,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.core.stack.StackEvent
@ -40,6 +41,7 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreen
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.source.interactor.GetRemoteManga import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.i18n.kmk.KMR import tachiyomi.i18n.kmk.KMR
@ -57,6 +59,7 @@ fun feedTab(
val state by screenModel.state.collectAsState() val state by screenModel.state.collectAsState()
// KMK --> // KMK -->
val scope = rememberCoroutineScope()
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
val showingFeedOrderScreen = rememberSaveable { mutableStateOf(false) } val showingFeedOrderScreen = rememberSaveable { mutableStateOf(false) }
@ -175,21 +178,27 @@ fun feedTab(
// KMK --> // KMK -->
onLongClickFeed = screenModel::openActionsDialog, onLongClickFeed = screenModel::openActionsDialog,
// KMK <-- // KMK <--
onClickManga = { manga -> onClickManga = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(manga) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
navigator.push(MangaScreen(manga.id, true)) } else {
// KMK <--
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
// KMK --> // KMK -->
onLongClickManga = { manga -> onLongClickManga = {
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (!bulkFavoriteState.selectionMode) {
navigator.push(MangaScreen(manga.id, true)) bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
} else {
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,

View file

@ -4,6 +4,7 @@ import androidx.activity.compose.BackHandler
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@ -14,6 +15,7 @@ import eu.kanade.tachiyomi.ui.browse.BulkFavoriteScreenModel
import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog
import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen import eu.kanade.tachiyomi.ui.browse.migration.advanced.process.MigrationListScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import tachiyomi.core.common.util.lang.launchIO
class MigrateSearchScreen(private val mangaId: Long, private val validSources: List<Long>) : Screen() { class MigrateSearchScreen(private val mangaId: Long, private val validSources: List<Long>) : Screen() {
@ -28,6 +30,7 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
val dialogState by dialogScreenModel.state.collectAsState() val dialogState by dialogScreenModel.state.collectAsState()
// KMK --> // KMK -->
val scope = rememberCoroutineScope()
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
@ -52,21 +55,31 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
}, },
onClickItem = { onClickItem = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(it) val manga = screenModel.networkToLocalManga.getLocal(it)
} else if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
{ } else
// SY --> // KMK <--
navigator.items {
.filterIsInstance<MigrationListScreen>() // SY -->
.last() navigator.items
.newSelectedItem = mangaId to it.id .filterIsInstance<MigrationListScreen>()
navigator.popUntil { it is MigrationListScreen } .last()
// SY <-- .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 --> // KMK -->
bulkFavoriteScreenModel = bulkFavoriteScreenModel, bulkFavoriteScreenModel = bulkFavoriteScreenModel,
hasPinnedSources = screenModel.hasPinnedSources(), hasPinnedSources = screenModel.hasPinnedSources(),

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalUriHandler import androidx.compose.ui.platform.LocalUriHandler
@ -34,6 +35,7 @@ import eu.kanade.tachiyomi.util.system.toast
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import mihon.presentation.core.util.collectAsLazyPagingItems import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.Constants import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -67,6 +69,7 @@ data class SourceSearchScreen(
// KMK --> // KMK -->
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope()
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
@ -74,6 +77,8 @@ data class SourceSearchScreen(
BackHandler(enabled = bulkFavoriteState.selectionMode) { BackHandler(enabled = bulkFavoriteState.selectionMode) {
bulkFavoriteScreenModel.toggleSelectionMode() bulkFavoriteScreenModel.toggleSelectionMode()
} }
val mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems()
// KMK <-- // KMK <--
Scaffold( Scaffold(
@ -86,12 +91,17 @@ data class SourceSearchScreen(
onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode,
onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite, onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite,
onSelectAll = { onSelectAll = {
state.mangaDisplayingList.forEach { manga -> mangaList.itemSnapshotList.items
bulkFavoriteScreenModel.select(manga) .map { it.value.first }
} .forEach { manga ->
bulkFavoriteScreenModel.select(manga)
}
}, },
onReverseSelection = { onReverseSelection = {
bulkFavoriteScreenModel.reverseSelection(state.mangaDisplayingList.toList()) bulkFavoriteScreenModel.reverseSelection(
mangaList.itemSnapshotList.items
.map { it.value.first },
)
}, },
) )
} else { } else {
@ -138,7 +148,7 @@ data class SourceSearchScreen(
} }
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), mangaList = mangaList,
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode, ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@ -158,19 +168,28 @@ data class SourceSearchScreen(
}, },
onHelpClick = { uriHandler.openUri(Constants.URL_HELP) }, onHelpClick = { uriHandler.openUri(Constants.URL_HELP) },
onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) }, onLocalSourceHelpClick = { uriHandler.openUri(LocalSource.HELP_URL) },
onMangaClick = { manga -> onMangaClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(manga) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
openMigrateDialog(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 --> // KMK -->
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,
browseSourceState = state,
// KMK <-- // KMK <--
) )
} }

View file

@ -171,6 +171,9 @@ data class BrowseSourceScreen(
assistUrl = (screenModel.source as? HttpSource)?.baseUrl assistUrl = (screenModel.source as? HttpSource)?.baseUrl
} }
// KMK -->
val mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems()
// KMK <--
Scaffold( Scaffold(
topBar = { topBar = {
Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) { Column(modifier = Modifier.background(MaterialTheme.colorScheme.surface)) {
@ -182,12 +185,17 @@ data class BrowseSourceScreen(
onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode,
onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite, onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite,
onSelectAll = { onSelectAll = {
state.mangaDisplayingList.forEach { manga -> mangaList.itemSnapshotList.items
bulkFavoriteScreenModel.select(manga) .map { it.value.first }
} .forEach { manga ->
bulkFavoriteScreenModel.select(manga)
}
}, },
onReverseSelection = { onReverseSelection = {
bulkFavoriteScreenModel.reverseSelection(state.mangaDisplayingList.toList()) bulkFavoriteScreenModel.reverseSelection(
mangaList.itemSnapshotList.items
.map { it.value.first },
)
}, },
) )
} else { } else {
@ -314,7 +322,7 @@ data class BrowseSourceScreen(
) { paddingValues -> ) { paddingValues ->
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), mangaList = mangaList,
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode, ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@ -327,30 +335,34 @@ data class BrowseSourceScreen(
onLocalSourceHelpClick = onHelpClick, onLocalSourceHelpClick = onHelpClick,
onMangaClick = { onMangaClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(it) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
navigator.push( } else {
MangaScreen( // KMK <--
it.id, navigator.push(
// KMK --> MangaScreen(
// Finding the entry to be merged to, so we don't want to expand description manga.id,
// so that user can see the `Merge to another` button // KMK -->
smartSearchConfig != null, // Finding the entry to be merged to, so we don't want to expand description
// KMK <-- // so that user can see the `Merge to another` button
smartSearchConfig, smartSearchConfig != null,
), // KMK <--
) smartSearchConfig,
),
)
}
} }
}, },
onMangaLongClick = { manga -> onMangaLongClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
navigator.push(MangaScreen(manga.id, true)) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- navigator.push(MangaScreen(manga.id, true))
scope.launchIO { } else {
// KMK <--
val duplicateManga = screenModel.getDuplicateLibraryManga(manga) val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
when { when {
manga.favorite -> screenModel.setDialog( manga.favorite -> screenModel.setDialog(
@ -370,7 +382,6 @@ data class BrowseSourceScreen(
}, },
// KMK --> // KMK -->
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,
browseSourceState = state,
// KMK <-- // KMK <--
) )
} }

View file

@ -40,7 +40,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
@ -103,7 +102,7 @@ open class BrowseSourceScreenModel(
private val setMangaCategories: SetMangaCategories = Injekt.get(), private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(), private val setMangaDefaultChapterFlags: SetMangaDefaultChapterFlags = Injekt.get(),
private val getManga: GetManga = Injekt.get(), private val getManga: GetManga = Injekt.get(),
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(), val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
private val addTracks: AddTracks = Injekt.get(), private val addTracks: AddTracks = Injekt.get(),
@ -212,9 +211,13 @@ open class BrowseSourceScreenModel(
// SY <-- // SY <--
}.flow.map { pagingData -> }.flow.map { pagingData ->
pagingData.map { (it, metadata) -> pagingData.map { (it, metadata) ->
networkToLocalManga.await(it.toDomainManga(sourceId)) // KMK -->
.let { localManga -> getManga.subscribe(localManga.url, localManga.source) } it.toDomainManga(sourceId)
.filterNotNull() .let { manga ->
getManga.subscribe(manga.url, manga.source)
.map { it ?: manga }
}
// KMK <--
// SY --> // SY -->
.combineMetadata(metadata) .combineMetadata(metadata)
// SY <-- // SY <--
@ -520,9 +523,6 @@ open class BrowseSourceScreenModel(
val savedSearches: ImmutableList<EXHSavedSearch> = persistentListOf(), val savedSearches: ImmutableList<EXHSavedSearch> = persistentListOf(),
val filterable: Boolean = true, val filterable: Boolean = true,
// SY <-- // SY <--
// KMK -->
val mangaDisplayingList: MutableSet<Manga> = mutableSetOf(),
// KMK <--
) { ) {
val isUserQuery get() = listing is Listing.Search && !listing.query.isNullOrEmpty() val isUserQuery get() = listing is Listing.Search && !listing.query.isNullOrEmpty()
} }

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
@ -43,6 +44,7 @@ import eu.kanade.tachiyomi.util.system.toast
import exh.md.follows.MangaDexFollowsScreen import exh.md.follows.MangaDexFollowsScreen
import exh.source.isEhBasedSource import exh.source.isEhBasedSource
import exh.util.nullIfBlank import exh.util.nullIfBlank
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.interactor.GetRemoteManga import tachiyomi.domain.source.interactor.GetRemoteManga
import tachiyomi.domain.source.model.SavedSearch import tachiyomi.domain.source.model.SavedSearch
@ -67,6 +69,7 @@ class SourceFeedScreen(val sourceId: Long) : Screen() {
val context = LocalContext.current val context = LocalContext.current
// KMK --> // KMK -->
val scope = rememberCoroutineScope()
screenModel.source.let { screenModel.source.let {
if (it is StubSource) { if (it is StubSource) {
MissingSourceScreen( MissingSourceScreen(
@ -126,11 +129,14 @@ class SourceFeedScreen(val sourceId: Long) : Screen() {
// KMK <-- // KMK <--
onClickManga = { onClickManga = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(it) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
onMangaClick(navigator, it) } else {
// KMK <--
onMangaClick(navigator, manga)
}
} }
}, },
onClickSearch = { onSearchClick(navigator, screenModel.source, it) }, onClickSearch = { onSearchClick(navigator, screenModel.source, it) },
@ -173,11 +179,14 @@ class SourceFeedScreen(val sourceId: Long) : Screen() {
.filterIsInstance<SourceFeedUI.SourceSavedSearch>() .filterIsInstance<SourceFeedUI.SourceSavedSearch>()
.isNotEmpty() .isNotEmpty()
}, },
onLongClickManga = { manga -> onLongClickManga = {
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (!bulkFavoriteState.selectionMode) {
navigator.push(MangaScreen(manga.id, true)) bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
} else {
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
bulkFavoriteScreenModel = bulkFavoriteScreenModel, bulkFavoriteScreenModel = bulkFavoriteScreenModel,

View file

@ -65,7 +65,7 @@ open class SourceFeedScreenModel(
uiPreferences: UiPreferences = Injekt.get(), uiPreferences: UiPreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val getManga: GetManga = 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 getFeedSavedSearchBySourceId: GetFeedSavedSearchBySourceId = Injekt.get(),
private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(), private val getSavedSearchBySourceIdFeed: GetSavedSearchBySourceIdFeed = Injekt.get(),
private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(), private val countFeedSavedSearchBySourceId: CountFeedSavedSearchBySourceId = Injekt.get(),
@ -236,7 +236,9 @@ open class SourceFeedScreenModel(
val titles = withIOContext { val titles = withIOContext {
page.map { page.map {
networkToLocalManga.await(it.toDomainManga(source.id)) // KMK -->
it.toDomainManga(source.id)
// KMK <--
} }
} }
@ -271,8 +273,10 @@ open class SourceFeedScreenModel(
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga -> .collectLatest { manga ->
if (manga == null) return@collectLatest
value = manga value = manga
// KMK -->
?: initialManga
// KMK <--
} }
} }
} }

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
@ -23,6 +24,7 @@ import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog
import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
class GlobalSearchScreen( class GlobalSearchScreen(
@ -51,6 +53,7 @@ class GlobalSearchScreen(
} }
// KMK --> // KMK -->
val scope = rememberCoroutineScope()
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
@ -93,20 +96,26 @@ class GlobalSearchScreen(
}, },
onClickItem = { onClickItem = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(it) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
navigator.push(MangaScreen(it.id, true)) } else {
// KMK <--
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
onLongClickItem = { manga -> onLongClickItem = {
// KMK --> // KMK -->
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (!bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
navigator.push(MangaScreen(manga.id, true)) } else {
// KMK <--
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
// KMK --> // KMK -->

View file

@ -20,7 +20,7 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -39,7 +39,7 @@ abstract class SearchScreenModel(
sourcePreferences: SourcePreferences = Injekt.get(), sourcePreferences: SourcePreferences = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val extensionManager: ExtensionManager = 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 getManga: GetManga = Injekt.get(),
private val preferences: SourcePreferences = Injekt.get(), private val preferences: SourcePreferences = Injekt.get(),
) : StateScreenModel<SearchScreenModel.State>(initialState) { ) : StateScreenModel<SearchScreenModel.State>(initialState) {
@ -83,9 +83,11 @@ abstract class SearchScreenModel(
fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> { fun getManga(initialManga: Manga): androidx.compose.runtime.State<Manga> {
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.filterNotNull()
.collectLatest { manga -> .collectLatest { manga ->
value = manga value = manga
// KMK -->
?: initialManga
// KMK <--
} }
} }
} }
@ -187,7 +189,9 @@ abstract class SearchScreenModel(
} }
val titles = page.mangas.map { val titles = page.mangas.map {
networkToLocalManga.await(it.toDomainManga(source.id)) // KMK -->
it.toDomainManga(source.id)
// KMK <--
} }
if (isActive) { if (isActive) {

View file

@ -32,7 +32,6 @@ import androidx.core.net.toUri
import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
import cafe.adriel.voyager.core.model.screenModelScope
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@ -367,12 +366,22 @@ class MangaScreen(
getMangaState = { screenModel.getManga(initialManga = it) }, getMangaState = { screenModel.getManga(initialManga = it) },
onRelatedMangasScreenClick = { onRelatedMangasScreenClick = {
if (successState.isRelatedMangasFetched == null) { if (successState.isRelatedMangasFetched == null) {
screenModel.screenModelScope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) } scope.launchIO { screenModel.fetchRelatedMangasFromSource(onDemand = true) }
} }
showRelatedMangasScreen() showRelatedMangasScreen()
}, },
onRelatedMangaClick = { navigator.push(MangaScreen(it.id, true)) }, onRelatedMangaClick = {
onRelatedMangaLongClick = { bulkFavoriteScreenModel.addRemoveManga(it, haptic) }, 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 = { onSourceClick = {
if (successState.source !is StubSource) { if (successState.source !is StubSource) {
val screen = when { val screen = when {

View file

@ -95,7 +95,6 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.distinctUntilChangedBy import kotlinx.coroutines.flow.distinctUntilChangedBy
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapConcat import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
@ -191,7 +190,7 @@ class MangaScreenModel(
private val smartSearchMerge: SmartSearchMerge = Injekt.get(), private val smartSearchMerge: SmartSearchMerge = Injekt.get(),
// KMK <-- // KMK <--
private val updateMergedSettings: UpdateMergedSettings = Injekt.get(), 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 deleteMergeById: DeleteMergeById = Injekt.get(),
private val getFlatMetadata: GetFlatMetadataById = Injekt.get(), private val getFlatMetadata: GetFlatMetadataById = Injekt.get(),
private val getPagePreviews: GetPagePreviews = Injekt.get(), private val getPagePreviews: GetPagePreviews = Injekt.get(),
@ -702,10 +701,12 @@ class MangaScreenModel(
fun getManga(initialManga: Manga): RuntimeState<Manga> { fun getManga(initialManga: Manga): RuntimeState<Manga> {
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.filterNotNull()
.flowWithLifecycle(lifecycle) .flowWithLifecycle(lifecycle)
.collectLatest { manga -> .collectLatest { manga ->
value = manga value = manga
// KMK -->
?: initialManga
// KMK <--
} }
} }
} }
@ -1150,7 +1151,9 @@ class MangaScreenModel(
/* Push found related mangas into collection */ /* Push found related mangas into collection */
val relatedManga = RelatedManga.Success.fromPair(pair) { mangaList -> val relatedManga = RelatedManga.Success.fromPair(pair) { mangaList ->
mangaList.map { mangaList.map {
networkToLocalManga.await(it.toDomainManga(state.source.id)) // KMK -->
it.toDomainManga(state.source.id)
// KMK <--
} }
} }
@ -1985,13 +1988,13 @@ sealed interface RelatedManga {
} }
internal fun List<RelatedManga>.removeDuplicates(manga: Manga): List<RelatedManga> { internal fun List<RelatedManga>.removeDuplicates(manga: Manga): List<RelatedManga> {
val mangaIds = HashSet<Long>().apply { add(manga.id) } val mangaHashes = HashSet<Int>().apply { add(manga.url.hashCode()) }
return map { relatedManga -> return map { relatedManga ->
if (relatedManga is Success) { if (relatedManga is Success) {
val stripedList = relatedManga.mangaList.mapNotNull { val stripedList = relatedManga.mangaList.mapNotNull {
if (!mangaIds.contains(it.id)) { if (!mangaHashes.contains(it.url.hashCode())) {
mangaIds.add(it.id) mangaHashes.add(it.url.hashCode())
it it
} else { } else {
null null

View file

@ -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.browse.BrowseSourceScreen
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchScreen
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.library.service.LibraryPreferences import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -88,18 +89,24 @@ fun RelatedMangasScreen(
columns = getColumnsPreference(LocalConfiguration.current.orientation), columns = getColumnsPreference(LocalConfiguration.current.orientation),
displayMode = displayMode, displayMode = displayMode,
contentPadding = paddingValues, contentPadding = paddingValues,
onMangaClick = { manga -> onMangaClick = {
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(manga) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
navigator.push(MangaScreen(manga.id, true)) bulkFavoriteScreenModel.toggleSelection(manga)
} else {
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
onMangaLongClick = { manga -> onMangaLongClick = {
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (!bulkFavoriteState.selectionMode) {
navigator.push(MangaScreen(manga.id, true)) bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
} else {
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
onKeywordClick = { query -> onKeywordClick = { query ->

View file

@ -65,6 +65,9 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
// KMK -->
val mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems()
// KMK <--
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
// KMK --> // KMK -->
@ -75,12 +78,17 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode,
onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite, onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite,
onSelectAll = { onSelectAll = {
state.mangaDisplayingList.forEach { manga -> mangaList.itemSnapshotList.items
bulkFavoriteScreenModel.select(manga) .map { it.value.first }
} .forEach { manga ->
bulkFavoriteScreenModel.select(manga)
}
}, },
onReverseSelection = { onReverseSelection = {
bulkFavoriteScreenModel.reverseSelection(state.mangaDisplayingList.toList()) bulkFavoriteScreenModel.reverseSelection(
mangaList.itemSnapshotList.items
.map { it.value.first },
)
}, },
) )
} else { } else {
@ -104,7 +112,7 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
) { paddingValues -> ) { paddingValues ->
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), mangaList = mangaList,
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode, ehentaiBrowseDisplayMode = screenModel.ehentaiBrowseDisplayMode,
@ -117,20 +125,24 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
onLocalSourceHelpClick = null, onLocalSourceHelpClick = null,
onMangaClick = { onMangaClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(it) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
navigator.push(MangaScreen(it.id, true)) } else {
// KMK <--
navigator.push(MangaScreen(manga.id, true))
}
} }
}, },
onMangaLongClick = { manga -> onMangaLongClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
navigator.push(MangaScreen(manga.id, true)) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- navigator.push(MangaScreen(manga.id, true))
scope.launchIO { } else {
// KMK <--
val duplicateManga = screenModel.getDuplicateLibraryManga(manga) val duplicateManga = screenModel.getDuplicateLibraryManga(manga)
when { when {
manga.favorite -> screenModel.setDialog( manga.favorite -> screenModel.setDialog(
@ -150,7 +162,6 @@ class MangaDexFollowsScreen(private val sourceId: Long) : Screen() {
}, },
// KMK --> // KMK -->
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,
browseSourceState = state,
// KMK <-- // KMK <--
) )
} }

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
@ -25,6 +26,7 @@ import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog
import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog
import eu.kanade.tachiyomi.ui.manga.MangaScreen import eu.kanade.tachiyomi.ui.manga.MangaScreen
import mihon.presentation.core.util.collectAsLazyPagingItems import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.i18n.sy.SYMR import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
@ -44,7 +46,7 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
// KMK --> // KMK -->
val state by screenModel.state.collectAsState() val scope = rememberCoroutineScope()
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
@ -61,6 +63,9 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
// KMK -->
val mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems()
// KMK <--
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
// KMK --> // KMK -->
@ -71,12 +76,17 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode,
onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite, onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite,
onSelectAll = { onSelectAll = {
state.mangaDisplayingList.forEach { manga -> mangaList.itemSnapshotList.items
bulkFavoriteScreenModel.select(manga) .map { it.value.first }
} .forEach { manga ->
bulkFavoriteScreenModel.select(manga)
}
}, },
onReverseSelection = { onReverseSelection = {
bulkFavoriteScreenModel.reverseSelection(state.mangaDisplayingList.toList()) bulkFavoriteScreenModel.reverseSelection(
mangaList.itemSnapshotList.items
.map { it.value.first },
)
}, },
) )
} else { } else {
@ -98,7 +108,7 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
) { paddingValues -> ) { paddingValues ->
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), mangaList = mangaList,
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = false, ehentaiBrowseDisplayMode = false,
@ -109,27 +119,32 @@ class MangaDexSimilarScreen(val mangaId: Long, val sourceId: Long) : Screen() {
onWebViewClick = null, onWebViewClick = null,
onHelpClick = null, onHelpClick = null,
onLocalSourceHelpClick = null, onLocalSourceHelpClick = null,
onMangaClick = { manga -> onMangaClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(manga) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
onMangaClick(manga) } else {
// KMK <--
onMangaClick(manga)
}
} }
}, },
onMangaLongClick = { manga -> onMangaLongClick = {
// KMK --> // KMK -->
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (!bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
onMangaClick(manga) } else {
// KMK <--
onMangaClick(manga)
}
} }
}, },
// KMK --> // KMK -->
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,
browseSourceState = state,
// KMK <-- // KMK <--
) )
} }

View file

@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalHapticFeedback import androidx.compose.ui.platform.LocalHapticFeedback
import cafe.adriel.voyager.core.model.rememberScreenModel import cafe.adriel.voyager.core.model.rememberScreenModel
@ -26,6 +27,7 @@ import eu.kanade.tachiyomi.ui.browse.ChangeMangasCategoryDialog
import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog import eu.kanade.tachiyomi.ui.browse.RemoveMangaDialog
import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen import eu.kanade.tachiyomi.ui.browse.source.SourcesScreen
import mihon.presentation.core.util.collectAsLazyPagingItems import mihon.presentation.core.util.collectAsLazyPagingItems
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.presentation.core.components.material.Scaffold import tachiyomi.presentation.core.components.material.Scaffold
import tachiyomi.presentation.core.screens.LoadingScreen import tachiyomi.presentation.core.screens.LoadingScreen
@ -43,7 +45,7 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
// KMK --> // KMK -->
val state by screenModel.state.collectAsState() val scope = rememberCoroutineScope()
val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() } val bulkFavoriteScreenModel = rememberScreenModel { BulkFavoriteScreenModel() }
val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState() val bulkFavoriteState by bulkFavoriteScreenModel.state.collectAsState()
@ -60,6 +62,9 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
val snackbarHostState = remember { SnackbarHostState() } val snackbarHostState = remember { SnackbarHostState() }
// KMK -->
val mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems()
// KMK <--
Scaffold( Scaffold(
topBar = { scrollBehavior -> topBar = { scrollBehavior ->
// KMK --> // KMK -->
@ -70,12 +75,17 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode, onClickClearSelection = bulkFavoriteScreenModel::toggleSelectionMode,
onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite, onChangeCategoryClick = bulkFavoriteScreenModel::addFavorite,
onSelectAll = { onSelectAll = {
state.mangaDisplayingList.forEach { manga -> mangaList.itemSnapshotList.items
bulkFavoriteScreenModel.select(manga) .map { it.value.first }
} .forEach { manga ->
bulkFavoriteScreenModel.select(manga)
}
}, },
onReverseSelection = { onReverseSelection = {
bulkFavoriteScreenModel.reverseSelection(state.mangaDisplayingList.toList()) bulkFavoriteScreenModel.reverseSelection(
mangaList.itemSnapshotList.items
.map { it.value.first },
)
}, },
) )
} else { } else {
@ -97,7 +107,7 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
) { paddingValues -> ) { paddingValues ->
BrowseSourceContent( BrowseSourceContent(
source = screenModel.source, source = screenModel.source,
mangaList = screenModel.mangaPagerFlowFlow.collectAsLazyPagingItems(), mangaList = mangaList,
columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation), columns = screenModel.getColumnsPreference(LocalConfiguration.current.orientation),
// SY --> // SY -->
ehentaiBrowseDisplayMode = false, ehentaiBrowseDisplayMode = false,
@ -108,25 +118,30 @@ class RecommendsScreen(val mangaId: Long, val sourceId: Long) : Screen() {
onWebViewClick = null, onWebViewClick = null,
onHelpClick = null, onHelpClick = null,
onLocalSourceHelpClick = null, onLocalSourceHelpClick = null,
onMangaClick = { manga -> onMangaClick = {
// KMK --> // KMK -->
if (bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.toggleSelection(manga) val manga = screenModel.networkToLocalManga.getLocal(it)
} else { if (bulkFavoriteState.selectionMode) {
// KMK <-- bulkFavoriteScreenModel.toggleSelection(manga)
onMangaClick(manga) } else {
// KMK <--
onMangaClick(manga)
}
} }
}, },
onMangaLongClick = { manga -> onMangaLongClick = {
// KMK --> // KMK -->
if (!bulkFavoriteState.selectionMode) { scope.launchIO {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic) val manga = screenModel.networkToLocalManga.getLocal(it)
if (!bulkFavoriteState.selectionMode) {
bulkFavoriteScreenModel.addRemoveManga(manga, haptic)
}
} }
// KMK <-- // KMK <--
}, },
// KMK --> // KMK -->
selection = bulkFavoriteState.selection, selection = bulkFavoriteState.selection,
browseSourceState = state,
// KMK <-- // KMK <--
) )
} }

View file

@ -25,6 +25,14 @@ class NetworkToLocalManga(
} }
} }
// KMK -->
suspend fun getLocal(manga: Manga): Manga = if (manga.id <= 0) {
await(manga)
} else {
manga
}
// KMK <--
private suspend fun getManga(url: String, sourceId: Long): Manga? { private suspend fun getManga(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId) return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
} }