From 865a4458b1ccced30817b674ada0d29bdafa1d79 Mon Sep 17 00:00:00 2001 From: Dude so hot Date: Mon, 26 May 2025 00:05:12 +0800 Subject: [PATCH 1/3] Ensure rememberSerializable return original object if possible --- app/build.gradle.kts | 1 + .../com/hippo/ehviewer/ui/tools/State.kt | 40 ++++++++++++++++++- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + 4 files changed, 41 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c76c5837ce..b6bc75ee4f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,6 +10,7 @@ plugins { alias(libs.plugins.android.application) alias(libs.plugins.kotlin.android) alias(libs.plugins.kotlin.compose) + alias(libs.plugins.kotlin.parcelize) alias(libs.plugins.kotlin.serialization) alias(libs.plugins.ksp) alias(libs.plugins.spotless) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/State.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/State.kt index c83a56697c..f8070942ce 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/State.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/State.kt @@ -1,14 +1,22 @@ package com.hippo.ehviewer.ui.tools +import android.os.Bundle +import android.os.Parcelable import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer import androidx.savedstate.serialization.SavedStateConfiguration +import androidx.savedstate.serialization.decodeFromSavedState +import androidx.savedstate.serialization.encodeToSavedState import kotlin.reflect.KMutableProperty0 +import kotlinx.parcelize.IgnoredOnParcel +import kotlinx.parcelize.Parcelize import kotlinx.serialization.KSerializer import kotlinx.serialization.serializer @@ -34,10 +42,38 @@ val MutableState.rememberedAccessor: KMutableProperty0 }::value } +@Parcelize +data class SavedStateExt( + val bundle: Bundle, +) : Parcelable { + @IgnoredOnParcel + var data: T? = null +} + +fun saver( + serializer: KSerializer, + configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT, +) = Saver( + save = { original: Serializable -> + val bundle = encodeToSavedState(serializer, original, configuration) + SavedStateExt(bundle).apply { + data = original + } + }, + restore = { savedState -> + savedState.data ?: decodeFromSavedState(serializer, savedState.bundle, configuration).also { + savedState.data = it + } + }, +) + @Composable inline fun rememberSerializable( vararg inputs: Any?, - stateSerializer: KSerializer = serializer(), + serializer: KSerializer = serializer(), configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT, noinline init: () -> MutableState, -) = rememberSaveable(inputs = inputs, stateSerializer, configuration, init) +): MutableState { + val saver = saver(MutableStateSerializer(serializer), configuration) + return rememberSaveable(*inputs, saver = saver, init = init) +} diff --git a/build.gradle.kts b/build.gradle.kts index bb85bc7db4..3db8dadc9f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,6 +2,7 @@ plugins { alias(libs.plugins.android.application) apply false alias(libs.plugins.kotlin.android) apply false alias(libs.plugins.kotlin.compose) apply false + alias(libs.plugins.kotlin.parcelize) apply false alias(libs.plugins.kotlin.serialization) apply false alias(libs.plugins.ksp) apply false alias(libs.plugins.spotless) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e58ed23e98..751a5720dd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,6 +136,7 @@ android-application = { id = "com.android.application", version.ref = "agp" } composeCompilerReportGenerator = { id = "dev.shreyaspatil.compose-compiler-report-generator", version = "1.4.2" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } +kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ksp = { id = "com.google.devtools.ksp", version.ref = "ksp" } spotless = { id = "com.diffplug.spotless", version = "7.0.3" } From b3e3d2363d8465b37aa2b9783963ac4845fc5919 Mon Sep 17 00:00:00 2001 From: Dude so hot Date: Mon, 26 May 2025 00:08:38 +0800 Subject: [PATCH 2/3] Drop GalleryListViewModel --- .../ehviewer/ui/screen/GalleryListScreen.kt | 67 +++++++++++++++--- .../ui/screen/GalleryListViewModel.kt | 69 ------------------- 2 files changed, 58 insertions(+), 78 deletions(-) delete mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt index 65f83b27ca..9e0ff03603 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListScreen.kt @@ -62,9 +62,13 @@ import androidx.compose.ui.res.stringArrayResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.util.fastForEach -import androidx.lifecycle.createSavedStateHandle -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.viewModelScope import androidx.paging.LoadState +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.cachedIn import androidx.paging.compose.collectAsLazyPagingItems import arrow.core.raise.ensure import arrow.core.raise.ensureNotNull @@ -72,8 +76,10 @@ import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.asMutableState +import com.hippo.ehviewer.client.EhEngine import com.hippo.ehviewer.client.EhTagDatabase import com.hippo.ehviewer.client.EhUtils +import com.hippo.ehviewer.client.data.BaseGalleryInfo import com.hippo.ehviewer.client.data.ListUrlBuilder import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_IMAGE_SEARCH import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_NORMAL @@ -111,19 +117,24 @@ import com.hippo.ehviewer.ui.tools.asyncState import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel import com.hippo.ehviewer.ui.tools.awaitInputText import com.hippo.ehviewer.ui.tools.awaitInputTextWithCheckBox +import com.hippo.ehviewer.ui.tools.foldToLoadResult import com.hippo.ehviewer.ui.tools.rememberHapticFeedback +import com.hippo.ehviewer.ui.tools.rememberInVM import com.hippo.ehviewer.ui.tools.rememberMutableStateInDataStore +import com.hippo.ehviewer.ui.tools.rememberSerializable import com.hippo.ehviewer.ui.tools.thenIf import com.hippo.ehviewer.util.FavouriteStatusRouter import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import com.ramcosta.composedestinations.spec.Direction +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import kotlin.math.roundToInt import kotlin.random.Random import kotlinx.coroutines.delay import moe.tarsin.coroutines.onEachLatest +import moe.tarsin.coroutines.runSuspendCatching import moe.tarsin.launch import moe.tarsin.launchIO import moe.tarsin.navigate @@ -150,13 +161,9 @@ fun AnimatedVisibilityScope.ToplistScreen(navigator: DestinationsNavigator) = Ga @Destination @Composable -fun AnimatedVisibilityScope.GalleryListScreen( - lub: ListUrlBuilder, - navigator: DestinationsNavigator, - viewModel: GalleryListViewModel = viewModel { GalleryListViewModel(lub, createSavedStateHandle()) }, -) = Screen(navigator) { +fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = Screen(navigator) { val searchFieldState = rememberTextFieldState() - var urlBuilder by viewModel.urlBuilder + var urlBuilder by rememberSerializable { mutableStateOf(lub) } var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } val animateItems by Settings.animateItems.collectAsState() @@ -187,7 +194,49 @@ fun AnimatedVisibilityScope.GalleryListScreen( val exHint = stringResource(R.string.gallery_list_search_bar_hint_exhentai) val searchBarHint by rememberUpdatedState(if (EhUtils.isExHentai) exHint else ehHint) val suitableTitle = getSuitableTitleForUrlBuilder(urlBuilder) - val data = viewModel.data.collectAsLazyPagingItems() + val data = rememberInVM { + Pager(PagingConfig(25)) { + object : PagingSource() { + override fun getRefreshKey(state: PagingState): String? = null + override suspend fun load(params: LoadParams) = withIOContext { + if (urlBuilder.mode == MODE_TOPLIST) { + // TODO: Since we know total pages, let pager support jump + val key = (params.key ?: urlBuilder.jumpTo)?.toInt() ?: 0 + val prev = (key - 1).takeIf { it > 0 } + val next = (key + 1).takeIf { it < TOPLIST_PAGES } + runSuspendCatching { + urlBuilder.setJumpTo(key) + EhEngine.getGalleryList(urlBuilder.build()) + }.foldToLoadResult { result -> + LoadResult.Page(result.galleryInfoList, prev?.toString(), next?.toString()) + } + } else { + when (params) { + is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false) + is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true) + is LoadParams.Refresh -> { + val key = params.key + if (key.isNullOrBlank()) { + if (urlBuilder.jumpTo != null) { + urlBuilder.next ?: urlBuilder.setIndex("2", true) + } + } else { + urlBuilder.setIndex(key, false) + } + } + } + runSuspendCatching { + val url = urlBuilder.build() + EhEngine.getGalleryList(url) + }.foldToLoadResult { result -> + urlBuilder.jumpTo = null + LoadResult.Page(result.galleryInfoList, result.prev, result.next) + } + } + } + } + }.flow.cachedIn(viewModelScope) + }.collectAsLazyPagingItems() ReportDrawnWhen { data.loadState.refresh !is LoadState.Loading } FavouriteStatusRouter.Observe(data) val listMode by Settings.listMode.collectAsState() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt deleted file mode 100644 index 66a14feb00..0000000000 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.hippo.ehviewer.ui.screen - -import androidx.compose.runtime.mutableStateOf -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.serialization.saved -import androidx.lifecycle.viewModelScope -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingSource -import androidx.paging.PagingState -import androidx.paging.cachedIn -import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer -import com.hippo.ehviewer.client.EhEngine -import com.hippo.ehviewer.client.data.BaseGalleryInfo -import com.hippo.ehviewer.client.data.ListUrlBuilder -import com.hippo.ehviewer.client.data.ListUrlBuilder.Companion.MODE_TOPLIST -import com.hippo.ehviewer.ui.tools.foldToLoadResult -import eu.kanade.tachiyomi.util.lang.withIOContext -import moe.tarsin.coroutines.runSuspendCatching - -class GalleryListViewModel(lub: ListUrlBuilder, savedStateHandle: SavedStateHandle) : ViewModel() { - val urlBuilder by savedStateHandle.saved(MutableStateSerializer()) { - mutableStateOf(lub) - } - - val data = Pager(PagingConfig(25)) { - object : PagingSource() { - override fun getRefreshKey(state: PagingState): String? = null - override suspend fun load(params: LoadParams) = withIOContext { - val urlBuilder = urlBuilder.value - if (urlBuilder.mode == MODE_TOPLIST) { - // TODO: Since we know total pages, let pager support jump - val key = (params.key ?: urlBuilder.jumpTo)?.toInt() ?: 0 - val prev = (key - 1).takeIf { it > 0 } - val next = (key + 1).takeIf { it < TOPLIST_PAGES } - runSuspendCatching { - urlBuilder.setJumpTo(key) - EhEngine.getGalleryList(urlBuilder.build()) - }.foldToLoadResult { result -> - LoadResult.Page(result.galleryInfoList, prev?.toString(), next?.toString()) - } - } else { - when (params) { - is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false) - is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true) - is LoadParams.Refresh -> { - val key = params.key - if (key.isNullOrBlank()) { - if (urlBuilder.jumpTo != null) { - urlBuilder.next ?: urlBuilder.setIndex("2", true) - } - } else { - urlBuilder.setIndex(key, false) - } - } - } - runSuspendCatching { - val url = urlBuilder.build() - EhEngine.getGalleryList(url) - }.foldToLoadResult { result -> - urlBuilder.jumpTo = null - LoadResult.Page(result.galleryInfoList, result.prev, result.next) - } - } - } - } - }.flow.cachedIn(viewModelScope) -} From 6ed4ecd057e5e74f22dd3b9f8566d4c90bb29f4e Mon Sep 17 00:00:00 2001 From: Dude so hot Date: Mon, 26 May 2025 00:11:04 +0800 Subject: [PATCH 3/3] Drop FavoritesViewModel --- .../ehviewer/ui/screen/FavoritesScreen.kt | 74 ++++++++++++++-- .../ehviewer/ui/screen/FavoritesViewModel.kt | 84 ------------------- 2 files changed, 69 insertions(+), 89 deletions(-) delete mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt index 19c5346563..4889954d8f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesScreen.kt @@ -2,6 +2,7 @@ package com.hippo.ehviewer.ui.screen import android.content.Context import android.view.ViewConfiguration +import androidx.collection.MutableLongSet import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column @@ -41,6 +42,7 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Offset @@ -50,8 +52,14 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingSource +import androidx.paging.PagingState +import androidx.paging.cachedIn import androidx.paging.compose.collectAsLazyPagingItems +import androidx.paging.filter import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -76,23 +84,30 @@ import com.hippo.ehviewer.ui.startDownload import com.hippo.ehviewer.ui.tools.asyncState import com.hippo.ehviewer.ui.tools.awaitConfirmationOrCancel import com.hippo.ehviewer.ui.tools.awaitSelectItem +import com.hippo.ehviewer.ui.tools.foldToLoadResult +import com.hippo.ehviewer.ui.tools.rememberInVM +import com.hippo.ehviewer.ui.tools.rememberSerializable import com.hippo.ehviewer.ui.tools.thenIf import com.hippo.ehviewer.util.mapToLongArray import com.hippo.ehviewer.util.takeAndClear import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator +import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withUIContext import kotlin.math.roundToInt import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map import moe.tarsin.coroutines.onEachLatest +import moe.tarsin.coroutines.runSuspendCatching import moe.tarsin.coroutines.runSwallowingWithUI import moe.tarsin.launch import moe.tarsin.navigate @Destination @Composable -fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, viewModel: FavoritesViewModel = viewModel()) = Screen(navigator) { +fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = Screen(navigator) { // Immutables val localFavName = stringResource(R.string.local_favorites) val cloudFavName = stringResource(R.string.cloud_favorites) @@ -100,7 +115,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, v val hasSignedIn by Settings.hasSignedIn.collectAsState() // Meta State - var urlBuilder by viewModel.urlBuilder + var urlBuilder by rememberSerializable { mutableStateOf(FavListUrlBuilder(favCat = Settings.recentFavCat)) } var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } @@ -120,7 +135,56 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, v } val density = LocalDensity.current val searchBarHint = stringResource(R.string.search_bar_hint, favCatName) - val data = viewModel.data.collectAsLazyPagingItems() + val data = rememberInVM { + snapshotFlow { urlBuilder.isLocal }.flatMapLatest { isLocalFav -> + if (isLocalFav) { + Pager(PagingConfig(20, jumpThreshold = 40)) { + val keywordNow = urlBuilder.keyword.orEmpty() + if (keywordNow.isBlank()) { + EhDB.localFavLazyList + } else { + EhDB.searchLocalFav(keywordNow) + } + } + } else { + Pager(PagingConfig(25)) { + object : PagingSource() { + override fun getRefreshKey(state: PagingState): String? = null + override suspend fun load(params: LoadParams) = withIOContext { + when (params) { + is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false) + is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true) + is LoadParams.Refresh -> { + val key = params.key + if (key.isNullOrBlank()) { + if (urlBuilder.jumpTo != null) { + urlBuilder.next ?: urlBuilder.setIndex("2", true) + } + } else { + urlBuilder.setIndex(key, false) + } + } + } + runSuspendCatching { + EhEngine.getFavorites(urlBuilder.build()) + }.foldToLoadResult { result -> + Settings.favCat = result.catArray.toTypedArray() + Settings.favCount = result.countArray.toIntArray() + Settings.favCloudCount = result.countArray.sum() + urlBuilder.jumpTo = null + LoadResult.Page(result.galleryInfoList, result.prev, result.next) + } + } + } + } + }.flow.map { pagingData -> + // https://github.com/FooIbar/EhViewer/issues/1190 + // Workaround for duplicate items when sorting by favorited time + val gidSet = MutableLongSet(50) + pagingData.filter { gidSet.add(it.gid) } + } + }.cachedIn(viewModelScope) + }.collectAsLazyPagingItems() fun refresh(newUrlBuilder: FavListUrlBuilder = urlBuilder.copy(jumpTo = null, prev = null, next = null)) { urlBuilder = newUrlBuilder @@ -128,7 +192,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, v } ProvideSideSheetContent { sheetState -> - val localFavCount by viewModel.localFavCount.collectAsState(0) + val localFavCount by rememberInVM { EhDB.localFavCount }.collectAsState(0) TopAppBar( title = { Text(text = stringResource(id = R.string.collections)) }, windowInsets = WindowInsets(), diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt deleted file mode 100644 index ee75456990..0000000000 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt +++ /dev/null @@ -1,84 +0,0 @@ -package com.hippo.ehviewer.ui.screen - -import androidx.collection.MutableLongSet -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.snapshotFlow -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.serialization.saved -import androidx.lifecycle.viewModelScope -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingSource -import androidx.paging.PagingState -import androidx.paging.cachedIn -import androidx.paging.filter -import androidx.savedstate.compose.serialization.serializers.MutableStateSerializer -import com.hippo.ehviewer.EhDB -import com.hippo.ehviewer.Settings -import com.hippo.ehviewer.client.EhEngine -import com.hippo.ehviewer.client.data.BaseGalleryInfo -import com.hippo.ehviewer.client.data.FavListUrlBuilder -import com.hippo.ehviewer.ui.tools.foldToLoadResult -import eu.kanade.tachiyomi.util.lang.withIOContext -import kotlinx.coroutines.flow.flatMapLatest -import kotlinx.coroutines.flow.map -import moe.tarsin.coroutines.runSuspendCatching - -class FavoritesViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { - val urlBuilder by savedStateHandle.saved(MutableStateSerializer()) { - mutableStateOf(FavListUrlBuilder(favCat = Settings.recentFavCat)) - } - - val localFavCount = EhDB.localFavCount - - val data = snapshotFlow { urlBuilder.value.isLocal }.flatMapLatest { isLocalFav -> - if (isLocalFav) { - Pager(PagingConfig(20, jumpThreshold = 40)) { - val keywordNow = urlBuilder.value.keyword.orEmpty() - if (keywordNow.isBlank()) { - EhDB.localFavLazyList - } else { - EhDB.searchLocalFav(keywordNow) - } - } - } else { - Pager(PagingConfig(25)) { - object : PagingSource() { - override fun getRefreshKey(state: PagingState): String? = null - override suspend fun load(params: LoadParams) = withIOContext { - val urlBuilder = urlBuilder.value - when (params) { - is LoadParams.Prepend -> urlBuilder.setIndex(params.key, isNext = false) - is LoadParams.Append -> urlBuilder.setIndex(params.key, isNext = true) - is LoadParams.Refresh -> { - val key = params.key - if (key.isNullOrBlank()) { - if (urlBuilder.jumpTo != null) { - urlBuilder.next ?: urlBuilder.setIndex("2", true) - } - } else { - urlBuilder.setIndex(key, false) - } - } - } - runSuspendCatching { - EhEngine.getFavorites(urlBuilder.build()) - }.foldToLoadResult { result -> - Settings.favCat = result.catArray.toTypedArray() - Settings.favCount = result.countArray.toIntArray() - Settings.favCloudCount = result.countArray.sum() - urlBuilder.jumpTo = null - LoadResult.Page(result.galleryInfoList, result.prev, result.next) - } - } - } - } - }.flow.map { pagingData -> - // https://github.com/FooIbar/EhViewer/issues/1190 - // Workaround for duplicate items when sorting by favorited time - val gidSet = MutableLongSet(50) - pagingData.filter { gidSet.add(it.gid) } - } - }.cachedIn(viewModelScope) -}