From 8efd90bb22df327bc6d7903ad683b7b015f5d683 Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sat, 27 Dec 2025 14:49:51 +0800 Subject: [PATCH] =?UTF-8?q?[=E4=BF=AE=E5=A4=8D]=20=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=E4=BA=86=E5=9C=A8=E5=8F=91=E7=8E=B0=E9=A1=B5=E6=97=B6=EF=BC=8C?= =?UTF-8?q?=E6=9F=90=E4=BA=9B=E4=B9=A6=E6=BA=90=E6=97=A0=E6=B3=95=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E5=88=B0=E5=B0=81=E9=9D=A2=E7=9A=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=20#503?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/io/legado/app/App.kt | 2 +- .../io/legado/app/help/coil/CoverFetcher.kt | 94 +++++++++++++++++++ .../legado/app/help/coil/CoverInterceptor.kt | 40 ++++++++ .../java/io/legado/app/model/BookCover.kt | 11 +++ .../app/model/analyzeRule/AnalyzeUrl.kt | 5 + .../legado/app/ui/widget/components/Cover.kt | 73 +++++++------- 6 files changed, 185 insertions(+), 40 deletions(-) create mode 100644 app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt create mode 100644 app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt diff --git a/app/src/main/java/io/legado/app/App.kt b/app/src/main/java/io/legado/app/App.kt index cd476715a..015393830 100644 --- a/app/src/main/java/io/legado/app/App.kt +++ b/app/src/main/java/io/legado/app/App.kt @@ -10,6 +10,7 @@ import android.content.res.Configuration import android.graphics.BitmapFactory import android.os.Build import androidx.core.graphics.scale +import coil.ImageLoader import com.github.liuyueyi.quick.transfer.constants.TransType import com.google.android.material.color.DynamicColors import com.google.android.material.color.DynamicColorsOptions @@ -114,7 +115,6 @@ class App : Application() { ) } } - super.onCreate() FirebaseManager.init(this) CrashHandler(this) diff --git a/app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt b/app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt new file mode 100644 index 000000000..dfa749ea7 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt @@ -0,0 +1,94 @@ +package io.legado.app.help.coil + +import android.net.Uri +import coil.ImageLoader +import coil.decode.DataSource +import coil.decode.ImageSource +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.fetch.SourceResult +import coil.network.HttpException +import coil.request.Options + +import io.legado.app.data.entities.BaseSource +import io.legado.app.help.http.await +import io.legado.app.model.ReadManga +import io.legado.app.utils.ImageUtils +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import okhttp3.Call +import okhttp3.HttpUrl +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okio.Buffer +import java.io.IOException + +class CoverFetcher( + private val url: String, + private val options: Options, + private val callFactory: Call.Factory +) : Fetcher { + + override suspend fun fetch(): FetchResult { + val source = options.tags.tag() + val isManga = options.parameters.value("manga") as? Boolean == true + + val request = Request.Builder() + .url(url) + .headers(options.headers) + .build() + + val response = callFactory.newCall(request).execute() + val body = response.body + + if (!response.isSuccessful) { + body.close() + throw HttpException(response) + } + + if (ImageUtils.skipDecode(source, !isManga)) { + return SourceResult( + source = ImageSource(source = body.source(), context = options.context), + mimeType = null, + dataSource = DataSource.NETWORK + ) + } + + return withContext(Dispatchers.IO) { + try { + val bytes = body.bytes() + val decodedBytes = if (isManga) { + ImageUtils.decode(url, bytes, false, source, ReadManga.book) + } else { + ImageUtils.decode(url, bytes, true, source) + } ?: throw IOException("图片解密失败") + + SourceResult( + source = ImageSource( + source = Buffer().write(decodedBytes), + context = options.context + ), + mimeType = null, + dataSource = DataSource.NETWORK + ) + } finally { + body.close() + } + } + } + + class Factory( + private val okHttpClient: OkHttpClient, + private val okHttpClientManga: OkHttpClient + ) : Fetcher.Factory { + override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? { + if (data.scheme != "http" && data.scheme != "https") return null + + val isManga = options.parameters.value("manga") as? Boolean == true + val client = if (isManga) okHttpClientManga else okHttpClient + + return CoverFetcher(data.toString(), options, client) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt b/app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt new file mode 100644 index 000000000..2c0f8bf98 --- /dev/null +++ b/app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt @@ -0,0 +1,40 @@ +package io.legado.app.help.coil + +import coil.intercept.Interceptor +import coil.request.ImageResult +import io.legado.app.data.entities.BaseSource +import io.legado.app.help.source.SourceHelp +import io.legado.app.model.analyzeRule.AnalyzeUrl +import io.legado.app.utils.isWifiConnect +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.io.IOException + +class CoverInterceptor : Interceptor { + override suspend fun intercept(chain: Interceptor.Chain): ImageResult { + val request = chain.request + val data = request.data + + if (data is String && data.isNotBlank()) { + val sourceOrigin = request.parameters.value("sourceOrigin") as? String + val source = sourceOrigin?.let { SourceHelp.getSource(it) } + + val (finalUrl, headers) = withContext(Dispatchers.IO) { + AnalyzeUrl(data, source = source).getUrlAndHeaders() + } + + val newRequest = request.newBuilder() + .data(finalUrl) + .apply { + headers.forEach { (key, value) -> + addHeader(key, value) + } + tag(BaseSource::class.java, source) + } + .build() + + return chain.proceed(newRequest) + } + return chain.proceed(request) + } +} \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/model/BookCover.kt b/app/src/main/java/io/legado/app/model/BookCover.kt index 75bb216b5..fa8b8d8fc 100644 --- a/app/src/main/java/io/legado/app/model/BookCover.kt +++ b/app/src/main/java/io/legado/app/model/BookCover.kt @@ -25,10 +25,14 @@ import io.legado.app.data.entities.BaseSource import io.legado.app.data.entities.Book import io.legado.app.help.CacheManager import io.legado.app.help.DefaultData +import io.legado.app.help.coil.CoverFetcher +import io.legado.app.help.coil.CoverInterceptor import io.legado.app.help.config.AppConfig import io.legado.app.help.glide.BlurTransformation import io.legado.app.help.glide.ImageLoader import io.legado.app.help.glide.OkHttpModelLoader +import io.legado.app.help.http.okHttpClient +import io.legado.app.help.http.okHttpClientManga import io.legado.app.model.analyzeRule.AnalyzeRule import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext import io.legado.app.model.analyzeRule.AnalyzeUrl @@ -68,6 +72,13 @@ object BookCover { lateinit var defaultDrawable: Drawable private set + val coverImageLoader = coil.ImageLoader.Builder(appCtx) + .components { + add(CoverInterceptor()) + add(CoverFetcher.Factory(okHttpClient, okHttpClientManga)) + } + .crossfade(true) + .build() init { upDefaultCover() diff --git a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt index bcb505e2c..cdc512371 100644 --- a/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt +++ b/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt @@ -649,6 +649,11 @@ class AnalyzeUrl( return GlideUrl(url, GlideHeaders(headerMap)) } + fun getUrlAndHeaders(): Pair> { + setCookie() + return Pair(url, headerMap) + } + fun getUserAgent(): String { return headerMap.get(UA_NAME, true) ?: AppConfig.userAgent } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/Cover.kt b/app/src/main/java/io/legado/app/ui/widget/components/Cover.kt index 9a9f74ccf..cee8887c0 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/Cover.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/Cover.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -23,17 +24,15 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp +import coil.Coil.imageLoader import coil.compose.AsyncImage import coil.request.ImageRequest - -private val DefaultCoverModifier = Modifier - .width(48.dp) - .height(68.dp) +import io.legado.app.model.BookCover.coverImageLoader @Composable fun Cover( path: Any?, - modifier: Modifier = DefaultCoverModifier, + modifier: Modifier = Modifier.width(48.dp), badgeContent: (@Composable RowScope.() -> Unit)? = null, loadOnlyWifi: Boolean = false, sourceOrigin: String? = null, @@ -41,40 +40,36 @@ fun Cover( ) { val context = LocalContext.current - Box(modifier = modifier) { - Box( - modifier = Modifier - .fillMaxSize() - .clip(RoundedCornerShape(4.dp)) - .background(MaterialTheme.colorScheme.surfaceContainerLow), - contentAlignment = Alignment.Center - ) { - if (path == null) { - Icon( - Icons.Default.Book, - contentDescription = null, - tint = MaterialTheme.colorScheme.surfaceContainerHighest, - modifier = Modifier.size(24.dp) - ) - } else { - AsyncImage( - model = ImageRequest.Builder(context) - .data(path) // 可以是 URL、File、Uri、DrawableRes - .crossfade(true) - .apply { - if (sourceOrigin != null) setParameter("sourceOrigin", sourceOrigin) - if (loadOnlyWifi) setParameter("loadOnlyWifi", true) - } - .listener( - onSuccess = { _, _ -> onLoadFinish?.invoke() }, - onError = { _, _ -> onLoadFinish?.invoke() } - ) - .build(), - contentDescription = null, - contentScale = ContentScale.Crop, - modifier = Modifier.fillMaxSize() - ) - } + val boxModifier = modifier + .aspectRatio(3f / 4f) + .clip(RoundedCornerShape(4.dp)) + .background(MaterialTheme.colorScheme.surfaceContainerLow) + + Box(modifier = boxModifier) { + if (path == null) { + Icon( + Icons.Default.Book, + contentDescription = null, + tint = MaterialTheme.colorScheme.surfaceContainerHighest, + modifier = Modifier.size(24.dp).align(Alignment.Center) + ) + } else { + AsyncImage( + model = ImageRequest.Builder(context) + .data(path) + .crossfade(true) + .setParameter("sourceOrigin", sourceOrigin) + .setParameter("loadOnlyWifi", loadOnlyWifi) + .listener( + onSuccess = { _, _ -> onLoadFinish?.invoke() }, + onError = { _, _ -> onLoadFinish?.invoke() } + ) + .build(), + imageLoader = coverImageLoader, + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) } if (badgeContent != null) {