diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c610fb412..8713b176e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -205,10 +205,9 @@ dependencies { implementation("com.github.inorichi.injekt:injekt-core:65b0440") // Image library - val glideVersion = "4.12.0" - implementation("com.github.bumptech.glide:glide:$glideVersion") - implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion") - kapt("com.github.bumptech.glide:compiler:$glideVersion") + val coilVersion = "1.2.0" + implementation("io.coil-kt:coil:$coilVersion") + implementation("io.coil-kt:coil-gif:$coilVersion") implementation("com.github.tachiyomiorg:subsampling-scale-image-view:547d9c0") @@ -318,7 +317,8 @@ tasks { "-Xuse-experimental=kotlinx.coroutines.FlowPreview", "-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi", "-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi", - "-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi" + "-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi", + "-Xuse-experimental=coil.annotation.ExperimentalCoilApi", ) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/App.kt b/app/src/main/java/eu/kanade/tachiyomi/App.kt index cc7a37945..8411a9bf5 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/App.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/App.kt @@ -1,16 +1,22 @@ package eu.kanade.tachiyomi +import android.app.ActivityManager import android.app.Application import android.content.Context import android.content.res.Configuration import android.graphics.Color import android.os.Build import android.os.Environment +import androidx.core.content.getSystemService import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleObserver import androidx.lifecycle.OnLifecycleEvent import androidx.lifecycle.ProcessLifecycleOwner import androidx.multidex.MultiDex +import coil.ImageLoader +import coil.ImageLoaderFactory +import coil.decode.GifDecoder +import coil.decode.ImageDecoderDecoder import com.elvishew.xlog.LogConfiguration import com.elvishew.xlog.LogLevel import com.elvishew.xlog.XLog @@ -26,8 +32,11 @@ import com.google.firebase.analytics.ktx.analytics import com.google.firebase.ktx.Firebase import com.ms_square.debugoverlay.DebugOverlay import com.ms_square.debugoverlay.modules.FpsModule +import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper +import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate import eu.kanade.tachiyomi.util.system.LocaleHelper import exh.debug.DebugToggles @@ -43,6 +52,7 @@ import io.realm.Realm 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.io.File import java.security.NoSuchAlgorithmException @@ -53,7 +63,7 @@ import javax.net.ssl.SSLContext import kotlin.time.ExperimentalTime import kotlin.time.days -open class App : Application(), LifecycleObserver { +open class App : Application(), LifecycleObserver, ImageLoaderFactory { private val preferences: PreferencesHelper by injectLazy() @@ -97,6 +107,23 @@ open class App : Application(), LifecycleObserver { LocaleHelper.updateConfiguration(this, newConfig, true) } + override fun newImageLoader(): ImageLoader { + return ImageLoader.Builder(this).apply { + componentRegistry { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + add(ImageDecoderDecoder(this@App)) + } else { + add(GifDecoder()) + } + add(ByteBufferFetcher()) + add(MangaCoverFetcher()) + } + okHttpClient(Injekt.get().coilClient) + crossfade(300) + allowRgb565(getSystemService()!!.isLowRamDevice) + }.build() + } + private fun workaroundAndroid7BrokenSSL() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N || Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1 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 4d1a00607..fbdfc52bd 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.data.cache import android.content.Context +import coil.imageLoader import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.util.storage.DiskUtil import java.io.File @@ -99,6 +100,13 @@ class CoverCache(private val context: Context) { } } + /** + * Clear coil's memory cache. + */ + fun clearMemoryCache() { + context.imageLoader.memoryCache.clear() + } + private fun getCacheDir(dir: String): File { return context.getExternalFilesDir(dir) ?: File(context.filesDir, dir).also { it.mkdirs() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/ByteBufferFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/ByteBufferFetcher.kt new file mode 100644 index 000000000..78d9c7ecd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/ByteBufferFetcher.kt @@ -0,0 +1,25 @@ +package eu.kanade.tachiyomi.data.coil + +import coil.bitmap.BitmapPool +import coil.decode.DataSource +import coil.decode.Options +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.fetch.SourceResult +import coil.size.Size +import okio.buffer +import okio.source +import java.io.ByteArrayInputStream +import java.nio.ByteBuffer + +class ByteBufferFetcher : Fetcher { + override suspend fun fetch(pool: BitmapPool, data: ByteBuffer, size: Size, options: Options): FetchResult { + return SourceResult( + source = ByteArrayInputStream(data.array()).source().buffer(), + mimeType = null, + dataSource = DataSource.MEMORY + ) + } + + override fun key(data: ByteBuffer): String? = null +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt new file mode 100644 index 000000000..6aae106cd --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/coil/MangaCoverFetcher.kt @@ -0,0 +1,172 @@ +package eu.kanade.tachiyomi.data.coil + +import coil.bitmap.BitmapPool +import coil.decode.DataSource +import coil.decode.Options +import coil.fetch.FetchResult +import coil.fetch.Fetcher +import coil.fetch.SourceResult +import coil.network.HttpException +import coil.request.get +import coil.size.Size +import eu.kanade.tachiyomi.data.cache.CoverCache +import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.network.NetworkHelper +import eu.kanade.tachiyomi.network.await +import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.HttpSource +import okhttp3.CacheControl +import okhttp3.Call +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody +import okio.buffer +import okio.sink +import okio.source +import uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get +import uy.kohesive.injekt.injectLazy +import java.io.File +import java.util.Date + +/** + * Coil component that fetches [Manga] cover while using the cached file in disk when available. + * + * Available request parameter: + * - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true + */ +class MangaCoverFetcher : Fetcher { + private val coverCache: CoverCache by injectLazy() + private val sourceManager: SourceManager by injectLazy() + private val defaultClient = Injekt.get().coilClient + + override fun key(data: Manga): String? { + if (data.thumbnail_url.isNullOrBlank()) return null + return data.thumbnail_url!! + } + + override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult { + // Use custom cover if exists + val useCustomCover = options.parameters[USE_CUSTOM_COVER] as? Boolean ?: true + val customCoverFile = coverCache.getCustomCoverFile(data) + if (useCustomCover && customCoverFile.exists()) { + return fileLoader(customCoverFile) + } + + val cover = data.thumbnail_url + return when (getResourceType(cover)) { + Type.URL -> httpLoader(data, options) + Type.File -> fileLoader(data) + null -> error("Invalid image") + } + } + + private suspend fun httpLoader(manga: Manga, options: Options): FetchResult { + val coverFile = coverCache.getCoverFile(manga) ?: error("No cover specified") + + // Use previously cached cover if exist + if (coverFile.exists() && options.diskCachePolicy.readEnabled) { + if (!manga.favorite) { + coverFile.setLastModified(Date().time) + } + return fileLoader(coverFile) + } + + val (response, body) = awaitGetCall(manga, options) + if (!response.isSuccessful) { + body.close() + throw HttpException(response) + } + + // Write to disk for future use + if (options.diskCachePolicy.writeEnabled) { + response.peekBody(Long.MAX_VALUE).source().use { input -> + val tmpFile = File(coverFile.absolutePath + "_tmp") + tmpFile.parentFile?.mkdirs() + tmpFile.sink().buffer().use { output -> + output.writeAll(input) + } + if (coverFile.exists()) { + coverFile.delete() + } + tmpFile.renameTo(coverFile) + } + } + + return SourceResult( + source = body.source(), + mimeType = "image/*", + dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK + ) + } + + private suspend fun awaitGetCall(manga: Manga, options: Options): Pair { + val call = getCall(manga, options) + val response = call.await() + return response to checkNotNull(response.body) { "Null response source" } + } + + private fun getCall(manga: Manga, options: Options): Call { + val source = sourceManager.get(manga.source) as? HttpSource + val client = source?.client ?: defaultClient + + val newClient = client.newBuilder().build() + + val request = Request.Builder().url(manga.thumbnail_url!!).also { + if (source != null) { + it.headers(source.headers) + } + + val networkRead = options.networkCachePolicy.readEnabled + val diskRead = options.diskCachePolicy.readEnabled + when { + !networkRead && diskRead -> { + it.cacheControl(CacheControl.FORCE_CACHE) + } + networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) { + it.cacheControl(CacheControl.FORCE_NETWORK) + } else { + it.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE) + } + !networkRead && !diskRead -> { + // This causes the request to fail with a 504 Unsatisfiable Request. + it.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE) + } + } + }.build() + + return newClient.newCall(request) + } + + private fun fileLoader(manga: Manga): FetchResult { + return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://"))) + } + + private fun fileLoader(file: File): FetchResult { + return SourceResult( + source = file.source().buffer(), + mimeType = "image/*", + dataSource = DataSource.DISK + ) + } + + private fun getResourceType(cover: String?): Type? { + return when { + cover.isNullOrEmpty() -> null + cover.startsWith("http") || cover.startsWith("Custom-", true) -> Type.URL + cover.startsWith("/") || cover.startsWith("file://") -> Type.File + else -> null + } + } + + private enum class Type { + File, URL + } + + companion object { + const val USE_CUSTOM_COVER = "use_custom_cover" + + 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/glide/FileFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt deleted file mode 100755 index a54c3cede..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt +++ /dev/null @@ -1,60 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import android.content.ContentValues.TAG -import android.util.Log -import com.bumptech.glide.Priority -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.data.DataFetcher -import timber.log.Timber -import java.io.File -import java.io.FileInputStream -import java.io.FileNotFoundException -import java.io.IOException -import java.io.InputStream - -open class FileFetcher(private val filePath: String = "") : DataFetcher { - - private var data: InputStream? = null - - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { - loadFromFile(callback) - } - - private fun loadFromFile(callback: DataFetcher.DataCallback) { - loadFromFile(File(filePath), callback) - } - - protected fun loadFromFile(file: File, callback: DataFetcher.DataCallback) { - try { - data = FileInputStream(file) - } catch (e: FileNotFoundException) { - if (Log.isLoggable(TAG, Log.DEBUG)) { - Timber.d(e, "Failed to open file") - } - callback.onLoadFailed(e) - return - } - - callback.onDataReady(data) - } - - override fun cleanup() { - try { - data?.close() - } catch (e: IOException) { - // Ignored. - } - } - - override fun cancel() { - // Do nothing. - } - - override fun getDataClass(): Class { - return InputStream::class.java - } - - override fun getDataSource(): DataSource { - return DataSource.LOCAL - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt deleted file mode 100644 index 3d04f40c2..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaCustomCoverFetcher.kt +++ /dev/null @@ -1,25 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.Priority -import com.bumptech.glide.load.data.DataFetcher -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.models.Manga -import java.io.File -import java.io.InputStream -import java.lang.Exception - -open class LibraryMangaCustomCoverFetcher( - private val manga: Manga, - private val coverCache: CoverCache -) : FileFetcher() { - - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { - getCustomCoverFile()?.let { - loadFromFile(it, callback) - } ?: callback.onLoadFailed(Exception("Custom cover file not found")) - } - - protected fun getCustomCoverFile(): File? { - return coverCache.getCustomCoverFile(manga).takeIf { it.exists() } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt deleted file mode 100644 index 5d40dd6a4..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt +++ /dev/null @@ -1,86 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.Priority -import com.bumptech.glide.load.data.DataFetcher -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.models.Manga -import java.io.File -import java.io.FileNotFoundException -import java.io.InputStream - -/** - * A [DataFetcher] for loading a cover of a library manga. - * It tries to load the cover from our custom cache, and if it's not found, it fallbacks to network - * and copies the result to the cache. - * - * @param networkFetcher the network fetcher for this cover. - * @param manga the manga of the cover to load. - * @param file the file where this cover should be. It may exists or not. - */ -class LibraryMangaUrlFetcher( - private val networkFetcher: DataFetcher, - private val manga: Manga, - private val coverCache: CoverCache -) : LibraryMangaCustomCoverFetcher(manga, coverCache) { - - override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { - getCustomCoverFile()?.let { - loadFromFile(it, callback) - return - } - - val cover = coverCache.getCoverFile(manga) - if (cover == null) { - callback.onLoadFailed(Exception("Null thumbnail url")) - return - } - - if (!cover.exists()) { - networkFetcher.loadData( - priority, - object : DataFetcher.DataCallback { - override fun onDataReady(data: InputStream?) { - if (data != null) { - val tmpFile = File(cover.path + ".tmp") - try { - // Retrieve destination stream, create parent folders if needed. - val output = try { - tmpFile.outputStream() - } catch (e: FileNotFoundException) { - tmpFile.parentFile!!.mkdirs() - tmpFile.outputStream() - } - - // Copy the file and rename to the original. - data.use { output.use { data.copyTo(output) } } - tmpFile.renameTo(cover) - loadFromFile(cover, callback) - } catch (e: Exception) { - tmpFile.delete() - callback.onLoadFailed(e) - } - } else { - callback.onLoadFailed(Exception("Null data")) - } - } - - override fun onLoadFailed(e: Exception) { - callback.onLoadFailed(e) - } - } - ) - } else { - loadFromFile(cover, callback) - } - } - - override fun cleanup() { - super.cleanup() - networkFetcher.cleanup() - } - - override fun cancel() { - super.cancel() - networkFetcher.cancel() - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnail.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnail.kt deleted file mode 100644 index ae0057eb4..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnail.kt +++ /dev/null @@ -1,15 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.load.Key -import eu.kanade.tachiyomi.data.database.models.Manga -import java.security.MessageDigest - -data class MangaThumbnail(val manga: Manga, val coverLastModified: Long) : Key { - val key = manga.url + coverLastModified - - override fun updateDiskCacheKey(messageDigest: MessageDigest) { - messageDigest.update(key.toByteArray(Key.CHARSET)) - } -} - -fun Manga.toMangaThumbnail() = MangaThumbnail(this, cover_last_modified) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt deleted file mode 100644 index b3cf79a15..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaThumbnailModelLoader.kt +++ /dev/null @@ -1,134 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher -import com.bumptech.glide.load.Options -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.load.model.Headers -import com.bumptech.glide.load.model.LazyHeaders -import com.bumptech.glide.load.model.ModelLoader -import com.bumptech.glide.load.model.ModelLoaderFactory -import com.bumptech.glide.load.model.MultiModelLoaderFactory -import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.network.NetworkHelper -import eu.kanade.tachiyomi.source.SourceManager -import eu.kanade.tachiyomi.source.online.HttpSource -import eu.kanade.tachiyomi.util.isLocal -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import uy.kohesive.injekt.injectLazy -import java.io.InputStream - -/** - * A class for loading a cover associated with a [Manga] that can be present in our own cache. - * Coupled with [LibraryMangaUrlFetcher], this class allows to implement the following flow: - * - * - Check in RAM LRU. - * - Check in disk LRU. - * - Check in this module. - * - Fetch from the network connection. - * - * @param context the application context. - */ -class MangaThumbnailModelLoader : ModelLoader { - - /** - * Cover cache where persistent covers are stored. - */ - private val coverCache: CoverCache by injectLazy() - - /** - * Source manager. - */ - private val sourceManager: SourceManager by injectLazy() - - /** - * Default network client. - */ - private val defaultClient = Injekt.get().client - - /** - * Map where request headers are stored for a source. - */ - private val cachedHeaders = hashMapOf() - - /** - * Factory class for creating [MangaThumbnailModelLoader] instances. - */ - class Factory : ModelLoaderFactory { - - override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { - return MangaThumbnailModelLoader() - } - - override fun teardown() {} - } - - override fun handles(model: MangaThumbnail): Boolean { - return true - } - - /** - * Returns a fetcher for the given manga or null if the url is empty. - * - * @param mangaThumbnail the model. - * @param width the width of the view where the resource will be loaded. - * @param height the height of the view where the resource will be loaded. - */ - override fun buildLoadData( - mangaThumbnail: MangaThumbnail, - width: Int, - height: Int, - options: Options - ): ModelLoader.LoadData? { - val manga = mangaThumbnail.manga - val url = manga.thumbnail_url - - if (url.isNullOrEmpty()) { - return if (!manga.favorite || manga.isLocal()) { - null - } else { - ModelLoader.LoadData(mangaThumbnail, LibraryMangaCustomCoverFetcher(manga, coverCache)) - } - } - - if (url.startsWith("http", true)) { - val source = sourceManager.get(manga.source) as? HttpSource - val glideUrl = GlideUrl(url, getHeaders(manga, source)) - - // Get the resource fetcher for this request url. - val networkFetcher = OkHttpStreamFetcher(source?.client ?: defaultClient, glideUrl) - - if (!manga.favorite) { - return ModelLoader.LoadData(glideUrl, networkFetcher) - } - - val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, coverCache) - - // Return an instance of the fetcher providing the needed elements. - return ModelLoader.LoadData(mangaThumbnail, libraryFetcher) - } else { - // Return an instance of the fetcher providing the needed elements. - return ModelLoader.LoadData(mangaThumbnail, FileFetcher(url.removePrefix("file://"))) - } - } - - /** - * Returns the request headers for a source copying its OkHttp headers and caching them. - * - * @param manga the model. - */ - private fun getHeaders(manga: Manga, source: HttpSource?): Headers { - if (source == null) return LazyHeaders.DEFAULT - - return cachedHeaders.getOrPut(manga.source) { - LazyHeaders.Builder().apply { - val nullStr: String? = null - setHeader("User-Agent", nullStr) - for ((key, value) in source.headers.toMultimap()) { - addHeader(key, value[0]) - } - }.build() - } - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt deleted file mode 100644 index dd6d546f8..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/PassthroughModelLoader.kt +++ /dev/null @@ -1,72 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.Priority -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.Options -import com.bumptech.glide.load.data.DataFetcher -import com.bumptech.glide.load.model.ModelLoader -import com.bumptech.glide.load.model.ModelLoaderFactory -import com.bumptech.glide.load.model.MultiModelLoaderFactory -import com.bumptech.glide.signature.ObjectKey -import java.io.IOException -import java.io.InputStream - -class PassthroughModelLoader : ModelLoader { - - override fun buildLoadData( - model: InputStream, - width: Int, - height: Int, - options: Options - ): ModelLoader.LoadData? { - return ModelLoader.LoadData(ObjectKey(model), Fetcher(model)) - } - - override fun handles(model: InputStream): Boolean { - return true - } - - class Fetcher(private val stream: InputStream) : DataFetcher { - - override fun getDataClass(): Class { - return InputStream::class.java - } - - override fun cleanup() { - try { - stream.close() - } catch (e: IOException) { - // Do nothing - } - } - - override fun getDataSource(): DataSource { - return DataSource.LOCAL - } - - override fun cancel() { - // Do nothing - } - - override fun loadData( - priority: Priority, - callback: DataFetcher.DataCallback - ) { - callback.onDataReady(stream) - } - } - - /** - * Factory class for creating [PassthroughModelLoader] instances. - */ - class Factory : ModelLoaderFactory { - - override fun build( - multiFactory: MultiModelLoaderFactory - ): ModelLoader { - return PassthroughModelLoader() - } - - override fun teardown() {} - } -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt deleted file mode 100755 index dca2e0879..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt +++ /dev/null @@ -1,55 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import android.content.Context -import android.graphics.drawable.Drawable -import com.bumptech.glide.Glide -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.Registry -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.DecodeFormat -import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.module.AppGlideModule -import com.bumptech.glide.request.RequestOptions -import eu.kanade.tachiyomi.network.NetworkHelper -import uy.kohesive.injekt.Injekt -import uy.kohesive.injekt.api.get -import java.io.InputStream - -/** - * Class used to update Glide module settings - */ -@GlideModule -class TachiGlideModule : AppGlideModule() { - - override fun applyOptions(context: Context, builder: GlideBuilder) { - builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024)) - builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565)) - builder.setDefaultTransitionOptions( - Drawable::class.java, - DrawableTransitionOptions.withCrossFade() - ) - } - - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - val networkFactory = OkHttpUrlLoader.Factory(Injekt.get().client) - - registry.replace( - GlideUrl::class.java, - InputStream::class.java, - networkFactory - ) - registry.append( - MangaThumbnail::class.java, - InputStream::class.java, - MangaThumbnailModelLoader.Factory() - ) - registry.append( - InputStream::class.java, - InputStream::class.java, - PassthroughModelLoader.Factory() - ) - } -} 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 c50c57fb2..ce50b31f0 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 @@ -6,20 +6,23 @@ import android.content.Context import android.content.Intent import android.graphics.Bitmap import android.graphics.BitmapFactory +import android.graphics.drawable.BitmapDrawable import android.net.Uri import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat -import com.bumptech.glide.Glide +import coil.imageLoader +import coil.request.ImageRequest +import coil.transform.CircleCropTransformation 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.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.util.lang.chop +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.system.notification import eu.kanade.tachiyomi.util.system.notificationBuilder import eu.kanade.tachiyomi.util.system.notificationManager @@ -166,14 +169,17 @@ class LibraryUpdateNotifier(private val context: Context) { // Per-manga notification if (!preferences.hideNotificationContent()) { - updates.forEach { (manga, chapters) -> - notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters)) + launchUI { + updates.forEach { (manga, chapters) -> + notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters)) + } } } } } - private fun createNewChaptersNotification(manga: Manga, chapters: Array): Notification { + private suspend fun createNewChaptersNotification(manga: Manga, chapters: Array): Notification { + val icon = getMangaIcon(manga) return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) { setContentTitle(manga.title) @@ -183,7 +189,6 @@ class LibraryUpdateNotifier(private val context: Context) { setSmallIcon(R.drawable.ic_tachi) - val icon = getMangaIcon(manga) if (icon != null) { setLargeIcon(icon) } @@ -227,23 +232,14 @@ class LibraryUpdateNotifier(private val context: Context) { context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS) } - private fun getMangaIcon(manga: Manga): Bitmap? { - return try { - Glide.with(context) - .asBitmap() - .load(manga.toMangaThumbnail()) - .dontTransform() - .centerCrop() - .circleCrop() - .override( - NOTIF_ICON_SIZE, - NOTIF_ICON_SIZE - ) - .submit() - .get() - } catch (e: Exception) { - null - } + private suspend fun getMangaIcon(manga: Manga): Bitmap? { + val request = ImageRequest.Builder(context) + .data(manga) + .transformations(CircleCropTransformation()) + .size(NOTIF_ICON_SIZE) + .build() + val drawable = context.imageLoader.execute(request).drawable + return (drawable as? BitmapDrawable)?.bitmap } private fun getNewChaptersDescription(chapters: Array): String { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 81629e488..9754ee040 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -507,6 +507,7 @@ class LibraryUpdateService( } } + coverCache.clearMemoryCache() notifier.cancelProgressNotification() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt index a108cc9ca..21e4dab5a 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -1,6 +1,7 @@ package eu.kanade.tachiyomi.network import android.content.Context +import coil.util.CoilUtils import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.data.preference.PreferencesHelper import okhttp3.Cache @@ -18,30 +19,34 @@ import java.util.concurrent.TimeUnit private val cacheSize = 5L * 1024 * 1024 // 5 MiB - /* SY --> */ open /* SY <-- */ val cookieManager = AndroidCookieJar() + /* SY --> */ open /* SY <-- */val cookieManager = AndroidCookieJar() - /* SY --> */ open /* SY <-- */ val client by lazy { - val builder = OkHttpClient.Builder() - .cookieJar(cookieManager) - .cache(Cache(cacheDir, cacheSize)) - .connectTimeout(30, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) - .addInterceptor(UserAgentInterceptor()) + private val baseClientBuilder: OkHttpClient.Builder + get() { + val builder = OkHttpClient.Builder() + .cookieJar(cookieManager) + .connectTimeout(30, TimeUnit.SECONDS) + .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(UserAgentInterceptor()) - if (BuildConfig.DEBUG) { - val httpLoggingInterceptor = HttpLoggingInterceptor().apply { - level = HttpLoggingInterceptor.Level.HEADERS + if (BuildConfig.DEBUG) { + val httpLoggingInterceptor = HttpLoggingInterceptor().apply { + level = HttpLoggingInterceptor.Level.HEADERS + } + builder.addInterceptor(httpLoggingInterceptor) } - builder.addInterceptor(httpLoggingInterceptor) + + when (preferences.dohProvider()) { + PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() + PREF_DOH_GOOGLE -> builder.dohGoogle() + } + + return builder } - when (preferences.dohProvider()) { - PREF_DOH_CLOUDFLARE -> builder.dohCloudflare() - PREF_DOH_GOOGLE -> builder.dohGoogle() - } + /* SY --> */ open /* SY <-- */val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() } - builder.build() - } + val coilClient by lazy { baseClientBuilder.cache(CoilUtils.createDefaultCache(context)).build() } /* SY --> */ open /* SY <-- */val cloudflareClient by lazy { client.newBuilder() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt index 57e83c7f8..6f5446244 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/extension/ExtensionHolder.kt @@ -2,9 +2,10 @@ package eu.kanade.tachiyomi.ui.browse.extension import android.annotation.SuppressLint import android.view.View +import coil.clear +import coil.load import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi @@ -47,11 +48,9 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) : // SY <-- }.toUpperCase() - GlideApp.with(itemView.context).clear(binding.image) + binding.image.clear() if (extension is Extension.Available) { - GlideApp.with(itemView.context) - .load(extension.iconUrl) - .into(binding.image) + binding.image.load(extension.iconUrl) } else { extension.getApplicationIcon(itemView.context)?.let { binding.image.setImageDrawable(it) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/latest/LatestCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/latest/LatestCardHolder.kt index fc3292f10..a78be2ccb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/latest/LatestCardHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/latest/LatestCardHolder.kt @@ -1,11 +1,14 @@ package eu.kanade.tachiyomi.ui.browse.latest import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.widget.StateImageViewTarget @@ -42,15 +45,18 @@ class LatestCardHolder(view: View, adapter: LatestCardAdapter) : } fun setImage(manga: Manga) { - GlideApp.with(itemView.context).clear(binding.cover) + binding.cover.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - GlideApp.with(itemView.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .centerCrop() - .skipMemoryCache(true) - .placeholder(android.R.color.transparent) - .into(StateImageViewTarget(binding.cover, binding.progress)) + val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(itemView.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt index 0b3ff1447..fd482f008 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/advanced/process/MigrationProcessHolder.kt @@ -4,13 +4,12 @@ import android.view.View import android.widget.PopupMenu import androidx.core.view.isInvisible import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.loadAny import eu.davidea.viewholders.FlexibleViewHolder 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.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.MigrationMangaCardBinding import eu.kanade.tachiyomi.databinding.MigrationProcessItemBinding import eu.kanade.tachiyomi.source.Source @@ -128,6 +127,7 @@ class MigrationProcessHolder( private fun MigrationMangaCardBinding.resetManga() { loadingGroup.isVisible = true + thumbnail.clear() thumbnail.setImageDrawable(null) title.text = "" mangaSourceLabel.text = "" @@ -138,12 +138,9 @@ class MigrationProcessHolder( private suspend fun MigrationMangaCardBinding.attachManga(manga: Manga, source: Source) { loadingGroup.isVisible = false - GlideApp.with(view.context.applicationContext) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .dontAnimate() - .into(thumbnail) + // For rounded corners + card.clipToOutline = true + thumbnail.loadAny(manga) title.text = if (manga.title.isBlank()) { view.context.getString(R.string.unknown) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaHolder.kt index c503b78ab..d4b0a1d9e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/manga/MigrationMangaHolder.kt @@ -1,14 +1,11 @@ package eu.kanade.tachiyomi.ui.browse.migration.manga import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.loadAny +import coil.transform.RoundedCornersTransformation import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceListItemBinding class MigrationMangaHolder( @@ -28,15 +25,10 @@ class MigrationMangaHolder( binding.title.text = item.manga.originalTitle // Update the cover. - GlideApp.with(itemView.context).clear(binding.thumbnail) - - val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - GlideApp.with(itemView.context) - .load(item.manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .apply(requestOptions) - .dontAnimate() - .into(binding.thumbnail) + val radius = itemView.context.resources.getDimension(R.dimen.card_radius) + binding.thumbnail.clear() + binding.thumbnail.loadAny(item.manga) { + transformations(RoundedCornersTransformation(radius)) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt index 4a8f7f790..088204872 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceComfortableGridHolder.kt @@ -2,12 +2,15 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.view.View import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding import eu.kanade.tachiyomi.widget.StateImageViewTarget import exh.metadata.metadata.MangaDexSearchMetadata @@ -60,14 +63,18 @@ class SourceComfortableGridHolder(private val view: View, private val adapter: F // For rounded corners binding.card.clipToOutline = true - GlideApp.with(view.context).clear(binding.thumbnail) + binding.thumbnail.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - GlideApp.with(view.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .centerCrop() - .placeholder(android.R.color.transparent) - .into(StateImageViewTarget(binding.thumbnail, binding.progress)) + val crossfadeDuration = view.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(view.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceEnhancedEHentaiListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceEnhancedEHentaiListHolder.kt index 23290e382..37525d323 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceEnhancedEHentaiListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceEnhancedEHentaiListHolder.kt @@ -1,17 +1,18 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceEnhancedEhentaiListItemBinding import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.widget.StateImageViewTarget import exh.metadata.MetadataUtil import exh.metadata.metadata.EHentaiSearchMetadata import exh.metadata.metadata.base.RaisedSearchMetadata @@ -91,18 +92,21 @@ class SourceEnhancedEHentaiListHolder(private val view: View, adapter: FlexibleA } override fun setImage(manga: Manga) { - GlideApp.with(view.context).clear(binding.thumbnail) + // For rounded corners + binding.card.clipToOutline = true + binding.thumbnail.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - GlideApp.with(view.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .apply(requestOptions) - .dontAnimate() - .placeholder(android.R.color.transparent) - .into(binding.thumbnail) + val crossfadeDuration = view.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(view.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt index a1f8bf58c..e6ef6d62e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceGridHolder.kt @@ -2,12 +2,15 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.view.View import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding import eu.kanade.tachiyomi.widget.StateImageViewTarget import exh.metadata.metadata.MangaDexSearchMetadata @@ -57,14 +60,18 @@ open class SourceGridHolder(private val view: View, private val adapter: Flexibl // For rounded corners binding.card.clipToOutline = true - GlideApp.with(view.context).clear(binding.thumbnail) + binding.thumbnail.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - GlideApp.with(view.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .centerCrop() - .placeholder(android.R.color.transparent) - .into(StateImageViewTarget(binding.thumbnail, binding.progress)) + val crossfadeDuration = view.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(view.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt index f15a3c098..c9dca5e83 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/SourceListHolder.kt @@ -2,15 +2,14 @@ package eu.kanade.tachiyomi.ui.browse.source.browse import android.view.View import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.loadAny +import coil.request.CachePolicy +import coil.transform.RoundedCornersTransformation import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceListItemBinding import eu.kanade.tachiyomi.util.system.getResourceColor import exh.metadata.metadata.MangaDexSearchMetadata @@ -60,18 +59,14 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) : // SY <-- override fun setImage(manga: Manga) { - GlideApp.with(view.context).clear(binding.thumbnail) - + binding.thumbnail.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - GlideApp.with(view.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .apply(requestOptions) - .dontAnimate() - .placeholder(android.R.color.transparent) - .into(binding.thumbnail) + val radius = view.context.resources.getDimension(R.dimen.card_radius) + binding.thumbnail.loadAny(manga) { + setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + transformations(RoundedCornersTransformation(radius)) + diskCachePolicy(CachePolicy.DISABLED) + } } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchCardHolder.kt index 8b0310f1f..d7c83b063 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchCardHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/globalsearch/GlobalSearchCardHolder.kt @@ -1,11 +1,14 @@ package eu.kanade.tachiyomi.ui.browse.source.globalsearch import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.widget.StateImageViewTarget @@ -42,15 +45,18 @@ class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) : } fun setImage(manga: Manga) { - GlideApp.with(itemView.context).clear(binding.cover) + binding.cover.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - GlideApp.with(itemView.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .centerCrop() - .skipMemoryCache(true) - .placeholder(android.R.color.transparent) - .into(StateImageViewTarget(binding.cover, binding.progress)) + val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(itemView.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt index 3ae3391c9..3f98241c2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/index/IndexCardHolder.kt @@ -1,11 +1,14 @@ package eu.kanade.tachiyomi.ui.browse.source.index import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest +import coil.transition.CrossfadeTransition import eu.davidea.viewholders.FlexibleViewHolder +import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding import eu.kanade.tachiyomi.widget.StateImageViewTarget @@ -42,15 +45,18 @@ class IndexCardHolder(view: View, adapter: IndexCardAdapter) : } fun setImage(manga: Manga) { - GlideApp.with(itemView.context).clear(binding.cover) + binding.cover.clear() if (!manga.thumbnail_url.isNullOrEmpty()) { - GlideApp.with(itemView.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.DATA) - .centerCrop() - .skipMemoryCache(true) - .placeholder(android.R.color.transparent) - .into(StateImageViewTarget(binding.cover, binding.progress)) + val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let { + if (it is CrossfadeTransition) it.durationMillis else 0 + } + val request = ImageRequest.Builder(itemView.context) + .data(manga) + .setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false) + .diskCachePolicy(CachePolicy.DISABLED) + .target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration)) + .build() + itemView.context.imageLoader.enqueue(request) } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt index bbf579420..d4bebe072 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryComfortableGridHolder.kt @@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.ui.library import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.loadAny import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding import eu.kanade.tachiyomi.util.isLocal import kotlinx.coroutines.flow.launchIn @@ -86,13 +85,8 @@ class LibraryComfortableGridHolder( binding.card.clipToOutline = true // Update the cover. - GlideApp.with(view.context).clear(binding.thumbnail) - GlideApp.with(view.context) - .load(item.manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .dontAnimate() - .into(binding.thumbnail) + binding.thumbnail.clear() + binding.thumbnail.loadAny(item.manga) } // SY --> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt index fe7a82a93..99264678b 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryCompactGridHolder.kt @@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.ui.library import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.loadAny import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding import eu.kanade.tachiyomi.util.isLocal import kotlinx.coroutines.flow.launchIn @@ -82,13 +81,8 @@ class LibraryCompactGridHolder( binding.card.clipToOutline = true // Update the cover. - GlideApp.with(view.context).clear(binding.thumbnail) - GlideApp.with(view.context) - .load(item.manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .dontAnimate() - .into(binding.thumbnail) + binding.thumbnail.clear() + binding.thumbnail.loadAny(item.manga) } // SY --> 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 0695f65c0..e8bb29130 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -3,15 +3,12 @@ package eu.kanade.tachiyomi.ui.library import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.loadAny +import coil.transform.RoundedCornersTransformation import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.items.IFlexible import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.SourceListItemBinding import eu.kanade.tachiyomi.util.isLocal @@ -66,15 +63,10 @@ class LibraryListHolder( } // Update the cover. - GlideApp.with(itemView.context).clear(binding.thumbnail) - - val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - GlideApp.with(itemView.context) - .load(item.manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .apply(requestOptions) - .dontAnimate() - .into(binding.thumbnail) + val radius = view.context.resources.getDimension(R.dimen.card_radius) + binding.thumbnail.clear() + binding.thumbnail.loadAny(item.manga) { + transformations(RoundedCornersTransformation(radius)) + } } } 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 7b6195b29..581b6eee2 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 @@ -10,18 +10,17 @@ import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import androidx.core.view.children import androidx.core.view.isVisible +import coil.loadAny +import coil.transform.RoundedCornersTransformation import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.customview.customView import com.afollestad.materialdialogs.input.getInputField import com.afollestad.materialdialogs.input.input -import com.bumptech.glide.load.engine.DiskCacheStrategy 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.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.EditMangaDialogBinding import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.source.model.SManga @@ -77,13 +76,10 @@ class EditMangaDialog : DialogController { } fun onViewCreated() { - val mangaThumbnail = manga.toMangaThumbnail() - - GlideApp.with(context) - .load(mangaThumbnail) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .into(binding.mangaCover) + val radius = context.resources.getDimension(R.dimen.card_radius) + binding.mangaCover.loadAny(manga) { + transformations(RoundedCornersTransformation(radius)) + } val isLocal = manga.source == LocalSource.ID @@ -183,11 +179,10 @@ class EditMangaDialog : DialogController { fun updateCover(uri: Uri) { willResetCover = false - GlideApp.with(context) - .load(uri) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .into(binding.mangaCover) + val radius = context.resources.getDimension(R.dimen.card_radius) + binding.mangaCover.loadAny(uri) { + transformations(RoundedCornersTransformation(radius)) + } customCoverUri = uri } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 25d2e3952..04ea3dfab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -30,11 +30,11 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import coil.loadAny import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.list.listItemsSingleChoice import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType -import com.bumptech.glide.load.engine.DiskCacheStrategy import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton import com.google.android.material.snackbar.Snackbar import dev.chrisbanes.insetter.applyInsetter @@ -48,8 +48,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.databinding.MangaControllerBinding @@ -591,12 +589,7 @@ class MangaController : mangaInfoAdapter?.update(manga, source) mangaInfoItemAdapter?.update(manga, source, presenter.meta) - val mangaThumbnail = manga.toMangaThumbnail() - GlideApp.with(activity!!) - .load(mangaThumbnail) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .fitCenter() - .into(binding.expandedImage) + binding.expandedImage.loadAny(manga) } else { // Initialize manga. fetchMangaInfoFromSource() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 49cf6149d..e05e0dcb5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -674,6 +674,7 @@ class MangaPresenter( } else if (manga.favorite) { coverCache.setCustomCoverToCache(manga, it) manga.updateCoverLastModified(db) + coverCache.clearMemoryCache() } } } @@ -690,6 +691,7 @@ class MangaPresenter( .fromCallable { coverCache.deleteCustomCover(manga) manga.updateCoverLastModified(db) + coverCache.clearMemoryCache() } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt index e20a07107..8377f2630 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoHeaderAdapter.kt @@ -5,13 +5,10 @@ import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.loadAny 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.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.MangaThumbnail -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding import eu.kanade.tachiyomi.source.Source @@ -52,7 +49,7 @@ class MangaInfoHeaderAdapter( private lateinit var binding: MangaInfoHeaderBinding - private var currentMangaThumbnail: MangaThumbnail? = null + private var initialLoad: Boolean = true override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder { binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false) @@ -284,17 +281,8 @@ class MangaInfoHeaderAdapter( setFavoriteButtonState(manga.favorite) // Set cover if changed. - val mangaThumbnail = manga.toMangaThumbnail() - if (mangaThumbnail != currentMangaThumbnail) { - currentMangaThumbnail = mangaThumbnail - listOf(binding.mangaCover, binding.backdrop) - .forEach { - GlideApp.with(view.context) - .load(mangaThumbnail) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .into(it) - } + listOf(binding.mangaCover, binding.backdrop).forEach { + it.loadAny(manga) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/merged/EditMergedMangaHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/merged/EditMergedMangaHolder.kt index cdd306980..4dd42f82f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/merged/EditMergedMangaHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/merged/EditMergedMangaHolder.kt @@ -1,11 +1,10 @@ package eu.kanade.tachiyomi.ui.manga.merged import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.loadAny +import coil.transform.RoundedCornersTransformation import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.EditMergedSettingsItemBinding import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.system.getResourceColor @@ -39,12 +38,11 @@ class EditMergedMangaHolder(view: View, val adapter: EditMergedMangaAdapter) : F fun bind(item: EditMergedMangaItem) { reference = item.mergedMangaReference - item.mergedManga?.toMangaThumbnail()?.let { - GlideApp.with(itemView.context) - .load(it) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .into(binding.cover) + item.mergedManga?.let { + val radius = itemView.context.resources.getDimension(R.dimen.card_radius) + binding.cover.loadAny(it) { + transformations(RoundedCornersTransformation(radius)) + } } binding.title.text = Injekt.get().getOrStub(item.mergedMangaReference.mangaSourceId).toString() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt index 2ee8ba352..af5c92b02 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/track/TrackSearchAdapter.kt @@ -5,9 +5,9 @@ import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.clear +import coil.load import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.track.model.TrackSearch import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding import eu.kanade.tachiyomi.util.view.inflate @@ -46,13 +46,9 @@ class TrackSearchAdapter(context: Context) : fun onSetValues(track: TrackSearch) { binding.trackSearchTitle.text = track.title binding.trackSearchSummary.text = track.summary - GlideApp.with(view.context).clear(binding.trackSearchCover) + binding.trackSearchCover.clear() if (track.cover_url.isNotEmpty()) { - GlideApp.with(view.context) - .load(track.cover_url) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .centerCrop() - .into(binding.trackSearchCover) + binding.trackSearchCover.load(track.cover_url) } val hasStatus = track.publishing_status.isNotBlank() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt index 627aede4b..fb80a24d8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/more/AboutController.kt @@ -1,7 +1,6 @@ package eu.kanade.tachiyomi.ui.more import android.app.Dialog -import android.os.Build import android.os.Bundle import androidx.core.os.bundleOf import androidx.preference.PreferenceScreen @@ -26,7 +25,6 @@ import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.copyToClipboard import eu.kanade.tachiyomi.util.system.toast import exh.syDebugVersion -import exh.util.under import timber.log.Timber import java.text.DateFormat import java.text.ParseException diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index b16dae5c8..840311aa8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt @@ -670,8 +670,8 @@ class ReaderPresenter( // Pictures directory. val baseDir = Environment.getExternalStorageDirectory().absolutePath + - File.separator + Environment.DIRECTORY_PICTURES + - File.separator + context.getString(R.string.app_name) + File.separator + Environment.DIRECTORY_PICTURES + + File.separator + context.getString(R.string.app_name) val destDir = if (preferences.folderPerManga()) { File(baseDir + File.separator + manga.title) } else { @@ -737,6 +737,7 @@ class ReaderPresenter( if (manga.favorite) { coverCache.setCustomCoverToCache(manga, stream()) manga.updateCoverLastModified(db) + coverCache.clearMemoryCache() SetAsCoverResult.Success } else { SetAsCoverResult.AddToLibraryFirst 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 2814b412d..98e844304 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt @@ -2,10 +2,12 @@ package eu.kanade.tachiyomi.ui.reader import android.content.Context import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import androidx.core.app.NotificationCompat -import com.bumptech.glide.load.engine.DiskCacheStrategy +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.notification.NotificationHandler import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.Notifications @@ -30,25 +32,25 @@ class SaveImageNotifier(private val context: Context) { get() = Notifications.ID_DOWNLOAD_IMAGE /** - * Called when image download/copy is complete. This method must be called in a background - * thread. + * Called when image download/copy is complete. * * @param file image file containing downloaded page image. */ fun onComplete(file: File) { - val bitmap = GlideApp.with(context) - .asBitmap() - .load(file) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .skipMemoryCache(true) - .submit(720, 1280) - .get() - - if (bitmap != null) { - showCompleteNotification(file, bitmap) - } else { - onError(null) - } + val request = ImageRequest.Builder(context) + .data(file) + .memoryCachePolicy(CachePolicy.DISABLED) + .size(720, 1280) + .target( + onSuccess = { result -> + showCompleteNotification(file, (result as BitmapDrawable).bitmap) + }, + onError = { + onError(null) + } + ) + .build() + context.imageLoader.enqueue(request) } private fun showCompleteNotification(file: File, image: Bitmap) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt index 911b3e0fc..0da437f45 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/pager/PagerPageHolder.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.graphics.BitmapFactory import android.graphics.PointF +import android.graphics.drawable.Animatable import android.graphics.drawable.Drawable import android.view.GestureDetector import android.view.Gravity @@ -16,19 +17,13 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.core.view.isVisible -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.load.resource.gif.GifDrawable -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import com.bumptech.glide.request.transition.NoTransition +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.github.chrisbanes.photoview.PhotoView import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.InsertPage @@ -50,6 +45,7 @@ import rx.schedulers.Schedulers import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import java.io.InputStream +import java.nio.ByteBuffer import java.util.concurrent.TimeUnit /** @@ -534,39 +530,25 @@ class PagerPageHolder( * Extension method to set a [stream] into this ImageView. */ private fun ImageView.setImage(stream: InputStream) { - GlideApp.with(this) - .load(stream) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(DrawableTransitionOptions.with(NoTransition.getFactory())) - .listener( - object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - onImageDecodeError() - return false - } - - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - if (resource is GifDrawable) { - resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) - } - onImageDecoded() - return false + val request = ImageRequest.Builder(context) + .data(ByteBuffer.wrap(stream.readBytes())) + .memoryCachePolicy(CachePolicy.DISABLED) + .diskCachePolicy(CachePolicy.DISABLED) + .target( + onSuccess = { result -> + if (result is Animatable) { + result.start() } + setImageDrawable(result) + onImageDecoded() + }, + onError = { + onImageDecodeError() } ) - .into(this) + .crossfade(false) + .build() + context.imageLoader.enqueue(request) } // SY --> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt index 9a042738e..d1b05f190 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonPageHolder.kt @@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon import android.annotation.SuppressLint import android.content.res.Resources -import android.graphics.drawable.Drawable +import android.graphics.drawable.Animatable import android.view.Gravity import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT @@ -14,18 +14,13 @@ import android.widget.TextView import androidx.appcompat.widget.AppCompatButton import androidx.appcompat.widget.AppCompatImageView import androidx.core.view.isVisible -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.load.resource.gif.GifDrawable -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.target.Target -import com.bumptech.glide.request.transition.NoTransition +import coil.clear +import coil.imageLoader +import coil.request.CachePolicy +import coil.request.ImageRequest import com.davemorrissey.labs.subscaleview.ImageSource import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.ui.reader.model.ReaderPage import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar @@ -37,6 +32,7 @@ import rx.Subscription import rx.android.schedulers.AndroidSchedulers import rx.schedulers.Schedulers import java.io.InputStream +import java.nio.ByteBuffer import java.util.concurrent.TimeUnit /** @@ -146,7 +142,7 @@ class WebtoonPageHolder( removeDecodeErrorLayout() subsamplingImageView?.recycle() subsamplingImageView?.isVisible = false - imageView?.let { GlideApp.with(frame).clear(it) } + imageView?.clear() imageView?.isVisible = false progressBar.setProgress(0) } @@ -515,38 +511,24 @@ class WebtoonPageHolder( * Extension method to set a [stream] into this ImageView. */ private fun ImageView.setImage(stream: InputStream) { - GlideApp.with(this) - .load(stream) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.NONE) - .transition(DrawableTransitionOptions.with(NoTransition.getFactory())) - .listener( - object : RequestListener { - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target?, - isFirstResource: Boolean - ): Boolean { - onImageDecodeError() - return false - } - - override fun onResourceReady( - resource: Drawable?, - model: Any?, - target: Target?, - dataSource: DataSource?, - isFirstResource: Boolean - ): Boolean { - if (resource is GifDrawable) { - resource.setLoopCount(GifDrawable.LOOP_INTRINSIC) - } - onImageDecoded() - return false + val request = ImageRequest.Builder(context) + .data(ByteBuffer.wrap(stream.readBytes())) + .memoryCachePolicy(CachePolicy.DISABLED) + .diskCachePolicy(CachePolicy.DISABLED) + .target( + onSuccess = { result -> + if (result is Animatable) { + result.start() } + setImageDrawable(result) + onImageDecoded() + }, + onError = { + onImageDecodeError() } ) - .into(this) + .crossfade(false) + .build() + context.imageLoader.enqueue(request) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt index 3d10d931d..8661cb845 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/history/HistoryHolder.kt @@ -1,15 +1,12 @@ package eu.kanade.tachiyomi.ui.recent.history import android.view.View -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.loadAny +import coil.transform.RoundedCornersTransformation import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.HistoryItemBinding import eu.kanade.tachiyomi.util.lang.toTimestampString import java.util.Date @@ -68,15 +65,11 @@ class HistoryHolder( binding.mangaSubtitle.text = Date(history.last_read).toTimestampString() } - val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - // Set cover - GlideApp.with(itemView.context).clear(binding.cover) - GlideApp.with(itemView.context) - .load(manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .apply(requestOptions) - .into(binding.cover) + val radius = itemView.context.resources.getDimension(R.dimen.card_radius) + binding.cover.clear() + binding.cover.loadAny(item.manga) { + transformations(RoundedCornersTransformation(radius)) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt index e0afdcbdc..e3226de05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent/updates/UpdatesHolder.kt @@ -2,13 +2,10 @@ package eu.kanade.tachiyomi.ui.recent.updates import android.view.View import androidx.core.view.isVisible -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.resource.bitmap.CenterCrop -import com.bumptech.glide.load.resource.bitmap.RoundedCorners -import com.bumptech.glide.request.RequestOptions +import coil.clear +import coil.loadAny +import coil.transform.RoundedCornersTransformation import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.glide.GlideApp -import eu.kanade.tachiyomi.data.glide.toMangaThumbnail import eu.kanade.tachiyomi.databinding.UpdatesItemBinding import eu.kanade.tachiyomi.source.LocalSource import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder @@ -58,15 +55,10 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter) binding.download.setState(item.status, item.progress) // Set cover - GlideApp.with(itemView.context).clear(binding.mangaCover) - - val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius) - val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius)) - GlideApp.with(itemView.context) - .load(item.manga.toMangaThumbnail()) - .diskCacheStrategy(DiskCacheStrategy.RESOURCE) - .apply(requestOptions) - .dontAnimate() - .into(binding.mangaCover) + val radius = itemView.context.resources.getDimension(R.dimen.card_radius) + binding.mangaCover.clear() + binding.mangaCover.loadAny(item.manga) { + transformations(RoundedCornersTransformation(radius)) + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt index a48800230..0600e5d61 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt @@ -3,61 +3,41 @@ package eu.kanade.tachiyomi.widget import android.graphics.drawable.Drawable import android.view.View import android.widget.ImageView -import android.widget.ImageView.ScaleType -import androidx.appcompat.content.res.AppCompatResources import androidx.core.view.isVisible -import com.bumptech.glide.request.target.ImageViewTarget -import com.bumptech.glide.request.transition.Transition -import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.util.system.getResourceColor +import coil.drawable.CrossfadeDrawable +import coil.target.ImageViewTarget /** - * A glide target to display an image with an optional view to show while loading and a configurable - * error drawable. + * A Coil target to display an image with an optional view to show while loading. * - * @param view the view where the image will be loaded - * @param progress an optional view to show when the image is loading. - * @param errorDrawableRes the error drawable resource to show. - * @param errorScaleType the scale type for the error drawable, [ScaleType.CENTER] by default. + * @param target the view where the image will be loaded + * @param progress the view to show when the image is loading. + * @param crossfadeDuration duration in millisecond to crossfade the result drawable */ class StateImageViewTarget( - view: ImageView, - val progress: View? = null, - private val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp, - private val errorScaleType: ScaleType = ScaleType.CENTER -) : ImageViewTarget(view) { - - private var resource: Drawable? = null - - private val imageScaleType = view.scaleType - - override fun setResource(resource: Drawable?) { - view.setImageDrawable(resource) + private val target: ImageView, + private val progress: View, + private val crossfadeDuration: Int = 0 +) : ImageViewTarget(target) { + override fun onStart(placeholder: Drawable?) { + progress.isVisible = true } - override fun onLoadStarted(placeholder: Drawable?) { - progress?.isVisible = true - super.onLoadStarted(placeholder) + override fun onSuccess(result: Drawable) { + progress.isVisible = false + if (crossfadeDuration > 0) { + val crossfadeResult = CrossfadeDrawable(target.drawable, result, durationMillis = crossfadeDuration) + target.setImageDrawable(crossfadeResult) + crossfadeResult.start() + } else { + target.setImageDrawable(result) + } } - override fun onLoadFailed(errorDrawable: Drawable?) { - progress?.isVisible = false - view.scaleType = errorScaleType - - val vector = AppCompatResources.getDrawable(view.context, errorDrawableRes) - vector?.setTint(view.context.getResourceColor(R.attr.colorOnBackground, 0.38f)) - view.setImageDrawable(vector) - } - - override fun onLoadCleared(placeholder: Drawable?) { - progress?.isVisible = false - super.onLoadCleared(placeholder) - } - - override fun onResourceReady(resource: Drawable, transition: Transition?) { - progress?.isVisible = false - view.scaleType = imageScaleType - super.onResourceReady(resource, transition) - this.resource = resource + override fun onError(error: Drawable?) { + progress.isVisible = false + if (error != null) { + target.setImageDrawable(error) + } } } diff --git a/app/src/main/java/exh/EXHMigrations.kt b/app/src/main/java/exh/EXHMigrations.kt index 31f1fb6d5..1ef6cf62a 100644 --- a/app/src/main/java/exh/EXHMigrations.kt +++ b/app/src/main/java/exh/EXHMigrations.kt @@ -232,9 +232,9 @@ object EXHMigrations { // Reset rotation to Free after replacing Lock preferences.rotation().set(1) // Disable update check for Android 5.x users - //if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT under Build.VERSION_CODES.M) { - // UpdaterJob.cancelTask(context) - //} + // if (BuildConfig.INCLUDE_UPDATER && Build.VERSION.SDK_INT under Build.VERSION_CODES.M) { + // UpdaterJob.cancelTask(context) + // } } // if (oldVersion under 1) { } (1 is current release version) diff --git a/app/src/main/res/layout/edit_manga_dialog.xml b/app/src/main/res/layout/edit_manga_dialog.xml index 51adabaae..615098733 100644 --- a/app/src/main/res/layout/edit_manga_dialog.xml +++ b/app/src/main/res/layout/edit_manga_dialog.xml @@ -19,7 +19,8 @@ android:layout_height="150dp" android:contentDescription="@string/description_cover" android:background="@drawable/rounded_rectangle" - android:src="@mipmap/ic_launcher"/> + android:src="@mipmap/ic_launcher" + android:scaleType="centerCrop"/> - - + diff --git a/app/src/main/res/layout/source_compact_grid_item.xml b/app/src/main/res/layout/source_compact_grid_item.xml index 0bee8b19b..bdd1ee41f 100644 --- a/app/src/main/res/layout/source_compact_grid_item.xml +++ b/app/src/main/res/layout/source_compact_grid_item.xml @@ -17,6 +17,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/colorSurface" + android:scaleType="centerCrop" tools:ignore="ContentDescription" tools:src="@mipmap/ic_launcher" /> diff --git a/app/src/main/res/layout/source_enhanced_ehentai_list_item.xml b/app/src/main/res/layout/source_enhanced_ehentai_list_item.xml index 09450e07d..abae011b1 100644 --- a/app/src/main/res/layout/source_enhanced_ehentai_list_item.xml +++ b/app/src/main/res/layout/source_enhanced_ehentai_list_item.xml @@ -10,16 +10,33 @@ tools:layout_editor_absoluteX="0dp" tools:layout_editor_absoluteY="25dp"> - - + app:layout_constraintTop_toTopOf="parent"> + + + + + + @@ -43,7 +60,7 @@ android:ellipsize="end" android:maxLines="1" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@+id/thumbnail" + app:layout_constraintStart_toEndOf="@+id/card" app:layout_constraintTop_toBottomOf="@+id/title" tools:text="Manga title for the life of me I cant think yes totally" /> @@ -55,7 +72,7 @@ android:layout_marginBottom="8dp" android:background="@drawable/rounded_rectangle" app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintStart_toEndOf="@+id/thumbnail"> + app:layout_constraintStart_toEndOf="@+id/card"> + app:layout_constraintStart_toEndOf="@+id/card" />