Glide v4
This commit is contained in:
parent
f45efe2aa8
commit
1470e9d5ca
20 changed files with 351 additions and 292 deletions
|
@ -3,6 +3,7 @@ import java.text.SimpleDateFormat
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: 'kotlin-android'
|
apply plugin: 'kotlin-android'
|
||||||
apply plugin: 'kotlin-android-extensions'
|
apply plugin: 'kotlin-android-extensions'
|
||||||
|
apply plugin: 'kotlin-kapt'
|
||||||
|
|
||||||
if (file("custom.gradle").exists()) {
|
if (file("custom.gradle").exists()) {
|
||||||
apply from: "custom.gradle"
|
apply from: "custom.gradle"
|
||||||
|
@ -169,10 +170,12 @@ dependencies {
|
||||||
compile "uy.kohesive.injekt:injekt-core:1.16.1"
|
compile "uy.kohesive.injekt:injekt-core:1.16.1"
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
compile 'com.github.bumptech.glide:glide:3.8.0'
|
compile 'com.github.bumptech.glide:glide:4.1.1'
|
||||||
compile 'com.github.bumptech.glide:okhttp3-integration:1.5.0@aar'
|
compile 'com.github.bumptech.glide:okhttp3-integration:4.1.1'
|
||||||
|
kapt 'com.github.bumptech.glide:compiler:4.1.1'
|
||||||
|
|
||||||
// Transformations
|
// Transformations
|
||||||
compile 'jp.wasabeef:glide-transformations:2.0.2'
|
compile 'jp.wasabeef:glide-transformations:3.0.1'
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
compile 'com.jakewharton.timber:timber:4.5.1'
|
compile 'com.jakewharton.timber:timber:4.5.1'
|
||||||
|
|
1
app/proguard-rules.pro
vendored
1
app/proguard-rules.pro
vendored
|
@ -24,6 +24,7 @@
|
||||||
# Glide specific rules #
|
# Glide specific rules #
|
||||||
# https://github.com/bumptech/glide
|
# https://github.com/bumptech/glide
|
||||||
-keep public class * implements com.bumptech.glide.module.GlideModule
|
-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$** {
|
-keep public enum com.bumptech.glide.load.resource.bitmap.ImageHeaderParser$** {
|
||||||
**[] $VALUES;
|
**[] $VALUES;
|
||||||
public *;
|
public *;
|
||||||
|
|
|
@ -95,10 +95,6 @@
|
||||||
android:name=".data.backup.BackupRestoreService"
|
android:name=".data.backup.BackupRestoreService"
|
||||||
android:exported="false"/>
|
android:exported="false"/>
|
||||||
|
|
||||||
<meta-data
|
|
||||||
android:name="eu.kanade.tachiyomi.data.glide.AppGlideModule"
|
|
||||||
android:value="GlideModule" />
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
@ -1,27 +1,39 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
|
import android.content.ContentValues.TAG
|
||||||
|
import android.util.Log
|
||||||
import com.bumptech.glide.Priority
|
import com.bumptech.glide.Priority
|
||||||
|
import com.bumptech.glide.load.DataSource
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
import com.bumptech.glide.load.data.DataFetcher
|
||||||
import java.io.File
|
import java.io.*
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
open class FileFetcher(private val file: File) : DataFetcher<InputStream> {
|
open class FileFetcher(private val file: File) : DataFetcher<InputStream> {
|
||||||
|
|
||||||
private var data: InputStream? = null
|
private var data: InputStream? = null
|
||||||
|
|
||||||
override fun loadData(priority: Priority): InputStream {
|
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
||||||
data = file.inputStream()
|
loadFromFile(callback)
|
||||||
return data!!
|
}
|
||||||
|
|
||||||
|
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() {
|
override fun cleanup() {
|
||||||
data?.let { data ->
|
|
||||||
try {
|
try {
|
||||||
data.close()
|
data?.close()
|
||||||
} catch (e: IOException) {
|
} catch (e: IOException) {
|
||||||
// Ignore
|
// Ignored.
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,7 +41,11 @@ open class FileFetcher(private val file: File) : DataFetcher<InputStream> {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getId(): String {
|
override fun getDataClass(): Class<InputStream> {
|
||||||
return file.toString()
|
return InputStream::class.java
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDataSource(): DataSource {
|
||||||
|
return DataSource.LOCAL
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,23 +1,24 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
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.*
|
||||||
import com.bumptech.glide.load.model.stream.StreamModelLoader
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
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.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A class for loading a cover associated with a [Manga] that can be present in our own cache.
|
* 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 RAM LRU.
|
||||||
* - Check in disk LRU.
|
* - Check in disk LRU.
|
||||||
|
@ -26,7 +27,7 @@ import java.io.InputStream
|
||||||
*
|
*
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
*/
|
*/
|
||||||
class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
class MangaModelLoader : ModelLoader<Manga, InputStream> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cover cache where persistent covers are stored.
|
* Cover cache where persistent covers are stored.
|
||||||
|
@ -39,16 +40,15 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base network loader.
|
* Default network client.
|
||||||
*/
|
*/
|
||||||
private val baseUrlLoader = Glide.buildModelLoader(GlideUrl::class.java,
|
private val defaultClient = Injekt.get<NetworkHelper>().client
|
||||||
InputStream::class.java, context)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LRU cache whose key is the thumbnail url of the manga, and the value contains the request url
|
* 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.
|
* 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.
|
* Map where request headers are stored for a source.
|
||||||
|
@ -60,12 +60,17 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
*/
|
*/
|
||||||
class Factory : ModelLoaderFactory<Manga, InputStream> {
|
class Factory : ModelLoaderFactory<Manga, InputStream> {
|
||||||
|
|
||||||
override fun build(context: Context, factories: GenericLoaderFactory)
|
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<Manga, InputStream> {
|
||||||
= MangaModelLoader(context)
|
return MangaModelLoader()
|
||||||
|
}
|
||||||
|
|
||||||
override fun teardown() {}
|
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.
|
* 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 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.
|
* @param height the height of the view where the resource will be loaded.
|
||||||
*/
|
*/
|
||||||
override fun getResourceFetcher(manga: Manga,
|
override fun buildLoadData(manga: Manga, width: Int, height: Int,
|
||||||
width: Int,
|
options: Options?): ModelLoader.LoadData<InputStream>? {
|
||||||
height: Int): DataFetcher<InputStream>? {
|
|
||||||
|
|
||||||
// Check thumbnail is not null or empty
|
// Check thumbnail is not null or empty
|
||||||
val url = manga.thumbnail_url
|
val url = manga.thumbnail_url
|
||||||
if (url == null || url.isEmpty()) {
|
if (url == null || url.isEmpty()) {
|
||||||
|
@ -85,26 +88,28 @@ class MangaModelLoader(context: Context) : StreamModelLoader<Manga> {
|
||||||
|
|
||||||
if (url.startsWith("http")) {
|
if (url.startsWith("http")) {
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
|
val glideUrl = GlideUrl(url, getHeaders(manga, source))
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the resource fetcher for this request url.
|
// Get the resource fetcher for this request url.
|
||||||
val networkFetcher = source?.let { OkHttpStreamFetcher(it.client, glideUrl) }
|
val networkFetcher = OkHttpStreamFetcher(source?.client ?: defaultClient, glideUrl)
|
||||||
?: baseUrlLoader.getResourceFetcher(glideUrl, width, height)
|
|
||||||
|
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 an instance of the fetcher providing the needed elements.
|
||||||
return MangaUrlFetcher(networkFetcher, file, manga)
|
return ModelLoader.LoadData(MangaSignature(manga, file), libraryFetcher)
|
||||||
} else {
|
} else {
|
||||||
// Get the file from the url, removing the scheme if present.
|
// Get the file from the url, removing the scheme if present.
|
||||||
val file = File(url.substringAfter("file://"))
|
val file = File(url.substringAfter("file://"))
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
// 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +1,18 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
package eu.kanade.tachiyomi.data.glide
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import com.bumptech.glide.Glide
|
import com.bumptech.glide.Glide
|
||||||
import com.bumptech.glide.GlideBuilder
|
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.integration.okhttp3.OkHttpUrlLoader
|
||||||
|
import com.bumptech.glide.load.DecodeFormat
|
||||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
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.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
@ -16,17 +22,20 @@ import java.io.InputStream
|
||||||
/**
|
/**
|
||||||
* Class used to update Glide module settings
|
* Class used to update Glide module settings
|
||||||
*/
|
*/
|
||||||
class AppGlideModule : GlideModule {
|
@GlideModule
|
||||||
|
class TachiGlideModule : AppGlideModule() {
|
||||||
|
|
||||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
||||||
// Set the cache size of Glide to 15 MiB
|
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
|
||||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 15 * 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)
|
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
|
||||||
|
|
||||||
glide.register(GlideUrl::class.java, InputStream::class.java, networkFactory)
|
registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory)
|
||||||
glide.register(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
registry.append(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,10 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.catalogue
|
package eu.kanade.tachiyomi.ui.catalogue
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
|
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) {
|
override fun setImage(manga: Manga) {
|
||||||
Glide.clear(view.thumbnail)
|
GlideApp.with(view.context).clear(view.thumbnail)
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(view.context)
|
GlideApp.with(view.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.placeholder(android.R.color.transparent)
|
.placeholder(android.R.color.transparent)
|
||||||
.into(StateImageViewTarget(view.thumbnail, view.progress))
|
.into(StateImageViewTarget(view.thumbnail, view.progress))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.ui.catalogue
|
package eu.kanade.tachiyomi.ui.catalogue
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
import jp.wasabeef.glide.transformations.CropCircleTransformation
|
|
||||||
import kotlinx.android.synthetic.main.catalogue_list_item.view.*
|
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) {
|
override fun setImage(manga: Manga) {
|
||||||
Glide.clear(view.thumbnail)
|
GlideApp.with(view.context).clear(view.thumbnail)
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(view.context)
|
GlideApp.with(view.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.bitmapTransform(CropCircleTransformation(view.context))
|
.circleCrop()
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.placeholder(android.R.color.transparent)
|
.placeholder(android.R.color.transparent)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.catalogue.global_search
|
package eu.kanade.tachiyomi.ui.catalogue.global_search
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
import kotlinx.android.synthetic.main.catalogue_global_search_controller_card_item.view.*
|
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) {
|
fun setImage(manga: Manga) {
|
||||||
Glide.clear(itemView.itemImage)
|
GlideApp.with(itemView.context).clear(itemView.itemImage)
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(itemView.context)
|
GlideApp.with(itemView.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
|
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.placeholder(android.R.color.transparent)
|
.placeholder(android.R.color.transparent)
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||||
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
|
import kotlinx.android.synthetic.main.catalogue_grid_item.view.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,10 +38,10 @@ class LibraryGridHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
Glide.clear(view.thumbnail)
|
GlideApp.with(view.context).clear(view.thumbnail)
|
||||||
Glide.with(view.context)
|
GlideApp.with(view.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(view.thumbnail)
|
.into(view.thumbnail)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.library
|
package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
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.*
|
import kotlinx.android.synthetic.main.catalogue_list_item.view.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -46,12 +45,12 @@ class LibraryListHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
Glide.clear(itemView.thumbnail)
|
GlideApp.with(itemView.context).clear(itemView.thumbnail)
|
||||||
Glide.with(itemView.context)
|
GlideApp.with(itemView.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.bitmapTransform(CropCircleTransformation(itemView.context))
|
.circleCrop()
|
||||||
.dontAnimate()
|
.dontAnimate()
|
||||||
.into(itemView.thumbnail)
|
.into(itemView.thumbnail)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.manga.info
|
package eu.kanade.tachiyomi.ui.manga.info
|
||||||
|
|
||||||
|
import android.app.Dialog
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -12,20 +14,22 @@ import android.support.v4.content.pm.ShortcutManagerCompat
|
||||||
import android.support.v4.graphics.drawable.IconCompat
|
import android.support.v4.graphics.drawable.IconCompat
|
||||||
import android.view.*
|
import android.view.*
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
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.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.support.v4.widget.refreshes
|
||||||
import com.jakewharton.rxbinding.view.clicks
|
import com.jakewharton.rxbinding.view.clicks
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
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.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
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.base.controller.NucleusController
|
||||||
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
import eu.kanade.tachiyomi.ui.library.ChangeMangaCategoriesDialog
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
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.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.snack
|
import eu.kanade.tachiyomi.util.snack
|
||||||
import eu.kanade.tachiyomi.util.toast
|
import eu.kanade.tachiyomi.util.toast
|
||||||
import jp.wasabeef.glide.transformations.CropCircleTransformation
|
|
||||||
import jp.wasabeef.glide.transformations.CropSquareTransformation
|
import jp.wasabeef.glide.transformations.CropSquareTransformation
|
||||||
import jp.wasabeef.glide.transformations.MaskTransformation
|
import jp.wasabeef.glide.transformations.MaskTransformation
|
||||||
import jp.wasabeef.glide.transformations.RoundedCornersTransformation
|
|
||||||
import kotlinx.android.synthetic.main.manga_info_controller.view.*
|
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 uy.kohesive.injekt.injectLazy
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
|
||||||
|
@ -157,16 +155,16 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||||
|
|
||||||
// Set cover if it wasn't already.
|
// Set cover if it wasn't already.
|
||||||
if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) {
|
if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(context)
|
GlideApp.with(context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(manga_cover)
|
.into(manga_cover)
|
||||||
|
|
||||||
if (backdrop != null) {
|
if (backdrop != null) {
|
||||||
Glide.with(context)
|
GlideApp.with(context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(backdrop)
|
.into(backdrop)
|
||||||
}
|
}
|
||||||
|
@ -316,51 +314,78 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Choose the shape of the icon
|
* Add a shortcut of the manga to the home screen
|
||||||
* Only use for pre Oreo devices.
|
|
||||||
*/
|
*/
|
||||||
private fun chooseIconDialog() {
|
private fun addToHomeScreen() {
|
||||||
val activity = activity ?: return
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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,
|
val modes = intArrayOf(R.string.circular_icon,
|
||||||
R.string.rounded_icon,
|
R.string.rounded_icon,
|
||||||
R.string.square_icon,
|
R.string.square_icon,
|
||||||
R.string.star_icon)
|
R.string.star_icon)
|
||||||
|
|
||||||
val request = Glide.with(activity).load(presenter.manga).asBitmap()
|
return MaterialDialog.Builder(activity!!)
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
val dialog = MaterialDialog.Builder(activity)
|
|
||||||
.title(R.string.icon_shape)
|
.title(R.string.icon_shape)
|
||||||
.negativeText(android.R.string.cancel)
|
.negativeText(android.R.string.cancel)
|
||||||
.items(modes.map { activity.getString(it) })
|
.items(modes.map { activity?.getString(it) })
|
||||||
.itemsCallback { _, _, i, _ ->
|
.itemsCallback { _, _, i, _ ->
|
||||||
Observable.fromCallable { getIcon(i) }
|
(targetController as? MangaInfoController)?.createShortcutForShape(i)
|
||||||
.subscribeOn(Schedulers.io())
|
}
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.build()
|
||||||
.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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
.show()
|
|
||||||
|
|
||||||
untilDestroySubscriptions.add(Subscriptions.create { dialog.dismiss() })
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun BitmapRequestBuilder<out Any, Bitmap>.toIcon() = this.into(96,96).get()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create shortcut using ShortcutManager.
|
* Create shortcut using ShortcutManager.
|
||||||
|
*
|
||||||
|
* @param icon The image of the shortcut.
|
||||||
*/
|
*/
|
||||||
private fun createShortcut(icon: Bitmap) {
|
private fun createShortcut(icon: Bitmap) {
|
||||||
val activity = activity ?: return
|
val activity = activity ?: return
|
||||||
|
@ -375,49 +400,29 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
|
||||||
|
|
||||||
// Check if shortcut placement is supported
|
// Check if shortcut placement is supported
|
||||||
if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
|
if (ShortcutManagerCompat.isRequestPinShortcutSupported(activity)) {
|
||||||
|
val shortcutId = "manga-shortcut-${presenter.manga.title}-${presenter.source.name}"
|
||||||
|
|
||||||
// Create shortcut info
|
// 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)
|
.setShortLabel(presenter.manga.title)
|
||||||
.setIcon(IconCompat.createWithBitmap(icon))
|
.setIcon(IconCompat.createWithBitmap(icon))
|
||||||
.setIntent(shortcutIntent).build()
|
.setIntent(shortcutIntent)
|
||||||
|
.build()
|
||||||
|
|
||||||
val successCallback: PendingIntent
|
val successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
|
|
||||||
successCallback = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
|
||||||
// Create the CallbackIntent.
|
// 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.
|
// Configure the intent so that the broadcast receiver gets the callback successfully.
|
||||||
PendingIntent.getBroadcast(activity, 0, pinnedShortcutCallbackIntent, 0)
|
PendingIntent.getBroadcast(activity, 0, intent, 0)
|
||||||
} else{
|
} else {
|
||||||
NotificationReceiver.shortcutCreatedBroadcast(activity)
|
NotificationReceiver.shortcutCreatedBroadcast(activity)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request shortcut.
|
// Request shortcut.
|
||||||
ShortcutManagerCompat.requestPinShortcut(activity, pinShortcutInfo,
|
ShortcutManagerCompat.requestPinShortcut(activity, shortcutInfo,
|
||||||
successCallback.intentSender)
|
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.ui.reader
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.support.v4.app.NotificationCompat
|
import android.support.v4.app.NotificationCompat
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.kanade.tachiyomi.R
|
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.NotificationHandler
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
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.
|
* @param file image file containing downloaded page image.
|
||||||
*/
|
*/
|
||||||
fun onComplete(file: File) {
|
fun onComplete(file: File) {
|
||||||
val bitmap = Glide.with(context)
|
val bitmap = GlideApp.with(context)
|
||||||
.load(file)
|
|
||||||
.asBitmap()
|
.asBitmap()
|
||||||
|
.load(file)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||||
.skipMemoryCache(true)
|
.skipMemoryCache(true)
|
||||||
.into(720, 1280)
|
.submit(720, 1280)
|
||||||
.get()
|
.get()
|
||||||
|
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
|
|
|
@ -2,14 +2,13 @@ package eu.kanade.tachiyomi.ui.recent_updates
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.PopupMenu
|
import android.widget.PopupMenu
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
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.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.setVectorCompat
|
import eu.kanade.tachiyomi.util.setVectorCompat
|
||||||
import jp.wasabeef.glide.transformations.CropCircleTransformation
|
|
||||||
import kotlinx.android.synthetic.main.recent_chapters_item.view.*
|
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))
|
view.chapter_menu_icon.setVectorCompat(R.drawable.ic_more_horiz_black_24dp, view.context.getResourceColor(R.attr.icon_color))
|
||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
Glide.clear(itemView.manga_cover)
|
GlideApp.with(itemView.context).clear(itemView.manga_cover)
|
||||||
if (!item.manga.thumbnail_url.isNullOrEmpty()) {
|
if (!item.manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(itemView.context)
|
GlideApp.with(itemView.context)
|
||||||
.load(item.manga)
|
.load(item.manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.bitmapTransform(CropCircleTransformation(view.context))
|
.circleCrop()
|
||||||
.into(itemView.manga_cover)
|
.into(itemView.manga_cover)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.ui.recently_read
|
package eu.kanade.tachiyomi.ui.recently_read
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
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 kotlinx.android.synthetic.main.recently_read_item.view.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
|
@ -58,15 +58,15 @@ class RecentlyReadHolder(
|
||||||
itemView.last_read.text = adapter.dateFormat.format(Date(history.last_read))
|
itemView.last_read.text = adapter.dateFormat.format(Date(history.last_read))
|
||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
Glide.clear(itemView.cover)
|
GlideApp.with(itemView.context).clear(itemView.cover)
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
Glide.with(itemView.context)
|
GlideApp.with(itemView.context)
|
||||||
.load(manga)
|
.load(manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESULT)
|
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||||
.centerCrop()
|
.centerCrop()
|
||||||
.into(itemView.cover)
|
.into(itemView.cover)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,8 @@ import android.support.graphics.drawable.VectorDrawableCompat
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ImageView.ScaleType
|
import android.widget.ImageView.ScaleType
|
||||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable
|
import com.bumptech.glide.request.target.ImageViewTarget
|
||||||
import com.bumptech.glide.request.animation.GlideAnimation
|
import com.bumptech.glide.request.transition.Transition
|
||||||
import com.bumptech.glide.request.target.GlideDrawableImageViewTarget
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.util.getResourceColor
|
import eu.kanade.tachiyomi.util.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.gone
|
import eu.kanade.tachiyomi.util.gone
|
||||||
|
@ -26,16 +25,23 @@ class StateImageViewTarget(view: ImageView,
|
||||||
val progress: View? = null,
|
val progress: View? = null,
|
||||||
val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp,
|
val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp,
|
||||||
val errorScaleType: ScaleType = ScaleType.CENTER) :
|
val errorScaleType: ScaleType = ScaleType.CENTER) :
|
||||||
GlideDrawableImageViewTarget(view) {
|
|
||||||
|
ImageViewTarget<Drawable>(view) {
|
||||||
|
|
||||||
|
private var resource: Drawable? = null
|
||||||
|
|
||||||
private val imageScaleType = view.scaleType
|
private val imageScaleType = view.scaleType
|
||||||
|
|
||||||
|
override fun setResource(resource: Drawable?) {
|
||||||
|
view.setImageDrawable(resource)
|
||||||
|
}
|
||||||
|
|
||||||
override fun onLoadStarted(placeholder: Drawable?) {
|
override fun onLoadStarted(placeholder: Drawable?) {
|
||||||
progress?.visible()
|
progress?.visible()
|
||||||
super.onLoadStarted(placeholder)
|
super.onLoadStarted(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(e: Exception?, errorDrawable: Drawable?) {
|
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||||
progress?.gone()
|
progress?.gone()
|
||||||
view.scaleType = errorScaleType
|
view.scaleType = errorScaleType
|
||||||
|
|
||||||
|
@ -49,9 +55,10 @@ class StateImageViewTarget(view: ImageView,
|
||||||
super.onLoadCleared(placeholder)
|
super.onLoadCleared(placeholder)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceReady(resource: GlideDrawable?, animation: GlideAnimation<in GlideDrawable>?) {
|
override fun onResourceReady(resource: Drawable?, transition: Transition<in Drawable>?) {
|
||||||
progress?.gone()
|
progress?.gone()
|
||||||
view.scaleType = imageScaleType
|
view.scaleType = imageScaleType
|
||||||
super.onResourceReady(resource, animation)
|
super.onResourceReady(resource, transition)
|
||||||
|
this.resource = resource
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in a new issue