From 8207be59401f3666982fda823a9e72ca51756e67 Mon Sep 17 00:00:00 2001 From: FooIbar <118464521+FooIbar@users.noreply.github.com> Date: Sun, 25 May 2025 22:27:16 +0800 Subject: [PATCH] Reapply "Migrate from Parcelize to kotlinx.serialization" (#2357) * Reapply "Migrate from Parcelize to kotlinx.serialization" (#2356) This reverts commit c2e53cc8b915672b7f8b9b7df7749f449a5db2f0. * Fix stale states captured by lambdas in ViewModels --- app/build.gradle.kts | 1 - .../ehviewer/client/data/BaseGalleryInfo.kt | 5 +- .../ehviewer/client/data/FavListUrlBuilder.kt | 10 ++- .../ehviewer/client/data/GalleryComment.kt | 6 +- .../client/data/GalleryCommentList.kt | 6 +- .../ehviewer/client/data/GalleryDetail.kt | 5 +- .../ehviewer/client/data/GalleryPreview.kt | 6 +- .../hippo/ehviewer/client/data/GalleryTag.kt | 6 +- .../ehviewer/client/data/GalleryTagGroup.kt | 6 +- .../ehviewer/client/data/ListUrlBuilder.kt | 7 +- .../ehviewer/client/data/TagNamespace.kt | 6 +- .../ehviewer/client/parser/HomeParser.kt | 13 +-- .../ehviewer/download/DownloadService.kt | 28 +++++-- .../ehviewer/shortcuts/ShortcutsActivity.kt | 6 +- .../com/hippo/ehviewer/ui/CommonOperations.kt | 22 +---- .../com/hippo/ehviewer/ui/MainActivity.kt | 16 ++-- .../hippo/ehviewer/ui/main/AdvanceTable.kt | 5 +- .../hippo/ehviewer/ui/reader/ReaderScreen.kt | 17 ++-- .../ui/screen/DownloadsFilterState.kt | 7 +- .../ehviewer/ui/screen/DownloadsScreen.kt | 25 ++---- .../ehviewer/ui/screen/FavoritesScreen.kt | 77 ++--------------- .../ehviewer/ui/screen/FavoritesViewModel.kt | 84 +++++++++++++++++++ .../ui/screen/GalleryCommentsScreen.kt | 2 +- .../ehviewer/ui/screen/GalleryDetailScreen.kt | 10 +-- .../ehviewer/ui/screen/GalleryListScreen.kt | 68 +++------------ .../ui/screen/GalleryListViewModel.kt | 69 +++++++++++++++ .../com/hippo/ehviewer/ui/tools/State.kt | 12 +++ build.gradle.kts | 1 - gradle/libs.versions.toml | 1 - 29 files changed, 263 insertions(+), 264 deletions(-) create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt create mode 100644 app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index b6bc75ee4..c76c5837c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -10,7 +10,6 @@ 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/client/data/BaseGalleryInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt index 8ba2b13dc..99b954fec 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/BaseGalleryInfo.kt @@ -15,7 +15,6 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity import androidx.room.Ignore @@ -23,10 +22,8 @@ import androidx.room.PrimaryKey import androidx.room.TypeConverter import androidx.room.TypeConverters import com.hippo.ehviewer.client.data.GalleryInfo.Companion.NOT_FAVORITED -import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable -@Parcelize @Serializable @Entity(tableName = "GALLERIES") @TypeConverters(SimpleTagsConverter::class) @@ -88,7 +85,7 @@ data class BaseGalleryInfo( @Ignore override var favoriteNote: String? = null, -) : GalleryInfo, Parcelable +) : GalleryInfo class SimpleTagsConverter { @TypeConverter diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/FavListUrlBuilder.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/FavListUrlBuilder.kt index 493b811c1..9b8db11f4 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/FavListUrlBuilder.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/FavListUrlBuilder.kt @@ -15,21 +15,23 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.addQueryParameter import com.hippo.ehviewer.client.addQueryParameterIfNotBlank import com.hippo.ehviewer.client.ehUrl -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable -@Parcelize +@Serializable data class FavListUrlBuilder( val favCat: Int = FAV_CAT_ALL, val keyword: String? = null, var jumpTo: String? = null, var prev: String? = null, var next: String? = null, -) : Parcelable { +) { + val isLocal + get() = favCat == FAV_CAT_LOCAL + fun setIndex(index: String?, isNext: Boolean) { next = index.takeIf { isNext } prev = index.takeUnless { isNext } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryComment.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryComment.kt index 15af5f588..8941f4d2d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryComment.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryComment.kt @@ -15,10 +15,6 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize data class GalleryComment( val id: Long, val score: Int, @@ -33,4 +29,4 @@ data class GalleryComment( val user: String?, val comment: String, val lastEdited: Long, -) : Parcelable +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryCommentList.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryCommentList.kt index a0901f751..71fbd18bb 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryCommentList.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryCommentList.kt @@ -15,11 +15,7 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize data class GalleryCommentList( val comments: List, val hasMore: Boolean, -) : Parcelable +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt index 28cfabdca..e50aaed91 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryDetail.kt @@ -15,10 +15,8 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable import androidx.compose.ui.util.fastFlatMap import com.hippo.ehviewer.client.data.GalleryInfo.Companion.S_LANGS -import kotlinx.parcelize.Parcelize private val LANGUAGES = arrayOf( "English", @@ -37,7 +35,6 @@ private val LANGUAGES = arrayOf( "Dutch", ) -@Parcelize data class GalleryDetail( val galleryInfo: BaseGalleryInfo = BaseGalleryInfo(), var apiUid: Long = -1L, @@ -55,7 +52,7 @@ data class GalleryDetail( val tagGroups: List, var comments: GalleryCommentList, val previewList: List, -) : GalleryInfo by galleryInfo, Parcelable { +) : GalleryInfo by galleryInfo { fun fillInfo() { val index = LANGUAGES.indexOf(language) if (index != -1) simpleLanguage = S_LANGS[index] diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt index 9b1a8822f..50c373c39 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryPreview.kt @@ -15,17 +15,14 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable import com.hippo.ehviewer.client.keyToUrl -import kotlinx.parcelize.Parcelize -sealed interface GalleryPreview : Parcelable { +sealed interface GalleryPreview { val url: String val imageKey: String val position: Int } -@Parcelize data class V1GalleryPreview( override val imageKey: String, override val position: Int, @@ -33,7 +30,6 @@ data class V1GalleryPreview( override val url get() = keyToUrl(imageKey) } -@Parcelize data class V2GalleryPreview( override val url: String, override val imageKey: String, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt index 04b348678..7abd23d15 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTag.kt @@ -1,8 +1,5 @@ package com.hippo.ehviewer.client.data -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - enum class VoteStatus(val display: String) { NONE(""), UP("+1"), @@ -15,9 +12,8 @@ enum class PowerStatus { WEAK, } -@Parcelize data class GalleryTag( val text: String, val power: PowerStatus, val vote: VoteStatus, -) : Parcelable +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt index 1ac423e1f..e05cfebcc 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryTagGroup.kt @@ -15,11 +15,7 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize data class GalleryTagGroup( val nameSpace: TagNamespace, val tags: List, -) : Parcelable +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/ListUrlBuilder.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/ListUrlBuilder.kt index 3ffc375e0..3b3591b29 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/ListUrlBuilder.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/ListUrlBuilder.kt @@ -15,7 +15,6 @@ */ package com.hippo.ehviewer.client.data -import android.os.Parcelable import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhUtils @@ -26,9 +25,9 @@ import com.hippo.ehviewer.dao.QuickSearch import com.hippo.ehviewer.ui.main.AdvanceTable import io.ktor.http.Parameters import kotlin.text.toIntOrNull -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable -@Parcelize +@Serializable data class ListUrlBuilder( var mode: Int = MODE_NORMAL, private var prev: String? = null, @@ -44,7 +43,7 @@ data class ListUrlBuilder( var minRating: Int = -1, var pageFrom: Int = -1, var pageTo: Int = -1, -) : Parcelable { +) { fun setIndex(index: String, isNext: Boolean = true) { next = index.takeIf { isNext } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/TagNamespace.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/TagNamespace.kt index 78c80a35c..10c2bc253 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/TagNamespace.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/TagNamespace.kt @@ -1,10 +1,6 @@ package com.hippo.ehviewer.client.data -import android.os.Parcelable -import kotlinx.parcelize.Parcelize - -@Parcelize -sealed class TagNamespace(val value: String, val prefix: String?) : Parcelable { +sealed class TagNamespace(val value: String, val prefix: String?) { object Artist : TagNamespace("artist", "a") object Cosplayer : TagNamespace("cosplayer", "cos") object Character : TagNamespace("character", "c") diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt index 165720fa6..7a6a866a6 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/parser/HomeParser.kt @@ -1,12 +1,9 @@ package com.hippo.ehviewer.client.parser -import android.os.Parcelable import com.hippo.ehviewer.client.exception.InsufficientFundsException import com.hippo.ehviewer.client.exception.ParseException import java.nio.ByteBuffer import kotlin.random.Random -import kotlinx.parcelize.Parcelize -import kotlinx.serialization.Serializable object HomeParser { private val TorrentKeyRegex = Regex("Your current key is: <[^>]*>([^<]*)<") @@ -35,15 +32,11 @@ object HomeParser { throw ParseException("Parse funds error") } - @Parcelize - data class Result(val limits: Limits, val funds: Funds, private val id: Int = Random.nextInt()) : Parcelable + data class Result(val limits: Limits, val funds: Funds, private val id: Int = Random.nextInt()) } -@Parcelize -data class Funds(val gp: Int, val credit: Int) : Parcelable +data class Funds(val gp: Int, val credit: Int) -@Parcelize -@Serializable -data class Limits(val current: Int, val maximum: Int, val resetCost: Int) : Parcelable +data class Limits(val current: Int, val maximum: Int, val resetCost: Int) private external fun parseLimit(body: ByteBuffer, limit: Int = body.limit()): Int diff --git a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadService.kt b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadService.kt index fe8d2aa0f..16a7cd417 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadService.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/download/DownloadService.kt @@ -18,6 +18,7 @@ package com.hippo.ehviewer.download import android.Manifest import android.app.PendingIntent import android.app.Service +import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.os.Bundle @@ -27,6 +28,9 @@ import androidx.core.app.NotificationChannelCompat import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import androidx.core.app.ServiceCompat +import androidx.core.content.ContextCompat +import androidx.savedstate.serialization.decodeFromSavedState +import androidx.savedstate.serialization.encodeToSavedState import com.hippo.ehviewer.R import com.hippo.ehviewer.client.EhUtils import com.hippo.ehviewer.client.data.BaseGalleryInfo @@ -34,7 +38,6 @@ import com.hippo.ehviewer.dao.DownloadInfo import com.hippo.ehviewer.ui.MainActivity import com.hippo.ehviewer.util.FileUtils import com.hippo.ehviewer.util.ReadableTime -import com.hippo.ehviewer.util.getParcelableExtraCompat import com.hippo.ehviewer.util.unsafeLazy import eu.kanade.tachiyomi.util.system.logcat import kotlinx.coroutines.CoroutineScope @@ -95,11 +98,9 @@ class DownloadService : private suspend fun handleIntent(intent: Intent?) { when (intent?.action) { ACTION_START -> { - val gi = intent.getParcelableExtraCompat(KEY_GALLERY_INFO) + val gi = intent.getBundleExtra(KEY_GALLERY_INFO) ?: return val label = intent.getStringExtra(KEY_LABEL) - if (gi != null) { - deferredMgr.await().startDownload(gi, label) - } + deferredMgr.await().startDownload(decodeFromSavedState(gi), label) } ACTION_START_RANGE -> { @@ -454,6 +455,23 @@ class DownloadService : private var sFinishedCount = 0 private var sDownloadedCount = 0 + context(ctx: Context) + inline fun startService(action: String, intentBuilder: Intent.() -> Unit = {}) { + val intent = Intent(ctx, DownloadService::class.java).setAction(action).apply(intentBuilder) + ContextCompat.startForegroundService(ctx, intent) + } + + context(_: Context) + fun startDownload(info: BaseGalleryInfo, label: String? = null) = startService(ACTION_START) { + putExtra(KEY_GALLERY_INFO, encodeToSavedState(info)) + label?.let { putExtra(KEY_LABEL, it) } + } + + context(_: Context) + fun startRangeDownload(gidList: LongArray) = startService(ACTION_START_RANGE) { + putExtra(KEY_GID_LIST, gidList) + } + fun clear() { sFailedCount = 0 sFinishedCount = 0 diff --git a/app/src/main/kotlin/com/hippo/ehviewer/shortcuts/ShortcutsActivity.kt b/app/src/main/kotlin/com/hippo/ehviewer/shortcuts/ShortcutsActivity.kt index b1eafe5c0..f57b05ee0 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/shortcuts/ShortcutsActivity.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/shortcuts/ShortcutsActivity.kt @@ -15,10 +15,8 @@ */ package com.hippo.ehviewer.shortcuts -import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity -import androidx.core.content.ContextCompat import com.hippo.ehviewer.download.DownloadService /** @@ -27,8 +25,8 @@ import com.hippo.ehviewer.download.DownloadService class ShortcutsActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - when (intent?.action) { - DownloadService.ACTION_START_ALL, DownloadService.ACTION_STOP_ALL -> ContextCompat.startForegroundService(this, Intent(this, DownloadService::class.java).setAction(intent.action)) + when (val action = intent?.action) { + DownloadService.ACTION_START_ALL, DownloadService.ACTION_STOP_ALL -> DownloadService.startService(action) } finish() } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt index 02445eaa0..e2a627c7a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/CommonOperations.kt @@ -17,8 +17,6 @@ package com.hippo.ehviewer.ui import android.Manifest import android.content.Context -import android.content.Intent -import android.net.Uri import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth @@ -37,7 +35,6 @@ import androidx.compose.material3.Text import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -124,11 +121,8 @@ suspend fun startDownload(forceDefault: Boolean, vararg galleryInfos: BaseGaller } val (toStart, toAdd) = galleryInfos.partition { DownloadManager.containDownloadInfo(it.gid) } if (toStart.isNotEmpty()) { - val intent = Intent(context, DownloadService::class.java) - intent.action = DownloadService.ACTION_START_RANGE val list = toStart.mapToLongArray(GalleryInfo::gid) - intent.putExtra(DownloadService.KEY_GID_LIST, list) - ContextCompat.startForegroundService(context, intent) + DownloadService.startRangeDownload(list) } if (toAdd.isEmpty()) { return tip(R.string.added_to_download_list) @@ -148,11 +142,7 @@ suspend fun startDownload(forceDefault: Boolean, vararg galleryInfos: BaseGaller if (justStart) { // Got default label for (gi in toAdd) { - val intent = Intent(context, DownloadService::class.java) - intent.action = DownloadService.ACTION_START - intent.putExtra(DownloadService.KEY_LABEL, label) - intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi) - ContextCompat.startForegroundService(context, intent) + DownloadService.startDownload(gi, label) } // Notify tip(R.string.added_to_download_list) @@ -173,11 +163,7 @@ suspend fun startDownload(forceDefault: Boolean, vararg galleryInfos: BaseGaller val label1 = if (selected == 0) null else items[selected].takeIf { DownloadManager.containLabel(it) } // Start download for (gi in toAdd) { - val intent = Intent(context, DownloadService::class.java) - intent.action = DownloadService.ACTION_START - intent.putExtra(DownloadService.KEY_LABEL, label1) - intent.putExtra(DownloadService.KEY_GALLERY_INFO, gi) - ContextCompat.startForegroundService(context, intent) + DownloadService.startDownload(gi, label1) } // Save settings if (checked) { @@ -286,7 +272,7 @@ context(_: DestinationsNavigator) fun navToReader(info: BaseGalleryInfo, page: Int = -1) = navToReader(ReaderScreenArgs.Gallery(info, page)) context(_: DestinationsNavigator) -fun navToReader(uri: Uri) = navToReader(ReaderScreenArgs.Archive(uri)) +fun navToReader(path: String) = navToReader(ReaderScreenArgs.Archive(path)) context(nav: DestinationsNavigator) private fun navToReader(args: ReaderScreenArgs) = nav.navigate(ReaderScreenDestination(args)) { launchSingleTop = true } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt index 4918a0593..97ac08b8b 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/MainActivity.kt @@ -258,32 +258,32 @@ class MainActivity : EhActivity() { LaunchedEffect(Unit) { intentFlow.collect { intent -> when (intent.action) { - Intent.ACTION_VIEW -> { + Intent.ACTION_VIEW -> with(navigator) { val uri = intent.data ?: return@collect when (uri.scheme) { - SCHEME_CONTENT, SCHEME_FILE -> with(navigator) { navToReader(uri) } - + SCHEME_FILE -> navToReader(uri.path!!) + SCHEME_CONTENT -> navToReader(uri.toString()) else -> { val url = uri.toString() - if (!with(navigator) { navWithUrl(url) }) { + if (!navWithUrl(url)) { val new = awaitInputText(initial = url, title = cannotParse) addTextToClipboard(new) } } } } - Intent.ACTION_SEND -> { + Intent.ACTION_SEND -> with(navigator) { val type = intent.type if ("text/plain" == type) { val keyword = intent.getStringExtra(Intent.EXTRA_TEXT) - if (keyword != null && !with(navigator) { navWithUrl(keyword) }) { - navigator.navigate(ListUrlBuilder(mKeyword = keyword).asDst()) + if (keyword != null && !navWithUrl(keyword)) { + navigate(ListUrlBuilder(mKeyword = keyword).asDst()) } } else if (type != null && type.startsWith("image/")) { val uri = intent.getParcelableExtraCompat(Intent.EXTRA_STREAM) if (null != uri) { val hash = withIOContext { uri.toOkioPath().sha1() } - navigator.navigate( + navigate( ListUrlBuilder( mode = ListUrlBuilder.MODE_IMAGE_SEARCH, hash = hash, diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AdvanceTable.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AdvanceTable.kt index 5787cb876..154db399c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AdvanceTable.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/AdvanceTable.kt @@ -1,7 +1,5 @@ package com.hippo.ehviewer.ui.main -import android.os.Parcelable -import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable import kotlinx.serialization.Transient @@ -13,7 +11,6 @@ object AdvanceTable { const val SFT = 0x10 } -@Parcelize @Serializable data class AdvancedSearchOption( @Transient @@ -21,4 +18,4 @@ data class AdvancedSearchOption( val minRating: Int = 0, val fromPage: Int = 0, val toPage: Int = 0, -) : Parcelable +) diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt index 3b6a2d6a9..baf8de85e 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/ReaderScreen.kt @@ -1,8 +1,6 @@ package com.hippo.ehviewer.ui.reader import android.content.Context -import android.net.Uri -import android.os.Parcelable import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.background @@ -83,7 +81,6 @@ import com.hippo.ehviewer.ui.tools.dialog import com.hippo.ehviewer.ui.tools.thenIf import com.hippo.ehviewer.util.displayString import com.hippo.ehviewer.util.hasAds -import com.hippo.files.toOkioPath import com.ramcosta.composedestinations.annotation.Destination import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator @@ -100,17 +97,19 @@ import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onCompletion import kotlinx.coroutines.flow.sample import kotlinx.coroutines.suspendCancellableCoroutine -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable import moe.tarsin.kt.unreachable import moe.tarsin.launch import moe.tarsin.string +import okio.Path.Companion.toPath -sealed interface ReaderScreenArgs : Parcelable { - @Parcelize +@Serializable +sealed interface ReaderScreenArgs { + @Serializable data class Gallery(val info: BaseGalleryInfo, val page: Int) : ReaderScreenArgs - @Parcelize - data class Archive(val uri: Uri) : ReaderScreenArgs + @Serializable + data class Archive(val path: String) : ReaderScreenArgs } @Composable @@ -382,7 +381,7 @@ suspend inline fun usePageLoader(args: ReaderScreenArgs, crossinline block: } } is ReaderScreenArgs.Archive -> useArchivePageLoader( - args.uri.toOkioPath(), + args.path.toPath(), passwdProvider = { invalidator -> awaitInputText( title = string(R.string.archive_need_passwd), diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsFilterState.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsFilterState.kt index 7676ee209..af461a3b8 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsFilterState.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsFilterState.kt @@ -1,18 +1,17 @@ package com.hippo.ehviewer.ui.screen -import android.os.Parcelable import com.hippo.ehviewer.dao.DownloadInfo import com.hippo.ehviewer.download.DownloadsFilterMode import com.hippo.ehviewer.util.containsIgnoreCase -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable -@Parcelize +@Serializable data class DownloadsFilterState( val mode: DownloadsFilterMode, val label: String?, val state: Int = -1, val keyword: String = "", -) : Parcelable +) fun DownloadsFilterState.take(info: DownloadInfo) = mode.take(info, label) && (state == -1 || info.state == state) && diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt index 0c738c460..63ba50d41 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/DownloadsScreen.kt @@ -1,8 +1,6 @@ package com.hippo.ehviewer.ui.screen -import android.app.Activity import android.content.Context -import android.content.Intent import android.view.ViewConfiguration import androidx.compose.animation.AnimatedVisibilityScope import androidx.compose.animation.Crossfade @@ -79,7 +77,6 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.dimensionResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat import arrow.core.partially1 import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R @@ -121,6 +118,7 @@ import com.hippo.ehviewer.ui.tools.awaitSelectItemWithCheckBox import com.hippo.ehviewer.ui.tools.awaitSingleChoice import com.hippo.ehviewer.ui.tools.rememberHapticFeedback 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 @@ -145,7 +143,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = var gridView by Settings.gridView.asMutableState() var sortMode by Settings.downloadSortMode.asMutableState() val filterMode by Settings.downloadFilterMode.collectAsState { DownloadsFilterMode.from(it) } - var filterState by rememberSaveable { mutableStateOf(DownloadsFilterState(filterMode, Settings.recentDownloadLabel.value)) } + var filterState by rememberSerializable { mutableStateOf(DownloadsFilterState(filterMode, Settings.recentDownloadLabel.value)) } var invalidateKey by rememberSaveable { mutableStateOf(false) } var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } @@ -483,9 +481,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = text = { Text(text = stringResource(id = R.string.download_start_all)) }, onClick = { expanded = false - val intent = Intent(contextOf(), DownloadService::class.java) - intent.action = DownloadService.ACTION_START_ALL - ContextCompat.startForegroundService(contextOf(), intent) + DownloadService.startService(DownloadService.ACTION_START_ALL) }, ) DropdownMenuItem( @@ -517,10 +513,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = onClick = { expanded = false val gidList = list.filter { it.state != DownloadInfo.STATE_FINISH }.asReversed().mapToLongArray(DownloadInfo::gid) - val intent = Intent(contextOf(), DownloadService::class.java) - intent.action = DownloadService.ACTION_START_RANGE - intent.putExtra(DownloadService.KEY_GID_LIST, gidList) - ContextCompat.startForegroundService(contextOf(), intent) + DownloadService.startRangeDownload(gidList) }, ) } @@ -602,10 +595,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = checkedInfoMap[info.gid] = info }, onStart = { - val intent = Intent(contextOf(), DownloadService::class.java) - intent.action = DownloadService.ACTION_START - intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo) - ContextCompat.startForegroundService(contextOf(), intent) + DownloadService.startDownload(info.galleryInfo) }, onStop = { launchIO { DownloadManager.stopDownload(info.gid) } }, info = info, @@ -696,10 +686,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) = } onClick(Icons.Default.PlayArrow) { val gidList = checkedInfoMap.takeAndClear().mapToLongArray(DownloadInfo::gid) - val intent = Intent(contextOf(), DownloadService::class.java) - intent.action = DownloadService.ACTION_START_RANGE - intent.putExtra(DownloadService.KEY_GID_LIST, gidList) - ContextCompat.startForegroundService(contextOf(), intent) + DownloadService.startRangeDownload(gidList) } onClick(Icons.Default.Pause) { val gidList = checkedInfoMap.takeAndClear().mapToLongArray(DownloadInfo::gid) 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 1e8d43937..19c534656 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,7 +2,6 @@ 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 @@ -51,14 +50,8 @@ 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.viewModelScope -import androidx.paging.Pager -import androidx.paging.PagingConfig -import androidx.paging.PagingSource -import androidx.paging.PagingState -import androidx.paging.cachedIn +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.paging.compose.collectAsLazyPagingItems -import androidx.paging.filter import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -83,28 +76,23 @@ 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.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.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) = Screen(navigator) { +fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator, viewModel: FavoritesViewModel = viewModel()) = Screen(navigator) { // Immutables val localFavName = stringResource(R.string.local_favorites) val cloudFavName = stringResource(R.string.cloud_favorites) @@ -112,13 +100,12 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = val hasSignedIn by Settings.hasSignedIn.collectAsState() // Meta State - var urlBuilder by rememberSaveable { mutableStateOf(FavListUrlBuilder(favCat = Settings.recentFavCat)) } + var urlBuilder by viewModel.urlBuilder var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } // Derived State val keyword = urlBuilder.keyword - val isLocalFav = urlBuilder.favCat == FavListUrlBuilder.FAV_CAT_LOCAL val favCatName = remember(urlBuilder) { when (val favCat = urlBuilder.favCat) { in 0..9 -> Settings.favCat[favCat] @@ -132,58 +119,8 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = stringResource(R.string.favorites_title_2, favCatName, keyword) } val density = LocalDensity.current - val localFavCountFlow = rememberInVM { EhDB.localFavCount } val searchBarHint = stringResource(R.string.search_bar_hint, favCatName) - val data = rememberInVM(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() + val data = viewModel.data.collectAsLazyPagingItems() fun refresh(newUrlBuilder: FavListUrlBuilder = urlBuilder.copy(jumpTo = null, prev = null, next = null)) { urlBuilder = newUrlBuilder @@ -191,7 +128,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = } ProvideSideSheetContent { sheetState -> - val localFavCount by localFavCountFlow.collectAsState(0) + val localFavCount by viewModel.localFavCount.collectAsState(0) TopAppBar( title = { Text(text = stringResource(id = R.string.collections)) }, windowInsets = WindowInsets(), @@ -249,7 +186,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = }, title = title, searchFieldHint = searchBarHint, - tagNamespace = !isLocalFav, + tagNamespace = !urlBuilder.isLocal, searchBarOffsetY = { searchBarOffsetY }, trailingIcon = { val sheetState = LocalSideSheetState.current @@ -366,7 +303,7 @@ fun AnimatedVisibilityScope.FavouritesScreen(navigator: DestinationsNavigator) = autoCancel = !selectMode, ) { if (!selectMode) { - if (isLocalFav) { + if (urlBuilder.isLocal) { onClick(Icons.Default.Shuffle) { EhDB.randomLocalFav()?.let { info -> withUIContext { navigate(info.asDst()) } 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 new file mode 100644 index 000000000..ee7545699 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/FavoritesViewModel.kt @@ -0,0 +1,84 @@ +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) +} diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryCommentsScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryCommentsScreen.kt index a14c66ea7..2529600d3 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryCommentsScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryCommentsScreen.kt @@ -186,7 +186,7 @@ fun AnimatedVisibilityScope.GalleryCommentsScreen(gid: Long, navigator: Destinat val userCommentBackField = remember { mutableStateOf(TextFieldValue()) } var userComment by userCommentBackField var commentId by remember { mutableLongStateOf(-1) } - var comments by rememberSaveable { mutableStateOf(galleryDetail.comments) } + var comments by remember(galleryDetail) { mutableStateOf(galleryDetail.comments) } LaunchedEffect(comments) { galleryDetail.comments = comments } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt index e660a9dd7..8143d0a30 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailScreen.kt @@ -1,6 +1,5 @@ package com.hippo.ehviewer.ui.screen -import android.os.Parcelable import androidx.activity.result.contract.ActivityResultContracts.CreateDocument import androidx.collection.SieveCache import androidx.compose.animation.AnimatedVisibilityScope @@ -75,7 +74,7 @@ import com.ramcosta.composedestinations.annotation.RootGraph import com.ramcosta.composedestinations.navigation.DestinationsNavigator import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.system.logcat -import kotlinx.parcelize.Parcelize +import kotlinx.serialization.Serializable import moe.tarsin.coroutines.runSuspendCatching import moe.tarsin.launchIO import moe.tarsin.snackbar @@ -85,14 +84,15 @@ typealias VoteTag = suspend GalleryDetail.(String, Int) -> Unit val detailCache = SieveCache(25) -sealed interface GalleryDetailScreenArgs : Parcelable +@Serializable +sealed interface GalleryDetailScreenArgs -@Parcelize +@Serializable data class GalleryInfoArgs( val galleryInfo: BaseGalleryInfo, ) : GalleryDetailScreenArgs -@Parcelize +@Serializable data class TokenArgs( val gid: Long, val token: String, 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 e4409e50f..65f83b27c 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,13 +62,9 @@ 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.viewModelScope +import androidx.lifecycle.createSavedStateHandle +import androidx.lifecycle.viewmodel.compose.viewModel 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 @@ -76,10 +72,8 @@ 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 @@ -117,9 +111,7 @@ 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.thenIf import com.hippo.ehviewer.util.FavouriteStatusRouter @@ -127,13 +119,11 @@ 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 @@ -160,9 +150,13 @@ fun AnimatedVisibilityScope.ToplistScreen(navigator: DestinationsNavigator) = Ga @Destination @Composable -fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: DestinationsNavigator) = Screen(navigator) { +fun AnimatedVisibilityScope.GalleryListScreen( + lub: ListUrlBuilder, + navigator: DestinationsNavigator, + viewModel: GalleryListViewModel = viewModel { GalleryListViewModel(lub, createSavedStateHandle()) }, +) = Screen(navigator) { val searchFieldState = rememberTextFieldState() - var urlBuilder by rememberSaveable { mutableStateOf(lub) } + var urlBuilder by viewModel.urlBuilder var searchBarExpanded by rememberSaveable { mutableStateOf(false) } var searchBarOffsetY by remember { mutableIntStateOf(0) } val animateItems by Settings.animateItems.collectAsState() @@ -193,49 +187,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De 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 = 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() + val data = viewModel.data.collectAsLazyPagingItems() ReportDrawnWhen { data.loadState.refresh !is LoadState.Loading } FavouriteStatusRouter.Observe(data) val listMode by Settings.listMode.collectAsState() @@ -660,7 +612,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De } } -private const val TOPLIST_PAGES = 200 +const val TOPLIST_PAGES = 200 context(_: Context) @Composable 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 new file mode 100644 index 000000000..66a14feb0 --- /dev/null +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryListViewModel.kt @@ -0,0 +1,69 @@ +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) +} 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 aacfbef6e..c83a56697 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 @@ -5,8 +5,12 @@ 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.rememberSaveable import androidx.compose.runtime.setValue +import androidx.savedstate.serialization.SavedStateConfiguration import kotlin.reflect.KMutableProperty0 +import kotlinx.serialization.KSerializer +import kotlinx.serialization.serializer val KMutableProperty0.observed: MutableState @Composable @@ -29,3 +33,11 @@ val MutableState.rememberedAccessor: KMutableProperty0 var value by this@rememberedAccessor }::value } + +@Composable +inline fun rememberSerializable( + vararg inputs: Any?, + stateSerializer: KSerializer = serializer(), + configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT, + noinline init: () -> MutableState, +) = rememberSaveable(inputs = inputs, stateSerializer, configuration, init) diff --git a/build.gradle.kts b/build.gradle.kts index 3db8dadc9..bb85bc7db 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,7 +2,6 @@ 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 751a5720d..e58ed23e9 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -136,7 +136,6 @@ 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" }