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
This commit is contained in:
parent
f312936629
commit
10eef282fa
24 changed files with 286 additions and 204 deletions
|
@ -20,9 +20,10 @@ 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 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
|
||||||
|
@ -121,17 +122,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())
|
||||||
|
@ -190,3 +194,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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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,130 +20,181 @@ 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
|
||||||
|
if (snapshot != null) {
|
||||||
|
return SourceResult(
|
||||||
|
source = snapshot.toImageSource(),
|
||||||
|
mimeType = "image/*",
|
||||||
|
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)
|
throw HttpException(response)
|
||||||
}
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
if (coverCacheFile != null && options.diskCachePolicy.writeEnabled) {
|
private fun newRequest(): Request {
|
||||||
@Suppress("BlockingMethodInNonBlockingContext")
|
val request = Request.Builder()
|
||||||
response.peekBody(Long.MAX_VALUE).source().use { input ->
|
.url(url)
|
||||||
coverCacheFile.parentFile?.mkdirs()
|
.headers(options.headers)
|
||||||
if (coverCacheFile.exists()) {
|
// Support attaching custom data to the network request.
|
||||||
coverCacheFile.delete()
|
.tag(Parameters::class.java, options.parameters)
|
||||||
}
|
|
||||||
coverCacheFile.sink().buffer().use { output ->
|
val diskRead = options.diskCachePolicy.readEnabled
|
||||||
output.writeAll(input)
|
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 SourceResult(
|
return request.build()
|
||||||
source = body.source(),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun awaitGetCall(manga: Manga, options: Options): Pair<Response, ResponseBody> {
|
private fun readFromDiskCache(): DiskCache.Snapshot? {
|
||||||
val call = getCall(manga, options)
|
return if (options.diskCachePolicy.readEnabled) diskCacheLazy.value[diskCacheKey!!] else null
|
||||||
val response = call.await()
|
|
||||||
return response to checkNotNull(response.body) { "Null response source" }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getCall(manga: Manga, options: Options): Call {
|
private fun writeToDiskCache(snapshot: DiskCache.Snapshot?, response: Response): DiskCache.Snapshot? {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
if (!options.diskCachePolicy.writeEnabled) {
|
||||||
val request = Request.Builder().url(manga.thumbnail_url!!).also {
|
snapshot?.closeQuietly()
|
||||||
if (source != null) {
|
return null
|
||||||
it.headers(source.headers)
|
}
|
||||||
|
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()
|
||||||
val networkRead = options.networkCachePolicy.readEnabled
|
} catch (e: Exception) {
|
||||||
val diskRead = options.diskCachePolicy.readEnabled
|
try {
|
||||||
when {
|
editor.abort()
|
||||||
!networkRead && diskRead -> {
|
} catch (ignored: Exception) {
|
||||||
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()
|
throw e
|
||||||
|
}
|
||||||
val client = source?.client?.newBuilder()?.cache(defaultClient.cache)?.build() ?: defaultClient
|
|
||||||
return client.newCall(request)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fileLoader(manga: Manga): FetchResult {
|
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
|
||||||
return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://")))
|
return ImageSource(file = data, diskCacheKey = diskCacheKey, closeable = this)
|
||||||
}
|
|
||||||
|
|
||||||
private fun fileLoader(file: File): FetchResult {
|
|
||||||
return SourceResult(
|
|
||||||
source = file.source().buffer(),
|
|
||||||
mimeType = "image/*",
|
|
||||||
dataSource = DataSource.DISK
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getResourceType(cover: String?): Type? {
|
private fun getResourceType(cover: String?): Type? {
|
||||||
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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() }
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.CloudflareInterceptor
|
||||||
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
||||||
|
@ -49,8 +48,6 @@ class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
|
val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
|
||||||
|
|
||||||
val coilClient by lazy { baseClientBuilder.cache(CoilUtils.createDefaultCache(context)).build() }
|
|
||||||
|
|
||||||
val cloudflareClient by lazy {
|
val cloudflareClient by lazy {
|
||||||
client.newBuilder()
|
client.newBuilder()
|
||||||
.addInterceptor(CloudflareInterceptor(context))
|
.addInterceptor(CloudflareInterceptor(context))
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.extension
|
||||||
|
|
||||||
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
|
||||||
|
@ -39,7 +39,7 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||||
else -> ""
|
else -> ""
|
||||||
}.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) {
|
||||||
|
|
|
@ -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.title
|
binding.title.text = item.manga.title
|
||||||
|
|
||||||
// Update the cover
|
// Update the cover
|
||||||
binding.thumbnail.clear()
|
binding.thumbnail.dispose()
|
||||||
binding.thumbnail.loadAny(item.manga)
|
binding.thumbnail.load(item.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -48,10 +48,10 @@ class SourceComfortableGridHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -48,10 +48,10 @@ class SourceCompactGridHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|
|
@ -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
|
||||||
|
@ -50,9 +50,9 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
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 eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
|
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
|
||||||
|
@ -55,7 +55,7 @@ class LibraryComfortableGridHolder(
|
||||||
binding.badges.localText.isVisible = item.isLocal
|
binding.badges.localText.isVisible = item.isLocal
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
binding.thumbnail.clear()
|
binding.thumbnail.dispose()
|
||||||
binding.thumbnail.loadAnyAutoPause(item.manga)
|
binding.thumbnail.loadAutoPause(item.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
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 coil.dispose
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
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
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
|
* Class used to hold the displayed data of a manga in the library, like the cover or the title.
|
||||||
|
@ -54,11 +54,11 @@ class LibraryCompactGridHolder(
|
||||||
binding.badges.localText.isVisible = item.isLocal
|
binding.badges.localText.isVisible = item.isLocal
|
||||||
|
|
||||||
// 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
|
||||||
|
@ -66,7 +66,7 @@ class LibraryCompactGridHolder(
|
||||||
}
|
}
|
||||||
binding.thumbnail.foreground = null
|
binding.thumbnail.foreground = null
|
||||||
} else {
|
} else {
|
||||||
binding.thumbnail.loadAnyAutoPause(item.manga)
|
binding.thumbnail.loadAutoPause(item.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -334,7 +334,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
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
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 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
|
||||||
|
@ -286,8 +286,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
|
||||||
binding.mangaSummarySection.setTags(manga.getGenres(), controller::performGenreSearch)
|
binding.mangaSummarySection.setTags(manga.getGenres(), controller::performGenreSearch)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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"
|
||||||
|
|
Reference in a new issue