[修复] 修复了在发现页时,某些书源无法获取到封面的问题 #503

This commit is contained in:
HapeLee
2025-12-27 14:49:51 +08:00
parent 9d9edb2376
commit 8efd90bb22
6 changed files with 185 additions and 40 deletions
+1 -1
View File
@@ -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)
@@ -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<BaseSource>()
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<Uri> {
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)
}
}
}
@@ -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)
}
}
@@ -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()
@@ -649,6 +649,11 @@ class AnalyzeUrl(
return GlideUrl(url, GlideHeaders(headerMap))
}
fun getUrlAndHeaders(): Pair<String, Map<String, String>> {
setCookie()
return Pair(url, headerMap)
}
fun getUserAgent(): String {
return headerMap.get(UA_NAME, true) ?: AppConfig.userAgent
}
@@ -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) {