From 1470e9d5ca089aaffede6b894296e79010317940 Mon Sep 17 00:00:00 2001 From: len Date: Sat, 14 Oct 2017 18:16:11 +0200 Subject: [PATCH] Glide v4 --- app/build.gradle | 9 +- app/proguard-rules.pro | 1 + app/src/main/AndroidManifest.xml | 4 - .../tachiyomi/data/glide/FileFetcher.kt | 84 +++++---- .../data/glide/LibraryMangaUrlFetcher.kt | 72 ++++++++ .../tachiyomi/data/glide/MangaFileFetcher.kt | 18 -- .../tachiyomi/data/glide/MangaModelLoader.kt | 70 +++++--- .../tachiyomi/data/glide/MangaSignature.kt | 27 +++ .../tachiyomi/data/glide/MangaUrlFetcher.kt | 71 -------- ...{AppGlideModule.kt => TachiGlideModule.kt} | 23 ++- .../ui/catalogue/CatalogueGridHolder.kt | 9 +- .../ui/catalogue/CatalogueListHolder.kt | 11 +- .../CatalogueSearchCardHolder.kt | 8 +- .../tachiyomi/ui/library/LibraryGridHolder.kt | 8 +- .../tachiyomi/ui/library/LibraryListHolder.kt | 11 +- .../ui/manga/info/MangaInfoController.kt | 167 +++++++++--------- .../tachiyomi/ui/reader/SaveImageNotifier.kt | 8 +- .../ui/recent_updates/RecentChapterHolder.kt | 11 +- .../ui/recently_read/RecentlyReadHolder.kt | 10 +- .../tachiyomi/widget/StateImageViewTarget.kt | 21 ++- 20 files changed, 351 insertions(+), 292 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaSignature.kt delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt rename app/src/main/java/eu/kanade/tachiyomi/data/glide/{AppGlideModule.kt => TachiGlideModule.kt} (50%) diff --git a/app/build.gradle b/app/build.gradle index d2200e0a3..3c464147e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ import java.text.SimpleDateFormat apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' if (file("custom.gradle").exists()) { apply from: "custom.gradle" @@ -169,10 +170,12 @@ dependencies { compile "uy.kohesive.injekt:injekt-core:1.16.1" // Image library - compile 'com.github.bumptech.glide:glide:3.8.0' - compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar' + compile 'com.github.bumptech.glide:glide:4.1.1' + compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1' + kapt 'com.github.bumptech.glide:compiler:4.1.1' + // Transformations - compile 'jp.wasabeef:glide-transformations:2.0.2' + compile 'jp.wasabeef:glide-transformations:3.0.1' // Logging compile 'com.jakewharton.timber:timber:4.5.1' diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index dac8ffbf9..cc477df74 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -24,6 +24,7 @@ # Glide specific rules # # https://github.com/bumptech/glide -keep public class * implements com.bumptech.glide.module.GlideModule +-keep public class * extends com.bumptech.glide.AppGlideModule -keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** { **[] $VALUES; public *; diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 74ddb8c84..0c88aa9d7 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -95,10 +95,6 @@ android:name=".data.backup.BackupRestoreService" android:exported="false"/> - - 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 index 6e1e06ff4..a795e2e05 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt @@ -1,35 +1,51 @@ -package eu.kanade.tachiyomi.data.glide - -import com.bumptech.glide.Priority -import com.bumptech.glide.load.data.DataFetcher -import java.io.File -import java.io.IOException -import java.io.InputStream - -open class FileFetcher(private val file: File) : DataFetcher { - - private var data: InputStream? = null - - override fun loadData(priority: Priority): InputStream { - data = file.inputStream() - return data!! - } - - override fun cleanup() { - data?.let { data -> - try { - data.close() - } catch (e: IOException) { - // Ignore - } - } - } - - override fun cancel() { - // Do nothing. - } - - override fun getId(): String { - return file.toString() - } +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 java.io.* + +open class FileFetcher(private val file: File) : DataFetcher { + + private var data: InputStream? = null + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + loadFromFile(callback) + } + + protected fun loadFromFile(callback: DataFetcher.DataCallback) { + try { + data = FileInputStream(file) + } catch (e: FileNotFoundException) { + if (Log.isLoggable(TAG, Log.DEBUG)) { + Log.d(TAG, "Failed to open file", e) + } + 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 + } } \ No newline at end of file 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 new file mode 100644 index 000000000..0f8b55fcb --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt @@ -0,0 +1,72 @@ +package eu.kanade.tachiyomi.data.glide + +import com.bumptech.glide.Priority +import com.bumptech.glide.load.data.DataFetcher +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 file: File) +: FileFetcher(file) { + + override fun loadData(priority: Priority, callback: DataFetcher.DataCallback) { + if (!file.exists()) { + networkFetcher.loadData(priority, object : DataFetcher.DataCallback { + override fun onDataReady(data: InputStream?) { + if (data != null) { + val tmpFile = File(file.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(file) + } catch (e: Exception) { + tmpFile.delete() + callback.onLoadFailed(e) + } + loadFromFile(callback) + } else { + callback.onLoadFailed(Exception("Null data")) + } + } + + override fun onLoadFailed(e: Exception) { + callback.onLoadFailed(e) + } + + }) + } else { + loadFromFile(callback) + } + } + + override fun cleanup() { + super.cleanup() + networkFetcher.cleanup() + } + + override fun cancel() { + super.cancel() + networkFetcher.cancel() + } + +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt deleted file mode 100644 index 5e594e496..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.data.glide - -import eu.kanade.tachiyomi.data.database.models.Manga -import java.io.File - -open class MangaFileFetcher(private val file: File, private val manga: Manga) : FileFetcher(file) { - - /** - * Returns the id for this manga's cover. - * - * Appending the file's modified date to the url, we can force Glide to skip its memory and disk - * lookup step and fetch from our custom cache. This allows us to invalidate Glide's cache when - * the file has changed. If the file doesn't exist it will append a 0. - */ - override fun getId(): String { - return manga.thumbnail_url + file.lastModified() - } -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt index f5342c451..682a2b39c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt @@ -1,23 +1,24 @@ package eu.kanade.tachiyomi.data.glide -import android.content.Context import android.util.LruCache -import com.bumptech.glide.Glide import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher -import com.bumptech.glide.load.data.DataFetcher +import com.bumptech.glide.load.Options import com.bumptech.glide.load.model.* -import com.bumptech.glide.load.model.stream.StreamModelLoader 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 uy.kohesive.injekt.Injekt +import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy import java.io.File import java.io.InputStream + /** * A class for loading a cover associated with a [Manga] that can be present in our own cache. - * Coupled with [MangaUrlFetcher], this class allows to implement the following flow: + * Coupled with [LibraryMangaUrlFetcher], this class allows to implement the following flow: * * - Check in RAM LRU. * - Check in disk LRU. @@ -26,7 +27,7 @@ import java.io.InputStream * * @param context the application context. */ -class MangaModelLoader(context: Context) : StreamModelLoader { +class MangaModelLoader : ModelLoader { /** * Cover cache where persistent covers are stored. @@ -39,16 +40,15 @@ class MangaModelLoader(context: Context) : StreamModelLoader { private val sourceManager: SourceManager by injectLazy() /** - * Base network loader. + * Default network client. */ - private val baseUrlLoader = Glide.buildModelLoader(GlideUrl::class.java, - InputStream::class.java, context) + private val defaultClient = Injekt.get().client /** * LRU cache whose key is the thumbnail url of the manga, and the value contains the request url * and the file where it should be stored in case the manga is a favorite. */ - private val lruCache = LruCache>(100) + private val lruCache = LruCache(100) /** * Map where request headers are stored for a source. @@ -60,12 +60,17 @@ class MangaModelLoader(context: Context) : StreamModelLoader { */ class Factory : ModelLoaderFactory { - override fun build(context: Context, factories: GenericLoaderFactory) - = MangaModelLoader(context) + override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader { + return MangaModelLoader() + } override fun teardown() {} } + override fun handles(model: Manga): Boolean { + return true + } + /** * Returns a fetcher for the given manga or null if the url is empty. * @@ -73,10 +78,8 @@ class MangaModelLoader(context: Context) : StreamModelLoader { * @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 getResourceFetcher(manga: Manga, - width: Int, - height: Int): DataFetcher? { - + override fun buildLoadData(manga: Manga, width: Int, height: Int, + options: Options?): ModelLoader.LoadData? { // Check thumbnail is not null or empty val url = manga.thumbnail_url if (url == null || url.isEmpty()) { @@ -85,26 +88,28 @@ class MangaModelLoader(context: Context) : StreamModelLoader { if (url.startsWith("http")) { val source = sourceManager.get(manga.source) as? HttpSource - - // Obtain the request url and the file for this url from the LRU cache, or calculate it - // and add them to the cache. - val (glideUrl, file) = lruCache.get(url) ?: - Pair(GlideUrl(url, getHeaders(manga, source)), coverCache.getCoverFile(url)).apply { - lruCache.put(url, this) - } + val glideUrl = GlideUrl(url, getHeaders(manga, source)) // Get the resource fetcher for this request url. - val networkFetcher = source?.let { OkHttpStreamFetcher(it.client, glideUrl) } - ?: baseUrlLoader.getResourceFetcher(glideUrl, width, height) + val networkFetcher = OkHttpStreamFetcher(source?.client ?: defaultClient, glideUrl) + + if (!manga.favorite) { + return ModelLoader.LoadData(glideUrl, networkFetcher) + } + + // Obtain the file for this url from the LRU cache, or retrieve and add it to the cache. + val file = lruCache.getOrPut(glideUrl) { coverCache.getCoverFile(url) } + + val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, file) // Return an instance of the fetcher providing the needed elements. - return MangaUrlFetcher(networkFetcher, file, manga) + return ModelLoader.LoadData(MangaSignature(manga, file), libraryFetcher) } else { // Get the file from the url, removing the scheme if present. val file = File(url.substringAfter("file://")) // Return an instance of the fetcher providing the needed elements. - return MangaFileFetcher(file, manga) + return ModelLoader.LoadData(MangaSignature(manga, file), FileFetcher(file)) } } @@ -127,4 +132,15 @@ class MangaModelLoader(context: Context) : StreamModelLoader { } } + private inline fun LruCache.getOrPut(key: K, defaultValue: () -> V): V { + val value = get(key) + return if (value == null) { + val answer = defaultValue() + put(key, answer) + answer + } else { + value + } + } + } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaSignature.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaSignature.kt new file mode 100644 index 000000000..aa3ebf6f9 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaSignature.kt @@ -0,0 +1,27 @@ +package eu.kanade.tachiyomi.data.glide + +import com.bumptech.glide.load.Key +import eu.kanade.tachiyomi.data.database.models.Manga +import java.io.File +import java.security.MessageDigest + +class MangaSignature(manga: Manga, file: File) : Key { + + private val key = manga.thumbnail_url + file.lastModified() + + override fun equals(other: Any?): Boolean { + return if (other is MangaSignature) { + key == other.key + } else { + false + } + } + + override fun hashCode(): Int { + return key.hashCode() + } + + override fun updateDiskCacheKey(md: MessageDigest) { + md.update(key.toByteArray(Key.CHARSET)) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt deleted file mode 100644 index 193309583..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt +++ /dev/null @@ -1,71 +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.database.models.Manga -import java.io.File -import java.io.FileNotFoundException -import java.io.InputStream - -/** - * A [DataFetcher] for loading a cover of a manga depending on its favorite status. - * If the manga is favorite, it tries to load the cover from our cache, and if it's not found, it - * fallbacks to network and copies it to the cache. - * If the manga is not favorite, it tries to delete the cover from our cache and always fallback - * to network for fetching. - * - * @param networkFetcher the network fetcher for this cover. - * @param file the file where this cover should be. It may exists or not. - * @param manga the manga of the cover to load. - */ -class MangaUrlFetcher(private val networkFetcher: DataFetcher, - private val file: File, - private val manga: Manga) -: MangaFileFetcher(file, manga) { - - override fun loadData(priority: Priority): InputStream { - if (manga.favorite) { - synchronized(file) { - if (!file.exists()) { - val tmpFile = File(file.path + ".tmp") - try { - // Retrieve source stream. - val input = networkFetcher.loadData(priority) - ?: throw Exception("Couldn't open source stream") - - // 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. - input.use { output.use { input.copyTo(output) } } - tmpFile.renameTo(file) - } catch (e: Exception) { - tmpFile.delete() - throw e - } - } - } - return super.loadData(priority) - } else { - if (file.exists()) { - file.delete() - } - return networkFetcher.loadData(priority) - } - } - - override fun cancel() { - networkFetcher.cancel() - } - - override fun cleanup() { - super.cleanup() - networkFetcher.cleanup() - } - -} \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt similarity index 50% rename from app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt rename to app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt index b1b722acb..457f8d228 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt @@ -1,12 +1,18 @@ 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.module.GlideModule +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.request.RequestOptions import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.network.NetworkHelper import uy.kohesive.injekt.Injekt @@ -16,17 +22,20 @@ import java.io.InputStream /** * Class used to update Glide module settings */ -class AppGlideModule : GlideModule { +@GlideModule +class TachiGlideModule : AppGlideModule() { override fun applyOptions(context: Context, builder: GlideBuilder) { - // Set the cache size of Glide to 15 MiB - builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 1024 * 1024)) + 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) { + override fun registerComponents(context: Context, glide: Glide, registry: Registry) { val networkFactory = OkHttpUrlLoader.Factory(Injekt.get().client) - glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory) - glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory()) + registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory) + registry.append(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt index 4cd6554fa..3fdba1e2e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.ui.catalogue import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.widget.StateImageViewTarget import kotlinx.android.synthetic.main.catalogue_grid_item.view.* @@ -36,16 +36,15 @@ class CatalogueGridHolder(private val view: View, private val adapter: FlexibleA } override fun setImage(manga: Manga) { - Glide.clear(view.thumbnail) + GlideApp.with(view.context).clear(view.thumbnail) if (!manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(view.context) + GlideApp.with(view.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .diskCacheStrategy(DiskCacheStrategy.DATA) .centerCrop() .skipMemoryCache(true) .placeholder(android.R.color.transparent) .into(StateImageViewTarget(view.thumbnail, view.progress)) - } } } \ No newline at end of file diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt index 5b782b167..a12ec77d2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt @@ -1,12 +1,11 @@ package eu.kanade.tachiyomi.ui.catalogue import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.util.getResourceColor -import jp.wasabeef.glide.transformations.CropCircleTransformation import kotlinx.android.synthetic.main.catalogue_list_item.view.* /** @@ -37,13 +36,13 @@ class CatalogueListHolder(private val view: View, adapter: FlexibleAdapter<*>) : } override fun setImage(manga: Manga) { - Glide.clear(view.thumbnail) + GlideApp.with(view.context).clear(view.thumbnail) if (!manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(view.context) + GlideApp.with(view.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .diskCacheStrategy(DiskCacheStrategy.DATA) .centerCrop() - .bitmapTransform(CropCircleTransformation(view.context)) + .circleCrop() .dontAnimate() .skipMemoryCache(true) .placeholder(android.R.color.transparent) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardHolder.kt index 02c102b8f..e35a2ceb7 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardHolder.kt @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.ui.catalogue.global_search import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.widget.StateImageViewTarget import kotlinx.android.synthetic.main.catalogue_global_search_controller_card_item.view.* @@ -28,11 +28,11 @@ class CatalogueSearchCardHolder(view: View, adapter: CatalogueSearchCardAdapter) } fun setImage(manga: Manga) { - Glide.clear(itemView.itemImage) + GlideApp.with(itemView.context).clear(itemView.itemImage) if (!manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(itemView.context) + GlideApp.with(itemView.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.SOURCE) + .diskCacheStrategy(DiskCacheStrategy.DATA) .centerCrop() .skipMemoryCache(true) .placeholder(android.R.color.transparent) 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 fcf3789e4..b13616c8d 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 @@ -1,10 +1,10 @@ package eu.kanade.tachiyomi.ui.library import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp import kotlinx.android.synthetic.main.catalogue_grid_item.view.* /** @@ -38,10 +38,10 @@ class LibraryGridHolder( } // Update the cover. - Glide.clear(view.thumbnail) - Glide.with(view.context) + GlideApp.with(view.context).clear(view.thumbnail) + GlideApp.with(view.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .centerCrop() .into(view.thumbnail) } 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 6de899532..5895b5acb 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 @@ -1,11 +1,10 @@ package eu.kanade.tachiyomi.ui.library import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.flexibleadapter.FlexibleAdapter import eu.kanade.tachiyomi.data.database.models.Manga -import jp.wasabeef.glide.transformations.CropCircleTransformation +import eu.kanade.tachiyomi.data.glide.GlideApp import kotlinx.android.synthetic.main.catalogue_list_item.view.* /** @@ -46,12 +45,12 @@ class LibraryListHolder( } // Update the cover. - Glide.clear(itemView.thumbnail) - Glide.with(itemView.context) + GlideApp.with(itemView.context).clear(itemView.thumbnail) + GlideApp.with(itemView.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .centerCrop() - .bitmapTransform(CropCircleTransformation(itemView.context)) + .circleCrop() .dontAnimate() .into(itemView.thumbnail) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt index 651ded878..b9b982d04 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt @@ -1,8 +1,10 @@ package eu.kanade.tachiyomi.ui.manga.info +import android.app.Dialog import android.app.PendingIntent import android.content.Intent import android.graphics.Bitmap +import android.graphics.drawable.Drawable import android.net.Uri import android.os.Build import android.os.Bundle @@ -12,20 +14,22 @@ import android.support.v4.content.pm.ShortcutManagerCompat import android.support.v4.graphics.drawable.IconCompat import android.view.* import com.afollestad.materialdialogs.MaterialDialog -import com.bumptech.glide.BitmapRequestBuilder -import com.bumptech.glide.Glide 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.target.SimpleTarget +import com.bumptech.glide.request.transition.Transition import com.jakewharton.rxbinding.support.v4.widget.refreshes import com.jakewharton.rxbinding.view.clicks import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Manga +import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.ui.base.controller.DialogController import eu.kanade.tachiyomi.ui.base.controller.NucleusController import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog import eu.kanade.tachiyomi.ui.main.MainActivity @@ -33,15 +37,9 @@ import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.snack import eu.kanade.tachiyomi.util.toast -import jp.wasabeef.glide.transformations.CropCircleTransformation import jp.wasabeef.glide.transformations.CropSquareTransformation import jp.wasabeef.glide.transformations.MaskTransformation -import jp.wasabeef.glide.transformations.RoundedCornersTransformation import kotlinx.android.synthetic.main.manga_info_controller.view.* -import rx.Observable -import rx.android.schedulers.AndroidSchedulers -import rx.schedulers.Schedulers -import rx.subscriptions.Subscriptions import uy.kohesive.injekt.injectLazy import java.text.DecimalFormat @@ -157,16 +155,16 @@ class MangaInfoController : NucleusController(), // Set cover if it wasn't already. if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(context) + GlideApp.with(context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .centerCrop() .into(manga_cover) if (backdrop != null) { - Glide.with(context) + GlideApp.with(context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .centerCrop() .into(backdrop) } @@ -316,51 +314,78 @@ class MangaInfoController : NucleusController(), } /** - * Choose the shape of the icon - * Only use for pre Oreo devices. + * Add a shortcut of the manga to the home screen */ - private fun chooseIconDialog() { - val activity = activity ?: return - - val modes = intArrayOf(R.string.circular_icon, - R.string.rounded_icon, - R.string.square_icon, - R.string.star_icon) - - val request = Glide.with(activity).load(presenter.manga).asBitmap() - - fun getIcon(i: Int): Bitmap? = when (i) { - 0 -> request.transform(CropCircleTransformation(activity)).toIcon() - 1 -> request.transform(RoundedCornersTransformation(activity, 5, 0)).toIcon() - 2 -> request.transform(CropSquareTransformation(activity)).toIcon() - 3 -> request.transform(CenterCrop(activity), - MaskTransformation(activity, R.drawable.mask_star)).toIcon() - else -> null + private fun addToHomeScreen() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + // TODO are transformations really unsupported or is it just the Pixel Launcher? + createShortcutForShape() + } else { + ChooseShapeDialog(this).showDialog(router) } - - val dialog = MaterialDialog.Builder(activity) - .title(R.string.icon_shape) - .negativeText(android.R.string.cancel) - .items(modes.map { activity.getString(it) }) - .itemsCallback { _, _, i, _ -> - Observable.fromCallable { getIcon(i) } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ icon -> - if (icon != null) createShortcut(icon) - }, { - activity.toast(R.string.icon_creation_fail) - }) - } - .show() - - untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() }) } - private fun BitmapRequestBuilder.toIcon() = this.into(96,96).get() + /** + * Dialog to choose a shape for the icon. + */ + private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) { + + constructor(target: MangaInfoController) : this() { + targetController = target + } + + override fun onCreateDialog(savedViewState: Bundle?): Dialog { + val modes = intArrayOf(R.string.circular_icon, + R.string.rounded_icon, + R.string.square_icon, + R.string.star_icon) + + return MaterialDialog.Builder(activity!!) + .title(R.string.icon_shape) + .negativeText(android.R.string.cancel) + .items(modes.map { activity?.getString(it) }) + .itemsCallback { _, _, i, _ -> + (targetController as? MangaInfoController)?.createShortcutForShape(i) + } + .build() + } + } + + /** + * Retrieves the bitmap of the shortcut with the requested shape and calls [createShortcut] when + * the resource is available. + * + * @param i The shape index to apply. No transformation is performed if the parameter is not + * provided. + */ + private fun createShortcutForShape(i: Int = 0) { + GlideApp.with(activity) + .asBitmap() + .load(presenter.manga) + .diskCacheStrategy(DiskCacheStrategy.NONE) + .apply { + when (i) { + 0 -> circleCrop() + 1 -> transform(RoundedCorners(5)) + 2 -> transform(CropSquareTransformation()) + 3 -> centerCrop().transform(MaskTransformation(R.drawable.mask_star)) + } + } + .into(object : SimpleTarget(96, 96) { + override fun onResourceReady(resource: Bitmap, transition: Transition?) { + createShortcut(resource) + } + + override fun onLoadFailed(errorDrawable: Drawable?) { + activity?.toast(R.string.icon_creation_fail) + } + }) + } /** * Create shortcut using ShortcutManager. + * + * @param icon The image of the shortcut. */ private fun createShortcut(icon: Bitmap) { val activity = activity ?: return @@ -375,49 +400,29 @@ class MangaInfoController : NucleusController(), // Check if shortcut placement is supported if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) { + val shortcutId = "manga-shortcut-${presenter.manga.title}-${presenter.source.name}" // Create shortcut info - val pinShortcutInfo = ShortcutInfoCompat.Builder(activity, "manga-shortcut-${presenter.manga.title}-${presenter.source.name}") + val shortcutInfo = ShortcutInfoCompat.Builder(activity, shortcutId) .setShortLabel(presenter.manga.title) .setIcon(IconCompat.createWithBitmap(icon)) - .setIntent(shortcutIntent).build() + .setIntent(shortcutIntent) + .build() - val successCallback: PendingIntent - - successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { // Create the CallbackIntent. - val pinnedShortcutCallbackIntent = ShortcutManagerCompat.createShortcutResultIntent(activity, pinShortcutInfo) + val intent = ShortcutManagerCompat.createShortcutResultIntent(activity, shortcutInfo) // Configure the intent so that the broadcast receiver gets the callback successfully. - PendingIntent.getBroadcast(activity, 0, pinnedShortcutCallbackIntent, 0) - } else{ + PendingIntent.getBroadcast(activity, 0, intent, 0) + } else { NotificationReceiver.shortcutCreatedBroadcast(activity) } // Request shortcut. - ShortcutManagerCompat.requestPinShortcut(activity, pinShortcutInfo, + ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo, successCallback.intentSender) } } - /** - * Add a shortcut of the manga to the home screen - */ - private fun addToHomeScreen() { - // Get bitmap icon - val bitmap = Glide.with(activity).load(presenter.manga).asBitmap() - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){ - Observable.fromCallable { - bitmap.toIcon() - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({icon -> - createShortcut(icon) - }) - }else{ - chooseIconDialog() - } - } -} +} \ No newline at end of file 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 38441d3b8..ab690c819 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 @@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.reader import android.content.Context import android.graphics.Bitmap import android.support.v4.app.NotificationCompat -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy 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 @@ -34,12 +34,12 @@ class SaveImageNotifier(private val context: Context) { * @param file image file containing downloaded page image. */ fun onComplete(file: File) { - val bitmap = Glide.with(context) - .load(file) + val bitmap = GlideApp.with(context) .asBitmap() + .load(file) .diskCacheStrategy(DiskCacheStrategy.NONE) .skipMemoryCache(true) - .into(720, 1280) + .submit(720, 1280) .get() if (bitmap != null) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt index 0c3a1f030..37a72f537 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt @@ -2,14 +2,13 @@ package eu.kanade.tachiyomi.ui.recent_updates import android.view.View import android.widget.PopupMenu -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy import eu.davidea.viewholders.FlexibleViewHolder import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.download.model.Download +import eu.kanade.tachiyomi.data.glide.GlideApp import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.setVectorCompat -import jp.wasabeef.glide.transformations.CropCircleTransformation import kotlinx.android.synthetic.main.recent_chapters_item.view.* /** @@ -68,12 +67,12 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha view.chapter_menu_icon.setVectorCompat(R.drawable.ic_more_horiz_black_24dp, view.context.getResourceColor(R.attr.icon_color)) // Set cover - Glide.clear(itemView.manga_cover) + GlideApp.with(itemView.context).clear(itemView.manga_cover) if (!item.manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(itemView.context) + GlideApp.with(itemView.context) .load(item.manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) - .bitmapTransform(CropCircleTransformation(view.context)) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) + .circleCrop() .into(itemView.manga_cover) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt index 2d4464ffa..ecabfb873 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt @@ -1,11 +1,11 @@ package eu.kanade.tachiyomi.ui.recently_read import android.view.View -import com.bumptech.glide.Glide import com.bumptech.glide.load.engine.DiskCacheStrategy 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 kotlinx.android.synthetic.main.recently_read_item.view.* import java.util.* @@ -58,15 +58,15 @@ class RecentlyReadHolder( itemView.last_read.text = adapter.dateFormat.format(Date(history.last_read)) // Set cover - Glide.clear(itemView.cover) + GlideApp.with(itemView.context).clear(itemView.cover) if (!manga.thumbnail_url.isNullOrEmpty()) { - Glide.with(itemView.context) + GlideApp.with(itemView.context) .load(manga) - .diskCacheStrategy(DiskCacheStrategy.RESULT) + .diskCacheStrategy(DiskCacheStrategy.RESOURCE) .centerCrop() .into(itemView.cover) } - } + } 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 af12756dd..75d6e74c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt @@ -5,9 +5,8 @@ import android.support.graphics.drawable.VectorDrawableCompat import android.view.View import android.widget.ImageView import android.widget.ImageView.ScaleType -import com.bumptech.glide.load.resource.drawable.GlideDrawable -import com.bumptech.glide.request.animation.GlideAnimation -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget +import com.bumptech.glide.request.target.ImageViewTarget +import com.bumptech.glide.request.transition.Transition import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.gone @@ -26,16 +25,23 @@ class StateImageViewTarget(view: ImageView, val progress: View? = null, val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp, val errorScaleType: ScaleType = ScaleType.CENTER) : - GlideDrawableImageViewTarget(view) { + + ImageViewTarget(view) { + + private var resource: Drawable? = null private val imageScaleType = view.scaleType + override fun setResource(resource: Drawable?) { + view.setImageDrawable(resource) + } + override fun onLoadStarted(placeholder: Drawable?) { progress?.visible() super.onLoadStarted(placeholder) } - override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) { + override fun onLoadFailed(errorDrawable: Drawable?) { progress?.gone() view.scaleType = errorScaleType @@ -49,9 +55,10 @@ class StateImageViewTarget(view: ImageView, super.onLoadCleared(placeholder) } - override fun onResourceReady(resource: GlideDrawable?, animation: GlideAnimation?) { + override fun onResourceReady(resource: Drawable?, transition: Transition?) { progress?.gone() view.scaleType = imageScaleType - super.onResourceReady(resource, animation) + super.onResourceReady(resource, transition) + this.resource = resource } } \ No newline at end of file