[修复] 修复了在发现页时,某些书源无法获取到封面的问题 #503
This commit is contained in:
@@ -10,6 +10,7 @@ import android.content.res.Configuration
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.core.graphics.scale
|
import androidx.core.graphics.scale
|
||||||
|
import coil.ImageLoader
|
||||||
import com.github.liuyueyi.quick.transfer.constants.TransType
|
import com.github.liuyueyi.quick.transfer.constants.TransType
|
||||||
import com.google.android.material.color.DynamicColors
|
import com.google.android.material.color.DynamicColors
|
||||||
import com.google.android.material.color.DynamicColorsOptions
|
import com.google.android.material.color.DynamicColorsOptions
|
||||||
@@ -114,7 +115,6 @@ class App : Application() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
FirebaseManager.init(this)
|
FirebaseManager.init(this)
|
||||||
CrashHandler(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.data.entities.Book
|
||||||
import io.legado.app.help.CacheManager
|
import io.legado.app.help.CacheManager
|
||||||
import io.legado.app.help.DefaultData
|
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.config.AppConfig
|
||||||
import io.legado.app.help.glide.BlurTransformation
|
import io.legado.app.help.glide.BlurTransformation
|
||||||
import io.legado.app.help.glide.ImageLoader
|
import io.legado.app.help.glide.ImageLoader
|
||||||
import io.legado.app.help.glide.OkHttpModelLoader
|
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
|
||||||
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
|
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
|
||||||
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
import io.legado.app.model.analyzeRule.AnalyzeUrl
|
||||||
@@ -68,6 +72,13 @@ object BookCover {
|
|||||||
lateinit var defaultDrawable: Drawable
|
lateinit var defaultDrawable: Drawable
|
||||||
private set
|
private set
|
||||||
|
|
||||||
|
val coverImageLoader = coil.ImageLoader.Builder(appCtx)
|
||||||
|
.components {
|
||||||
|
add(CoverInterceptor())
|
||||||
|
add(CoverFetcher.Factory(okHttpClient, okHttpClientManga))
|
||||||
|
}
|
||||||
|
.crossfade(true)
|
||||||
|
.build()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
upDefaultCover()
|
upDefaultCover()
|
||||||
|
|||||||
@@ -649,6 +649,11 @@ class AnalyzeUrl(
|
|||||||
return GlideUrl(url, GlideHeaders(headerMap))
|
return GlideUrl(url, GlideHeaders(headerMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUrlAndHeaders(): Pair<String, Map<String, String>> {
|
||||||
|
setCookie()
|
||||||
|
return Pair(url, headerMap)
|
||||||
|
}
|
||||||
|
|
||||||
fun getUserAgent(): String {
|
fun getUserAgent(): String {
|
||||||
return headerMap.get(UA_NAME, true) ?: AppConfig.userAgent
|
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.Box
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.RowScope
|
import androidx.compose.foundation.layout.RowScope
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
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.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import coil.Coil.imageLoader
|
||||||
import coil.compose.AsyncImage
|
import coil.compose.AsyncImage
|
||||||
import coil.request.ImageRequest
|
import coil.request.ImageRequest
|
||||||
|
import io.legado.app.model.BookCover.coverImageLoader
|
||||||
private val DefaultCoverModifier = Modifier
|
|
||||||
.width(48.dp)
|
|
||||||
.height(68.dp)
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun Cover(
|
fun Cover(
|
||||||
path: Any?,
|
path: Any?,
|
||||||
modifier: Modifier = DefaultCoverModifier,
|
modifier: Modifier = Modifier.width(48.dp),
|
||||||
badgeContent: (@Composable RowScope.() -> Unit)? = null,
|
badgeContent: (@Composable RowScope.() -> Unit)? = null,
|
||||||
loadOnlyWifi: Boolean = false,
|
loadOnlyWifi: Boolean = false,
|
||||||
sourceOrigin: String? = null,
|
sourceOrigin: String? = null,
|
||||||
@@ -41,40 +40,36 @@ fun Cover(
|
|||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
Box(modifier = modifier) {
|
val boxModifier = modifier
|
||||||
Box(
|
.aspectRatio(3f / 4f)
|
||||||
modifier = Modifier
|
.clip(RoundedCornerShape(4.dp))
|
||||||
.fillMaxSize()
|
.background(MaterialTheme.colorScheme.surfaceContainerLow)
|
||||||
.clip(RoundedCornerShape(4.dp))
|
|
||||||
.background(MaterialTheme.colorScheme.surfaceContainerLow),
|
Box(modifier = boxModifier) {
|
||||||
contentAlignment = Alignment.Center
|
if (path == null) {
|
||||||
) {
|
Icon(
|
||||||
if (path == null) {
|
Icons.Default.Book,
|
||||||
Icon(
|
contentDescription = null,
|
||||||
Icons.Default.Book,
|
tint = MaterialTheme.colorScheme.surfaceContainerHighest,
|
||||||
contentDescription = null,
|
modifier = Modifier.size(24.dp).align(Alignment.Center)
|
||||||
tint = MaterialTheme.colorScheme.surfaceContainerHighest,
|
)
|
||||||
modifier = Modifier.size(24.dp)
|
} else {
|
||||||
)
|
AsyncImage(
|
||||||
} else {
|
model = ImageRequest.Builder(context)
|
||||||
AsyncImage(
|
.data(path)
|
||||||
model = ImageRequest.Builder(context)
|
.crossfade(true)
|
||||||
.data(path) // 可以是 URL、File、Uri、DrawableRes
|
.setParameter("sourceOrigin", sourceOrigin)
|
||||||
.crossfade(true)
|
.setParameter("loadOnlyWifi", loadOnlyWifi)
|
||||||
.apply {
|
.listener(
|
||||||
if (sourceOrigin != null) setParameter("sourceOrigin", sourceOrigin)
|
onSuccess = { _, _ -> onLoadFinish?.invoke() },
|
||||||
if (loadOnlyWifi) setParameter("loadOnlyWifi", true)
|
onError = { _, _ -> onLoadFinish?.invoke() }
|
||||||
}
|
)
|
||||||
.listener(
|
.build(),
|
||||||
onSuccess = { _, _ -> onLoadFinish?.invoke() },
|
imageLoader = coverImageLoader,
|
||||||
onError = { _, _ -> onLoadFinish?.invoke() }
|
contentDescription = null,
|
||||||
)
|
contentScale = ContentScale.Crop,
|
||||||
.build(),
|
modifier = Modifier.fillMaxSize()
|
||||||
contentDescription = null,
|
)
|
||||||
contentScale = ContentScale.Crop,
|
|
||||||
modifier = Modifier.fillMaxSize()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (badgeContent != null) {
|
if (badgeContent != null) {
|
||||||
|
|||||||
Reference in New Issue
Block a user