diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 48affbb09b..4a92b63a7a 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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", diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index 824431737a..05a217b8e8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -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().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()!!.isLowRamDevice) + allowHardware(true) + if (BuildConfig.DEBUG) { + logger(DebugLogger()) + } + }.build() + } } private const val ACTION_DISABLE_INCOGNITO_MODE = "tachi.action.DISABLE_INCOGNITO_MODE" diff --git a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt index 7fb5fa0a13..e939be381e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/appwidget/UpdatesGridGlanceWidget.kt @@ -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()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 3a905aa254..4318389ebe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/CoilDiskCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/CoilDiskCache.kt new file mode 100644 index 0000000000..cfd29f0f76 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/CoilDiskCache.kt @@ -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 } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoverViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/CoverViewTarget.kt similarity index 71% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoverViewTarget.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/CoverViewTarget.kt index 4fe176bb67..6cba222f9e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoverViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/CoverViewTarget.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/InputStreamFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/InputStreamFetcher.kt similarity index 65% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/InputStreamFetcher.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/InputStreamFetcher.kt index 5c7755b1de..9a52220764 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/InputStreamFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/InputStreamFetcher.kt @@ -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, diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt similarity index 89% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt index 40d067dbfa..4490e1178b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/LibraryMangaImageTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/LibraryMangaImageTarget.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt similarity index 85% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverFetcher.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt index c7685f4082..11b872d4aa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverKeyer.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt similarity index 82% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverKeyer.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt index 221b3b5a96..8a041e2048 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/MangaCoverKeyer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverKeyer.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/TachiyomiImageDecoder.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt similarity index 88% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/TachiyomiImageDecoder.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt index 330b82b17b..960bcafd5d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/TachiyomiImageDecoder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/TachiyomiImageDecoder.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/Utils.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt similarity index 50% rename from app/src/main/java/eu/kanade/tachiyomi/data/image/coil/Utils.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt index 9c5718a29e..7a920bf398 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/Utils.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/Utils.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoilSetup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoilSetup.kt deleted file mode 100644 index a8e44cd3f9..0000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/image/coil/CoilSetup.kt +++ /dev/null @@ -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().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()!!.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 } - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt index a07997d56a..744145a357 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateJob.kt @@ -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() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 8a1831dec3..084fe7a8be 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt index fc1fbafc63..cb833d281d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/extension/ExtensionHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt index 3d085d0b0e..814c7ca5fc 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 5099c3ff77..3998aae715 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt index c618eec2bd..7c70e0cf93 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/EditMangaDialog.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index 48fead9445..2968e09510 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -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 = { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index 9a89552f0f..dcaa7293e2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index a6d45bb274..7a58bb1b80 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchItem.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchItem.kt index 9b967b2995..102da326bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchItem.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchItem.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt index 8a49f2902b..0149631070 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/MangaHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt index 872f62850e..d5733827ac 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/migration/manga/process/MigrationProcessHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index b044f53c27..79fc18ef45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 416a649d51..cf56551c9f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt index 3f87f74b6a..990e9d103e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/ReaderPageImageView.kt @@ -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() }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt index eda65eddcd..ae73cfc29f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recents/RecentMangaHolder.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt index c2e731cb38..15c9eab1ff 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt @@ -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 -> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt index 90c5d14610..704b751b16 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt @@ -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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt index b1e2135119..2e9cfad610 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/globalsearch/GlobalSearchMangaHolder.kt @@ -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) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt index 9b4f3addef..8780e73cea 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaCoverMetadata.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt index 20f70d8f27..404e4a9944 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/manga/MangaShortcutManager.kt @@ -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( diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/CoilExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/CoilExtensions.kt new file mode 100644 index 0000000000..2d1fa727cd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/CoilExtensions.kt @@ -0,0 +1,9 @@ +package eu.kanade.tachiyomi.util.system + +import coil3.Extras +import coil3.request.ImageRequest + +fun ImageRequest.Builder.setExtras(extraKey: Extras.Key, value: T): ImageRequest.Builder { + this.extras[extraKey] = value + return this +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt index cb2b24d5dd..d989fdc7b1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/ImageUtil.kt @@ -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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/GifViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/GifViewTarget.kt index 408688c115..baedc0313b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/GifViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/GifViewTarget.kt @@ -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) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e3f031f0b3..f4224f0ce8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" ]