Răsfoiți Sursa

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

HapeLee 10 luni în urmă
părinte
comite
8efd90bb22

+ 1 - 1
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)

+ 94 - 0
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<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)
+        }
+    }
+}

+ 40 - 0
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)
+    }
+}

+ 11 - 0
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()

+ 5 - 0
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<String, Map<String, String>> {
+        setCookie()
+        return Pair(url, headerMap)
+    }
+
     fun getUserAgent(): String {
         return headerMap.get(UA_NAME, true) ?: AppConfig.userAgent
     }

+ 34 - 39
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) {