[修复] 修复了在发现页时,某些书源无法获取到封面的问题 #503
This commit is contained in:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user