Coil 2.x upgrade (#6725)

* Migrate to Coil 2

* Adapt to use coil disk cache

* Update to alpha 7

* Update to alpha 8

* Update to rc01

(cherry picked from commit 10eef282fa2fc0d8ae919d8b86bff1b529b4330d)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/App.kt
#	app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt
This commit is contained in:
Ivan Iskandar 2022-03-05 04:04:32 +07:00 committed by Jobobby04
parent 4b1d6400a4
commit 1f8072f18b
30 changed files with 310 additions and 226 deletions

View File

@ -22,6 +22,7 @@ import coil.ImageLoader
import coil.ImageLoaderFactory import coil.ImageLoaderFactory
import coil.decode.GifDecoder import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder import coil.decode.ImageDecoderDecoder
import coil.disk.DiskCache
import coil.util.DebugLogger import coil.util.DebugLogger
import com.elvishew.xlog.LogConfiguration import com.elvishew.xlog.LogConfiguration
import com.elvishew.xlog.LogLevel import com.elvishew.xlog.LogLevel
@ -35,8 +36,8 @@ import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase import com.google.firebase.ktx.Firebase
import com.ms_square.debugoverlay.DebugOverlay import com.ms_square.debugoverlay.DebugOverlay
import com.ms_square.debugoverlay.modules.FpsModule import com.ms_square.debugoverlay.modules.FpsModule
import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.MangaCoverKeyer
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferenceValues
@ -150,17 +151,20 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
override fun newImageLoader(): ImageLoader { override fun newImageLoader(): ImageLoader {
return ImageLoader.Builder(this).apply { return ImageLoader.Builder(this).apply {
componentRegistry { val callFactoryInit = { Injekt.get<NetworkHelper>().client }
val diskCacheInit = { CoilDiskCache.get(this@App) }
components {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder(this@App)) add(ImageDecoderDecoder.Factory())
} else { } else {
add(GifDecoder()) add(GifDecoder.Factory())
} }
add(TachiyomiImageDecoder(this@App.resources)) add(TachiyomiImageDecoder.Factory())
add(ByteBufferFetcher()) add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaCoverFetcher()) add(MangaCoverKeyer())
} }
okHttpClient(Injekt.get<NetworkHelper>().coilClient) callFactory(callFactoryInit)
diskCache(diskCacheInit)
crossfade((300 * this@App.animatorDurationScale).toInt()) crossfade((300 * this@App.animatorDurationScale).toInt())
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice) allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
if (preferences.verboseLogging()) logger(DebugLogger()) if (preferences.verboseLogging()) logger(DebugLogger())
@ -296,3 +300,24 @@ open class App : Application(), DefaultLifecycleObserver, ImageLoaderFactory {
} }
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE" private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"
/**
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
*/
internal object CoilDiskCache {
private const val FOLDER_NAME = "image_cache"
private var instance: DiskCache? = null
@Synchronized
fun get(context: Context): DiskCache {
return instance ?: run {
val safeCacheDir = context.cacheDir.apply { mkdirs() }
// Create the singleton disk cache instance.
DiskCache.Builder()
.directory(safeCacheDir.resolve(FOLDER_NAME))
.build()
.also { instance = it }
}
}
}

View File

@ -104,7 +104,7 @@ class CoverCache(private val context: Context) {
* Clear coil's memory cache. * Clear coil's memory cache.
*/ */
fun clearMemoryCache() { fun clearMemoryCache() {
context.imageLoader.memoryCache.clear() context.imageLoader.memoryCache?.clear()
} }
private fun getCacheDir(dir: String): File { private fun getCacheDir(dir: String): File {

View File

@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.data.coil
import coil.bitmap.BitmapPool
import coil.decode.DataSource
import coil.decode.Options
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.size.Size
import okio.buffer
import okio.source
import java.io.ByteArrayInputStream
import java.nio.ByteBuffer
class ByteBufferFetcher : Fetcher<ByteBuffer> {
override suspend fun fetch(pool: BitmapPool, data: ByteBuffer, size: Size, options: Options): FetchResult {
return SourceResult(
source = ByteArrayInputStream(data.array()).source().buffer(),
mimeType = null,
dataSource = DataSource.MEMORY
)
}
override fun key(data: ByteBuffer): String? = null
}

View File

@ -1,18 +1,18 @@
package eu.kanade.tachiyomi.data.coil package eu.kanade.tachiyomi.data.coil
import coil.bitmap.BitmapPool import coil.ImageLoader
import coil.decode.DataSource import coil.decode.DataSource
import coil.decode.Options import coil.decode.ImageSource
import coil.disk.DiskCache
import coil.fetch.FetchResult import coil.fetch.FetchResult
import coil.fetch.Fetcher import coil.fetch.Fetcher
import coil.fetch.SourceResult import coil.fetch.SourceResult
import coil.network.HttpException import coil.network.HttpException
import coil.request.get import coil.request.Options
import coil.size.Size import coil.request.Parameters
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher.Companion.USE_CUSTOM_COVER
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.await import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
@ -20,132 +20,183 @@ import okhttp3.CacheControl
import okhttp3.Call import okhttp3.Call
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody import okhttp3.internal.closeQuietly
import okio.buffer import okio.Path.Companion.toOkioPath
import okio.sink
import okio.source
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.net.HttpURLConnection
/** /**
* Coil component that fetches [Manga] cover while using the cached file in disk when available. * A [Fetcher] that fetches cover image for [Manga] object.
*
* It uses [Manga.thumbnail_url] if custom cover is not set by the user.
* Disk caching for library items is handled by [CoverCache], otherwise
* handled by Coil's [DiskCache].
* *
* Available request parameter: * Available request parameter:
* - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true * - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true
*/ */
class MangaCoverFetcher : Fetcher<Manga> { class MangaCoverFetcher(
private val coverCache: CoverCache by injectLazy() private val manga: Manga,
private val sourceManager: SourceManager by injectLazy() private val sourceLazy: Lazy<HttpSource?>,
private val defaultClient = Injekt.get<NetworkHelper>().coilClient private val options: Options,
private val coverCache: CoverCache,
private val callFactoryLazy: Lazy<Call.Factory>,
private val diskCacheLazy: Lazy<DiskCache>
) : Fetcher {
override fun key(data: Manga): String? { // For non-custom cover
if (data.thumbnail_url.isNullOrBlank()) return null private val diskCacheKey: String? by lazy { MangaCoverKeyer().key(manga, options) }
return data.thumbnail_url!! private lateinit var url: String
}
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult { override suspend fun fetch(): FetchResult {
// Use custom cover if exists // Use custom cover if exists
val useCustomCover = options.parameters[USE_CUSTOM_COVER] as? Boolean ?: true val useCustomCover = options.parameters.value(USE_CUSTOM_COVER) ?: true
val customCoverFile = coverCache.getCustomCoverFile(data) val customCoverFile = coverCache.getCustomCoverFile(manga)
if (useCustomCover && customCoverFile.exists()) { if (useCustomCover && customCoverFile.exists()) {
return fileLoader(customCoverFile) return fileLoader(customCoverFile)
} }
val cover = data.thumbnail_url // diskCacheKey is thumbnail_url
return when (getResourceType(cover)) { url = diskCacheKey ?: error("No cover specified")
Type.URL -> httpLoader(data, options) return when (getResourceType(url)) {
Type.File -> fileLoader(data) Type.URL -> httpLoader()
Type.File -> fileLoader(File(url.substringAfter("file://")))
null -> error("Invalid image") null -> error("Invalid image")
} }
} }
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult { private fun fileLoader(file: File): FetchResult {
return SourceResult(
source = ImageSource(file = file.toOkioPath(), diskCacheKey = diskCacheKey),
mimeType = "image/*",
dataSource = DataSource.DISK
)
}
private suspend fun httpLoader(): FetchResult {
// Only cache separately if it's a library item // Only cache separately if it's a library item
val coverCacheFile = if (manga.favorite) { val coverCacheFile = if (manga.favorite) {
coverCache.getCoverFile(manga) ?: error("No cover specified") coverCache.getCoverFile(manga) ?: error("No cover specified")
} else { } else {
null null
} }
if (coverCacheFile?.exists() == true && options.diskCachePolicy.readEnabled) { if (coverCacheFile?.exists() == true && options.diskCachePolicy.readEnabled) {
return fileLoader(coverCacheFile) return fileLoader(coverCacheFile)
} }
val (response, body) = awaitGetCall(manga, options) var snapshot = readFromDiskCache()
if (!response.isSuccessful) { try {
body.close() // Fetch from disk cache
throw HttpException(response) if (snapshot != null) {
}
if (coverCacheFile != null && options.diskCachePolicy.writeEnabled) {
@Suppress("BlockingMethodInNonBlockingContext")
response.peekBody(Long.MAX_VALUE).source().use { input ->
coverCacheFile.parentFile?.mkdirs()
if (coverCacheFile.exists()) {
coverCacheFile.delete()
}
coverCacheFile.sink().buffer().use { output ->
output.writeAll(input)
}
}
}
return SourceResult( return SourceResult(
source = body.source(), source = snapshot.toImageSource(),
mimeType = "image/*",
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
)
}
private suspend fun awaitGetCall(manga: Manga, options: Options): Pair<Response, ResponseBody> {
val call = getCall(manga, options)
val response = call.await()
return response to checkNotNull(response.body) { "Null response source" }
}
private fun getCall(manga: Manga, options: Options): Call {
val source = sourceManager.get(manga.source) as? HttpSource
val request = Request.Builder().url(manga.thumbnail_url!!).also {
if (source != null) {
it.headers(source.headers)
}
val networkRead = options.networkCachePolicy.readEnabled
val diskRead = options.diskCachePolicy.readEnabled
when {
!networkRead && diskRead -> {
it.cacheControl(CacheControl.FORCE_CACHE)
}
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
it.cacheControl(CacheControl.FORCE_NETWORK)
} else {
it.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
}
!networkRead && !diskRead -> {
// This causes the request to fail with a 504 Unsatisfiable Request.
it.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
}
}
}.build()
val client = source?.client?.newBuilder()?.cache(defaultClient.cache)?.build() ?: defaultClient
return client.newCall(request)
}
private fun fileLoader(manga: Manga): FetchResult {
return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://")))
}
private fun fileLoader(file: File): FetchResult {
return SourceResult(
source = file.source().buffer(),
mimeType = "image/*", mimeType = "image/*",
dataSource = DataSource.DISK dataSource = DataSource.DISK
) )
} }
// Fetch from network
val response = executeNetworkRequest()
val responseBody = checkNotNull(response.body) { "Null response source" }
try {
snapshot = writeToDiskCache(snapshot, response)
// Read from disk cache
if (snapshot != null) {
return SourceResult(
source = snapshot.toImageSource(),
mimeType = "image/*",
dataSource = DataSource.NETWORK
)
}
// Read from response if cache is unused or unusable
return SourceResult(
source = ImageSource(source = responseBody.source(), context = options.context),
mimeType = "image/*",
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
)
} catch (e: Exception) {
responseBody.closeQuietly()
throw e
} finally {
response.close()
}
} catch (e: Exception) {
snapshot?.closeQuietly()
throw e
}
}
private suspend fun executeNetworkRequest(): Response {
val client = sourceLazy.value?.client ?: callFactoryLazy.value
val response = client.newCall(newRequest()).await()
if (!response.isSuccessful && response.code != HttpURLConnection.HTTP_NOT_MODIFIED) {
response.body?.closeQuietly()
throw HttpException(response)
}
return response
}
private fun newRequest(): Request {
val request = Request.Builder()
.url(url)
.headers(options.headers)
// Support attaching custom data to the network request.
.tag(Parameters::class.java, options.parameters)
val diskRead = options.diskCachePolicy.readEnabled
val networkRead = options.networkCachePolicy.readEnabled
when {
!networkRead && diskRead -> {
request.cacheControl(CacheControl.FORCE_CACHE)
}
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
request.cacheControl(CacheControl.FORCE_NETWORK)
} else {
request.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
}
!networkRead && !diskRead -> {
// This causes the request to fail with a 504 Unsatisfiable Request.
request.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
}
}
return request.build()
}
private fun readFromDiskCache(): DiskCache.Snapshot? {
return if (options.diskCachePolicy.readEnabled) diskCacheLazy.value[diskCacheKey!!] else null
}
private fun writeToDiskCache(snapshot: DiskCache.Snapshot?, response: Response): DiskCache.Snapshot? {
if (!options.diskCachePolicy.writeEnabled) {
snapshot?.closeQuietly()
return null
}
val editor = if (snapshot != null) {
snapshot.closeAndEdit()
} else {
diskCacheLazy.value.edit(diskCacheKey!!)
} ?: return null
try {
diskCacheLazy.value.fileSystem.write(editor.data) {
response.body!!.source().readAll(this)
}
return editor.commitAndGet()
} catch (e: Exception) {
try {
editor.abort()
} catch (ignored: Exception) {
}
throw e
}
}
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
return ImageSource(file = data, diskCacheKey = diskCacheKey, closeable = this)
}
private fun getResourceType(cover: String?): Type? { private fun getResourceType(cover: String?): Type? {
return when { return when {
cover.isNullOrEmpty() -> null cover.isNullOrEmpty() -> null
@ -159,6 +210,20 @@ class MangaCoverFetcher : Fetcher<Manga> {
File, URL File, URL
} }
class Factory(
private val callFactoryLazy: Lazy<Call.Factory>,
private val diskCacheLazy: Lazy<DiskCache>
) : Fetcher.Factory<Manga> {
private val coverCache: CoverCache by injectLazy()
private val sourceManager: SourceManager by injectLazy()
override fun create(data: Manga, options: Options, imageLoader: ImageLoader): Fetcher {
val source = lazy { sourceManager.get(data.source) as? HttpSource }
return MangaCoverFetcher(data, source, options, coverCache, callFactoryLazy, diskCacheLazy)
}
}
companion object { companion object {
const val USE_CUSTOM_COVER = "use_custom_cover" const val USE_CUSTOM_COVER = "use_custom_cover"

View File

@ -0,0 +1,11 @@
package eu.kanade.tachiyomi.data.coil
import coil.key.Keyer
import coil.request.Options
import eu.kanade.tachiyomi.data.database.models.Manga
class MangaCoverKeyer : Keyer<Manga> {
override fun key(data: Manga, options: Options): String? {
return data.thumbnail_url?.takeIf { it.isNotBlank() }
}
}

View File

@ -1,13 +1,14 @@
package eu.kanade.tachiyomi.data.coil package eu.kanade.tachiyomi.data.coil
import android.content.res.Resources
import android.os.Build import android.os.Build
import androidx.core.graphics.drawable.toDrawable import androidx.core.graphics.drawable.toDrawable
import coil.bitmap.BitmapPool import coil.ImageLoader
import coil.decode.DecodeResult import coil.decode.DecodeResult
import coil.decode.Decoder import coil.decode.Decoder
import coil.decode.Options import coil.decode.ImageDecoderDecoder
import coil.size.Size import coil.decode.ImageSource
import coil.fetch.SourceResult
import coil.request.Options
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import okio.BufferedSource import okio.BufferedSource
import tachiyomi.decoder.ImageDecoder import tachiyomi.decoder.ImageDecoder
@ -15,26 +16,10 @@ import tachiyomi.decoder.ImageDecoder
/** /**
* A [Decoder] that uses built-in [ImageDecoder] to decode images that is not supported by the system. * A [Decoder] that uses built-in [ImageDecoder] to decode images that is not supported by the system.
*/ */
class TachiyomiImageDecoder(private val resources: Resources) : Decoder { class TachiyomiImageDecoder(private val resources: ImageSource, private val options: Options) : Decoder {
override fun handles(source: BufferedSource, mimeType: String?): Boolean { override suspend fun decode(): DecodeResult {
val type = source.peek().inputStream().use { val decoder = resources.sourceOrNull()?.use {
ImageUtil.findImageType(it)
}
return when (type) {
ImageUtil.ImageType.AVIF, ImageUtil.ImageType.JXL -> true
ImageUtil.ImageType.HEIF -> Build.VERSION.SDK_INT < Build.VERSION_CODES.O
else -> false
}
}
override suspend fun decode(
pool: BitmapPool,
source: BufferedSource,
size: Size,
options: Options
): DecodeResult {
val decoder = source.use {
ImageDecoder.newInstance(it.inputStream()) ImageDecoder.newInstance(it.inputStream())
} }
@ -46,8 +31,31 @@ class TachiyomiImageDecoder(private val resources: Resources) : Decoder {
check(bitmap != null) { "Failed to decode image." } check(bitmap != null) { "Failed to decode image." }
return DecodeResult( return DecodeResult(
drawable = bitmap.toDrawable(resources), drawable = bitmap.toDrawable(options.context.resources),
isSampled = false isSampled = false
) )
} }
class Factory : Decoder.Factory {
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (!isApplicable(result.source.source())) return null
return TachiyomiImageDecoder(result.source, options)
}
private fun isApplicable(source: BufferedSource): Boolean {
val type = source.peek().inputStream().use {
ImageUtil.findImageType(it)
}
return when (type) {
ImageUtil.ImageType.AVIF, ImageUtil.ImageType.JXL -> true
ImageUtil.ImageType.HEIF -> Build.VERSION.SDK_INT < Build.VERSION_CODES.O
else -> false
}
}
override fun equals(other: Any?) = other is ImageDecoderDecoder.Factory
override fun hashCode() = javaClass.hashCode()
}
} }

View File

@ -1,7 +1,6 @@
package eu.kanade.tachiyomi.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import coil.util.CoilUtils
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
@ -50,8 +49,6 @@ import java.util.concurrent.TimeUnit
/* SY --> */ open /* SY <-- */val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() } /* SY --> */ open /* SY <-- */val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
val coilClient by lazy { baseClientBuilder.cache(CoilUtils.createDefaultCache(context)).build() }
/* SY --> */ open /* SY <-- */val cloudflareClient by lazy { /* SY --> */ open /* SY <-- */val cloudflareClient by lazy {
client.newBuilder() client.newBuilder()
.addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor(context))

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.extension
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.load import coil.load
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -45,7 +45,7 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
// SY <-- // SY <--
}.uppercase() }.uppercase()
binding.icon.clear() binding.icon.dispose()
if (extension is Extension.Available) { if (extension is Extension.Available) {
binding.icon.load(extension.iconUrl) binding.icon.load(extension.iconUrl)
} else if (extension is Extension.Installed) { } else if (extension is Extension.Installed) {

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.latest
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
@ -54,10 +54,10 @@ class LatestCardHolder(view: View, adapter: LatestCardAdapter) :
} }
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.clear() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(itemView.context) val request = ImageRequest.Builder(itemView.context)
.data(manga) .data(manga)

View File

@ -4,7 +4,7 @@ import android.view.View
import android.widget.PopupMenu import android.widget.PopupMenu
import androidx.core.view.isInvisible import androidx.core.view.isInvisible
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -16,7 +16,7 @@ import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction import eu.kanade.tachiyomi.ui.base.controller.withFadeTransaction
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.lang.launchUI
import eu.kanade.tachiyomi.util.view.loadAnyAutoPause import eu.kanade.tachiyomi.util.view.loadAutoPause
import eu.kanade.tachiyomi.util.view.setVectorCompat import eu.kanade.tachiyomi.util.view.setVectorCompat
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.util.executeOnIO import exh.util.executeOnIO
@ -127,7 +127,7 @@ class MigrationProcessHolder(
private fun MigrationMangaCardBinding.resetManga() { private fun MigrationMangaCardBinding.resetManga() {
progress.isVisible = true progress.isVisible = true
thumbnail.clear() thumbnail.dispose()
thumbnail.setImageDrawable(null) thumbnail.setImageDrawable(null)
title.text = "" title.text = ""
mangaSourceLabel.text = "" mangaSourceLabel.text = ""
@ -138,7 +138,7 @@ class MigrationProcessHolder(
private suspend fun MigrationMangaCardBinding.attachManga(manga: Manga, source: Source) { private suspend fun MigrationMangaCardBinding.attachManga(manga: Manga, source: Source) {
progress.isVisible = false progress.isVisible = false
thumbnail.loadAnyAutoPause(manga) thumbnail.loadAutoPause(manga)
title.text = if (manga.title.isBlank()) { title.text = if (manga.title.isBlank()) {
view.context.getString(R.string.unknown) view.context.getString(R.string.unknown)

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.browse.migration.manga package eu.kanade.tachiyomi.ui.browse.migration.manga
import android.view.View import android.view.View
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.databinding.SourceListItemBinding import eu.kanade.tachiyomi.databinding.SourceListItemBinding
@ -23,7 +23,7 @@ class MigrationMangaHolder(
binding.title.text = item.manga.originalTitle binding.title.text = item.manga.originalTitle
// Update the cover // Update the cover
binding.thumbnail.clear() binding.thumbnail.dispose()
binding.thumbnail.loadAny(item.manga) binding.thumbnail.load(item.manga)
} }
} }

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.browse.source.browse package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.transition.CrossfadeTransition import coil.transition.CrossfadeTransition
@ -66,10 +66,10 @@ class SourceComfortableGridHolder(
// SY <-- // SY <--
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
binding.thumbnail.clear() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = binding.root.context.imageLoader.defaults.transition.let { val crossfadeDuration = binding.root.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(binding.root.context) val request = ImageRequest.Builder(binding.root.context)
.data(manga) .data(manga)

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.browse.source.browse package eu.kanade.tachiyomi.ui.browse.source.browse
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.transition.CrossfadeTransition import coil.transition.CrossfadeTransition
@ -66,10 +66,10 @@ class SourceCompactGridHolder(
// SY <-- // SY <--
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
binding.thumbnail.clear() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = binding.root.context.imageLoader.defaults.transition.let { val crossfadeDuration = binding.root.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(binding.root.context) val request = ImageRequest.Builder(binding.root.context)
.data(manga) .data(manga)

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
@ -106,10 +106,10 @@ class SourceEnhancedEHentaiListHolder(view: View, adapter: FlexibleAdapter<*>) :
// For rounded corners // For rounded corners
binding.card.clipToOutline = true binding.card.clipToOutline = true
binding.thumbnail.clear() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(itemView.context) val request = ImageRequest.Builder(itemView.context)
.data(manga) .data(manga)

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.browse.source.browse
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
@ -67,9 +67,9 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
// SY <-- // SY <--
override fun setImage(manga: Manga) { override fun setImage(manga: Manga) {
binding.thumbnail.clear() binding.thumbnail.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
binding.thumbnail.loadAny(manga) { binding.thumbnail.load(manga) {
setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
} }
} }

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.transition.CrossfadeTransition import coil.transition.CrossfadeTransition
@ -53,10 +53,10 @@ class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) :
} }
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.clear() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(itemView.context) val request = ImageRequest.Builder(itemView.context)
.data(manga) .data(manga)

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.source.index
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
@ -54,10 +54,10 @@ class IndexCardHolder(view: View, adapter: IndexCardAdapter) :
} }
fun setImage(manga: Manga) { fun setImage(manga: Manga) {
binding.cover.clear() binding.cover.dispose()
if (!manga.thumbnail_url.isNullOrEmpty()) { if (!manga.thumbnail_url.isNullOrEmpty()) {
val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { val crossfadeDuration = itemView.context.imageLoader.defaults.transitionFactory.let {
if (it is CrossfadeTransition) it.durationMillis else 0 if (it is CrossfadeTransition.Factory) it.durationMillis else 0
} }
val request = ImageRequest.Builder(itemView.context) val request = ImageRequest.Builder(itemView.context)
.data(manga) .data(manga)

View File

@ -1,11 +1,13 @@
package eu.kanade.tachiyomi.ui.library package eu.kanade.tachiyomi.ui.library
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import androidx.recyclerview.widget.RecyclerView
import coil.dispose
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
import eu.kanade.tachiyomi.util.view.loadAnyAutoPause import eu.kanade.tachiyomi.util.view.loadAutoPause
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
@ -21,7 +23,7 @@ import reactivecircus.flowbinding.android.view.clicks
*/ */
class LibraryComfortableGridHolder( class LibraryComfortableGridHolder(
override val binding: SourceComfortableGridItemBinding, override val binding: SourceComfortableGridItemBinding,
adapter: FlexibleAdapter<*> adapter: FlexibleAdapter<IFlexible<RecyclerView.ViewHolder>>
) : LibraryHolder<SourceComfortableGridItemBinding>(binding.root, adapter) { ) : LibraryHolder<SourceComfortableGridItemBinding>(binding.root, adapter) {
// SY --> // SY -->
@ -76,8 +78,8 @@ class LibraryComfortableGridHolder(
// SY <-- // SY <--
// Update the cover. // Update the cover.
binding.thumbnail.clear() binding.thumbnail.dispose()
binding.thumbnail.loadAnyAutoPause(item.manga) binding.thumbnail.loadAutoPause(item.manga)
} }
// SY --> // SY -->

View File

@ -3,11 +3,11 @@ package eu.kanade.tachiyomi.ui.library
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams import androidx.core.view.updateLayoutParams
import coil.clear import coil.dispose
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
import eu.kanade.tachiyomi.util.view.loadAnyAutoPause import eu.kanade.tachiyomi.util.view.loadAutoPause
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks import reactivecircus.flowbinding.android.view.clicks
@ -98,11 +98,11 @@ class LibraryCompactGridHolder(
// SY <-- // SY <--
// Update the cover. // Update the cover.
binding.thumbnail.clear() binding.thumbnail.dispose()
if (coverOnly) { if (coverOnly) {
// Cover only mode: Hides title text unless thumbnail is unavailable // Cover only mode: Hides title text unless thumbnail is unavailable
if (!item.manga.thumbnail_url.isNullOrEmpty()) { if (!item.manga.thumbnail_url.isNullOrEmpty()) {
binding.thumbnail.loadAnyAutoPause(item.manga) binding.thumbnail.loadAutoPause(item.manga)
binding.title.isVisible = false binding.title.isVisible = false
} else { } else {
binding.title.text = item.manga.title binding.title.text = item.manga.title
@ -110,7 +110,7 @@ class LibraryCompactGridHolder(
} }
binding.thumbnail.foreground = null binding.thumbnail.foreground = null
} else { } else {
binding.thumbnail.loadAnyAutoPause(item.manga) binding.thumbnail.loadAutoPause(item.manga)
} }
} }

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.library
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.kanade.tachiyomi.databinding.SourceListItemBinding import eu.kanade.tachiyomi.databinding.SourceListItemBinding
@ -61,7 +61,7 @@ class LibraryListHolder(
} }
// Update the cover // Update the cover
binding.thumbnail.clear() binding.thumbnail.dispose()
binding.thumbnail.loadAny(item.manga) binding.thumbnail.load(item.manga)
} }
} }

View File

@ -9,7 +9,7 @@ import android.widget.ScrollView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf import androidx.core.os.bundleOf
import androidx.core.view.children import androidx.core.view.children
import coil.loadAny import coil.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import com.google.android.material.chip.Chip import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup import com.google.android.material.chip.ChipGroup
@ -170,7 +170,7 @@ class EditMangaDialog : DialogController {
fun loadCover() { fun loadCover() {
val radius = context.resources.getDimension(R.dimen.card_radius) val radius = context.resources.getDimension(R.dimen.card_radius)
binding.mangaCover.loadAny(manga) { binding.mangaCover.load(manga) {
transformations(RoundedCornersTransformation(radius)) transformations(RoundedCornersTransformation(radius))
} }
} }
@ -178,7 +178,7 @@ class EditMangaDialog : DialogController {
fun updateCover(uri: Uri) { fun updateCover(uri: Uri) {
willResetCover = false willResetCover = false
val radius = context.resources.getDimension(R.dimen.card_radius) val radius = context.resources.getDimension(R.dimen.card_radius)
binding.mangaCover.loadAny(uri) { binding.mangaCover.load(uri) {
transformations(RoundedCornersTransformation(radius)) transformations(RoundedCornersTransformation(radius))
} }
customCoverUri = uri customCoverUri = uri

View File

@ -667,7 +667,7 @@ class MangaPresenter(
* @return cover as Bitmap or null if there is no thumbnail cached with the memoryCacheKey * @return cover as Bitmap or null if there is no thumbnail cached with the memoryCacheKey
*/ */
private fun coverBitmapFromImageLoader(context: Context, memoryCacheKey: MemoryCache.Key): Bitmap? { private fun coverBitmapFromImageLoader(context: Context, memoryCacheKey: MemoryCache.Key): Bitmap? {
return context.imageLoader.memoryCache[memoryCacheKey] return context.imageLoader.memoryCache?.get(memoryCacheKey)?.bitmap
} }
/** /**

View File

@ -21,7 +21,7 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight import eu.kanade.tachiyomi.ui.base.controller.getMainAppBarHeight
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.copyToClipboard
import eu.kanade.tachiyomi.util.view.loadAnyAutoPause import eu.kanade.tachiyomi.util.view.loadAutoPause
import exh.merged.sql.models.MergedMangaReference import exh.merged.sql.models.MergedMangaReference
import exh.metadata.metadata.base.RaisedSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
@ -358,8 +358,8 @@ class MangaInfoHeaderAdapter(
setFavoriteButtonState(manga.favorite) setFavoriteButtonState(manga.favorite)
// Set cover if changed. // Set cover if changed.
binding.backdrop.loadAnyAutoPause(manga) binding.backdrop.loadAutoPause(manga)
binding.mangaCover.loadAnyAutoPause(manga) binding.mangaCover.loadAutoPause(manga)
// Manga info section // Manga info section
// SY --> // SY -->

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.manga.merged package eu.kanade.tachiyomi.ui.manga.merged
import android.view.View import android.view.View
import coil.loadAny import coil.load
import coil.transform.RoundedCornersTransformation import coil.transform.RoundedCornersTransformation
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
@ -40,7 +40,7 @@ class EditMergedMangaHolder(view: View, val adapter: EditMergedMangaAdapter) : F
reference = item.mergedMangaReference reference = item.mergedMangaReference
item.mergedManga?.let { item.mergedManga?.let {
val radius = itemView.context.resources.getDimension(R.dimen.card_radius) val radius = itemView.context.resources.getDimension(R.dimen.card_radius)
binding.cover.loadAny(it) { binding.cover.load(it) {
transformations(RoundedCornersTransformation(radius)) transformations(RoundedCornersTransformation(radius))
} }
} }

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.manga.track
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding
import java.util.Locale import java.util.Locale
@ -20,9 +20,9 @@ class TrackSearchHolder(
} }
binding.trackSearchTitle.text = track.title binding.trackSearchTitle.text = track.title
binding.trackSearchCover.clear() binding.trackSearchCover.dispose()
if (track.cover_url.isNotEmpty()) { if (track.cover_url.isNotEmpty()) {
binding.trackSearchCover.loadAny(track.cover_url) binding.trackSearchCover.load(track.cover_url)
} }
val hasStatus = track.publishing_status.isNotBlank() val hasStatus = track.publishing_status.isNotBlank()

View File

@ -17,7 +17,7 @@ import androidx.annotation.CallSuper
import androidx.annotation.StyleRes import androidx.annotation.StyleRes
import androidx.appcompat.widget.AppCompatImageView import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.imageLoader import coil.imageLoader
import coil.request.CachePolicy import coil.request.CachePolicy
import coil.request.ImageRequest import coil.request.ImageRequest
@ -152,7 +152,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
fun recycle() = pageView?.let { fun recycle() = pageView?.let {
when (it) { when (it) {
is SubsamplingScaleImageView -> it.recycle() is SubsamplingScaleImageView -> it.recycle()
is AppCompatImageView -> it.clear() is AppCompatImageView -> it.dispose()
} }
it.isVisible = false it.isVisible = false
} }

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.recent.history package eu.kanade.tachiyomi.ui.recent.history
import android.view.View import android.view.View
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
@ -65,7 +65,7 @@ class HistoryHolder(
} }
// Set cover // Set cover
binding.cover.clear() binding.cover.dispose()
binding.cover.loadAny(item.manga) binding.cover.load(item.manga)
} }
} }

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.ui.recent.updates
import android.view.View import android.view.View
import androidx.core.view.isVisible import androidx.core.view.isVisible
import coil.clear import coil.dispose
import coil.loadAny import coil.load
import eu.kanade.tachiyomi.databinding.UpdatesItemBinding import eu.kanade.tachiyomi.databinding.UpdatesItemBinding
import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder
@ -58,7 +58,7 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter)
binding.download.setState(item.status, item.progress) binding.download.setState(item.status, item.progress)
// Set cover // Set cover
binding.mangaCover.clear() binding.mangaCover.dispose()
binding.mangaCover.loadAny(item.manga) binding.mangaCover.load(item.manga)
} }
} }

View File

@ -8,7 +8,7 @@ import androidx.annotation.DrawableRes
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import coil.ImageLoader import coil.ImageLoader
import coil.imageLoader import coil.imageLoader
import coil.loadAny import coil.load
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.target.ImageViewTarget import coil.target.ImageViewTarget
import eu.kanade.tachiyomi.util.system.animatorDurationScale import eu.kanade.tachiyomi.util.system.animatorDurationScale
@ -33,12 +33,13 @@ fun ImageView.setVectorCompat(@DrawableRes drawable: Int, @AttrRes tint: Int? =
* and if the image is animated, this will also disable that animation * and if the image is animated, this will also disable that animation
* if [Context.animatorDurationScale] is 0 * if [Context.animatorDurationScale] is 0
*/ */
fun ImageView.loadAnyAutoPause( fun ImageView.loadAutoPause(
data: Any?, data: Any?,
loader: ImageLoader = context.imageLoader, loader: ImageLoader = context.imageLoader,
builder: ImageRequest.Builder.() -> Unit = {} builder: ImageRequest.Builder.() -> Unit = {}
) { ) {
this.loadAny(data, loader) { // Build the original request so we can add on our success listener
load(data, loader) {
// Build the original request so we can add on our success listener // Build the original request so we can add on our success listener
val originalBuild = apply(builder).build() val originalBuild = apply(builder).build()
listener( listener(

View File

@ -2,7 +2,7 @@
aboutlib_version = "8.9.4" aboutlib_version = "8.9.4"
okhttp_version = "4.9.1" okhttp_version = "4.9.1"
nucleus_version = "3.0.0" nucleus_version = "3.0.0"
coil_version = "1.4.0" coil_version = "2.0.0-rc01"
conductor_version = "3.1.2" conductor_version = "3.1.2"
flowbinding_version = "1.2.0" flowbinding_version = "1.2.0"
shizuku_version = "12.1.0" shizuku_version = "12.1.0"