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 67390b0d3..18e632cc4 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 @@ -23,6 +23,7 @@ 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.IgnoredOnParcel import kotlinx.parcelize.Parcelize import kotlinx.serialization.Serializable @@ -90,6 +91,9 @@ data class BaseGalleryInfo( override var favoriteNote: String? = null, ) : GalleryInfo, Parcelable { + @IgnoredOnParcel + override val hasAds by lazy { simpleTags?.any { "extraneous ads" in it } == true } + constructor() : this(0, "") } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryInfo.kt b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryInfo.kt index 6b9c5a854..cde4ed643 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryInfo.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/client/data/GalleryInfo.kt @@ -37,6 +37,7 @@ interface GalleryInfo { var favoriteSlot: Int var favoriteName: String? var favoriteNote: String? + val hasAds: Boolean fun generateSLang() { simpleLanguage = simpleTags?.let { generateSLangFromTags(it) } @@ -141,6 +142,3 @@ fun GalleryInfo.findBaseInfo(): BaseGalleryInfo = when (this) { } fun GalleryInfo.asGalleryDetail(): GalleryDetail? = this as? GalleryDetail - -val GalleryInfo.hasAds - get() = simpleTags?.any { "extraneous ads" in it } ?: false 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 1d142165b..9b3e20693 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 @@ -18,7 +18,7 @@ package com.hippo.ehviewer.client.data import android.os.Parcelable import kotlinx.parcelize.Parcelize -interface GalleryPreview : Parcelable { +sealed interface GalleryPreview : Parcelable { val url: String val position: Int } 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 58603dbdb..deb3a4266 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 @@ -16,7 +16,6 @@ package com.hippo.ehviewer.client.data import android.os.Parcelable -import androidx.annotation.IntDef import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.EhUrl import com.hippo.ehviewer.client.EhUtils @@ -32,13 +31,12 @@ import kotlinx.parcelize.Parcelize @Parcelize data class ListUrlBuilder( - @get:Mode @Mode var mode: Int = MODE_NORMAL, - private var mPrev: String? = null, - var mNext: String? = null, + private var prev: String? = null, + var next: String? = null, // Reset to null after initial loading - var mJumpTo: String? = null, - private var mRange: Int = 0, + var jumpTo: String? = null, + private var range: Int = 0, var category: Int = EhUtils.NONE, private var mKeyword: String? = null, var hash: String? = null, @@ -47,24 +45,23 @@ data class ListUrlBuilder( var minRating: Int = -1, var pageFrom: Int = -1, var pageTo: Int = -1, - var imagePath: String? = null, ) : Parcelable { fun setIndex(index: String, isNext: Boolean = true) { - mNext = index.takeIf { isNext } - mPrev = index.takeUnless { isNext } - mRange = 0 + next = index.takeIf { isNext } + prev = index.takeUnless { isNext } + range = 0 } - fun setJumpTo(jumpTo: Int) { - mJumpTo = jumpTo.takeUnless { it == 0 }?.toString() + fun setJumpTo(to: Int) { + jumpTo = to.takeUnless { it == 0 }?.toString() } - fun setRange(range: Int) { - mRange = range - mPrev = null - mNext = null - mJumpTo = null + fun setRange(to: Int) { + range = to + prev = null + next = null + jumpTo = null } var keyword: String? @@ -81,7 +78,7 @@ data class ListUrlBuilder( minRating = q.minRating pageFrom = q.pageFrom pageTo = q.pageTo - mNext = q.name.substringAfterLast('@', "").ifEmpty { null } + next = q.name.substringAfterLast('@', "").ifEmpty { null } } fun toQuickSearch(name: String): QuickSearch = QuickSearch( @@ -277,10 +274,10 @@ data class ListUrlBuilder( } } addQueryParameterIfNotBlank("f_search", query) - addQueryParameterIfNotBlank("prev", mPrev) - addQueryParameterIfNotBlank("next", mNext) - addQueryParameterIfNotBlank("seek", mJumpTo) - addQueryParameterIfNotBlank("range", mRange.takeIf { it > 0 }?.toString()) + addQueryParameterIfNotBlank("prev", prev) + addQueryParameterIfNotBlank("next", next) + addQueryParameterIfNotBlank("seek", jumpTo) + addQueryParameterIfNotBlank("range", range.takeIf { it > 0 }?.toString()) // Advance search if (advanceSearch > 0 || minRating > 0 || pageFrom > 0 || pageTo > 0) { addQueryParameter("advsearch", "1") @@ -322,10 +319,10 @@ data class ListUrlBuilder( MODE_UPLOADER, MODE_TAG -> { val path = if (mode == MODE_UPLOADER) "uploader" else "tag" ehUrl(listOf(path, requireNotNull(mKeyword))) { - addQueryParameterIfNotBlank("prev", mPrev) - addQueryParameterIfNotBlank("next", mNext) - addQueryParameterIfNotBlank("seek", mJumpTo) - addQueryParameterIfNotBlank("range", mRange.takeIf { it > 0 }?.toString()) + addQueryParameterIfNotBlank("prev", prev) + addQueryParameterIfNotBlank("next", next) + addQueryParameterIfNotBlank("seek", jumpTo) + addQueryParameterIfNotBlank("range", range.takeIf { it > 0 }?.toString()) }.buildString() } @@ -333,27 +330,14 @@ data class ListUrlBuilder( MODE_IMAGE_SEARCH -> ehUrl { addQueryParameter("f_shash", requireNotNull(hash)) }.buildString() - MODE_TOPLIST -> { - ehUrl("toplist.php", EhUrl.DOMAIN_E) { - addQueryParameter("tl", requireNotNull(mKeyword)) - addQueryParameterIfNotBlank("p", mJumpTo) - }.buildString() - } + MODE_TOPLIST -> ehUrl("toplist.php", EhUrl.DOMAIN_E) { + addQueryParameter("tl", requireNotNull(mKeyword)) + addQueryParameterIfNotBlank("p", jumpTo) + }.buildString() else -> throw IllegalStateException("Unexpected value: $mode") } - @IntDef( - MODE_NORMAL, - MODE_UPLOADER, - MODE_TAG, - MODE_WHATS_HOT, - MODE_IMAGE_SEARCH, - MODE_SUBSCRIPTION, - MODE_TOPLIST, - ) - @Retention(AnnotationRetention.SOURCE) - private annotation class Mode companion object { const val MODE_NORMAL = 0x0 const val MODE_UPLOADER = 0x1 diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt index 7ab263562..26e674168 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/Extensions.kt @@ -9,12 +9,16 @@ import coil3.network.NetworkHeaders import coil3.network.httpHeaders import coil3.request.CachePolicy import coil3.request.ImageRequest +import coil3.request.allowHardware import coil3.size.Size +import com.hippo.ehviewer.BuildConfig import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.CHROME_ACCEPT import com.hippo.ehviewer.client.CHROME_ACCEPT_LANGUAGE +import com.hippo.ehviewer.client.data.GalleryDetail import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.client.data.GalleryPreview +import com.hippo.ehviewer.client.data.LargeGalleryPreview import com.hippo.ehviewer.client.data.NormalGalleryPreview import com.hippo.ehviewer.client.imageKey import com.hippo.ehviewer.spider.DownloadInfoMagics.encodeMagicRequestOrUrl @@ -34,11 +38,24 @@ fun ImageRequest.Builder.ehUrl(info: GalleryInfo) = apply { httpHeaders(header) } -fun ImageRequest.Builder.ehPreview(preview: GalleryPreview) = apply { - data(preview.url) - memoryCacheKey(preview.imageKey) - diskCacheKey(preview.imageKey) - if (preview is NormalGalleryPreview) size(Size.ORIGINAL) +context(GalleryDetail) +fun ImageRequest.Builder.ehPreview( + preview: GalleryPreview, +) = with(preview) { + data(url) + memoryCacheKey(imageKey) + diskCacheKey(imageKey) + when (preview) { + is NormalGalleryPreview -> size(Size.ORIGINAL) + is LargeGalleryPreview -> { + if (hasAds && detectAds(position, pages, BuildConfig.DEBUG)) { + size(Size.ORIGINAL) + detectQrCode(true) + allowHardware(false) + hardwareThreshold(0) + } + } + } httpHeaders(header) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/QrCodeInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/QrCodeInterceptor.kt index 0e42ec20b..4571a5db4 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/QrCodeInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/QrCodeInterceptor.kt @@ -1,12 +1,18 @@ package com.hippo.ehviewer.coil +import androidx.collection.SieveCache import coil3.Extras +import coil3.Image import coil3.getExtra import coil3.intercept.Interceptor +import coil3.memory.MemoryCache import coil3.request.ImageRequest import coil3.request.ImageResult import coil3.request.SuccessResult +import com.hippo.ehviewer.Settings import com.hippo.ehviewer.image.hasQrCode +import eu.kanade.tachiyomi.util.system.logcat +import moe.tarsin.coroutines.runSuspendCatching private val detectQrCodeKey = Extras.Key(default = false) @@ -14,6 +20,8 @@ fun ImageRequest.Builder.detectQrCode(enable: Boolean) = apply { extras[detectQrCodeKey] = enable } +private val lruCache = SieveCache(50) + val ImageRequest.detectQrCode: Boolean get() = getExtra(detectQrCodeKey) @@ -23,7 +31,11 @@ object QrCodeInterceptor : Interceptor { if (chain.request.detectQrCode && result is SuccessResult) { val image = result.image if (image is BitmapImageWithExtraInfo) { - val hasQrCode = hasQrCode(image.image.bitmap) + fun compute() = runSuspendCatching { hasQrCode(image.image.bitmap) }.onFailure { logcat(it) }.getOrThrow() + val hasQrCode = when (val key = result.memoryCacheKey) { + is MemoryCache.Key -> lruCache[key] ?: compute().also { lruCache[key] = it } + null -> compute() + } val new = image.copy(hasQrCode = hasQrCode) return result.copy(image = new) } @@ -31,3 +43,11 @@ object QrCodeInterceptor : Interceptor { return result } } + +val Image.hasQrCode + get() = when (this) { + is BitmapImageWithExtraInfo -> hasQrCode + else -> false + } + +fun detectAds(index: Int, size: Int, enable: Boolean = true) = index > size - 10 && Settings.stripExtraneousAds.value && enable diff --git a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt index 4b1ebcc8f..d38535b7f 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/ArchivePageLoader.kt @@ -16,8 +16,8 @@ package com.hippo.ehviewer.gallery import android.os.ParcelFileDescriptor -import com.hippo.ehviewer.Settings import com.hippo.ehviewer.Settings.archivePasswds +import com.hippo.ehviewer.coil.detectAds import com.hippo.ehviewer.image.ByteBufferSource import com.hippo.ehviewer.image.Image import com.hippo.ehviewer.jni.closeArchive @@ -73,7 +73,6 @@ class ArchivePageLoader( } override var size = 0 - private set override fun start() { super.start() @@ -87,17 +86,17 @@ class ArchivePageLoader( logcat(DEBUG_TAG) { "Close archive ${file.toUri().displayPath} successfully!" } } - private val mJobMap = hashMapOf() - private val mWorkerMutex = NamedMutex() - private val mSemaphore = Semaphore(4) + private val jobs = hashMapOf() + private val workerMutex = NamedMutex() + private val semaphore = Semaphore(4) override fun onRequest(index: Int) { - synchronized(mJobMap) { - val current = mJobMap[index] + synchronized(jobs) { + val current = jobs[index] if (current?.isActive != true) { - mJobMap[index] = launch { - mWorkerMutex.withLock(index) { - mSemaphore.withPermit { + jobs[index] = launch { + workerMutex.withLock(index) { + semaphore.withPermit { doRealWork(index) } } @@ -106,8 +105,6 @@ class ArchivePageLoader( } } - private fun mayBeAd(index: Int) = index > size - 10 - private suspend fun doRealWork(index: Int) { val buffer = extractToByteBuffer(index) ?: return notifyPageFailed(index, null) check(buffer.isDirect) @@ -124,7 +121,7 @@ class ArchivePageLoader( src.close() throw it } - val image = Image.decode(src, hasAds && Settings.stripExtraneousAds.value && mayBeAd(index)) ?: return notifyPageFailed(index, null) + val image = Image.decode(src, hasAds && detectAds(index, size)) ?: return notifyPageFailed(index, null) runCatching { currentCoroutineContext().ensureActive() }.onFailure { diff --git a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt similarity index 94% rename from app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt rename to app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt index 1b790ea3a..918308089 100644 --- a/app/src/main/kotlin/eu/kanade/tachiyomi/ui/reader/loader/PageLoader.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader.kt @@ -1,12 +1,8 @@ -package eu.kanade.tachiyomi.ui.reader.loader +package com.hippo.ehviewer.gallery import androidx.annotation.CallSuper import androidx.collection.lruCache import com.hippo.ehviewer.Settings -import com.hippo.ehviewer.gallery.Page -import com.hippo.ehviewer.gallery.PageStatus -import com.hippo.ehviewer.gallery.reset -import com.hippo.ehviewer.gallery.status import com.hippo.ehviewer.image.Image import com.hippo.ehviewer.util.OSUtils import com.hippo.ehviewer.util.isAtLeastO diff --git a/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader2.kt b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader2.kt index 0dca881c9..22466d533 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader2.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/gallery/PageLoader2.kt @@ -17,7 +17,6 @@ package com.hippo.ehviewer.gallery import com.hippo.ehviewer.EhDB import com.hippo.ehviewer.util.FileUtils -import eu.kanade.tachiyomi.ui.reader.loader.PageLoader import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers diff --git a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt index c39b6d5e5..1df8620b9 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/image/Image.kt @@ -37,6 +37,7 @@ import com.hippo.ehviewer.Settings import com.hippo.ehviewer.coil.BitmapImageWithExtraInfo import com.hippo.ehviewer.coil.detectQrCode import com.hippo.ehviewer.coil.hardwareThreshold +import com.hippo.ehviewer.coil.hasQrCode import com.hippo.ehviewer.coil.maybeCropBorder import com.hippo.ehviewer.jni.isGif import com.hippo.ehviewer.jni.mmap @@ -55,10 +56,7 @@ import splitties.init.appCtx class Image private constructor(image: CoilImage, private val src: ImageSource) { val intrinsicSize = with(image) { IntSize(width, height) } val allocationSize = image.size - val hasQrCode = when (image) { - is BitmapImageWithExtraInfo -> image.hasQrCode - else -> false - } + val hasQrCode = image.hasQrCode var innerImage: CoilImage? = when (image) { is BitmapImageWithExtraInfo -> image.image diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ktbuilder/Coil.kt b/app/src/main/kotlin/com/hippo/ehviewer/ktbuilder/Coil.kt index 7d534b31e..e8ba89747 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ktbuilder/Coil.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ktbuilder/Coil.kt @@ -5,6 +5,7 @@ import coil3.ImageLoader import coil3.disk.DiskCache import coil3.imageLoader import coil3.request.ImageRequest +import com.hippo.ehviewer.client.data.GalleryDetail import com.hippo.ehviewer.client.data.GalleryInfo import com.hippo.ehviewer.client.data.GalleryPreview import com.hippo.ehviewer.coil.ehPreview @@ -12,6 +13,7 @@ import com.hippo.ehviewer.coil.ehUrl import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.launch +context(GalleryDetail) inline fun Context.imageRequest(preview: GalleryPreview, builder: ImageRequest.Builder.() -> Unit = {}) = ImageRequest.Builder(this).ehPreview(preview).apply(builder).build() inline fun Context.imageRequest(info: GalleryInfo, builder: ImageRequest.Builder.() -> Unit = {}) = ImageRequest.Builder(this).ehUrl(info).apply(builder).build() inline fun Context.imageRequest(builder: ImageRequest.Builder.() -> Unit = {}) = ImageRequest.Builder(this).apply(builder).build() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt index d32115d02..9e541cc0d 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/spider/SpiderQueen.kt @@ -17,8 +17,6 @@ package com.hippo.ehviewer.spider import androidx.annotation.IntDef -import androidx.collection.LongSparseArray -import androidx.collection.set import arrow.core.partially1 import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings @@ -28,7 +26,6 @@ import com.hippo.ehviewer.client.EhUrl.getGalleryDetailUrl import com.hippo.ehviewer.client.EhUrl.getGalleryMultiPageViewerUrl import com.hippo.ehviewer.client.EhUrl.referer import com.hippo.ehviewer.client.data.GalleryInfo -import com.hippo.ehviewer.client.data.hasAds import com.hippo.ehviewer.client.ehRequest import com.hippo.ehviewer.client.exception.QuotaExceededException import com.hippo.ehviewer.client.fetchUsingAsText @@ -37,6 +34,7 @@ import com.hippo.ehviewer.client.parser.GalleryDetailParser.parsePreviewList import com.hippo.ehviewer.client.parser.GalleryDetailParser.parsePreviewPages import com.hippo.ehviewer.client.parser.GalleryMultiPageViewerPTokenParser import com.hippo.ehviewer.client.parser.GalleryPageUrlParser +import com.hippo.ehviewer.coil.detectAds import com.hippo.ehviewer.image.Image import com.hippo.ehviewer.util.displayString import com.hippo.files.find @@ -473,11 +471,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS const val STATE_FINISHED = 2 const val STATE_FAILED = 3 const val SPIDER_INFO_FILENAME = ".ehviewer" - private val sQueenMap = LongSparseArray() + private val sQueenMap = mutableMapOf() fun obtainSpiderQueen(galleryInfo: GalleryInfo, @Mode mode: Int): SpiderQueen { val gid = galleryInfo.gid - return (sQueenMap[gid] ?: SpiderQueen(galleryInfo).also { sQueenMap[gid] = it }).apply { + return (sQueenMap.getOrPut(gid) { SpiderQueen(galleryInfo) }).apply { setMode(mode) launch { updateMode() } } @@ -740,13 +738,11 @@ class SpiderQueen private constructor(val galleryInfo: GalleryInfo) : CoroutineS } } - private fun mayBeAd(index: Int) = index > size - 10 - private val hasAds = galleryInfo.hasAds private suspend fun doInJob(index: Int) { val src = mSpiderDen.getImageSource(index) ?: return - val image = Image.decode(src, hasAds && Settings.stripExtraneousAds.value && mayBeAd(index)) + val image = Image.decode(src, hasAds && detectAds(index, size)) checkNotNull(image) runCatching { currentCoroutineContext().ensureActive() diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt index 18b2246df..35c6c998a 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/main/GalleryPreview.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.NonRestartableComposable 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.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -23,12 +24,16 @@ import coil3.BitmapImage import coil3.compose.AsyncImage import coil3.compose.AsyncImagePainter import coil3.request.ImageRequest +import com.hippo.ehviewer.client.data.GalleryDetail import com.hippo.ehviewer.client.data.GalleryPreview import com.hippo.ehviewer.client.data.NormalGalleryPreview +import com.hippo.ehviewer.coil.hasQrCode import com.hippo.ehviewer.ktbuilder.imageRequest +import com.hippo.ehviewer.ui.reader.AdsPlaceholder import com.hippo.ehviewer.ui.tools.CrystalCard import com.hippo.ehviewer.ui.tools.shouldCrop +context(GalleryDetail) @Composable @NonRestartableComposable fun requestOf(model: GalleryPreview): ImageRequest { @@ -36,50 +41,60 @@ fun requestOf(model: GalleryPreview): ImageRequest { return remember(model) { context.imageRequest(model) } } +context(GalleryDetail) @Composable fun EhAsyncPreview( model: GalleryPreview, modifier: Modifier = Modifier, ) { var contentScale by remember(model) { mutableStateOf(ContentScale.Fit) } - AsyncImage( - model = requestOf(model), - contentDescription = null, - modifier = modifier, - transform = { - model.run { - if (it is AsyncImagePainter.State.Success && this is NormalGalleryPreview) { - it.copy( - painter = BitmapPainter( - (it.result.image as BitmapImage).bitmap.asImageBitmap(), - IntOffset(offsetX, 0), - IntSize(clipWidth - 1, clipHeight - 1), - ), - ) - } else { - it + var blocked by rememberSaveable(model) { mutableStateOf(false) } + if (blocked) { + AdsPlaceholder( + modifier = modifier, + contentScale = ContentScale.Fit, + ) + } else { + AsyncImage( + model = requestOf(model), + contentDescription = null, + modifier = modifier, + transform = { + with(model) { + when (it) { + is AsyncImagePainter.State.Success if it.result.image.hasQrCode -> it.also { blocked = true } + is AsyncImagePainter.State.Success if this is NormalGalleryPreview -> it.copy( + painter = BitmapPainter( + (it.result.image as BitmapImage).bitmap.asImageBitmap(), + IntOffset(offsetX, 0), + IntSize(clipWidth - 1, clipHeight - 1), + ), + ) + else -> it + } } - } - }, - onState = { - if (it is AsyncImagePainter.State.Success) { - model.run { - if (this is NormalGalleryPreview) { - if (shouldCrop) { - contentScale = ContentScale.Crop - } - } else { - if (it.result.image.shouldCrop) { - contentScale = ContentScale.Crop + }, + onState = { + if (it is AsyncImagePainter.State.Success) { + with(model) { + if (this is NormalGalleryPreview) { + if (shouldCrop) { + contentScale = ContentScale.Crop + } + } else { + if (it.result.image.shouldCrop) { + contentScale = ContentScale.Crop + } } } } - } - }, - contentScale = contentScale, - ) + }, + contentScale = contentScale, + ) + } } +context(GalleryDetail) @Composable @NonRestartableComposable fun EhPreviewItem( diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt index d69289703..cd60d15b5 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/reader/PagerItem.kt @@ -35,12 +35,12 @@ import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.gallery.Page +import com.hippo.ehviewer.gallery.PageLoader import com.hippo.ehviewer.gallery.PageStatus import com.hippo.ehviewer.gallery.progressObserved import com.hippo.ehviewer.gallery.statusObserved import com.hippo.ehviewer.image.Image import com.hippo.ehviewer.ui.settings.AdsPlaceholderFile -import eu.kanade.tachiyomi.ui.reader.loader.PageLoader import eu.kanade.tachiyomi.ui.reader.viewer.CombinedCircularProgressIndicator import kotlinx.coroutines.flow.drop import moe.tarsin.kt.unreachable @@ -101,26 +101,10 @@ fun PagerItem( ) } is PageStatus.Blocked -> { - SubcomposeAsyncImage( - model = AdsPlaceholderFile, - contentDescription = null, + AdsPlaceholder( modifier = modifier.fillMaxSize(), contentScale = if (contentScale == ContentScale.Inside) ContentScale.Fit else contentScale, - ) { - val placeholderState by painter.state.collectAsState() - if (placeholderState is AsyncImagePainter.State.Success) { - SubcomposeAsyncImageContent() - } else { - Box( - modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT), - contentAlignment = Alignment.Center, - ) { - if (placeholderState is AsyncImagePainter.State.Error) { - Text(text = stringResource(id = R.string.blocked_image)) - } - } - } - } + ) } is PageStatus.Error -> { Box(modifier = modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT)) { @@ -168,3 +152,28 @@ private val grayScaleAndInvertMatrix = ColorMatrix().also { mtx -> private val grayScaleFilter = ColorFilter.colorMatrix(grayScaleMatrix) private val invertFilter = ColorFilter.colorMatrix(invertMatrix) private val grayScaleAndInvertFilter = ColorFilter.colorMatrix(grayScaleAndInvertMatrix) + +@Composable +fun AdsPlaceholder( + modifier: Modifier = Modifier, + contentScale: ContentScale, +) = SubcomposeAsyncImage( + model = AdsPlaceholderFile, + contentDescription = null, + modifier = modifier, + contentScale = contentScale, +) { + val placeholderState by painter.state.collectAsState() + if (placeholderState is AsyncImagePainter.State.Success) { + SubcomposeAsyncImageContent() + } else { + Box( + modifier = Modifier.fillMaxWidth().aspectRatio(DEFAULT_ASPECT), + contentAlignment = Alignment.Center, + ) { + if (placeholderState is AsyncImagePainter.State.Error) { + Text(text = stringResource(id = R.string.blocked_image)) + } + } + } +} 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 eb7baa4a5..478092188 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 @@ -54,7 +54,6 @@ import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.hippo.ehviewer.R import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.data.BaseGalleryInfo -import com.hippo.ehviewer.client.data.hasAds import com.hippo.ehviewer.collectAsState import com.hippo.ehviewer.download.DownloadManager import com.hippo.ehviewer.download.archiveFile diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt index 943cd0c53..36073878c 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/screen/GalleryDetailContent.kt @@ -747,8 +747,10 @@ private fun List.getArtistTag(): String? { return null } +typealias Previews = Pair> + @Composable -private fun Context.collectPreviewItems(detail: GalleryDetail, prefetchDistance: Int) = rememberInVM { +private fun Context.collectPreviewItems(detail: GalleryDetail, prefetchDistance: Int) = rememberInVM(detail) { val pageSize = detail.previewList.size val pages = detail.pages val previewPagesMap = detail.previewList.associateBy { it.position } as MutableMap @@ -787,15 +789,18 @@ private fun Context.collectPreviewItems(detail: GalleryDetail, prefetchDistance: override val jumpingSupported = true } }.flow.cachedIn(viewModelScope) -}.collectAsLazyPagingItems() +}.collectAsLazyPagingItems().let { remember(detail, it) { detail to it } } -private fun LazyGridScope.galleryPreview(data: LazyPagingItems, onClick: (Int) -> Unit) { +private fun LazyGridScope.galleryPreview(previews: Previews, onClick: (Int) -> Unit) { + val (detail, data) = previews items( count = data.itemCount, key = data.itemKey(key = { item -> item.position }), contentType = { "preview" }, ) { index -> val item = data[index] - EhPreviewItem(item, index) { onClick(index) } + with(detail) { + EhPreviewItem(item, index) { onClick(index) } + } } } 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 077e67a5c..5a30bd3f0 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 @@ -150,8 +150,10 @@ fun AnimatedVisibilityScope.GalleryDetailScreen(args: GalleryDetailScreenArgs, n galleryDetailCache.put(galleryDetail.gid, galleryDetail) if (Settings.preloadThumbAggressively) { launchIO { - galleryDetail.previewList.forEach { - imageLoader.enqueue(imageRequest(it) { justDownload() }) + with(galleryDetail) { + previewList.forEach { + imageLoader.enqueue(imageRequest(it) { justDownload() }) + } } } } 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 93a43de92..c194a1570 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 @@ -221,7 +221,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De 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.mJumpTo)?.toInt() ?: 0 + val key = (params.key ?: urlBuilder.jumpTo)?.toInt() ?: 0 val prev = (key - 1).takeIf { it > 0 } val next = (key + 1).takeIf { it < TOPLIST_PAGES } runSuspendCatching { @@ -237,8 +237,8 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De is LoadParams.Refresh -> { val key = params.key if (key.isNullOrBlank()) { - if (urlBuilder.mJumpTo != null) { - urlBuilder.mNext ?: urlBuilder.setIndex("2", true) + if (urlBuilder.jumpTo != null) { + urlBuilder.next ?: urlBuilder.setIndex("2", true) } } else { urlBuilder.setIndex(key, false) @@ -249,7 +249,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De val url = urlBuilder.build() EhEngine.getGalleryList(url) }.foldToLoadResult { result -> - urlBuilder.mJumpTo = null + urlBuilder.jumpTo = null LoadResult.Page(result.galleryInfoList, result.prev, result.next) } } @@ -736,7 +736,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De if (urlBuilder.mode != MODE_WHATS_HOT) { onClick(EhIcons.Default.GoTo) { if (isTopList) { - val page = urlBuilder.mJumpTo?.toIntOrNull() ?: 0 + val page = urlBuilder.jumpTo?.toIntOrNull() ?: 0 val hint = getString(R.string.go_to_hint, page + 1, TOPLIST_PAGES) val text = awaitInputText(title = gotoTitle, hint = hint, isNumber = true) { oriText -> val goto = ensureNotNull(oriText.trim().toIntOrNull()) { invalidNum } - 1 @@ -745,7 +745,7 @@ fun AnimatedVisibilityScope.GalleryListScreen(lub: ListUrlBuilder, navigator: De urlBuilder.setJumpTo(text) } else { val date = awaitSelectDate() - urlBuilder.mJumpTo = date + urlBuilder.jumpTo = date } data.refresh() } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/RememberGlobally.kt b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/RememberGlobally.kt index 6eaa988cc..a5e9afd32 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/RememberGlobally.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/ui/tools/RememberGlobally.kt @@ -46,7 +46,7 @@ inline fun rememberInVM( ) = with(viewModel()) { val key = currentCompositeKeyHash remember(*inputs) { - val states = statesMap[key] ?: ArrayDeque().also { statesMap[key] = it } + val states = statesMap.getOrPut(key) { ArrayDeque() } states.removeLastOrNull() as T? ?: init() }.also { value -> val valueState by rememberUpdatedState(value) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b0c1690ae..f1969e35a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -91,7 +91,7 @@ jsoup = "org.jsoup:jsoup:1.18.1" kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinx-coroutines" } kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" } -ktlint = "com.pinterest:ktlint:1.3.0" +ktlint = "com.pinterest:ktlint:1.4.0-kotlin-dev-20240924.151646-39" ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" } diff --git a/settings.gradle.kts b/settings.gradle.kts index c821e2278..d012017a2 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -11,6 +11,7 @@ dependencyResolutionManagement { repositories { google() mavenCentral() + maven("https://oss.sonatype.org/content/repositories/snapshots") } }