Reapply "Migrate from Parcelize to kotlinx.serialization" (#2357)
* Reapply "Migrate from Parcelize to kotlinx.serialization" (#2356)
This reverts commit c2e53cc8b9.
* Fix stale states captured by lambdas in ViewModels
This commit is contained in:
1 parent
c2e53cc8b9
commit
8207be5940
29 files changed
+263
-264
No files matched your search
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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<GalleryComment>,
|
||||
val hasMore: Boolean,
|
||||
) : Parcelable
|
||||
)
|
||||
@@ -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<GalleryTagGroup>,
|
||||
var comments: GalleryCommentList,
|
||||
val previewList: List<GalleryPreview>,
|
||||
) : GalleryInfo by galleryInfo, Parcelable {
|
||||
) : GalleryInfo by galleryInfo {
|
||||
fun fillInfo() {
|
||||
val index = LANGUAGES.indexOf(language)
|
||||
if (index != -1) simpleLanguage = S_LANGS[index]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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<GalleryTag>,
|
||||
) : Parcelable
|
||||
)
|
||||
@@ -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 }
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
@@ -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<BaseGalleryInfo>(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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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 }
|
||||
|
||||
@@ -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<Uri>(Intent.EXTRA_STREAM)
|
||||
if (null != uri) {
|
||||
val hash = withIOContext { uri.toOkioPath().sha1() }
|
||||
navigator.navigate(
|
||||
navigate(
|
||||
ListUrlBuilder(
|
||||
mode = ListUrlBuilder.MODE_IMAGE_SEARCH,
|
||||
hash = hash,
|
||||
|
||||
@@ -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
|
||||
)
|
||||
@@ -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 <T> 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),
|
||||
|
||||
@@ -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) &&
|
||||
|
||||
@@ -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<Activity>(), DownloadService::class.java)
|
||||
intent.action = DownloadService.ACTION_START_ALL
|
||||
ContextCompat.startForegroundService(contextOf<Activity>(), 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<Activity>(), DownloadService::class.java)
|
||||
intent.action = DownloadService.ACTION_START_RANGE
|
||||
intent.putExtra(DownloadService.KEY_GID_LIST, gidList)
|
||||
ContextCompat.startForegroundService(contextOf<Activity>(), intent)
|
||||
DownloadService.startRangeDownload(gidList)
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -602,10 +595,7 @@ fun AnimatedVisibilityScope.DownloadsScreen(navigator: DestinationsNavigator) =
|
||||
checkedInfoMap[info.gid] = info
|
||||
},
|
||||
onStart = {
|
||||
val intent = Intent(contextOf<Activity>(), DownloadService::class.java)
|
||||
intent.action = DownloadService.ACTION_START
|
||||
intent.putExtra(DownloadService.KEY_GALLERY_INFO, info.galleryInfo)
|
||||
ContextCompat.startForegroundService(contextOf<Activity>(), 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<Activity>(), DownloadService::class.java)
|
||||
intent.action = DownloadService.ACTION_START_RANGE
|
||||
intent.putExtra(DownloadService.KEY_GID_LIST, gidList)
|
||||
ContextCompat.startForegroundService(contextOf<Context>(), intent)
|
||||
DownloadService.startRangeDownload(gidList)
|
||||
}
|
||||
onClick(Icons.Default.Pause) {
|
||||
val gidList = checkedInfoMap.takeAndClear().mapToLongArray(DownloadInfo::gid)
|
||||
|
||||
@@ -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<RootGraph>
|
||||
@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<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = 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()) }
|
||||
|
||||
@@ -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<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = 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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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<Long, GalleryDetail>(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,
|
||||
|
||||
@@ -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<RootGraph>
|
||||
@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<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = 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
|
||||
|
||||
@@ -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<String, BaseGalleryInfo>() {
|
||||
override fun getRefreshKey(state: PagingState<String, BaseGalleryInfo>): String? = null
|
||||
override suspend fun load(params: LoadParams<String>) = 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)
|
||||
}
|
||||
@@ -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 <T> KMutableProperty0<T>.observed: MutableState<T>
|
||||
@Composable
|
||||
@@ -29,3 +33,11 @@ val <T> MutableState<T>.rememberedAccessor: KMutableProperty0<T>
|
||||
var value by this@rememberedAccessor
|
||||
}::value
|
||||
}
|
||||
|
||||
@Composable
|
||||
inline fun <reified T : Any> rememberSerializable(
|
||||
vararg inputs: Any?,
|
||||
stateSerializer: KSerializer<T> = serializer<T>(),
|
||||
configuration: SavedStateConfiguration = SavedStateConfiguration.DEFAULT,
|
||||
noinline init: () -> MutableState<T>,
|
||||
) = rememberSaveable(inputs = inputs, stateSerializer, configuration, init)
|
||||
@@ -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
|
||||
|
||||
@@ -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" }
|
||||
|
||||
Reference in new issue
Block a user