refactor: Migrate to Coil3

This commit is contained in:
Ahmad Ansori Palembani 2024-05-24 07:33:00 +07:00 committed by GitHub
commit e4fa355a8b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
38 changed files with 353 additions and 302 deletions

View file

@ -256,9 +256,7 @@ dependencies {
implementation(libs.injekt.core)
// Image library
implementation(libs.coil)
implementation(libs.coil.gif)
implementation(libs.coil.svg)
implementation(libs.bundles.coil)
// Logging
implementation(libs.timber)
@ -327,7 +325,7 @@ tasks {
"-opt-in=androidx.compose.foundation.ExperimentalFoundationApi",
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.graphics.ExperimentalAnimationGraphicsApi",
"-opt-in=coil.annotation.ExperimentalCoilApi",
"-opt-in=coil3.annotation.ExperimentalCoilApi",
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi
import android.Manifest
import android.annotation.SuppressLint
import android.app.ActivityManager
import android.app.Application
import android.app.PendingIntent
import android.content.BroadcastReceiver
@ -15,18 +16,33 @@ import androidx.appcompat.app.AppCompatDelegate
import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ProcessLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.multidex.MultiDex
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.memory.MemoryCache
import coil3.network.okhttp.OkHttpNetworkFetcherFactory
import coil3.request.allowHardware
import coil3.request.allowRgb565
import coil3.request.crossfade
import coil3.util.DebugLogger
import dev.yokai.domain.AppState
import eu.kanade.tachiyomi.appwidget.TachiyomiWidgetManager
import eu.kanade.tachiyomi.data.image.coil.CoilSetup
import eu.kanade.tachiyomi.data.coil.CoilDiskCache
import eu.kanade.tachiyomi.data.coil.InputStreamFetcher
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.notification.Notifications
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.di.AppModule
import eu.kanade.tachiyomi.di.PreferenceModule
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.ui.library.LibraryPresenter
import eu.kanade.tachiyomi.ui.recents.RecentsPresenter
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
@ -41,10 +57,11 @@ import kotlinx.coroutines.flow.onEach
import org.conscrypt.Conscrypt
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.security.Security
open class App : Application(), DefaultLifecycleObserver {
open class App : Application(), DefaultLifecycleObserver, SingletonImageLoader.Factory {
val preferences: PreferencesHelper by injectLazy()
@ -71,7 +88,6 @@ open class App : Application(), DefaultLifecycleObserver {
Injekt.importModule(PreferenceModule(this))
Injekt.importModule(AppModule(this))
CoilSetup(this)
setupNotificationChannels()
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
@ -171,6 +187,28 @@ open class App : Application(), DefaultLifecycleObserver {
}
}
}
override fun newImageLoader(context: PlatformContext): ImageLoader {
return ImageLoader.Builder(this@App).apply {
val callFactoryLazy = lazy { Injekt.get<NetworkHelper>().client }
val diskCacheLazy = lazy { CoilDiskCache.get(this@App) }
components {
add(OkHttpNetworkFetcherFactory(callFactoryLazy::value))
add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.Factory(callFactoryLazy, diskCacheLazy))
add(MangaCoverKeyer())
add(InputStreamFetcher.Factory())
}
diskCache(diskCacheLazy::value)
memoryCache { MemoryCache.Builder().maxSizePercent(this@App, 0.40).build() }
crossfade(true)
allowRgb565(this@App.getSystemService<ActivityManager>()!!.isLowRamDevice)
allowHardware(true)
if (BuildConfig.DEBUG) {
logger(DebugLogger())
}
}.build()
}
}
private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE"

View file

@ -16,13 +16,14 @@ import androidx.glance.appwidget.provideContent
import androidx.glance.appwidget.updateAll
import androidx.glance.background
import androidx.glance.layout.fillMaxSize
import coil.executeBlocking
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.size.Precision
import coil.size.Scale
import coil.transform.RoundedCornersTransformation
import coil3.executeBlocking
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.transformations
import coil3.size.Precision
import coil3.size.Scale
import coil3.transform.RoundedCornersTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.appwidget.components.CoverHeight
import eu.kanade.tachiyomi.appwidget.components.CoverWidth
@ -109,7 +110,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
}
}
.build()
Pair(updatesView.id!!, app.imageLoader.executeBlocking(request).drawable?.toBitmap())
Pair(updatesView.id!!, app.imageLoader.executeBlocking(request).image?.asDrawable(app.resources)?.toBitmap())
}
}

View file

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.data.cache
import android.content.Context
import android.text.format.Formatter
import coil.imageLoader
import coil.memory.MemoryCache
import coil3.imageLoader
import coil3.memory.MemoryCache
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga

View file

@ -0,0 +1,26 @@
package eu.kanade.tachiyomi.data.coil
import android.content.Context
import coil3.disk.DiskCache
import coil3.disk.directory
/**
* Direct copy of Coil's internal SingletonDiskCache so that [MangaCoverFetcher] can access it.
*/
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

@ -1,11 +1,12 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import android.graphics.drawable.Drawable
import android.view.View
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
import coil.target.ImageViewTarget
import coil3.Image
import coil3.target.ImageViewTarget
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.getResourceColor
@ -15,7 +16,9 @@ class CoverViewTarget(
val scaleType: ImageView.ScaleType = ImageView.ScaleType.CENTER_CROP,
) : ImageViewTarget(view) {
override fun onError(error: Drawable?) {
override fun onError(error: Image?) {
//val drawable = error?.asDrawable(view.context.resources)
progress?.isVisible = false
view.scaleType = ImageView.ScaleType.CENTER
val vector = VectorDrawableCompat.create(
@ -27,13 +30,17 @@ class CoverViewTarget(
view.setImageDrawable(vector)
}
override fun onStart(placeholder: Drawable?) {
override fun onStart(placeholder: Image?) {
//val drawable = placeholder?.asDrawable(view.context.resources)
progress?.isVisible = true
view.scaleType = scaleType
super.onStart(placeholder)
}
override fun onSuccess(result: Drawable) {
override fun onSuccess(result: Image) {
//val drawable = result?.asDrawable(view.context.resources)
progress?.isVisible = false
view.scaleType = scaleType
super.onSuccess(result)

View file

@ -1,12 +1,12 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.request.Options
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import okio.Buffer
import java.io.InputStream
@ -15,10 +15,10 @@ class InputStreamFetcher(
private val options: Options,
) : Fetcher {
override suspend fun fetch(): FetchResult {
return SourceResult(
return SourceFetchResult(
source = ImageSource(
source = stream.use { Buffer().readFrom(it) },
context = options.context,
fileSystem = options.fileSystem,
),
mimeType = null,
dataSource = DataSource.MEMORY,

View file

@ -1,15 +1,15 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import android.graphics.BitmapFactory
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.palette.graphics.Palette
import coil.ImageLoader
import coil.imageLoader
import coil.memory.MemoryCache
import coil.request.Disposable
import coil.request.ImageRequest
import coil.target.ImageViewTarget
import coil3.Image
import coil3.ImageLoader
import coil3.imageLoader
import coil3.memory.MemoryCache
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.target.ImageViewTarget
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.system.launchIO
@ -22,7 +22,7 @@ class LibraryMangaImageTarget(
private val coverCache: CoverCache by injectLazy()
override fun onError(error: Drawable?) {
override fun onError(error: Image?) {
super.onError(error)
if (manga.favorite) {
launchIO {

View file

@ -1,16 +1,16 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import android.webkit.MimeTypeMap
import coil.ImageLoader
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.disk.DiskCache
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.network.HttpException
import coil.request.Options
import coil.request.Parameters
import coil3.Extras
import coil3.ImageLoader
import coil3.decode.DataSource
import coil3.decode.ImageSource
import coil3.disk.DiskCache
import coil3.fetch.FetchResult
import coil3.fetch.Fetcher
import coil3.fetch.SourceFetchResult
import coil3.getOrDefault
import coil3.request.Options
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.network.await
@ -26,17 +26,16 @@ import okhttp3.Call
import okhttp3.Request
import okhttp3.Response
import okhttp3.internal.closeQuietly
import okio.FileSystem
import okio.Path.Companion.toOkioPath
import okio.Source
import okio.buffer
import okio.sink
import okio.source
import timber.log.Timber
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.io.File
import java.net.HttpURLConnection
import java.util.Date
import java.util.*
class MangaCoverFetcher(
private val manga: Manga,
@ -71,7 +70,7 @@ class MangaCoverFetcher(
val networkRead = options.networkCachePolicy.readEnabled
val onlyCache = !networkRead && diskRead
val shouldFetchRemotely = networkRead && !diskRead && !onlyCache
val useCustomCover = options.parameters.value(useCustomCover) ?: true
val useCustomCover = options.extras.getOrDefault(USE_CUSTOM_COVER_KEY)
// Use custom cover if exists
if (!shouldFetchRemotely) {
val customCoverFile by lazy { coverCache.getCustomCoverFile(manga) }
@ -101,7 +100,7 @@ class MangaCoverFetcher(
// Read from snapshot
setRatioAndColorsInScope(manga)
return SourceResult(
return SourceFetchResult(
source = snapshot.toImageSource(),
mimeType = "image/*",
dataSource = DataSource.DISK,
@ -122,7 +121,7 @@ class MangaCoverFetcher(
// Read from disk cache
snapshot = writeToDiskCache(snapshot, response)
if (snapshot != null) {
return SourceResult(
return SourceFetchResult(
source = snapshot.toImageSource(),
mimeType = "image/*",
dataSource = DataSource.NETWORK,
@ -130,8 +129,8 @@ class MangaCoverFetcher(
}
// Read from response if cache is unused or unusable
return SourceResult(
source = ImageSource(source = responseBody.source(), context = options.context),
return SourceFetchResult(
source = ImageSource(source = responseBody.source(), fileSystem = FileSystem.SYSTEM),
mimeType = "image/*",
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK,
)
@ -149,18 +148,20 @@ class MangaCoverFetcher(
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)
response.body.closeQuietly()
throw Exception(response.message) // FIXME: Should probably use something else other than generic Exception
}
return response
}
private fun newRequest(): Request {
val request = Request.Builder()
.url(url)
.headers(sourceLazy.value?.headers ?: options.headers)
// Support attaching custom data to the network request.
.tag(Parameters::class.java, options.parameters)
val request = Request.Builder().apply {
url(url)
val sourceHeaders = sourceLazy.value?.headers
if (sourceHeaders != null)
headers(sourceHeaders)
}
val diskRead = options.diskCachePolicy.readEnabled
val networkRead = options.networkCachePolicy.readEnabled
@ -227,7 +228,11 @@ class MangaCoverFetcher(
}
private fun readFromDiskCache(): DiskCache.Snapshot? {
return if (options.diskCachePolicy.readEnabled) diskCacheLazy.value[diskCacheKey!!] else null
return if (options.diskCachePolicy.readEnabled) {
diskCacheLazy.value.openSnapshot(diskCacheKey!!)
} else {
null
}
}
private fun writeToDiskCache(
@ -239,15 +244,15 @@ class MangaCoverFetcher(
return null
}
val editor = if (snapshot != null) {
snapshot.closeAndEdit()
snapshot.closeAndOpenEditor()
} else {
diskCacheLazy.value.edit(diskCacheKey!!)
diskCacheLazy.value.openEditor(diskCacheKey!!)
} ?: return null
try {
diskCacheLazy.value.fileSystem.write(editor.data) {
response.body!!.source().readAll(this)
response.body.source().readAll(this)
}
return editor.commitAndGet()
return editor.commitAndOpenSnapshot()
} catch (e: Exception) {
try {
editor.abort()
@ -258,7 +263,12 @@ class MangaCoverFetcher(
}
private fun DiskCache.Snapshot.toImageSource(): ImageSource {
return ImageSource(file = data, diskCacheKey = diskCacheKey, closeable = this)
return ImageSource(
file = data,
fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey,
closeable = this,
)
}
private fun setRatioAndColorsInScope(manga: Manga, ogFile: File? = null, force: Boolean = false) {
@ -283,8 +293,12 @@ class MangaCoverFetcher(
}
private fun fileLoader(file: File): FetchResult {
return SourceResult(
source = ImageSource(file = file.toOkioPath(), diskCacheKey = diskCacheKey),
return SourceFetchResult(
source = ImageSource(
file = file.toOkioPath(),
fileSystem = FileSystem.SYSTEM,
diskCacheKey = diskCacheKey,
),
mimeType = "image/*",
dataSource = DataSource.DISK,
)
@ -318,7 +332,7 @@ class MangaCoverFetcher(
}
companion object {
const val useCustomCover = "use_custom_cover"
val USE_CUSTOM_COVER_KEY = Extras.Key(true)
private val CACHE_CONTROL_FORCE_NETWORK_NO_CACHE = CacheControl.Builder().noCache().noStore().build()
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import coil.key.Keyer
import coil.request.Options
import coil3.key.Keyer
import coil3.request.Options
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.util.storage.DiskUtil

View file

@ -1,12 +1,16 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import android.graphics.Bitmap
import android.os.Build
import androidx.core.graphics.drawable.toDrawable
import coil.ImageLoader
import coil.decode.*
import coil.fetch.SourceResult
import coil.request.Options
import coil3.ImageLoader
import coil3.asCoilImage
import coil3.decode.DecodeResult
import coil3.decode.DecodeUtils
import coil3.decode.Decoder
import coil3.decode.ImageSource
import coil3.fetch.SourceFetchResult
import coil3.request.Options
import coil3.request.bitmapConfig
import eu.kanade.tachiyomi.util.system.GLUtil
import eu.kanade.tachiyomi.util.system.ImageUtil
import okio.BufferedSource
@ -79,14 +83,14 @@ class TachiyomiImageDecoder(private val resources: ImageSource, private val opti
*/
return DecodeResult(
drawable = bitmap.toDrawable(options.context.resources),
image = bitmap.asCoilImage(),
isSampled = sampleSize > 1,
)
}
class Factory : Decoder.Factory {
override fun create(result: SourceResult, options: Options, imageLoader: ImageLoader): Decoder? {
override fun create(result: SourceFetchResult, options: Options, imageLoader: ImageLoader): Decoder? {
if (isApplicable(result.source.source()) || options.customDecoder) return TachiyomiImageDecoder(result.source, options)
return null
}

View file

@ -1,14 +1,14 @@
package eu.kanade.tachiyomi.data.image.coil
package eu.kanade.tachiyomi.data.coil
import android.graphics.Bitmap
import android.os.Build
import coil.request.ImageRequest
import coil.request.Options
import coil.size.Dimension
import coil.size.Scale
import coil.size.Size
import coil.size.isOriginal
import coil.size.pxOrElse
import coil3.Extras
import coil3.getExtra
import coil3.request.ImageRequest
import coil3.request.Options
import coil3.size.Dimension
import coil3.size.Scale
import coil3.size.Size
import coil3.size.isOriginal
import coil3.size.pxOrElse
internal inline fun Size.widthPx(scale: Scale, original: () -> Int): Int {
return if (isOriginal) original() else width.toPx(scale)
@ -26,22 +26,19 @@ internal fun Dimension.toPx(scale: Scale): Int = pxOrElse {
}
fun ImageRequest.Builder.cropBorders(enable: Boolean) = apply {
setParameter(cropBordersKey, enable)
extras[cropBordersKey] = enable
}
val Options.cropBorders: Boolean
get() = parameters.value(cropBordersKey) ?: false
get() = getExtra(cropBordersKey)
private val cropBordersKey = "crop_borders"
private val cropBordersKey = Extras.Key(default = false)
fun ImageRequest.Builder.customDecoder(enable: Boolean) = apply {
setParameter(customDecoderKey, enable)
extras[customDecoderKey] = enable
}
val Options.customDecoder: Boolean
get() = parameters.value(customDecoderKey) ?: false
get() = getExtra(customDecoderKey)
private val customDecoderKey = "custom_decoder"
val Options.bitmapConfig: Bitmap.Config
get() = this.config
private val customDecoderKey = Extras.Key(default = false)

View file

@ -1,68 +0,0 @@
package eu.kanade.tachiyomi.data.image.coil
import android.app.ActivityManager
import android.content.Context
import android.os.Build
import androidx.core.content.getSystemService
import coil.Coil
import coil.ImageLoader
import coil.decode.GifDecoder
import coil.decode.ImageDecoderDecoder
import coil.disk.DiskCache
import coil.memory.MemoryCache
import coil.util.DebugLogger
import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class CoilSetup(context: Context) {
init {
val imageLoader = ImageLoader.Builder(context).apply {
val callFactoryInit = { Injekt.get<NetworkHelper>().client }
val diskCacheInit = { CoilDiskCache.get(context) }
components {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
add(ImageDecoderDecoder.Factory())
} else {
add(GifDecoder.Factory())
}
add(TachiyomiImageDecoder.Factory())
add(MangaCoverFetcher.Factory(lazy(callFactoryInit), lazy(diskCacheInit)))
add(MangaCoverKeyer())
add(InputStreamFetcher.Factory())
}
callFactory(callFactoryInit)
diskCache(diskCacheInit)
memoryCache { MemoryCache.Builder(context).maxSizePercent(0.40).build() }
crossfade(true)
allowRgb565(context.getSystemService<ActivityManager>()!!.isLowRamDevice)
allowHardware(true)
if (BuildConfig.DEBUG) {
logger(DebugLogger())
}
}.build()
Coil.setImageLoader(imageLoader)
}
}
/**
* 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

@ -16,9 +16,9 @@ import androidx.work.WorkInfo
import androidx.work.WorkManager
import androidx.work.WorkQuery
import androidx.work.WorkerParameters
import coil.Coil
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -261,21 +261,19 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val thumbnailUrl = manga.thumbnail_url
manga.copyFrom(networkManga)
manga.initialized = true
if (thumbnailUrl != manga.thumbnail_url) {
coverCache.deleteFromCache(thumbnailUrl)
// load new covers in background
val request =
val request: ImageRequest =
if (thumbnailUrl != manga.thumbnail_url) {
coverCache.deleteFromCache(thumbnailUrl)
// load new covers in background
ImageRequest.Builder(context).data(manga)
.memoryCachePolicy(CachePolicy.DISABLED).build()
Coil.imageLoader(context).execute(request)
} else {
val request =
} else {
ImageRequest.Builder(context).data(manga)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.WRITE_ONLY)
.build()
Coil.imageLoader(context).execute(request)
}
}
context.imageLoader.execute(request)
db.insertManga(manga).executeAsBlocking()
}
}

View file

@ -13,10 +13,11 @@ import androidx.core.app.ActivityCompat
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import coil.Coil
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.transform.CircleCropTransformation
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.transformations
import coil3.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
@ -191,11 +192,11 @@ class LibraryUpdateNotifier(private val context: Context) {
.transformations(CircleCropTransformation())
.size(width = ICON_SIZE, height = ICON_SIZE).build()
Coil.imageLoader(context)
.execute(request).drawable?.let { drawable ->
context.imageLoader
.execute(request).image?.asDrawable(context.resources)?.let { drawable ->
setLargeIcon((drawable as? BitmapDrawable)?.bitmap)
}
} catch (e: Exception) {
} catch (_: Exception) {
}
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
setContentTitle(manga.title)

View file

@ -9,10 +9,10 @@ import androidx.core.text.color
import androidx.core.text.scale
import androidx.core.view.isGone
import androidx.core.view.isVisible
import coil.dispose
import coil.load
import coil3.dispose
import coil3.load
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.coil.CoverViewTarget
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
import eu.kanade.tachiyomi.extension.model.Extension
import eu.kanade.tachiyomi.extension.model.InstallStep

View file

@ -10,12 +10,12 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.core.view.marginBottom
import androidx.core.view.updateLayoutParams
import coil.dispose
import coil.size.Precision
import coil.size.Scale
import coil3.dispose
import coil3.size.Precision
import coil3.size.Scale
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
import eu.kanade.tachiyomi.util.lang.highlightText
import eu.kanade.tachiyomi.util.manga.MangaCoverMetadata

View file

@ -4,9 +4,9 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import coil.dispose
import coil3.dispose
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.MangaListItemBinding
import eu.kanade.tachiyomi.util.lang.highlightText
import eu.kanade.tachiyomi.util.system.dpToPx

View file

@ -13,15 +13,14 @@ import android.view.inputmethod.InputMethodManager
import androidx.core.graphics.ColorUtils
import androidx.core.view.children
import androidx.core.view.isVisible
import coil.load
import coil.request.Parameters
import coil3.load
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.LocalSource
@ -215,10 +214,9 @@ class EditMangaDialog : DialogController {
binding.resetCover.setOnClickListener {
binding.mangaCover.load(
manga,
builder = {
parameters(Parameters.Builder().set(MangaCoverFetcher.useCustomCover, false).build())
},
)
) {
extras[MangaCoverFetcher.USE_CUSTOM_COVER_KEY] = false
}
customCoverUri = null
willResetCover = true
}

View file

@ -7,6 +7,7 @@ import android.app.PendingIntent
import android.content.ClipData
import android.content.Context
import android.content.Intent
import android.graphics.Bitmap
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
@ -40,8 +41,9 @@ import androidx.palette.graphics.Palette
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import coil.imageLoader
import coil.request.ImageRequest
import coil3.imageLoader
import coil3.request.ImageRequest
import coil3.request.allowHardware
import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.chip.Chip
@ -56,7 +58,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadJob
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.image.coil.getBestColor
import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.databinding.MangaDetailsControllerBinding
@ -569,8 +571,20 @@ class MangaDetailsController :
val request = ImageRequest.Builder(view.context).data(presenter.manga).allowHardware(false)
.memoryCacheKey(presenter.manga.key())
.target(
onSuccess = { drawable ->
val bitmap = (drawable as? BitmapDrawable)?.bitmap
onSuccess = { image ->
val drawable = image.asDrawable(view.context.resources)
val copy = (drawable as? BitmapDrawable)?.let {
BitmapDrawable(
view.context.resources,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
it.bitmap.copy(Bitmap.Config.HARDWARE, false)
else
it.bitmap.copy(it.bitmap.config, false),
)
} ?: drawable
val bitmap = (copy as? BitmapDrawable)?.bitmap
// Generate the Palette on a background thread.
if (bitmap != null) {
Palette.from(bitmap).generate {
@ -590,7 +604,7 @@ class MangaDetailsController :
}
}
}
binding.mangaCoverFull.setImageDrawable(drawable)
binding.mangaCoverFull.setImageDrawable(copy)
getHeader()?.updateCover(manga!!)
},
onError = {

View file

@ -4,12 +4,11 @@ import android.app.Application
import android.graphics.Bitmap
import android.net.Uri
import android.os.Environment
import coil.Coil
import coil.imageLoader
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.request.SuccessResult
import coil3.imageLoader
import coil3.memory.MemoryCache
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.SuccessResult
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -379,7 +378,7 @@ class MangaDetailsPresenter(
.diskCachePolicy(CachePolicy.WRITE_ONLY)
.build()
if (Coil.imageLoader(preferences.context).execute(request) is SuccessResult) {
if (preferences.context.imageLoader.execute(request) is SuccessResult) {
preferences.context.imageLoader.memoryCache?.remove(MemoryCache.Key(manga.key()))
withContext(Dispatchers.Main) {
view?.setPaletteColor()

View file

@ -25,12 +25,14 @@ import androidx.core.view.updateLayoutParams
import androidx.core.widget.TextViewCompat
import androidx.transition.TransitionSet
import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat
import coil.request.CachePolicy
import coil3.request.CachePolicy
import coil3.request.placeholder
import coil3.request.error
import com.google.android.material.button.MaterialButton
import com.google.android.material.chip.Chip
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.ChapterHeaderItemBinding
import eu.kanade.tachiyomi.databinding.MangaHeaderItemBinding
import eu.kanade.tachiyomi.source.SourceManager
@ -289,7 +291,7 @@ class MangaHeaderHolder(
}
}
@SuppressLint("SetTextI18n")
@SuppressLint("SetTextI18n", "StringFormatInvalid")
fun bind(item: MangaHeaderItem, manga: Manga) {
val presenter = adapter.delegate.mangaPresenter()
if (binding == null) {
@ -680,9 +682,10 @@ class MangaHeaderHolder(
diskCachePolicy(CachePolicy.READ_ONLY)
target(
onSuccess = {
val bitmap = (it as? BitmapDrawable)?.bitmap
val result = it.asDrawable(itemView.resources)
val bitmap = (result as? BitmapDrawable)?.bitmap
if (bitmap == null) {
binding.backdrop.setImageDrawable(it)
binding.backdrop.setImageDrawable(result)
return@target
}
val yOffset = (bitmap.height / 2 * 0.33).toInt()

View file

@ -2,8 +2,9 @@ package eu.kanade.tachiyomi.ui.manga.track
import android.view.View
import androidx.core.view.isVisible
import coil.dispose
import coil.load
import coil3.dispose
import coil3.load
import coil3.request.allowHardware
import com.google.android.material.shape.CornerFamily
import com.mikepenz.fastadapter.FastAdapter
import com.mikepenz.fastadapter.items.AbstractItem

View file

@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.migration
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import coil.dispose
import coil3.dispose
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.MangaListItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.view.setCards

View file

@ -5,13 +5,13 @@ import androidx.appcompat.widget.PopupMenu
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import coil.Coil
import coil.request.ImageRequest
import coil3.imageLoader
import coil3.request.ImageRequest
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.image.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
import eu.kanade.tachiyomi.databinding.MigrationProcessItemBinding
import eu.kanade.tachiyomi.source.Source
@ -20,6 +20,7 @@ import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.ui.library.setFreeformCoverRatio
import eu.kanade.tachiyomi.ui.manga.MangaDetailsController
import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.setExtras
import eu.kanade.tachiyomi.util.view.setCards
import eu.kanade.tachiyomi.util.view.setVectorCompat
import eu.kanade.tachiyomi.util.view.withFadeTransaction
@ -147,9 +148,9 @@ class MigrationProcessHolder(
val request = ImageRequest.Builder(view.context).data(manga)
.target(CoverViewTarget(coverThumbnail, progress))
.setParameter(MangaCoverFetcher.useCustomCover, false)
.setExtras(MangaCoverFetcher.USE_CUSTOM_COVER_KEY, false)
.build()
Coil.imageLoader(view.context).enqueue(request)
view.context.imageLoader.enqueue(request)
compactTitle.isVisible = true
gradient.isVisible = true

View file

@ -81,7 +81,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.core.preference.toggle
import eu.kanade.tachiyomi.data.image.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.coil.TachiyomiImageDecoder
import eu.kanade.tachiyomi.data.preference.changesIn
import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.databinding.ReaderActivityBinding

View file

@ -5,9 +5,9 @@ import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import coil.Coil
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationHandler
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -51,7 +51,7 @@ class SaveImageNotifier(private val context: Context) {
}
},
).build()
Coil.imageLoader(context).enqueue(request)
context.imageLoader.enqueue(request)
}
private fun showCompleteNotification(file: File, image: Bitmap) {

View file

@ -18,19 +18,20 @@ import androidx.annotation.CallSuper
import androidx.annotation.StyleRes
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible
import coil.dispose
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil.size.Precision
import coil.size.ViewSizeResolver
import coil3.dispose
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.crossfade
import coil3.size.Precision
import coil3.size.ViewSizeResolver
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView.SCALE_TYPE_CENTER_INSIDE
import com.github.chrisbanes.photoview.PhotoView
import dev.yokai.domain.ui.settings.ReaderPreferences.CutoutBehaviour
import eu.kanade.tachiyomi.data.image.coil.cropBorders
import eu.kanade.tachiyomi.data.image.coil.customDecoder
import eu.kanade.tachiyomi.data.coil.cropBorders
import eu.kanade.tachiyomi.data.coil.customDecoder
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.webtoon.WebtoonSubsamplingImageView
import eu.kanade.tachiyomi.util.system.DeviceUtil
@ -180,7 +181,7 @@ open class ReaderPageImageView @JvmOverloads constructor(
}
private fun setNonAnimatedImage(
image: Any,
data: Any,
config: Config,
) = (pageView as? SubsamplingScaleImageView)?.apply {
setDoubleTapZoomDuration(config.zoomDuration.getSystemScaledDuration())
@ -226,13 +227,13 @@ open class ReaderPageImageView @JvmOverloads constructor(
val useCoilPipeline = false // FIXME: "Bitmap too large to be uploaded into a texture"
if (isWebtoon && useCoilPipeline) {
val request = ImageRequest.Builder(context)
.data(image)
.data(data)
.memoryCachePolicy(CachePolicy.DISABLED)
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
val drawable = result as BitmapDrawable
setImage(ImageSource.bitmap(drawable.bitmap))
val image = result as BitmapDrawable
setImage(ImageSource.bitmap(image.bitmap))
isVisible = true
},
onError = {
@ -247,10 +248,10 @@ open class ReaderPageImageView @JvmOverloads constructor(
.build()
context.imageLoader.enqueue(request)
} else {
when (image) {
is BitmapDrawable -> setImage(ImageSource.bitmap(image.bitmap))
is InputStream -> setImage(ImageSource.inputStream(image))
else -> throw IllegalArgumentException("Not implemented for class ${image::class.simpleName}")
when (data) {
is BitmapDrawable -> setImage(ImageSource.bitmap(data.bitmap))
is InputStream -> setImage(ImageSource.inputStream(data))
else -> throw IllegalArgumentException("Not implemented for class ${data::class.simpleName}")
}
isVisible = true
}
@ -314,8 +315,9 @@ open class ReaderPageImageView @JvmOverloads constructor(
.diskCachePolicy(CachePolicy.DISABLED)
.target(
onSuccess = { result ->
setImageDrawable(result)
(result as? Animatable)?.start()
val drawable = result.asDrawable(context.resources)
setImageDrawable(drawable)
(drawable as? Animatable)?.start()
isVisible = true
this@ReaderPageImageView.onImageLoaded()
},

View file

@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterHistory
import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.image.coil.loadManga
import eu.kanade.tachiyomi.data.coil.loadManga
import eu.kanade.tachiyomi.databinding.RecentMangaItemBinding
import eu.kanade.tachiyomi.databinding.RecentSubChapterItemBinding
import eu.kanade.tachiyomi.ui.download.DownloadButton

View file

@ -5,16 +5,17 @@ import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.Coil
import coil.dispose
import coil.request.ImageRequest
import coil3.dispose
import coil3.imageLoader
import coil3.request.ImageRequest
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.image.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.databinding.MangaGridItemBinding
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
import eu.kanade.tachiyomi.util.system.setExtras
import eu.kanade.tachiyomi.util.view.setCards
/**
@ -67,9 +68,9 @@ class BrowseSourceGridHolder(
manga.id ?: return
val request = ImageRequest.Builder(view.context).data(manga)
.target(CoverViewTarget(binding.coverThumbnail, binding.progress))
.setParameter(MangaCoverFetcher.useCustomCover, false)
.setExtras(MangaCoverFetcher.USE_CUSTOM_COVER_KEY, false)
.build()
Coil.imageLoader(view.context).enqueue(request)
view.context.imageLoader.enqueue(request)
binding.coverThumbnail.alpha = if (manga.favorite) 0.34f else 1.0f
binding.card.strokeColorStateList?.defaultColor?.let { color ->

View file

@ -3,15 +3,16 @@ package eu.kanade.tachiyomi.ui.source.browse
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import coil.Coil
import coil.dispose
import coil.request.ImageRequest
import coil3.dispose
import coil3.imageLoader
import coil3.request.ImageRequest
import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.image.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.databinding.MangaListItemBinding
import eu.kanade.tachiyomi.util.system.setExtras
import eu.kanade.tachiyomi.util.view.setCards
/**
@ -56,9 +57,9 @@ class BrowseSourceListHolder(
manga.id ?: return
val request = ImageRequest.Builder(view.context).data(manga)
.target(CoverViewTarget(binding.coverThumbnail))
.setParameter(MangaCoverFetcher.useCustomCover, false)
.setExtras(MangaCoverFetcher.USE_CUSTOM_COVER_KEY, false)
.build()
Coil.imageLoader(view.context).enqueue(request)
view.context.imageLoader.enqueue(request)
binding.coverThumbnail.alpha = if (manga.favorite) 0.34f else 1.0f
}

View file

@ -3,16 +3,18 @@ package eu.kanade.tachiyomi.ui.source.globalsearch
import android.graphics.drawable.RippleDrawable
import android.view.View
import androidx.core.view.isVisible
import coil.Coil
import coil.dispose
import coil.request.CachePolicy
import coil.request.ImageRequest
import coil3.dispose
import coil3.imageLoader
import coil3.request.CachePolicy
import coil3.request.ImageRequest
import coil3.request.placeholder
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.image.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.data.coil.CoverViewTarget
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
import eu.kanade.tachiyomi.databinding.SourceGlobalSearchControllerCardItemBinding
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.setExtras
import eu.kanade.tachiyomi.util.view.makeShapeCorners
import eu.kanade.tachiyomi.util.view.setCards
@ -57,9 +59,9 @@ class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
.placeholder(android.R.color.transparent)
.memoryCachePolicy(CachePolicy.DISABLED)
.target(CoverViewTarget(binding.itemImage, binding.progress))
.setParameter(MangaCoverFetcher.useCustomCover, false)
.setExtras(MangaCoverFetcher.USE_CUSTOM_COVER_KEY, false)
.build()
Coil.imageLoader(itemView.context).enqueue(request)
itemView.context.imageLoader.enqueue(request)
}
}
}

View file

@ -5,7 +5,7 @@ import androidx.annotation.ColorInt
import androidx.palette.graphics.Palette
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.image.coil.getBestColor
import eu.kanade.tachiyomi.data.coil.getBestColor
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import uy.kohesive.injekt.injectLazy
import java.io.File

View file

@ -7,8 +7,8 @@ import android.content.pm.ShortcutManager
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Icon
import coil.Coil
import coil.request.ImageRequest
import coil3.imageLoader
import coil3.request.ImageRequest
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.appwidget.TachiyomiWidgetManager
import eu.kanade.tachiyomi.data.cache.CoverCache
@ -72,8 +72,8 @@ class MangaShortcutManager(
is Manga -> {
val request = ImageRequest.Builder(context).data(item).build()
val bitmap = (
Coil.imageLoader(context)
.execute(request).drawable as? BitmapDrawable
context.imageLoader
.execute(request).image?.asDrawable(context.resources) as? BitmapDrawable
)?.bitmap
ShortcutInfo.Builder(

View file

@ -0,0 +1,9 @@
package eu.kanade.tachiyomi.util.system
import coil3.Extras
import coil3.request.ImageRequest
fun <T> ImageRequest.Builder.setExtras(extraKey: Extras.Key<T>, value: T): ImageRequest.Builder {
this.extras[extraKey] = value
return this
}

View file

@ -93,18 +93,20 @@ object ImageUtil {
}
fun isAnimatedAndSupported(stream: InputStream): Boolean {
try {
return try {
val type = getImageType(stream) ?: return false
return when (type.format) {
// https://coil-kt.github.io/coil/getting_started/#supported-image-formats
when (type.format) {
Format.Gif -> true
// Coil supports animated WebP on Android 9.0+
// https://coil-kt.github.io/coil/getting_started/#supported-image-formats
// Animated WebP on Android 9+
Format.Webp -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P
// Animated Heif on Android 11+
Format.Heif -> type.isAnimated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
else -> false
}
} catch (_: Exception) {
false
}
return false
}
enum class ImageType(val mime: String, val extension: String) {

View file

@ -1,20 +1,20 @@
package eu.kanade.tachiyomi.widget
import android.graphics.drawable.Drawable
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.core.view.isVisible
import coil.target.ImageViewTarget
import coil3.Image
import coil3.target.ImageViewTarget
class GifViewTarget(view: ImageView, private val progressBar: View?, private val decodeErrorLayout: ViewGroup?) : ImageViewTarget(view) {
override fun onError(error: Drawable?) {
override fun onError(error: Image?) {
progressBar?.isVisible = false
decodeErrorLayout?.isVisible = true
}
override fun onSuccess(result: Drawable) {
override fun onSuccess(result: Image) {
progressBar?.isVisible = false
decodeErrorLayout?.isVisible = false
super.onSuccess(result)

View file

@ -1,6 +1,6 @@
[versions]
chucker = "3.5.2"
coil = "2.4.0"
coil3 = "3.0.0-alpha06"
flexible-adapter = "c8013533"
fast_adapter = "5.6.0"
nucleus = "3.0.0"
@ -11,9 +11,10 @@ shizuku = "12.1.0"
accompanist-webview = { module = "com.google.accompanist:accompanist-webview", version = "0.30.1" }
chucker-library-no-op = { module = "com.github.ChuckerTeam.Chucker:library-no-op", version.ref = "chucker" }
chucker-library = { module = "com.github.ChuckerTeam.Chucker:library", version.ref = "chucker" }
coil-svg = { module = "io.coil-kt:coil-svg", version.ref = "coil" }
coil-gif = { module = "io.coil-kt:coil-gif", version.ref = "coil" }
coil = { module = "io.coil-kt:coil", version.ref = "coil" }
coil3 = { module = "io.coil-kt.coil3:coil", version.ref = "coil3" }
coil3-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil3" }
coil3-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil3" }
coil3-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil3" }
compose-theme-adapter3 = { module = "com.google.accompanist:accompanist-themeadapter-material3", version = "0.33.2-alpha" }
conductor = { module = "com.bluelinelabs:conductor", version = "4.0.0-preview-4" }
conductor-support-preference = { module = "com.github.tachiyomiorg:conductor-support-preference", version = "3.0.0" }
@ -77,4 +78,5 @@ kotlinter = { id = "org.jmailen.kotlinter", version = "4.1.1" }
gradle-versions = { id = "com.github.ben-manes.versions", version = "0.42.0" }
[bundles]
coil = [ "coil3", "coil3-svg", "coil3-gif", "coil3-okhttp" ]
test = [ "junit", "mockk" ]