len %!s(int64=7) %!d(string=hai) anos
pai
achega
1470e9d5ca
Modificáronse 20 ficheiros con 347 adicións e 288 borrados
  1. 6 3
      app/build.gradle
  2. 1 0
      app/proguard-rules.pro
  3. 0 4
      app/src/main/AndroidManifest.xml
  4. 50 34
      app/src/main/java/eu/kanade/tachiyomi/data/glide/FileFetcher.kt
  5. 72 0
      app/src/main/java/eu/kanade/tachiyomi/data/glide/LibraryMangaUrlFetcher.kt
  6. 0 18
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt
  7. 43 27
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaModelLoader.kt
  8. 27 0
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaSignature.kt
  9. 0 71
      app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt
  10. 16 7
      app/src/main/java/eu/kanade/tachiyomi/data/glide/TachiGlideModule.kt
  11. 4 5
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueGridHolder.kt
  12. 5 6
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/CatalogueListHolder.kt
  13. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/catalogue/global_search/CatalogueSearchCardHolder.kt
  14. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryGridHolder.kt
  15. 5 6
      app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt
  16. 82 77
      app/src/main/java/eu/kanade/tachiyomi/ui/manga/info/MangaInfoController.kt
  17. 4 4
      app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt
  18. 5 6
      app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChapterHolder.kt
  19. 5 5
      app/src/main/java/eu/kanade/tachiyomi/ui/recently_read/RecentlyReadHolder.kt
  20. 14 7
      app/src/main/java/eu/kanade/tachiyomi/widget/StateImageViewTarget.kt

+ 6 - 3
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'

+ 1 - 0
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 *;

+ 0 - 4
app/src/main/AndroidManifest.xml

@@ -95,10 +95,6 @@
             android:name=".data.backup.BackupRestoreService"
             android:exported="false"/>
 
-        <meta-data
-            android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
-            android:value="GlideModule" />
-
     </application>
 
 </manifest>

+ 50 - 34
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<InputStream> {
-
-    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<InputStream> {
+
+    private var data: InputStream? = null
+
+    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
+        loadFromFile(callback)
+    }
+
+    protected fun loadFromFile(callback: DataFetcher.DataCallback<in InputStream>) {
+        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<InputStream> {
+        return InputStream::class.java
+    }
+
+    override fun getDataSource(): DataSource {
+        return DataSource.LOCAL
+    }
 }

+ 72 - 0
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<InputStream>,
+                             private val manga: Manga,
+                             private val file: File)
+: FileFetcher(file) {
+
+    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
+        if (!file.exists()) {
+            networkFetcher.loadData(priority, object : DataFetcher.DataCallback<InputStream> {
+                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()
+    }
+
+}

+ 0 - 18
app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaFileFetcher.kt

@@ -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()
-    }
-}

+ 43 - 27
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<Manga> {
+class MangaModelLoader : ModelLoader<Manga, InputStream> {
 
     /**
      * Cover cache where persistent covers are stored.
@@ -39,16 +40,15 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
     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<NetworkHelper>().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<String, Pair<GlideUrl, File>>(100)
+    private val lruCache = LruCache<GlideUrl, File>(100)
 
     /**
      * Map where request headers are stored for a source.
@@ -60,12 +60,17 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
      */
     class Factory : ModelLoaderFactory<Manga, InputStream> {
 
-        override fun build(context: Context, factories: GenericLoaderFactory)
-                = MangaModelLoader(context)
+        override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<Manga, InputStream> {
+            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<Manga> {
      * @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<InputStream>? {
-
+    override fun buildLoadData(manga: Manga, width: Int, height: Int,
+                               options: Options?): ModelLoader.LoadData<InputStream>? {
         // 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<Manga> {
 
         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<Manga> {
         }
     }
 
+    private inline fun <K, V> LruCache<K, V>.getOrPut(key: K, defaultValue: () -> V): V {
+        val value = get(key)
+        return if (value == null) {
+            val answer = defaultValue()
+            put(key, answer)
+            answer
+        } else {
+            value
+        }
+    }
+
 }

+ 27 - 0
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))
+    }
+}

+ 0 - 71
app/src/main/java/eu/kanade/tachiyomi/data/glide/MangaUrlFetcher.kt

@@ -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<InputStream>,
-                      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()
-    }
-
-}

+ 16 - 7
app/src/main/java/eu/kanade/tachiyomi/data/glide/AppGlideModule.kt → 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<NetworkHelper>().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())
     }
 }

+ 4 - 5
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))
-
         }
     }
 }

+ 5 - 6
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)

+ 4 - 4
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)

+ 4 - 4
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)
     }

+ 5 - 6
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)
     }

+ 82 - 77
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<MangaInfoPresenter>(),
 
             // 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<MangaInfoPresenter>(),
     }
 
     /**
-     * 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
+    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 modes = intArrayOf(R.string.circular_icon,
-                R.string.rounded_icon,
-                R.string.square_icon,
-                R.string.star_icon)
+    /**
+     * Dialog to choose a shape for the icon.
+     */
+    private class ChooseShapeDialog(bundle: Bundle? = null) : DialogController(bundle) {
 
-        val request = Glide.with(activity).load(presenter.manga).asBitmap()
+        constructor(target: MangaInfoController) : this() {
+            targetController = target
+        }
 
-        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
+        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()
         }
+    }
 
-        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)
-                            })
+    /**
+     * 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))
+                    }
                 }
-                .show()
-
-        untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() })
+                .into(object : SimpleTarget<Bitmap>(96, 96) {
+                    override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
+                        createShortcut(resource)
+                    }
+
+                    override fun onLoadFailed(errorDrawable: Drawable?) {
+                        activity?.toast(R.string.icon_creation_fail)
+                    }
+                })
     }
 
-    private fun BitmapRequestBuilder<out Any, Bitmap>.toIcon() = this.into(96,96).get()
-
     /**
      * 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<MangaInfoPresenter>(),
 
         // 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()
-
-            val successCallback: PendingIntent
+                    .setIntent(shortcutIntent)
+                    .build()
 
-            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()
-        }
-    }
-}
+}

+ 4 - 4
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) {

+ 5 - 6
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)
         }
 

+ 5 - 5
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)
         }
-
     }
 
+
 }

+ 14 - 7
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<Drawable>(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<in GlideDrawable>?) {
+    override fun onResourceReady(resource: Drawable?, transition: Transition<in Drawable>?) {
         progress?.gone()
         view.scaleType = imageScaleType
-        super.onResourceReady(resource, animation)
+        super.onResourceReady(resource, transition)
+        this.resource = resource
     }
 }