HapeLee 3 месяцев назад
Родитель
Сommit
83ff526bbc
36 измененных файлов с 149 добавлено и 109 удалено
  1. 1 0
      app/build.gradle.kts
  2. 6 6
      app/src/main/java/io/legado/app/App.kt
  3. 6 5
      app/src/main/java/io/legado/app/di/appModule.kt
  4. 26 0
      app/src/main/java/io/legado/app/help/coil/CoverExtras.kt
  5. 27 23
      app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt
  6. 7 10
      app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt
  7. 6 5
      app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt
  8. 4 4
      app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt
  9. 1 1
      app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterDetailScreen.kt
  10. 1 1
      app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterListScreen.kt
  11. 1 1
      app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterNetworkScreen.kt
  12. 3 3
      app/src/main/java/io/legado/app/ui/book/knowledge/CharacterAvatarCropDialog.kt
  13. 1 1
      app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBarFloating.kt
  14. 1 1
      app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBarTool.kt
  15. 4 3
      app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt
  16. 1 1
      app/src/main/java/io/legado/app/ui/book/read/TextActionSelectionMenu.kt
  17. 1 1
      app/src/main/java/io/legado/app/ui/book/read/sheet/GlobalThemePage.kt
  18. 1 1
      app/src/main/java/io/legado/app/ui/book/read/sheet/TextSelectMenuConfigSheet.kt
  19. 2 2
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverAlbumManageScreen.kt
  20. 1 2
      app/src/main/java/io/legado/app/ui/config/coverConfig/CoverAlbumSelectSheet.kt
  21. 1 1
      app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt
  22. 1 1
      app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt
  23. 5 5
      app/src/main/java/io/legado/app/ui/dict/DictHtmlContent.kt
  24. 1 1
      app/src/main/java/io/legado/app/ui/main/MainScreen.kt
  25. 2 2
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt
  26. 1 1
      app/src/main/java/io/legado/app/ui/theme/AppBackground.kt
  27. 8 5
      app/src/main/java/io/legado/app/ui/theme/ImageSeedColorExtractor.kt
  28. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/AppContainerBackground.kt
  29. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt
  30. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/ReorderableConfigList.kt
  31. 2 2
      app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoilBookCover.kt
  32. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoverBlurBackdrop.kt
  33. 5 3
      app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoverImageRequest.kt
  34. 10 6
      app/src/main/java/io/legado/app/ui/widget/components/image/sourceIcon/SourceIcon.kt
  35. 1 1
      app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt
  36. 5 4
      gradle/libs.versions.toml

+ 1 - 0
app/build.gradle.kts

@@ -273,6 +273,7 @@ dependencies {
     implementation(libs.coil.compose)
     implementation(libs.coil.gif)
     implementation(libs.coil.svg)
+    implementation(libs.coil.network.okhttp)
     implementation(libs.accompanist.webview)
     implementation(libs.kotlinx.coroutines.android)
     implementation(libs.androidx.lifecycle.viewmodel.compose)

+ 6 - 6
app/src/main/java/io/legado/app/App.kt

@@ -9,8 +9,8 @@ import android.content.pm.ApplicationInfo
 import android.graphics.BitmapFactory
 import android.os.Build
 import androidx.core.graphics.scale
-import coil.ImageLoader
-import coil.ImageLoaderFactory
+import coil3.ImageLoader
+import coil3.SingletonImageLoader
 import com.github.liuyueyi.quick.transfer.constants.TransType
 import com.google.android.material.color.DynamicColors
 import com.google.android.material.color.DynamicColorsOptions
@@ -71,10 +71,10 @@ import io.legado.app.utils.getPrefBoolean
 import io.legado.app.utils.getPrefString
 import io.legado.app.utils.isDebuggable
 import kotlinx.coroutines.Dispatchers.Main
-import kotlinx.coroutines.launch
-import kotlinx.coroutines.withContext
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
 import org.chromium.base.ThreadUtils
 import org.koin.android.ext.android.get
 import org.koin.android.ext.koin.androidContext
@@ -86,9 +86,9 @@ import java.net.URL
 import java.util.concurrent.TimeUnit
 import java.util.logging.Level
 
-class App : Application(), ImageLoaderFactory {
+class App : Application(), SingletonImageLoader.Factory {
 
-    override fun newImageLoader(): ImageLoader {
+    override fun newImageLoader(context: Context): ImageLoader {
         return get()
     }
 

+ 6 - 5
app/src/main/java/io/legado/app/di/appModule.kt

@@ -1,10 +1,11 @@
 package io.legado.app.di
 
 import android.os.Build
-import coil.ImageLoader
-import coil.decode.GifDecoder
-import coil.decode.ImageDecoderDecoder
-import coil.decode.SvgDecoder
+import coil3.ImageLoader
+import coil3.gif.AnimatedImageDecoder
+import coil3.gif.GifDecoder
+import coil3.request.crossfade
+import coil3.svg.SvgDecoder
 import io.legado.app.data.AppDatabase
 import io.legado.app.data.repository.AiArtifactRepository
 import io.legado.app.data.repository.AiChatRepository
@@ -475,7 +476,7 @@ val appModule = module {
         ImageLoader.Builder(get())
             .components {
                 if (Build.VERSION.SDK_INT >= 28) {
-                    add(ImageDecoderDecoder.Factory())
+                    add(AnimatedImageDecoder.Factory())
                 } else {
                     add(GifDecoder.Factory())
                 }

+ 26 - 0
app/src/main/java/io/legado/app/help/coil/CoverExtras.kt

@@ -0,0 +1,26 @@
+package io.legado.app.help.coil
+
+import coil3.Extras
+import io.legado.app.data.entities.BaseSource
+
+/**
+ * 封面请求在 Coil 3 中通过 [Extras] 传参。Extras.Key 只按实例相等比较,
+ * 因此写入方(封面请求构造)与读取方(CoverInterceptor / CoverFetcher)
+ * 必须共享这组单例 key。
+ */
+object CoverExtras {
+    /** 书源标识,CoverInterceptor 用它解析最终 URL 和请求头。 */
+    val SourceOrigin = Extras.Key<String?>(null)
+
+    /** CoverInterceptor 解析得到的书源,CoverFetcher 用它解密图片。 */
+    val Source = Extras.Key<BaseSource?>(null)
+
+    /** CoverInterceptor 解析得到的请求头,CoverFetcher 构造 OkHttp 请求用。 */
+    val Headers = Extras.Key<Map<String, String>?>(null)
+
+    /** 仅 WiFi 时加载。 */
+    val LoadOnlyWifi = Extras.Key<Boolean?>(null)
+
+    /** 漫画模式。 */
+    val Manga = Extras.Key<Boolean?>(null)
+}

+ 27 - 23
app/src/main/java/io/legado/app/help/coil/CoverFetcher.kt

@@ -1,16 +1,13 @@
 package io.legado.app.help.coil
 
-import android.net.Uri
 import android.util.Base64
-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 coil3.ImageLoader
+import coil3.decode.DataSource
+import coil3.decode.ImageSource
+import coil3.fetch.FetchResult
+import coil3.fetch.Fetcher
+import coil3.fetch.SourceFetchResult
+import coil3.request.Options
 import io.legado.app.model.ReadManga
 import io.legado.app.utils.ImageUtils
 import io.legado.app.utils.isWifiConnect
@@ -61,8 +58,8 @@ class CoverFetcher(
     }
 
     override suspend fun fetch(): FetchResult {
-        val source = options.tags.tag<BaseSource>()
-        val isManga = options.parameters.value("manga") as? Boolean == true
+        val source = options.extras[CoverExtras.Source]
+        val isManga = options.extras[CoverExtras.Manga] == true
 
         if (url.startsWith("data:", true)) {
             val base64Data = url.substringAfter("base64,", "")
@@ -70,8 +67,11 @@ class CoverFetcher(
                 throw IOException("Invalid data URI")
             }
             val bytes = Base64.decode(base64Data, Base64.DEFAULT)
-            return SourceResult(
-                source = ImageSource(source = Buffer().write(bytes), context = options.context),
+            return SourceFetchResult(
+                source = ImageSource(
+                    source = Buffer().write(bytes),
+                    fileSystem = options.fileSystem
+                ),
                 mimeType = null,
                 dataSource = DataSource.MEMORY
             )
@@ -86,9 +86,10 @@ class CoverFetcher(
         }
 
         // Try OkHttp HTTP cache: FORCE_CACHE serves from cache or returns 504 on miss
+        val requestHeaders = options.extras[CoverExtras.Headers]
         val cacheRequest = Request.Builder()
             .url(url)
-            .headers(options.headers)
+            .apply { requestHeaders?.forEach { (key, value) -> addHeader(key, value) } }
             .cacheControl(CacheControl.FORCE_CACHE)
             .build()
 
@@ -104,7 +105,7 @@ class CoverFetcher(
                     // Cache miss, fetch from network
                     val networkRequest = Request.Builder()
                         .url(url)
-                        .headers(options.headers)
+                        .apply { requestHeaders?.forEach { (key, value) -> addHeader(key, value) } }
                         .tag(COVER_REQUEST_TAG)
                         .cacheControl(
                             CacheControl.Builder()
@@ -116,7 +117,7 @@ class CoverFetcher(
                     val body = networkResponse.body
                     if (!networkResponse.isSuccessful) {
                         body.close()
-                        throw HttpException(networkResponse)
+                        throw IOException("HTTP ${networkResponse.code}")
                     }
                     body.use { it.bytes() }
                 }
@@ -139,8 +140,11 @@ class CoverFetcher(
             } ?: throw IOException("图片解密失败")
         }
 
-        return SourceResult(
-            source = ImageSource(source = Buffer().write(decodedBytes), context = options.context),
+        return SourceFetchResult(
+            source = ImageSource(
+                source = Buffer().write(decodedBytes),
+                fileSystem = options.fileSystem
+            ),
             mimeType = null,
             dataSource = if (fromCache) DataSource.DISK else DataSource.NETWORK
         )
@@ -149,13 +153,13 @@ class CoverFetcher(
     class Factory(
         private val okHttpClient: OkHttpClient,
         private val okHttpClientManga: OkHttpClient,
-    ) : Fetcher.Factory<Uri> {
-        override fun create(data: Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
+    ) : Fetcher.Factory<coil3.Uri> {
+        override fun create(data: coil3.Uri, options: Options, imageLoader: ImageLoader): Fetcher? {
             val scheme = data.scheme
             if (scheme != "http" && scheme != "https" && scheme != "data") return null
 
-            val isManga = options.parameters.value("manga") as? Boolean == true
-            val loadOnlyWifi = options.parameters.value("loadOnlyWifi") as? Boolean == true
+            val isManga = options.extras[CoverExtras.Manga] == true
+            val loadOnlyWifi = options.extras[CoverExtras.LoadOnlyWifi] == true
             val client = if (isManga) okHttpClientManga else okHttpClient
 
             return CoverFetcher(data.toString(), options, client, loadOnlyWifi)

+ 7 - 10
app/src/main/java/io/legado/app/help/coil/CoverInterceptor.kt

@@ -1,8 +1,7 @@
 package io.legado.app.help.coil
 
-import coil.intercept.Interceptor
-import coil.request.ImageResult
-import io.legado.app.data.entities.BaseSource
+import coil3.intercept.Interceptor
+import coil3.request.ImageResult
 import io.legado.app.help.source.SourceHelp
 import io.legado.app.model.analyzeRule.AnalyzeUrl
 import kotlinx.coroutines.Dispatchers
@@ -36,7 +35,7 @@ class CoverInterceptor : Interceptor {
         val data = request.data
 
         if (data is String && data.isNotBlank()) {
-            val sourceOrigin = request.parameters.value("sourceOrigin") as? String
+            val sourceOrigin = request.extras[CoverExtras.SourceOrigin]
             val source = sourceOrigin?.let { origin ->
                 withContext(Dispatchers.IO) {
                     SourceHelp.getSource(origin)
@@ -59,15 +58,13 @@ class CoverInterceptor : Interceptor {
             val newRequest = request.newBuilder()
                 .data(finalUrl)
                 .apply {
-                    headers.forEach { (key, value) ->
-                        addHeader(key, value)
-                    }
-                    tag(BaseSource::class.java, source)
+                    extras[CoverExtras.Source] = source
+                    extras[CoverExtras.Headers] = headers
                 }
                 .build()
 
-            return chain.proceed(newRequest)
+            return chain.withRequest(newRequest).proceed()
         }
-        return chain.proceed(request)
+        return chain.proceed()
     }
 }

+ 6 - 5
app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt

@@ -75,13 +75,14 @@ import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.unit.dp
-import coil.ImageLoader
-import coil.compose.AsyncImage
-import coil.size.Size
+import coil3.ImageLoader
+import coil3.compose.AsyncImage
+import coil3.size.Size
 import io.legado.app.R
 import io.legado.app.constant.BookType
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.SearchBook
+import io.legado.app.help.coil.CoverExtras
 import io.legado.app.ui.main.homepage.modules.BannerModule
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LocalHazeState
@@ -603,8 +604,8 @@ private fun rememberBookInfoColorTheme(
         data = coverPath,
         requestKey = requestKey,
     ) {
-        setParameter("sourceOrigin", sourceOrigin)
-        setParameter("loadOnlyWifi", loadOnlyWifi)
+        extras[CoverExtras.SourceOrigin] = sourceOrigin
+        extras[CoverExtras.LoadOnlyWifi] = loadOnlyWifi
     }
 
     return rememberThemeOverride(seedColor)

+ 4 - 4
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -5,10 +5,10 @@ import android.app.Application
 import android.content.Intent
 import android.graphics.Bitmap
 import android.net.Uri
-import androidx.core.graphics.drawable.toBitmap
 import androidx.lifecycle.viewModelScope
-import coil.ImageLoader
-import coil.request.SuccessResult
+import coil3.ImageLoader
+import coil3.request.SuccessResult
+import coil3.toBitmap
 import io.legado.app.R
 import io.legado.app.base.BaseViewModel
 import io.legado.app.constant.AppLog
@@ -647,7 +647,7 @@ class BookInfoViewModel(
             )
             val result = imageLoader.execute(request)
             if (result is SuccessResult) {
-                val bitmap = result.drawable.toBitmap()
+                val bitmap = result.image.toBitmap()
                 val outputStream = ByteArrayOutputStream()
                 bitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
                 val byteArray = outputStream.toByteArray()

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterDetailScreen.kt

@@ -36,7 +36,7 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.data.entities.BookCharacterProfile
 import io.legado.app.ui.theme.LegadoTheme

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterListScreen.kt

@@ -40,7 +40,7 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.compose.LocalLifecycleOwner
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.data.entities.BookCharacterProfile
 import io.legado.app.ui.ai.AiTaskResultSheet

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/knowledge/BookCharacterNetworkScreen.kt

@@ -37,7 +37,7 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.compose.LocalLifecycleOwner
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.adaptiveContentPadding

+ 3 - 3
app/src/main/java/io/legado/app/ui/book/knowledge/CharacterAvatarCropDialog.kt

@@ -19,17 +19,17 @@ import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
-import androidx.compose.ui.geometry.Size
-import androidx.compose.ui.layout.ScaleFactor
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.input.pointer.pointerInput
 import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.ScaleFactor
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.alert.AppAlertDialog

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBarFloating.kt

@@ -46,7 +46,7 @@ import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import com.kyant.backdrop.Backdrop
 import io.legado.app.R
 import io.legado.app.data.entities.Book

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBarTool.kt

@@ -42,7 +42,7 @@ import androidx.compose.ui.semantics.setProgress
 import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import com.kyant.backdrop.Backdrop
 import com.kyant.backdrop.backdrops.layerBackdrop
 import com.kyant.backdrop.backdrops.rememberBackdrop

+ 4 - 3
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -11,10 +11,11 @@ import androidx.compose.ui.res.stringArrayResource
 import androidx.compose.ui.res.stringResource
 import androidx.core.net.toUri
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import coil.ImageLoader
+import coil3.ImageLoader
 import io.legado.app.R
 import io.legado.app.data.repository.ReadPreferences
 import io.legado.app.domain.gateway.CoverSettingsGateway
+import io.legado.app.help.coil.CoverExtras
 import io.legado.app.ui.book.read.sheet.AiRewritePresetConfigSheet
 import io.legado.app.ui.book.read.sheet.AiTextCleanSheet
 import io.legado.app.ui.book.read.sheet.AiTextRewriteSheet
@@ -414,8 +415,8 @@ fun ReadBookScreen(
             data = coverPath,
             requestKey = requestKey,
         ) {
-            setParameter("sourceOrigin", sourceOrigin)
-            setParameter("loadOnlyWifi", loadOnlyWifi)
+            extras[CoverExtras.SourceOrigin] = sourceOrigin
+            extras[CoverExtras.LoadOnlyWifi] = loadOnlyWifi
         }
         rememberThemeOverride(seedColor)
     }

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/TextActionSelectionMenu.kt

@@ -60,7 +60,7 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Popup
 import androidx.compose.ui.window.PopupPositionProvider
 import androidx.compose.ui.window.PopupProperties
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.ProvideAppDensity

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/sheet/GlobalThemePage.kt

@@ -48,7 +48,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.toColorInt
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.data.repository.ReadPreferences
 import io.legado.app.domain.model.settings.ReadStyleItem

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/read/sheet/TextSelectMenuConfigSheet.kt

@@ -42,7 +42,7 @@ import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.book.read.ActionMenuItem
 import io.legado.app.ui.theme.LegadoTheme

+ 2 - 2
app/src/main/java/io/legado/app/ui/config/coverConfig/CoverAlbumManageScreen.kt

@@ -44,7 +44,7 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.adaptiveContentPadding
@@ -52,8 +52,8 @@ import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.AppTextField
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 import io.legado.app.ui.widget.components.button.AppIconButton
-import io.legado.app.ui.widget.components.button.series.SmallPlainButton
 import io.legado.app.ui.widget.components.button.series.MediumTonalButton
+import io.legado.app.ui.widget.components.button.series.SmallPlainButton
 import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.icon.AppIcon

+ 1 - 2
app/src/main/java/io/legado/app/ui/config/coverConfig/CoverAlbumSelectSheet.kt

@@ -5,7 +5,6 @@ import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.rememberScrollState
@@ -22,7 +21,7 @@ import androidx.compose.ui.draw.clip
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.button.series.MediumTonalButton

+ 1 - 1
app/src/main/java/io/legado/app/ui/config/themeConfig/BackgroundImageManageSheet.kt

@@ -19,7 +19,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.button.series.SmallTonalButton

+ 1 - 1
app/src/main/java/io/legado/app/ui/config/themeConfig/NavIconManageSheet.kt

@@ -20,7 +20,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.domain.model.settings.AppShellSettings
 import io.legado.app.ui.main.MainDestination

+ 5 - 5
app/src/main/java/io/legado/app/ui/dict/DictHtmlContent.kt

@@ -31,8 +31,8 @@ import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.em
 import androidx.compose.ui.unit.sp
 import androidx.core.graphics.toColorInt
-import coil.compose.AsyncImagePainter
-import coil.compose.rememberAsyncImagePainter
+import coil3.compose.AsyncImagePainter
+import coil3.compose.rememberAsyncImagePainter
 import io.legado.app.ui.theme.LegadoTheme
 
 @Composable
@@ -77,9 +77,9 @@ private fun DictHtmlParagraphContent(
         if (inline !is DictHtmlInline.Image) return@forEach
         val id = "dict-image-$paragraphIndex-${imageIndex++}"
         val painter = rememberAsyncImagePainter(model = inline.source)
-        val drawable = (painter.state as? AsyncImagePainter.State.Success)?.result?.drawable
-        val drawableWidth = drawable?.intrinsicWidth?.coerceAtLeast(1) ?: 1
-        val drawableHeight = drawable?.intrinsicHeight?.coerceAtLeast(1) ?: 1
+        val image = (painter.state.value as? AsyncImagePainter.State.Success)?.result?.image
+        val drawableWidth = image?.width?.coerceAtLeast(1) ?: 1
+        val drawableHeight = image?.height?.coerceAtLeast(1) ?: 1
         val scale = if (maxWidthPx in 1..<drawableWidth) {
             maxWidthPx.toFloat() / drawableWidth
         } else {

+ 1 - 1
app/src/main/java/io/legado/app/ui/main/MainScreen.kt

@@ -71,7 +71,7 @@ import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import com.kyant.backdrop.backdrops.layerBackdrop
 import com.kyant.backdrop.backdrops.rememberLayerBackdrop
 import dev.chrisbanes.haze.HazeState

+ 2 - 2
app/src/main/java/io/legado/app/ui/rss/article/RssArticlesCompose.kt

@@ -52,8 +52,8 @@ import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
-import coil.ImageLoader
-import coil.compose.AsyncImage
+import coil3.ImageLoader
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.constant.AppLog
 import io.legado.app.data.appDb

+ 1 - 1
app/src/main/java/io/legado/app/ui/theme/AppBackground.kt

@@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.blur
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.domain.model.settings.hasBackgroundImage
 import org.koin.compose.koinInject
 

+ 8 - 5
app/src/main/java/io/legado/app/ui/theme/ImageSeedColorExtractor.kt

@@ -14,10 +14,12 @@ import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalContext
 import androidx.core.graphics.drawable.toBitmap
 import androidx.core.graphics.scale
-import coil.ImageLoader
-import coil.request.ImageRequest
-import coil.request.SuccessResult
-import coil.size.Size
+import coil3.ImageLoader
+import coil3.asDrawable
+import coil3.request.ImageRequest
+import coil3.request.SuccessResult
+import coil3.request.allowHardware
+import coil3.size.Size
 import com.materialkolor.quantize.QuantizerCelebi
 import com.materialkolor.score.Score
 import kotlinx.coroutines.Dispatchers
@@ -45,7 +47,8 @@ suspend fun ImageLoader.extractSeedColor(
     } as? SuccessResult ?: return null
 
     return withContext(Dispatchers.Default) {
-        val bitmap = result.drawable.toSafeBitmap(IMAGE_COLOR_EXTRACT_SIZE_PX)
+        val bitmap = result.image.asDrawable(context.resources)
+            .toSafeBitmap(IMAGE_COLOR_EXTRACT_SIZE_PX)
         Color(bitmap.extractSeedColor())
     }
 }

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/AppContainerBackground.kt

@@ -14,8 +14,8 @@ import androidx.compose.ui.platform.LocalConfiguration
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.unit.dp
-import coil.compose.rememberAsyncImagePainter
-import coil.request.ImageRequest
+import coil3.compose.rememberAsyncImagePainter
+import coil3.request.ImageRequest
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LocalAppUiConfiguration
 import kotlinx.coroutines.Dispatchers

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/AppScaffold.kt

@@ -19,13 +19,13 @@ import androidx.compose.ui.draw.blur
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import dev.chrisbanes.haze.HazeState
 import dev.chrisbanes.haze.hazeSource
 import io.legado.app.domain.model.settings.hasBackgroundImage
 import io.legado.app.ui.theme.LegadoTheme
-import io.legado.app.ui.theme.LocalHazeState
 import io.legado.app.ui.theme.LocalAppUiConfiguration
+import io.legado.app.ui.theme.LocalHazeState
 import io.legado.app.ui.theme.ThemeResolver
 import io.legado.app.ui.theme.responsiveHazeSource
 import top.yukonga.miuix.kmp.theme.MiuixTheme

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/components/ReorderableConfigList.kt

@@ -34,7 +34,7 @@ import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.button.ConfirmDismissButtonsRow

+ 2 - 2
app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoilBookCover.kt

@@ -43,8 +43,8 @@ import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.withSave
-import coil.compose.AsyncImage
-import coil.request.ImageRequest
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LocalAppUiConfiguration
 import org.koin.compose.koinInject

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoverBlurBackdrop.kt

@@ -10,7 +10,7 @@ import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.layout.ContentScale
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
-import coil.size.Size
+import coil3.size.Size
 import io.legado.app.ui.theme.LegadoTheme
 
 @Composable

+ 5 - 3
app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoverImageRequest.kt

@@ -1,7 +1,9 @@
 package io.legado.app.ui.widget.components.image.cover
 
 import android.content.Context
-import coil.request.ImageRequest
+import coil3.request.ImageRequest
+import coil3.request.crossfade
+import io.legado.app.help.coil.CoverExtras
 
 fun buildCoverImageRequest(
     context: Context,
@@ -20,9 +22,9 @@ fun buildCoverImageRequest(
                 memoryCacheKey(memoryCacheKey)
                 placeholderMemoryCacheKey(memoryCacheKey)
             }
+            extras[CoverExtras.SourceOrigin] = sourceOrigin
+            extras[CoverExtras.LoadOnlyWifi] = loadOnlyWifi
         }
-        .setParameter("sourceOrigin", sourceOrigin)
-        .setParameter("loadOnlyWifi", loadOnlyWifi)
         .apply(configure)
         .build()
 }

+ 10 - 6
app/src/main/java/io/legado/app/ui/widget/components/image/sourceIcon/SourceIcon.kt

@@ -23,9 +23,11 @@ import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.compose.LocalLifecycleOwner
-import coil.ImageLoader
-import coil.compose.AsyncImage
-import coil.request.ImageRequest
+import coil3.ImageLoader
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
+import coil3.request.crossfade
+import io.legado.app.help.coil.CoverExtras
 import org.koin.compose.koinInject
 
 /**
@@ -59,8 +61,10 @@ fun SourceIcon(
         ImageRequest.Builder(context)
             .data(path)
             .crossfade(true)
-            .setParameter("sourceOrigin", sourceOrigin)
-            .setParameter("loadOnlyWifi", loadOnlyWifi)
+            .apply {
+                extras[CoverExtras.SourceOrigin] = sourceOrigin
+                extras[CoverExtras.LoadOnlyWifi] = loadOnlyWifi
+            }
             .build()
     }
 
@@ -109,7 +113,7 @@ fun SourceIcon(
                 modifier = Modifier.fillMaxSize(),
                 onSuccess = { state ->
                     imageLoaded = true
-                    animatedDrawable = (state.result.drawable as? Animatable)?.also { drawable ->
+                    animatedDrawable = (state.result.image as? Animatable)?.also { drawable ->
                         if (lifecycle.currentState.isAtLeast(Lifecycle.State.RESUMED)) {
                             if (!drawable.isRunning) drawable.start()
                         } else {

+ 1 - 1
app/src/main/java/io/legado/app/ui/widget/components/settingItem/TinySettingItems.kt

@@ -59,7 +59,7 @@ import androidx.compose.ui.state.ToggleableState
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.dp
-import coil.compose.AsyncImage
+import coil3.compose.AsyncImage
 import io.legado.app.R
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.widget.components.AccentColorButton

+ 5 - 4
gradle/libs.versions.toml

@@ -6,7 +6,7 @@ adaptiveLayout = "1.3.0-rc01"
 adaptiveNavigation = "1.3.0-rc01"
 animation = "1.11.4"
 biometric = "1.4.0-alpha07"
-coilCompose = "2.7.0"
+coilCompose = "3.5.0"
 composeBom = "2026.06.01"
 constraintlayoutCompose = "1.1.1"
 coreSplashscreen = "1.2.0"
@@ -143,9 +143,10 @@ koin-android = { group = "io.insert-koin", name = "koin-android" }
 koin-compose = { group = "io.insert-koin", name = "koin-androidx-compose" }
 koin-compose-viewmodel = { group = "io.insert-koin", name = "koin-compose-viewmodel" }
 
-coil-compose = { module = "io.coil-kt:coil-compose", version.ref = "coilCompose" }
-coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coilCompose" }
-coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coilCompose" }
+coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coilCompose" }
+coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coilCompose" }
+coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coilCompose" }
+coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coilCompose" }
 core-ktx = { module = "androidx.core:core-ktx", version.ref = "core" }
 
 androidx-espresso-core = { module = "androidx.test.espresso:espresso-core", version = "3.7.0" }