mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 15:01:52 +01:00
Switch to coil from Glide (#423)
* initial coil switch * more coil changes * fix extensions icons * remove last of glide * adjust local manga to actually update the covers as soon as you set it. Also adjusts the large cover and share * edit custom covers of a manga is now immediately reflected * fix edit covers submit not automatically submitting fix edit covers choosing cover not showing the selection in dialog * fix setting custom cover not reloading when going back * get gif's working * run ktlint fix setting custom cover to updated when returning back to details * fix non uniformed covers * get images working on resumes * add size to cover cache setting * remove log statement * remove set last cover date * put covers into cache when refresh enabled * fix comment
This commit is contained in:
parent
d99f4d1fac
commit
4d860c9396
@ -76,8 +76,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
setSourceCompatibility(1.8)
|
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||||
setTargetCompatibility(1.8)
|
targetCompatibility = JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = "1.8"
|
jvmTarget = "1.8"
|
||||||
@ -141,6 +141,21 @@ dependencies {
|
|||||||
debugImplementation ("com.github.ChuckerTeam.Chucker:library:$chuckerVersion")
|
debugImplementation ("com.github.ChuckerTeam.Chucker:library:$chuckerVersion")
|
||||||
releaseImplementation ("com.github.ChuckerTeam.Chucker:library-no-op:$chuckerVersion")
|
releaseImplementation ("com.github.ChuckerTeam.Chucker:library-no-op:$chuckerVersion")
|
||||||
|
|
||||||
|
//hyperion
|
||||||
|
val hyperionVersion = "0.9.27"
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-core:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-timber:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-core:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-attr:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-build-config:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-crash:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-disk:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-geiger-counter:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-measurement:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-phoenix:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-recorder:$hyperionVersion")
|
||||||
|
debugImplementation("com.willowtreeapps.hyperion:hyperion-shared-preferences:$hyperionVersion")
|
||||||
|
|
||||||
// REST
|
// REST
|
||||||
val retrofitVersion = "2.7.2"
|
val retrofitVersion = "2.7.2"
|
||||||
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||||
@ -184,10 +199,10 @@ dependencies {
|
|||||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
val glideVersion = "4.11.0"
|
val coilVersion = "0.10.1"
|
||||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
implementation("io.coil-kt:coil:$coilVersion")
|
||||||
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
|
implementation("io.coil-kt:coil-gif:$coilVersion")
|
||||||
kapt("com.github.bumptech.glide:compiler:$glideVersion")
|
implementation("io.coil-kt:coil-svg:$coilVersion")
|
||||||
|
|
||||||
// Logging
|
// Logging
|
||||||
implementation("com.jakewharton.timber:timber:4.7.1")
|
implementation("com.jakewharton.timber:timber:4.7.1")
|
||||||
|
8
app/proguard-rules.pro
vendored
8
app/proguard-rules.pro
vendored
@ -27,14 +27,6 @@
|
|||||||
-dontwarn javax.annotation.**
|
-dontwarn javax.annotation.**
|
||||||
-dontwarn retrofit2.Platform$Java8
|
-dontwarn retrofit2.Platform$Java8
|
||||||
|
|
||||||
# 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 *;
|
|
||||||
}
|
|
||||||
|
|
||||||
# RxJava 1.1.0
|
# RxJava 1.1.0
|
||||||
-dontwarn sun.misc.**
|
-dontwarn sun.misc.**
|
||||||
|
@ -8,6 +8,7 @@ import androidx.lifecycle.LifecycleObserver
|
|||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
|
import eu.kanade.tachiyomi.data.download.coil.CoilSetup
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -18,6 +19,7 @@ import org.acra.annotation.ReportsCrashes
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.InjektScope
|
import uy.kohesive.injekt.api.InjektScope
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||||
|
|
||||||
@ -37,6 +39,7 @@ open class App : Application(), LifecycleObserver {
|
|||||||
Injekt = InjektScope(DefaultRegistrar())
|
Injekt = InjektScope(DefaultRegistrar())
|
||||||
Injekt.importModule(AppModule(this))
|
Injekt.importModule(AppModule(this))
|
||||||
|
|
||||||
|
CoilSetup(this)
|
||||||
setupAcra()
|
setupAcra()
|
||||||
setupNotificationChannels()
|
setupNotificationChannels()
|
||||||
|
|
||||||
|
@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.data.cache
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.text.format.Formatter
|
import android.text.format.Formatter
|
||||||
|
import coil.Coil
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -11,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
import okhttp3.Cache
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -20,13 +23,12 @@ import java.io.InputStream
|
|||||||
/**
|
/**
|
||||||
* Class used to create cover cache.
|
* Class used to create cover cache.
|
||||||
* It is used to store the covers of the library.
|
* It is used to store the covers of the library.
|
||||||
* Makes use of Glide (which can avoid repeating requests) to download covers.
|
|
||||||
* Names of files are created with the md5 of the thumbnail URL.
|
* Names of files are created with the md5 of the thumbnail URL.
|
||||||
*
|
*
|
||||||
* @param context the application context.
|
* @param context the application context.
|
||||||
* @constructor creates an instance of the cover cache.
|
* @constructor creates an instance of the cover cache.
|
||||||
*/
|
*/
|
||||||
class CoverCache(private val context: Context) {
|
class CoverCache(val context: Context) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache directory used for cache management.
|
* Cache directory used for cache management.
|
||||||
@ -34,14 +36,18 @@ class CoverCache(private val context: Context) {
|
|||||||
private val cacheDir = context.getExternalFilesDir("covers")
|
private val cacheDir = context.getExternalFilesDir("covers")
|
||||||
?: File(context.filesDir, "covers").also { it.mkdirs() }
|
?: File(context.filesDir, "covers").also { it.mkdirs() }
|
||||||
|
|
||||||
|
val cache = Cache(cacheDir, 300 * 1024 * 1024) // 300MB
|
||||||
|
|
||||||
|
fun getChapterCacheSize(): String {
|
||||||
|
return Formatter.formatFileSize(context, DiskUtil.getDirectorySize(cacheDir))
|
||||||
|
}
|
||||||
|
|
||||||
fun deleteOldCovers() {
|
fun deleteOldCovers() {
|
||||||
GlobalScope.launch(Dispatchers.Default) {
|
GlobalScope.launch(Dispatchers.Default) {
|
||||||
val db = Injekt.get<DatabaseHelper>()
|
val db = Injekt.get<DatabaseHelper>()
|
||||||
var deletedSize = 0L
|
var deletedSize = 0L
|
||||||
val urls = db.getLibraryMangas().executeOnIO().mapNotNull {
|
val urls = db.getLibraryMangas().executeOnIO().mapNotNull {
|
||||||
it.thumbnail_url?.let { url ->
|
it.thumbnail_url?.let { url -> return@mapNotNull it.key() }
|
||||||
return@mapNotNull DiskUtil.hashKeyForDisk(url)
|
|
||||||
}
|
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
||||||
@ -68,8 +74,8 @@ class CoverCache(private val context: Context) {
|
|||||||
* @param thumbnailUrl the thumbnail url.
|
* @param thumbnailUrl the thumbnail url.
|
||||||
* @return cover image.
|
* @return cover image.
|
||||||
*/
|
*/
|
||||||
fun getCoverFile(thumbnailUrl: String): File {
|
fun getCoverFile(manga: Manga): File {
|
||||||
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
return File(cacheDir, manga.key())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,26 +86,26 @@ class CoverCache(private val context: Context) {
|
|||||||
* @throws IOException if there's any error.
|
* @throws IOException if there's any error.
|
||||||
*/
|
*/
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun copyToCache(thumbnailUrl: String, inputStream: InputStream) {
|
fun copyToCache(manga: Manga, inputStream: InputStream) {
|
||||||
// Get destination file.
|
// Get destination file.
|
||||||
val destFile = getCoverFile(thumbnailUrl)
|
val destFile = getCoverFile(manga)
|
||||||
|
|
||||||
destFile.outputStream().use { inputStream.copyTo(it) }
|
destFile.outputStream().use { inputStream.copyTo(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete the cover file from the cache.
|
* Delete the cover file from the disk cache and optional from memory cache
|
||||||
*
|
*
|
||||||
* @param thumbnailUrl the thumbnail url.
|
* @param thumbnailUrl the thumbnail url.
|
||||||
* @return status of deletion.
|
* @return status of deletion.
|
||||||
*/
|
*/
|
||||||
fun deleteFromCache(thumbnailUrl: String?): Boolean {
|
fun deleteFromCache(manga: Manga, deleteMemoryCache: Boolean = true) {
|
||||||
// Check if url is empty.
|
// Check if url is empty.
|
||||||
if (thumbnailUrl.isNullOrEmpty())
|
if (manga.thumbnail_url.isNullOrEmpty()) return
|
||||||
return false
|
|
||||||
|
|
||||||
// Remove file.
|
// Remove file
|
||||||
val file = getCoverFile(thumbnailUrl)
|
val file = getCoverFile(manga)
|
||||||
return file.exists() && file.delete()
|
if (deleteMemoryCache) Coil.imageLoader(context).invalidate(file.name)
|
||||||
|
if (file.exists()) file.delete()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,9 +5,11 @@ import eu.kanade.tachiyomi.R
|
|||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||||
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
interface Manga : SManga {
|
interface Manga : SManga {
|
||||||
|
|
||||||
@ -52,13 +54,15 @@ interface Manga : SManga {
|
|||||||
fun showChapterTitle(defaultShow: Boolean): Boolean = chapter_flags and DISPLAY_MASK == DISPLAY_NUMBER
|
fun showChapterTitle(defaultShow: Boolean): Boolean = chapter_flags and DISPLAY_MASK == DISPLAY_NUMBER
|
||||||
|
|
||||||
fun mangaType(context: Context): String {
|
fun mangaType(context: Context): String {
|
||||||
return context.getString(when (mangaType()) {
|
return context.getString(
|
||||||
|
when (mangaType()) {
|
||||||
TYPE_WEBTOON -> R.string.webtoon
|
TYPE_WEBTOON -> R.string.webtoon
|
||||||
TYPE_MANHWA -> R.string.manhwa
|
TYPE_MANHWA -> R.string.manhwa
|
||||||
TYPE_MANHUA -> R.string.manhua
|
TYPE_MANHUA -> R.string.manhua
|
||||||
TYPE_COMIC -> R.string.comic
|
TYPE_COMIC -> R.string.comic
|
||||||
else -> R.string.manga
|
else -> R.string.manga
|
||||||
}).toLowerCase(Locale.getDefault())
|
}
|
||||||
|
).toLowerCase(Locale.getDefault())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -97,14 +101,16 @@ interface Manga : SManga {
|
|||||||
return if (currentTags?.any
|
return if (currentTags?.any
|
||||||
{ tag ->
|
{ tag ->
|
||||||
tag == "long strip" || tag == "manhwa" || tag.contains("webtoon")
|
tag == "long strip" || tag == "manhwa" || tag.contains("webtoon")
|
||||||
} == true || isWebtoonSource(sourceName))
|
} == true || isWebtoonSource(sourceName)
|
||||||
|
)
|
||||||
ReaderActivity.WEBTOON
|
ReaderActivity.WEBTOON
|
||||||
else if (currentTags?.any
|
else if (currentTags?.any
|
||||||
{ tag ->
|
{ tag ->
|
||||||
tag == "chinese" || tag == "manhua" ||
|
tag == "chinese" || tag == "manhua" ||
|
||||||
tag.startsWith("english") || tag == "comic"
|
tag.startsWith("english") || tag == "comic"
|
||||||
} == true || (isComicSource(sourceName) && !sourceName.contains("tapastic", true)) ||
|
} == true || (isComicSource(sourceName) && !sourceName.contains("tapastic", true)) ||
|
||||||
sourceName.contains("manhua", true))
|
sourceName.contains("manhua", true)
|
||||||
|
)
|
||||||
ReaderActivity.LEFT_TO_RIGHT
|
ReaderActivity.LEFT_TO_RIGHT
|
||||||
else 0
|
else 0
|
||||||
}
|
}
|
||||||
@ -139,6 +145,19 @@ interface Manga : SManga {
|
|||||||
sourceName.contains("tapastic", true)
|
sourceName.contains("tapastic", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun key(): String {
|
||||||
|
return DiskUtil.hashKeyForDisk(thumbnail_url.orEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setCustomThumbnailUrl() {
|
||||||
|
removeCustomThumbnailUrl()
|
||||||
|
thumbnail_url = "Custom-${Random.nextInt(0, 1000)}-J2K-${thumbnail_url ?: id!!}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeCustomThumbnailUrl() {
|
||||||
|
thumbnail_url = thumbnail_url?.substringAfter("-J2K-")?.substringAfter("Custom-")
|
||||||
|
}
|
||||||
|
|
||||||
// Used to display the chapter's title one way or another
|
// Used to display the chapter's title one way or another
|
||||||
var displayMode: Int
|
var displayMode: Int
|
||||||
get() = chapter_flags and DISPLAY_MASK
|
get() = chapter_flags and DISPLAY_MASK
|
||||||
|
@ -67,14 +67,4 @@ open class MangaImpl : Manga {
|
|||||||
if (::url.isInitialized) return url.hashCode()
|
if (::url.isInitialized) return url.hashCode()
|
||||||
else return (id ?: 0L).hashCode()
|
else return (id ?: 0L).hashCode()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
|
||||||
private var lastCoverFetch: HashMap<Long, Long> = hashMapOf()
|
|
||||||
|
|
||||||
fun setLastCoverFetch(id: Long, time: Long) {
|
|
||||||
lastCoverFetch[id] = time
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getLastCoverFetch(id: Long) = lastCoverFetch[id] ?: 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,31 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.download.coil
|
||||||
|
|
||||||
|
import coil.bitmappool.BitmapPool
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.decode.Options
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.fetch.SourceResult
|
||||||
|
import coil.size.Size
|
||||||
|
import okio.buffer
|
||||||
|
import okio.source
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
|
||||||
|
class ByteArrayFetcher : Fetcher<ByteArray> {
|
||||||
|
|
||||||
|
override fun key(data: ByteArray): String? = null
|
||||||
|
|
||||||
|
override suspend fun fetch(
|
||||||
|
pool: BitmapPool,
|
||||||
|
data: ByteArray,
|
||||||
|
size: Size,
|
||||||
|
options: Options
|
||||||
|
): FetchResult {
|
||||||
|
val source = ByteArrayInputStream(data).source().buffer()
|
||||||
|
return SourceResult(
|
||||||
|
source = ByteArrayInputStream(data).source().buffer(),
|
||||||
|
mimeType = "image/gif",
|
||||||
|
dataSource = DataSource.MEMORY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.download.coil
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.os.Build
|
||||||
|
import coil.Coil
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.decode.GifDecoder
|
||||||
|
import coil.decode.ImageDecoderDecoder
|
||||||
|
import coil.decode.SvgDecoder
|
||||||
|
import coil.util.CoilUtils
|
||||||
|
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
|
||||||
|
class CoilSetup(context: Context) {
|
||||||
|
init {
|
||||||
|
val imageLoader = ImageLoader.Builder(context)
|
||||||
|
.availableMemoryPercentage(0.40)
|
||||||
|
.crossfade(true)
|
||||||
|
.allowRgb565(true)
|
||||||
|
.allowHardware(false)
|
||||||
|
.error(R.drawable.ic_broken_image_grey_24dp)
|
||||||
|
.componentRegistry {
|
||||||
|
if (Build.VERSION.SDK_INT >= 28) {
|
||||||
|
add(ImageDecoderDecoder())
|
||||||
|
} else {
|
||||||
|
add(GifDecoder())
|
||||||
|
}
|
||||||
|
add(SvgDecoder(context))
|
||||||
|
add(MangaFetcher())
|
||||||
|
add(ByteArrayFetcher())
|
||||||
|
}.okHttpClient {
|
||||||
|
OkHttpClient.Builder()
|
||||||
|
.cache(CoilUtils.createDefaultCache(context))
|
||||||
|
.addInterceptor(ChuckerInterceptor(context))
|
||||||
|
.build()
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Coil.setImageLoader(imageLoader)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.download.coil
|
||||||
|
|
||||||
|
import coil.bitmappool.BitmapPool
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.decode.Options
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.fetch.SourceResult
|
||||||
|
import coil.size.Size
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Request
|
||||||
|
import okio.buffer
|
||||||
|
import okio.sink
|
||||||
|
import okio.source
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MangaFetcher() : Fetcher<Manga> {
|
||||||
|
|
||||||
|
private val coverCache: CoverCache by injectLazy()
|
||||||
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
private val defaultClient = Injekt.get<NetworkHelper>().client
|
||||||
|
|
||||||
|
override fun key(manga: Manga): String? {
|
||||||
|
if (manga.thumbnail_url.isNullOrBlank()) return null
|
||||||
|
return DiskUtil.hashKeyForDisk(manga.thumbnail_url!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetch(pool: BitmapPool, manga: Manga, size: Size, options: Options): FetchResult {
|
||||||
|
val cover = manga.thumbnail_url
|
||||||
|
when (getResourceType(cover)) {
|
||||||
|
Type.File -> {
|
||||||
|
return fileLoader(manga)
|
||||||
|
}
|
||||||
|
Type.URL -> {
|
||||||
|
return httpLoader(manga)
|
||||||
|
}
|
||||||
|
Type.CUSTOM -> {
|
||||||
|
return customLoader(manga)
|
||||||
|
}
|
||||||
|
null -> error("Invalid image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun customLoader(manga: Manga): FetchResult {
|
||||||
|
val coverFile = coverCache.getCoverFile(manga)
|
||||||
|
if (coverFile.exists()) {
|
||||||
|
return fileLoader(coverFile)
|
||||||
|
}
|
||||||
|
manga.thumbnail_url = manga.thumbnail_url!!.substringAfter("-J2K-").substringAfter("CUSTOM-")
|
||||||
|
return httpLoader(manga)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun httpLoader(manga: Manga): FetchResult {
|
||||||
|
val coverFile = coverCache.getCoverFile(manga)
|
||||||
|
if (coverFile.exists()) {
|
||||||
|
return fileLoader(coverFile)
|
||||||
|
}
|
||||||
|
val call = getCall(manga)
|
||||||
|
val tmpFile = File(coverFile.absolutePath + "_tmp")
|
||||||
|
|
||||||
|
val response = call.execute()
|
||||||
|
val body = checkNotNull(response.body) { "Null response source" }
|
||||||
|
|
||||||
|
body.source().use { input ->
|
||||||
|
tmpFile.sink().buffer().use { output ->
|
||||||
|
output.writeAll(input)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpFile.renameTo(coverFile)
|
||||||
|
return fileLoader(coverFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fileLoader(manga: Manga): FetchResult {
|
||||||
|
return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://")))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fileLoader(file: File): FetchResult {
|
||||||
|
return SourceResult(
|
||||||
|
source = file.source().buffer(),
|
||||||
|
mimeType = "image/*",
|
||||||
|
dataSource = DataSource.DISK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCall(manga: Manga): Call {
|
||||||
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
|
val client = source?.client ?: defaultClient
|
||||||
|
|
||||||
|
val newClient = client.newBuilder()
|
||||||
|
.cache(coverCache.cache)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val request = Request.Builder().url(manga.thumbnail_url!!).also {
|
||||||
|
if (source != null) {
|
||||||
|
it.headers(source.headers)
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return newClient.newCall(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getResourceType(cover: String?): Type? {
|
||||||
|
return when {
|
||||||
|
cover.isNullOrEmpty() -> null
|
||||||
|
cover.startsWith("http") -> Type.URL
|
||||||
|
cover.startsWith("Custom-") -> Type.CUSTOM
|
||||||
|
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Type {
|
||||||
|
File, CUSTOM, URL;
|
||||||
|
}
|
||||||
|
}
|
@ -1,55 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import android.content.ContentValues.TAG
|
|
||||||
import android.util.Log
|
|
||||||
import com.bumptech.glide.Priority
|
|
||||||
import com.bumptech.glide.load.DataSource
|
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
|
||||||
import java.io.File
|
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileNotFoundException
|
|
||||||
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, 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
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,72 +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 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)
|
|
||||||
loadFromFile(callback)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
tmpFile.delete()
|
|
||||||
callback.onLoadFailed(e)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
callback.onLoadFailed(Exception("Null data"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadFailed(e: Exception) {
|
|
||||||
callback.onLoadFailed(e)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
loadFromFile(callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanup() {
|
|
||||||
super.cleanup()
|
|
||||||
networkFetcher.cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
super.cancel()
|
|
||||||
networkFetcher.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,156 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import android.util.LruCache
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpStreamFetcher
|
|
||||||
import com.bumptech.glide.load.Options
|
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
|
||||||
import com.bumptech.glide.load.model.Headers
|
|
||||||
import com.bumptech.glide.load.model.LazyHeaders
|
|
||||||
import com.bumptech.glide.load.model.ModelLoader
|
|
||||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import 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 [LibraryMangaUrlFetcher], this class allows to implement the following flow:
|
|
||||||
*
|
|
||||||
* - Check in RAM LRU.
|
|
||||||
* - Check in disk LRU.
|
|
||||||
* - Check in this module.
|
|
||||||
* - Fetch from the network connection.
|
|
||||||
*
|
|
||||||
* @param context the application context.
|
|
||||||
*/
|
|
||||||
class MangaModelLoader : ModelLoader<Manga, InputStream> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Cover cache where persistent covers are stored.
|
|
||||||
*/
|
|
||||||
private val coverCache: CoverCache by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Source manager.
|
|
||||||
*/
|
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default network client.
|
|
||||||
*/
|
|
||||||
private val defaultClient = Injekt.get<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<GlideUrl, File>(100)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map where request headers are stored for a source.
|
|
||||||
*/
|
|
||||||
private val cachedHeaders = hashMapOf<Long, LazyHeaders>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory class for creating [MangaModelLoader] instances.
|
|
||||||
*/
|
|
||||||
class Factory : ModelLoaderFactory<Manga, InputStream> {
|
|
||||||
|
|
||||||
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.
|
|
||||||
*
|
|
||||||
* @param manga the model.
|
|
||||||
* @param width the width of the view where the resource will be loaded.
|
|
||||||
* @param height the height of the view where the resource will be loaded.
|
|
||||||
*/
|
|
||||||
override fun buildLoadData(
|
|
||||||
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?.startsWith("http") == true) {
|
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
|
||||||
val glideUrl = GlideUrl(url, getHeaders(manga, source))
|
|
||||||
|
|
||||||
// Get the resource fetcher for this request url.
|
|
||||||
val networkFetcher = OkHttpStreamFetcher(source?.client ?: defaultClient, glideUrl)
|
|
||||||
|
|
||||||
if (!manga.favorite) {
|
|
||||||
return ModelLoader.LoadData(glideUrl, networkFetcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 ModelLoader.LoadData(MangaSignature(manga, file), libraryFetcher)
|
|
||||||
} else {
|
|
||||||
// Get the file from the url, removing the scheme if present, or from the cache if no url.
|
|
||||||
val file = when {
|
|
||||||
manga.hasCustomCover() -> coverCache.getCoverFile(manga.thumbnail_url!!)
|
|
||||||
url != null -> File(url.substringAfter("file://"))
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file?.exists() != true) return null
|
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
|
||||||
return ModelLoader.LoadData(MangaSignature(manga, file), FileFetcher(file))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request headers for a source copying its OkHttp headers and caching them.
|
|
||||||
*
|
|
||||||
* @param manga the model.
|
|
||||||
*/
|
|
||||||
fun getHeaders(manga: Manga, source: HttpSource?): Headers {
|
|
||||||
if (source == null) return LazyHeaders.DEFAULT
|
|
||||||
|
|
||||||
return cachedHeaders.getOrPut(manga.source) {
|
|
||||||
LazyHeaders.Builder().apply {
|
|
||||||
val nullStr: String? = null
|
|
||||||
setHeader("User-Agent", nullStr)
|
|
||||||
for ((key, value) in source.headers.toMultimap()) {
|
|
||||||
addHeader(key, value[0])
|
|
||||||
}
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
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,72 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import com.bumptech.glide.Priority
|
|
||||||
import com.bumptech.glide.load.DataSource
|
|
||||||
import com.bumptech.glide.load.Options
|
|
||||||
import com.bumptech.glide.load.data.DataFetcher
|
|
||||||
import com.bumptech.glide.load.model.ModelLoader
|
|
||||||
import com.bumptech.glide.load.model.ModelLoaderFactory
|
|
||||||
import com.bumptech.glide.load.model.MultiModelLoaderFactory
|
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
|
||||||
import java.io.IOException
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
class PassthroughModelLoader : ModelLoader<InputStream, InputStream> {
|
|
||||||
|
|
||||||
override fun buildLoadData(
|
|
||||||
model: InputStream,
|
|
||||||
width: Int,
|
|
||||||
height: Int,
|
|
||||||
options: Options
|
|
||||||
): ModelLoader.LoadData<InputStream>? {
|
|
||||||
return ModelLoader.LoadData(ObjectKey(model), Fetcher(model))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handles(model: InputStream): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
class Fetcher(private val stream: InputStream) : DataFetcher<InputStream> {
|
|
||||||
|
|
||||||
override fun getDataClass(): Class<InputStream> {
|
|
||||||
return InputStream::class.java
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanup() {
|
|
||||||
try {
|
|
||||||
stream.close()
|
|
||||||
} catch (e: IOException) {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getDataSource(): DataSource {
|
|
||||||
return DataSource.LOCAL
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun loadData(
|
|
||||||
priority: Priority,
|
|
||||||
callback: DataFetcher.DataCallback<in InputStream>
|
|
||||||
) {
|
|
||||||
callback.onDataReady(stream)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory class for creating [PassthroughModelLoader] instances.
|
|
||||||
*/
|
|
||||||
class Factory : ModelLoaderFactory<InputStream, InputStream> {
|
|
||||||
|
|
||||||
override fun build(
|
|
||||||
multiFactory: MultiModelLoaderFactory
|
|
||||||
): ModelLoader<InputStream, InputStream> {
|
|
||||||
return PassthroughModelLoader()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun teardown() {}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,39 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
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.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
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.InputStream
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Class used to update Glide module settings
|
|
||||||
*/
|
|
||||||
@GlideModule
|
|
||||||
class TachiGlideModule : AppGlideModule() {
|
|
||||||
|
|
||||||
override fun applyOptions(context: Context, builder: GlideBuilder) {
|
|
||||||
builder.setDiskCache(InternalCacheDiskCacheFactory(context, 50 * 1024 * 1024))
|
|
||||||
builder.setDefaultRequestOptions(RequestOptions().format(DecodeFormat.PREFER_RGB_565))
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun registerComponents(context: Context, glide: Glide, registry: Registry) {
|
|
||||||
val networkFactory = OkHttpUrlLoader.Factory(Injekt.get<NetworkHelper>().client)
|
|
||||||
|
|
||||||
registry.replace(GlideUrl::class.java, InputStream::class.java, networkFactory)
|
|
||||||
registry.append(Manga::class.java, InputStream::class.java, MangaModelLoader.Factory())
|
|
||||||
registry.append(InputStream::class.java, InputStream::class.java, PassthroughModelLoader
|
|
||||||
.Factory())
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,6 +6,7 @@ import android.app.Service
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
@ -13,16 +14,18 @@ import androidx.core.app.NotificationCompat
|
|||||||
import androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY
|
import androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import coil.Coil
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.LoadRequest
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
@ -56,7 +59,6 @@ import timber.log.Timber
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.ArrayList
|
import java.util.ArrayList
|
||||||
import java.util.Date
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
@ -530,10 +532,16 @@ class LibraryUpdateService(
|
|||||||
val thumbnailUrl = manga.thumbnail_url
|
val thumbnailUrl = manga.thumbnail_url
|
||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
db.insertManga(manga).executeAsBlocking()
|
// load new covers in background
|
||||||
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
if (!manga.hasCustomCover()) {
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
val request = LoadRequest.Builder(this@LibraryUpdateService)
|
||||||
|
.data(manga)
|
||||||
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.build()
|
||||||
|
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -604,10 +612,12 @@ class LibraryUpdateService(
|
|||||||
notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
try {
|
try {
|
||||||
val icon = GlideApp.with(this@LibraryUpdateService)
|
|
||||||
.asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
|
val request = LoadRequest.Builder(this@LibraryUpdateService).data(manga)
|
||||||
.override(256, 256).submit().get()
|
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
|
||||||
setLargeIcon(icon)
|
.target { drawable -> setLargeIcon((drawable as BitmapDrawable).bitmap) }.build()
|
||||||
|
|
||||||
|
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
}
|
}
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
||||||
|
@ -47,8 +47,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
val cover = File("${dir.absolutePath}/${manga.url}", COVER_NAME)
|
val cover = File("${dir.absolutePath}/${manga.url}", COVER_NAME)
|
||||||
|
if (cover.exists()) cover.delete()
|
||||||
|
|
||||||
// It might not exist if using the external SD card
|
|
||||||
cover.parentFile?.mkdirs()
|
cover.parentFile?.mkdirs()
|
||||||
input.use {
|
input.use {
|
||||||
cover.outputStream().use {
|
cover.outputStream().use {
|
||||||
|
@ -4,15 +4,18 @@ import android.content.res.ColorStateList
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
|
import coil.api.clear
|
||||||
|
import coil.api.load
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.InstallStep
|
import eu.kanade.tachiyomi.extension.model.InstallStep
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.util.view.resetStrokeColor
|
import eu.kanade.tachiyomi.util.view.resetStrokeColor
|
||||||
|
import eu.kanade.tachiyomi.widget.CoverViewTarget
|
||||||
import kotlinx.android.synthetic.main.extension_card_item.*
|
import kotlinx.android.synthetic.main.extension_card_item.*
|
||||||
|
import kotlinx.android.synthetic.main.source_global_search_controller_card_item.*
|
||||||
|
|
||||||
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||||
BaseFlexibleViewHolder(view, adapter) {
|
BaseFlexibleViewHolder(view, adapter) {
|
||||||
@ -35,11 +38,12 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
|||||||
itemView.context.getString(R.string.untrusted).toUpperCase()
|
itemView.context.getString(R.string.untrusted).toUpperCase()
|
||||||
}
|
}
|
||||||
|
|
||||||
GlideApp.with(itemView.context).clear(edit_button)
|
edit_button.clear()
|
||||||
|
|
||||||
if (extension is Extension.Available) {
|
if (extension is Extension.Available) {
|
||||||
GlideApp.with(itemView.context)
|
edit_button.load(extension.iconUrl) {
|
||||||
.load(extension.iconUrl)
|
target(CoverViewTarget(edit_button, progress))
|
||||||
.into(edit_button)
|
}
|
||||||
} else {
|
} else {
|
||||||
extension.getApplicationIcon(itemView.context)?.let { edit_button.setImageDrawable(it) }
|
extension.getApplicationIcon(itemView.context)?.let { edit_button.setImageDrawable(it) }
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@ import android.app.Activity
|
|||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import coil.api.loadAny
|
||||||
|
import coil.size.Precision
|
||||||
|
import coil.size.Scale
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||||
import kotlinx.android.synthetic.main.manga_grid_item.*
|
import kotlinx.android.synthetic.main.manga_grid_item.*
|
||||||
@ -65,29 +65,26 @@ class LibraryGridHolder(
|
|||||||
setReadingButton(item)
|
setReadingButton(item)
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
if (item.manga.thumbnail_url == null) GlideApp.with(view.context).clear(cover_thumbnail)
|
if (item.manga.thumbnail_url == null) cover_thumbnail.clear()
|
||||||
else {
|
else {
|
||||||
val id = item.manga.id ?: return
|
|
||||||
if (cover_thumbnail.height == 0) {
|
if (cover_thumbnail.height == 0) {
|
||||||
val oldPos = adapterPosition
|
val oldPos = adapterPosition
|
||||||
adapter.recyclerView.post {
|
adapter.recyclerView.post {
|
||||||
if (oldPos == adapterPosition)
|
if (oldPos == adapterPosition)
|
||||||
setCover(item.manga, id)
|
setCover(item.manga)
|
||||||
}
|
}
|
||||||
} else setCover(item.manga, id)
|
} else setCover(item.manga)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCover(manga: Manga, id: Long) {
|
private fun setCover(manga: Manga) {
|
||||||
if ((adapter.recyclerView.context as? Activity)?.isDestroyed == true) return
|
if ((adapter.recyclerView.context as? Activity)?.isDestroyed == true) return
|
||||||
GlideApp.with(adapter.recyclerView.context).load(manga)
|
cover_thumbnail.loadAny(manga) {
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
if (!fixedSize) {
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
|
precision(Precision.INEXACT)
|
||||||
.apply {
|
scale(Scale.FIT)
|
||||||
if (fixedSize) centerCrop()
|
}
|
||||||
else override(cover_thumbnail.maxHeight)
|
|
||||||
}
|
}
|
||||||
.into(cover_thumbnail)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun playButtonClicked() {
|
private fun playButtonClicked() {
|
||||||
|
@ -2,19 +2,19 @@ package eu.kanade.tachiyomi.ui.library
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import com.bumptech.glide.Glide
|
import coil.api.clear
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.loadAny
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
import coil.transform.RoundedCornersTransformation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||||
import eu.kanade.tachiyomi.util.view.visible
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||||
import kotlinx.android.synthetic.main.manga_list_item.*
|
import kotlinx.android.synthetic.main.manga_list_item.*
|
||||||
|
import kotlinx.android.synthetic.main.manga_list_item.title
|
||||||
import kotlinx.android.synthetic.main.manga_list_item.view.*
|
import kotlinx.android.synthetic.main.manga_list_item.view.*
|
||||||
|
import kotlinx.android.synthetic.main.recently_read_item.*
|
||||||
import kotlinx.android.synthetic.main.unread_download_badge.*
|
import kotlinx.android.synthetic.main.unread_download_badge.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -78,15 +78,13 @@ class LibraryListHolder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
if (item.manga.thumbnail_url == null) Glide.with(view.context).clear(cover_thumbnail)
|
if (item.manga.thumbnail_url == null) {
|
||||||
else {
|
cover_thumbnail.clear()
|
||||||
|
} else {
|
||||||
val id = item.manga.id ?: return
|
val id = item.manga.id ?: return
|
||||||
|
cover_thumbnail.loadAny(item.manga) {
|
||||||
GlideApp.with(view.context).load(item.manga)
|
transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
}
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
|
|
||||||
.centerCrop()
|
|
||||||
.into(cover_thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -641,7 +641,7 @@ class LibraryPresenter(
|
|||||||
val mangaToDelete = mangas.distinctBy { it.id }
|
val mangaToDelete = mangas.distinctBy { it.id }
|
||||||
mangaToDelete.forEach { manga ->
|
mangaToDelete.forEach { manga ->
|
||||||
db.resetMangaInfo(manga).executeOnIO()
|
db.resetMangaInfo(manga).executeOnIO()
|
||||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
coverCache.deleteFromCache(manga)
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
if (source != null)
|
if (source != null)
|
||||||
downloadManager.deleteManga(manga, source)
|
downloadManager.deleteManga(manga, source)
|
||||||
|
@ -4,15 +4,12 @@ import android.app.Dialog
|
|||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import coil.api.loadAny
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.customview.customView
|
import com.afollestad.materialdialogs.customview.customView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||||
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
||||||
@ -60,13 +57,7 @@ class EditMangaDialog : DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun onViewCreated(view: View) {
|
fun onViewCreated(view: View) {
|
||||||
GlideApp.with(view.context)
|
view.manga_cover.loadAny(manga)
|
||||||
.asDrawable()
|
|
||||||
.load(manga)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
|
||||||
.dontAnimate()
|
|
||||||
.into(view.manga_cover)
|
|
||||||
val isLocal = manga.source == LocalSource.ID
|
val isLocal = manga.source == LocalSource.ID
|
||||||
|
|
||||||
if (isLocal) {
|
if (isLocal) {
|
||||||
@ -93,7 +84,7 @@ class EditMangaDialog : DialogController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun updateCover(uri: Uri) {
|
fun updateCover(uri: Uri) {
|
||||||
GlideApp.with(dialogView!!.context).load(uri).into(dialogView!!.manga_cover)
|
dialogView!!.manga_cover.loadAny(uri)
|
||||||
customCoverUri = uri
|
customCoverUri = uri
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,11 +11,9 @@ import android.content.ClipData
|
|||||||
import android.content.ClipboardManager
|
import android.content.ClipboardManager
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.BitmapDrawable
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -24,7 +22,6 @@ import android.view.MenuInflater
|
|||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewPropertyAnimator
|
|
||||||
import android.view.WindowInsets
|
import android.view.WindowInsets
|
||||||
import android.view.animation.DecelerateInterpolator
|
import android.view.animation.DecelerateInterpolator
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
@ -41,16 +38,16 @@ import androidx.transition.ChangeBounds
|
|||||||
import androidx.transition.ChangeImageTransform
|
import androidx.transition.ChangeImageTransform
|
||||||
import androidx.transition.TransitionManager
|
import androidx.transition.TransitionManager
|
||||||
import androidx.transition.TransitionSet
|
import androidx.transition.TransitionSet
|
||||||
|
import coil.Coil
|
||||||
|
import coil.api.clear
|
||||||
|
import coil.api.loadAny
|
||||||
|
import coil.request.LoadRequest
|
||||||
import com.afollestad.materialdialogs.MaterialDialog
|
import com.afollestad.materialdialogs.MaterialDialog
|
||||||
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
import com.afollestad.materialdialogs.checkbox.checkBoxPrompt
|
||||||
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
||||||
import com.afollestad.materialdialogs.list.listItems
|
import com.afollestad.materialdialogs.list.listItems
|
||||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||||
import com.bluelinelabs.conductor.ControllerChangeType
|
import com.bluelinelabs.conductor.ControllerChangeType
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
|
||||||
import com.bumptech.glide.request.target.CustomTarget
|
|
||||||
import com.bumptech.glide.request.transition.Transition
|
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
|
||||||
import com.google.android.material.snackbar.BaseTransientBottomBar
|
import com.google.android.material.snackbar.BaseTransientBottomBar
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
@ -60,10 +57,8 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
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.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
@ -165,13 +160,10 @@ class MangaDetailsController : BaseController,
|
|||||||
var toolbarIsColored = false
|
var toolbarIsColored = false
|
||||||
private var snack: Snackbar? = null
|
private var snack: Snackbar? = null
|
||||||
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
|
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
|
||||||
var coverDrawable: Drawable? = null
|
|
||||||
private var trackingBottomSheet: TrackingBottomSheet? = null
|
private var trackingBottomSheet: TrackingBottomSheet? = null
|
||||||
private var startingDLChapterPos: Int? = null
|
private var startingDLChapterPos: Int? = null
|
||||||
private var editMangaDialog: EditMangaDialog? = null
|
private var editMangaDialog: EditMangaDialog? = null
|
||||||
var refreshTracker: Int? = null
|
var refreshTracker: Int? = null
|
||||||
private var textAnim: ViewPropertyAnimator? = null
|
|
||||||
private var scrollAnim: ViewPropertyAnimator? = null
|
|
||||||
var chapterPopupMenu: Pair<Int, PopupMenu>? = null
|
var chapterPopupMenu: Pair<Int, PopupMenu>? = null
|
||||||
|
|
||||||
private var query = ""
|
private var query = ""
|
||||||
@ -313,29 +305,23 @@ class MangaDetailsController : BaseController,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the color of the manga cover based on the current theme */
|
/** Get the color of the manga cover*/
|
||||||
fun setPaletteColor() {
|
fun setPaletteColor() {
|
||||||
val view = view ?: return
|
val view = view ?: return
|
||||||
coverColor = null
|
coverColor = null
|
||||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga!!.id!!).toString()))
|
val request = LoadRequest.Builder(view.context).data(manga).allowHardware(false)
|
||||||
.into(object : CustomTarget<Drawable>() {
|
.target { drawable ->
|
||||||
override fun onResourceReady(
|
val bitmap = (drawable as BitmapDrawable).bitmap
|
||||||
resource: Drawable,
|
// Generate the Palette on a background thread.
|
||||||
transition: Transition<in Drawable>?
|
Palette.from(bitmap).generate {
|
||||||
) {
|
|
||||||
coverDrawable = resource
|
|
||||||
val bitmapCover = resource as? BitmapDrawable ?: return
|
|
||||||
Palette.from(bitmapCover.bitmap).generate {
|
|
||||||
if (recycler == null || it == null) return@generate
|
if (recycler == null || it == null) return@generate
|
||||||
val colorBack = view.context.getResourceColor(
|
val colorBack = view.context.getResourceColor(
|
||||||
android.R.attr.colorBackground
|
android.R.attr.colorBackground
|
||||||
)
|
)
|
||||||
val backDropColor = if (!view.context.isInNightMode()) {
|
// this makes the color more consistent regardless of theme
|
||||||
it.getLightVibrantColor(colorBack)
|
val backDropColor = ColorUtils.blendARGB(it.getVibrantColor(colorBack), colorBack, .35f)
|
||||||
} else {
|
|
||||||
it.getDarkVibrantColor(colorBack)
|
|
||||||
}
|
|
||||||
coverColor = backDropColor
|
coverColor = backDropColor
|
||||||
getHeader()?.setBackDrop(backDropColor)
|
getHeader()?.setBackDrop(backDropColor)
|
||||||
if (toolbarIsColored) {
|
if (toolbarIsColored) {
|
||||||
@ -344,11 +330,14 @@ class MangaDetailsController : BaseController,
|
|||||||
activity?.window?.statusBarColor = translucentColor
|
activity?.window?.statusBarColor = translucentColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
getHeader()?.updateCover(presenter.manga)
|
}.build()
|
||||||
|
Coil.imageLoader(view.context).execute(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
fun resetCovers() {
|
||||||
})
|
manga_cover_full.clear()
|
||||||
|
manga_cover_full.loadAny(manga)
|
||||||
|
getHeader()?.updateCover(manga!!, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set toolbar theme for themes that are inverted (ie. light blue theme) */
|
/** Set toolbar theme for themes that are inverted (ie. light blue theme) */
|
||||||
@ -404,12 +393,15 @@ class MangaDetailsController : BaseController,
|
|||||||
super.onActivityResumed(activity)
|
super.onActivityResumed(activity)
|
||||||
presenter.isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
|
presenter.isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
|
||||||
presenter.headerItem.isLocked = presenter.isLockedFromSearch
|
presenter.headerItem.isLocked = presenter.isLockedFromSearch
|
||||||
|
manga!!.thumbnail_url = presenter.refreshMangaFromDb().thumbnail_url
|
||||||
presenter.fetchChapters(refreshTracker == null)
|
presenter.fetchChapters(refreshTracker == null)
|
||||||
if (refreshTracker != null) {
|
if (refreshTracker != null) {
|
||||||
trackingBottomSheet?.refreshItem(refreshTracker ?: 0)
|
trackingBottomSheet?.refreshItem(refreshTracker ?: 0)
|
||||||
presenter.refreshTracking()
|
presenter.refreshTracking()
|
||||||
refreshTracker = null
|
refreshTracker = null
|
||||||
}
|
}
|
||||||
|
// reset the covers and palette cause user might have set a custom cover
|
||||||
|
presenter.forceUpdateCovers(false)
|
||||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
||||||
this
|
this
|
||||||
if (isCurrentController) {
|
if (isCurrentController) {
|
||||||
@ -774,7 +766,7 @@ class MangaDetailsController : BaseController,
|
|||||||
), waitForPositiveButton = false, selection = { _, index, _ ->
|
), waitForPositiveButton = false, selection = { _, index, _ ->
|
||||||
when (index) {
|
when (index) {
|
||||||
0 -> changeCover()
|
0 -> changeCover()
|
||||||
else -> presenter.clearCover()
|
else -> presenter.clearCustomCover()
|
||||||
}
|
}
|
||||||
}).show()
|
}).show()
|
||||||
} else {
|
} else {
|
||||||
@ -810,20 +802,16 @@ class MangaDetailsController : BaseController,
|
|||||||
//endregion
|
//endregion
|
||||||
|
|
||||||
override fun prepareToShareManga() {
|
override fun prepareToShareManga() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && coverDrawable != null)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object :
|
val request = LoadRequest.Builder(activity!!).data(manga).target(onError = {
|
||||||
CustomTarget<Bitmap>() {
|
shareManga()
|
||||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
}, onSuccess = {
|
||||||
presenter.shareManga(resource)
|
presenter.shareManga((it as BitmapDrawable).bitmap)
|
||||||
}
|
}).build()
|
||||||
|
Coil.imageLoader(activity!!).execute(request)
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
} else {
|
||||||
|
|
||||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
|
||||||
shareManga()
|
shareManga()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
else shareManga()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shareManga(cover: File? = null) {
|
fun shareManga(cover: File? = null) {
|
||||||
@ -1257,7 +1245,6 @@ class MangaDetailsController : BaseController,
|
|||||||
if (editMangaDialog != null) editMangaDialog?.updateCover(uri)
|
if (editMangaDialog != null) editMangaDialog?.updateCover(uri)
|
||||||
else {
|
else {
|
||||||
presenter.editCoverWithStream(uri)
|
presenter.editCoverWithStream(uri)
|
||||||
setPaletteColor()
|
|
||||||
}
|
}
|
||||||
} catch (error: IOException) {
|
} catch (error: IOException) {
|
||||||
activity.toast(R.string.failed_to_update_cover)
|
activity.toast(R.string.failed_to_update_cover)
|
||||||
@ -1271,10 +1258,9 @@ class MangaDetailsController : BaseController,
|
|||||||
currentAnimator?.cancel()
|
currentAnimator?.cancel()
|
||||||
|
|
||||||
// Load the high-resolution "zoomed-in" image.
|
// Load the high-resolution "zoomed-in" image.
|
||||||
|
manga_cover_full?.loadAny(manga)
|
||||||
val expandedImageView = manga_cover_full ?: return
|
val expandedImageView = manga_cover_full ?: return
|
||||||
val fullBackdrop = full_backdrop
|
val fullBackdrop = full_backdrop
|
||||||
val image = coverDrawable ?: return
|
|
||||||
expandedImageView.setImageDrawable(image)
|
|
||||||
|
|
||||||
// Hide the thumbnail and show the zoomed-in view. When the animation
|
// Hide the thumbnail and show the zoomed-in view. When the animation
|
||||||
// begins, it will position the zoomed-in view in the place of the
|
// begins, it will position the zoomed-in view in the place of the
|
||||||
|
@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
|
|||||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Track
|
import eu.kanade.tachiyomi.data.database.models.Track
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
@ -27,6 +26,7 @@ import eu.kanade.tachiyomi.source.Source
|
|||||||
import eu.kanade.tachiyomi.source.fetchChapterListAsync
|
import eu.kanade.tachiyomi.source.fetchChapterListAsync
|
||||||
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
import eu.kanade.tachiyomi.ui.manga.chapter.ChapterItem
|
||||||
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
import eu.kanade.tachiyomi.ui.manga.track.TrackItem
|
||||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
@ -368,6 +368,12 @@ class MangaDetailsPresenter(
|
|||||||
if (update) controller.updateChapters(this.chapters)
|
if (update) controller.updateChapters(this.chapters)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun refreshMangaFromDb(): Manga {
|
||||||
|
val dbManga = db.getManga(manga.id!!).executeAsBlocking()
|
||||||
|
manga.copyFrom(dbManga!!)
|
||||||
|
return dbManga
|
||||||
|
}
|
||||||
|
|
||||||
/** Refresh Manga Info and Chapter List (not tracking) */
|
/** Refresh Manga Info and Chapter List (not tracking) */
|
||||||
fun refreshAll() {
|
fun refreshAll() {
|
||||||
if (controller.isNotOnline() && manga.source != LocalSource.ID) return
|
if (controller.isNotOnline() && manga.source != LocalSource.ID) return
|
||||||
@ -398,13 +404,16 @@ class MangaDetailsPresenter(
|
|||||||
if (networkManga != null) {
|
if (networkManga != null) {
|
||||||
manga.copyFrom(networkManga)
|
manga.copyFrom(networkManga)
|
||||||
manga.initialized = true
|
manga.initialized = true
|
||||||
db.insertManga(manga).executeAsBlocking()
|
|
||||||
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
if (shouldUpdateCover(thumbnailUrl, networkManga)) {
|
||||||
coverCache.deleteFromCache(thumbnailUrl)
|
coverCache.deleteFromCache(manga, false)
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
manga.thumbnail_url = networkManga.thumbnail_url
|
||||||
withContext(Dispatchers.Main) { controller.setPaletteColor() }
|
withContext(Dispatchers.Main) {
|
||||||
|
forceUpdateCovers()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
db.insertManga(manga).executeAsBlocking()
|
||||||
|
}
|
||||||
val finChapters = chapters.await()
|
val finChapters = chapters.await()
|
||||||
if (finChapters.isNotEmpty()) {
|
if (finChapters.isNotEmpty()) {
|
||||||
val newChapters = syncChaptersWithSource(db, finChapters, manga, source)
|
val newChapters = syncChaptersWithSource(db, finChapters, manga, source)
|
||||||
@ -451,6 +460,19 @@ class MangaDetailsPresenter(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun shouldUpdateCover(thumbnailUrl: String?, networkManga: SManga): Boolean {
|
||||||
|
val refreshCovers = preferences.refreshCoversToo().getOrDefault()
|
||||||
|
if (thumbnailUrl == networkManga.thumbnail_url && !refreshCovers) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (manga.hasCustomCover()) return false
|
||||||
|
|
||||||
|
return refreshCovers
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests an updated list of chapters from the source.
|
* Requests an updated list of chapters from the source.
|
||||||
*/
|
*/
|
||||||
@ -588,7 +610,9 @@ class MangaDetailsPresenter(
|
|||||||
manga.favorite = !manga.favorite
|
manga.favorite = !manga.favorite
|
||||||
|
|
||||||
when (manga.favorite) {
|
when (manga.favorite) {
|
||||||
true -> manga.date_added = Date().time
|
true -> {
|
||||||
|
manga.date_added = Date().time
|
||||||
|
}
|
||||||
false -> manga.date_added = 0
|
false -> manga.date_added = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -639,7 +663,7 @@ class MangaDetailsPresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun confirmDeletion() {
|
fun confirmDeletion() {
|
||||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
coverCache.deleteFromCache(manga)
|
||||||
db.resetMangaInfo(manga).executeAsBlocking()
|
db.resetMangaInfo(manga).executeAsBlocking()
|
||||||
downloadManager.deleteManga(manga, source)
|
downloadManager.deleteManga(manga, source)
|
||||||
asyncUpdateMangaAndChapters(true)
|
asyncUpdateMangaAndChapters(true)
|
||||||
@ -707,36 +731,41 @@ class MangaDetailsPresenter(
|
|||||||
db.updateMangaInfo(manga).executeAsBlocking()
|
db.updateMangaInfo(manga).executeAsBlocking()
|
||||||
}
|
}
|
||||||
if (uri != null) editCoverWithStream(uri)
|
if (uri != null) editCoverWithStream(uri)
|
||||||
controller.updateHeader()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearCover() {
|
/**
|
||||||
|
* Remvoe custom cover
|
||||||
|
*/
|
||||||
|
fun clearCustomCover() {
|
||||||
if (manga.hasCustomCover()) {
|
if (manga.hasCustomCover()) {
|
||||||
coverCache.deleteFromCache(manga.thumbnail_url!!)
|
coverCache.deleteFromCache(manga)
|
||||||
manga.thumbnail_url = manga.thumbnail_url?.removePrefix("Custom-")
|
manga.removeCustomThumbnailUrl()
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
forceUpdateCovers()
|
||||||
controller.updateHeader()
|
|
||||||
controller.setPaletteColor()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun forceUpdateCovers(deleteCache: Boolean = true) {
|
||||||
|
if (deleteCache) coverCache.deleteFromCache(manga)
|
||||||
|
controller.setPaletteColor()
|
||||||
|
controller.resetCovers()
|
||||||
|
}
|
||||||
|
|
||||||
fun editCoverWithStream(uri: Uri): Boolean {
|
fun editCoverWithStream(uri: Uri): Boolean {
|
||||||
val inputStream =
|
val inputStream =
|
||||||
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
||||||
if (manga.source == LocalSource.ID) {
|
if (manga.source == LocalSource.ID) {
|
||||||
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
forceUpdateCovers()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
if (!manga.hasCustomCover()) {
|
coverCache.deleteFromCache(manga)
|
||||||
manga.thumbnail_url = "Custom-${manga.thumbnail_url ?: manga.id!!}"
|
manga.setCustomThumbnailUrl()
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
coverCache.copyToCache(manga, inputStream)
|
||||||
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
|
forceUpdateCovers(false)
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
|
@ -9,14 +9,11 @@ import android.view.ViewGroup
|
|||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.graphics.ColorUtils
|
import androidx.core.graphics.ColorUtils
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.api.loadAny
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
@ -287,11 +284,15 @@ class MangaHeaderHolder(
|
|||||||
val presenter = adapter.delegate.mangaPresenter()
|
val presenter = adapter.delegate.mangaPresenter()
|
||||||
val tracked = presenter.isTracked()
|
val tracked = presenter.isTracked()
|
||||||
with(track_button) {
|
with(track_button) {
|
||||||
text = itemView.context.getString(if (tracked) R.string.tracked
|
text = itemView.context.getString(
|
||||||
else R.string.tracking)
|
if (tracked) R.string.tracked
|
||||||
|
else R.string.tracking
|
||||||
|
)
|
||||||
|
|
||||||
icon = ContextCompat.getDrawable(itemView.context, if (tracked) R.drawable
|
icon = ContextCompat.getDrawable(
|
||||||
.ic_check_white_24dp else R.drawable.ic_sync_black_24dp)
|
itemView.context, if (tracked) R.drawable
|
||||||
|
.ic_check_white_24dp else R.drawable.ic_sync_black_24dp
|
||||||
|
)
|
||||||
checked(tracked)
|
checked(tracked)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -307,22 +308,18 @@ class MangaHeaderHolder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateCover(manga: Manga) {
|
fun updateCover(manga: Manga, forceUpdate: Boolean = false) {
|
||||||
if (!isCached(manga)) return
|
if (!isCached(manga) && !forceUpdate) return
|
||||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
manga_cover.clear()
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
backdrop.clear()
|
||||||
.into(manga_cover)
|
manga_cover.loadAny(manga)
|
||||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
backdrop.loadAny(manga)
|
||||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())).centerCrop()
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade()).into(backdrop)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isCached(manga: Manga): Boolean {
|
private fun isCached(manga: Manga): Boolean {
|
||||||
if (manga.source == LocalSource.ID) return true
|
if (manga.source == LocalSource.ID) return true
|
||||||
val coverCache = adapter.delegate.mangaPresenter().coverCache
|
|
||||||
manga.thumbnail_url?.let {
|
manga.thumbnail_url?.let {
|
||||||
return if (manga.favorite) coverCache.getCoverFile(it).exists()
|
return adapter.delegate.mangaPresenter().coverCache.getCoverFile(manga).exists()
|
||||||
else true
|
|
||||||
}
|
}
|
||||||
return manga.initialized
|
return manga.initialized
|
||||||
}
|
}
|
||||||
|
@ -4,10 +4,9 @@ import android.content.Context
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.api.load
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.util.view.inflate
|
||||||
@ -46,12 +45,9 @@ class TrackSearchAdapter(context: Context) :
|
|||||||
fun onSetValues(track: TrackSearch) {
|
fun onSetValues(track: TrackSearch) {
|
||||||
view.track_search_title.text = track.title
|
view.track_search_title.text = track.title
|
||||||
view.track_search_summary.text = track.summary
|
view.track_search_summary.text = track.summary
|
||||||
GlideApp.with(view.context).clear(view.track_search_cover)
|
view.track_search_cover.clear()
|
||||||
if (!track.cover_url.isNullOrEmpty()) {
|
if (!track.cover_url.isNullOrEmpty()) {
|
||||||
GlideApp.with(view.context).load(track.cover_url)
|
view.track_search_cover.load(track.cover_url)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerCrop()
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(view.track_search_cover)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (track.publishing_status.isNullOrBlank()) {
|
if (track.publishing_status.isNullOrBlank()) {
|
||||||
|
@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.migration
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
|
import coil.api.loadAny
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import kotlinx.android.synthetic.main.manga_list_item.*
|
import kotlinx.android.synthetic.main.manga_list_item.*
|
||||||
|
|
||||||
@ -20,9 +20,7 @@ class MangaHolder(
|
|||||||
subtitle.text = item.manga.author?.trim()
|
subtitle.text = item.manga.author?.trim()
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
GlideApp.with(itemView.context).clear(cover_thumbnail)
|
cover_thumbnail.clear()
|
||||||
GlideApp.with(itemView.context).load(item.manga)
|
cover_thumbnail.loadAny(item.manga)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerCrop().dontAnimate()
|
|
||||||
.into(cover_thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.ui.migration.manga.process
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.appcompat.widget.PopupMenu
|
import androidx.appcompat.widget.PopupMenu
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.Coil
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.request.LoadRequest
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
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.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
@ -20,7 +19,7 @@ import eu.kanade.tachiyomi.util.view.invisible
|
|||||||
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
import eu.kanade.tachiyomi.util.view.setVectorCompat
|
||||||
import eu.kanade.tachiyomi.util.view.visible
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
import eu.kanade.tachiyomi.util.view.withFadeTransaction
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.CoverViewTarget
|
||||||
import kotlinx.android.synthetic.main.manga_grid_item.view.*
|
import kotlinx.android.synthetic.main.manga_grid_item.view.*
|
||||||
import kotlinx.android.synthetic.main.migration_process_item.*
|
import kotlinx.android.synthetic.main.migration_process_item.*
|
||||||
import kotlinx.android.synthetic.main.unread_download_badge.view.*
|
import kotlinx.android.synthetic.main.unread_download_badge.view.*
|
||||||
@ -133,14 +132,10 @@ class MigrationProcessHolder(
|
|||||||
private fun View.attachManga(manga: Manga, source: Source, isTo: Boolean) {
|
private fun View.attachManga(manga: Manga, source: Source, isTo: Boolean) {
|
||||||
(layoutParams as ConstraintLayout.LayoutParams).verticalBias = 1f
|
(layoutParams as ConstraintLayout.LayoutParams).verticalBias = 1f
|
||||||
progress.gone()
|
progress.gone()
|
||||||
GlideApp.with(view.context.applicationContext).load(manga).apply {
|
|
||||||
diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
val request = LoadRequest.Builder(view.context).data(manga)
|
||||||
if (isTo) {
|
.target(CoverViewTarget(cover_thumbnail, progress)).build()
|
||||||
transition(DrawableTransitionOptions.withCrossFade())
|
Coil.imageLoader(view.context).execute(request)
|
||||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
|
||||||
} else
|
|
||||||
into(cover_thumbnail)
|
|
||||||
}
|
|
||||||
|
|
||||||
compact_title.visible()
|
compact_title.visible()
|
||||||
gradient.visible()
|
gradient.visible()
|
||||||
@ -164,11 +159,15 @@ class MigrationProcessHolder(
|
|||||||
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
||||||
|
|
||||||
if (latestChapter > 0f) {
|
if (latestChapter > 0f) {
|
||||||
subtitle.text = context.getString(R.string.latest_,
|
subtitle.text = context.getString(
|
||||||
DecimalFormat("#.#").format(latestChapter))
|
R.string.latest_,
|
||||||
|
DecimalFormat("#.#").format(latestChapter)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
subtitle.text = context.getString(R.string.latest_,
|
subtitle.text = context.getString(
|
||||||
context.getString(R.string.unknown))
|
R.string.latest_,
|
||||||
|
context.getString(R.string.unknown)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
|||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.History
|
import eu.kanade.tachiyomi.data.database.models.History
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -536,18 +535,17 @@ class ReaderPresenter(
|
|||||||
.fromCallable {
|
.fromCallable {
|
||||||
if (manga.source == LocalSource.ID) {
|
if (manga.source == LocalSource.ID) {
|
||||||
val context = Injekt.get<Application>()
|
val context = Injekt.get<Application>()
|
||||||
|
coverCache.deleteFromCache(manga)
|
||||||
LocalSource.updateCover(context, manga, stream())
|
LocalSource.updateCover(context, manga, stream())
|
||||||
R.string.cover_updated
|
R.string.cover_updated
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
val thumbUrl = manga.thumbnail_url ?: throw Exception("Image url not found")
|
manga.thumbnail_url ?: throw Exception("Image url not found")
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
if (!manga.hasCustomCover()) {
|
coverCache.deleteFromCache(manga)
|
||||||
manga.thumbnail_url = "Custom-${manga.thumbnail_url ?: manga.id!!}"
|
manga.setCustomThumbnailUrl()
|
||||||
db.insertManga(manga).executeAsBlocking()
|
db.insertManga(manga).executeAsBlocking()
|
||||||
}
|
coverCache.copyToCache(manga, stream())
|
||||||
coverCache.copyToCache(manga.thumbnail_url!!, stream())
|
|
||||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
SetAsCoverResult.AddToLibraryFirst
|
SetAsCoverResult.AddToLibraryFirst
|
||||||
|
@ -2,11 +2,13 @@ package eu.kanade.tachiyomi.ui.reader
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.Coil
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.LoadRequest
|
||||||
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
|
||||||
@ -36,19 +38,19 @@ 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 = GlideApp.with(context)
|
|
||||||
.asBitmap()
|
|
||||||
.load(file)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.submit(720, 1280)
|
|
||||||
.get()
|
|
||||||
|
|
||||||
|
val request = LoadRequest.Builder(context).memoryCachePolicy(CachePolicy.DISABLED).diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.data(file)
|
||||||
|
.size(720, 1280)
|
||||||
|
.target(onSuccess = {
|
||||||
|
val bitmap = (it as BitmapDrawable).bitmap
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
showCompleteNotification(file, bitmap)
|
showCompleteNotification(file, bitmap)
|
||||||
} else {
|
} else {
|
||||||
onError(null)
|
onError(null)
|
||||||
}
|
}
|
||||||
|
}).build()
|
||||||
|
Coil.imageLoader(context).execute(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCompleteNotification(file: File, image: Bitmap) {
|
private fun showCompleteNotification(file: File, image: Bitmap) {
|
||||||
|
@ -18,19 +18,12 @@ import android.widget.FrameLayout
|
|||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import com.bumptech.glide.load.DataSource
|
import coil.api.loadAny
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.request.CachePolicy
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.bumptech.glide.request.transition.NoTransition
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
import com.github.chrisbanes.photoview.PhotoView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
@ -43,6 +36,7 @@ import eu.kanade.tachiyomi.util.system.isInNightMode
|
|||||||
import eu.kanade.tachiyomi.util.system.launchUI
|
import eu.kanade.tachiyomi.util.system.launchUI
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.util.view.visible
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
|
import eu.kanade.tachiyomi.widget.GifViewTarget
|
||||||
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||||
import kotlinx.coroutines.Dispatchers.Default
|
import kotlinx.coroutines.Dispatchers.Default
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -487,37 +481,11 @@ class PagerPageHolder(
|
|||||||
* Extension method to set a [stream] into this ImageView.
|
* Extension method to set a [stream] into this ImageView.
|
||||||
*/
|
*/
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
private fun ImageView.setImage(stream: InputStream) {
|
||||||
GlideApp.with(this)
|
this.loadAny(stream.readBytes()) {
|
||||||
.load(stream)
|
memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.skipMemoryCache(true)
|
diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
target(GifViewTarget(this@setImage, progressBar, decodeErrorLayout))
|
||||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
|
||||||
.listener(object : RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
onImageDecodeError()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
if (resource is GifDrawable) {
|
|
||||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
|
||||||
}
|
|
||||||
onImageDecoded()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into(this)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
@ -16,18 +15,12 @@ import android.widget.LinearLayout
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatButton
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import com.bumptech.glide.load.DataSource
|
import coil.api.clear
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.loadAny
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import coil.request.CachePolicy
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.bumptech.glide.request.transition.NoTransition
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||||
@ -36,6 +29,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx
|
|||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||||
import eu.kanade.tachiyomi.util.view.visible
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
|
import eu.kanade.tachiyomi.widget.GifViewTarget
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
@ -149,7 +143,7 @@ class WebtoonPageHolder(
|
|||||||
removeDecodeErrorLayout()
|
removeDecodeErrorLayout()
|
||||||
subsamplingImageView?.recycle()
|
subsamplingImageView?.recycle()
|
||||||
subsamplingImageView?.gone()
|
subsamplingImageView?.gone()
|
||||||
imageView?.let { GlideApp.with(frame).clear(it) }
|
imageView?.clear()
|
||||||
imageView?.gone()
|
imageView?.gone()
|
||||||
progressBar.setProgress(0)
|
progressBar.setProgress(0)
|
||||||
}
|
}
|
||||||
@ -491,36 +485,10 @@ class WebtoonPageHolder(
|
|||||||
* Extension method to set a [stream] into this ImageView.
|
* Extension method to set a [stream] into this ImageView.
|
||||||
*/
|
*/
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
private fun ImageView.setImage(stream: InputStream) {
|
||||||
GlideApp.with(this)
|
this.loadAny(stream.readBytes()) {
|
||||||
.load(stream)
|
memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.skipMemoryCache(true)
|
diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
target(GifViewTarget(this@setImage, progressBar, decodeErrorLayout))
|
||||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
}
|
||||||
.listener(object : RequestListener<Drawable> {
|
|
||||||
override fun onLoadFailed(
|
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
onImageDecodeError()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
if (resource is GifDrawable) {
|
|
||||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
|
||||||
}
|
|
||||||
onImageDecoded()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.into(this)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,10 @@ package eu.kanade.tachiyomi.ui.recent_updates
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
|
import coil.api.loadAny
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
@ -71,10 +72,9 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha
|
|||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
if ((view.context as? Activity)?.isDestroyed != true) {
|
if ((view.context as? Activity)?.isDestroyed != true) {
|
||||||
GlideApp.with(itemView.context).clear(manga_cover)
|
manga_cover.clear()
|
||||||
if (!item.manga.thumbnail_url.isNullOrEmpty()) {
|
manga_cover.loadAny(item.manga) {
|
||||||
GlideApp.with(itemView.context).load(item.manga)
|
transformations(CircleCropTransformation())
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).circleCrop().into(manga_cover)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
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.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
|
import coil.api.loadAny
|
||||||
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 eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||||
import kotlinx.android.synthetic.main.recently_read_item.*
|
import kotlinx.android.synthetic.main.recently_read_item.*
|
||||||
@ -59,13 +59,7 @@ class RecentlyReadHolder(
|
|||||||
last_read.text = Date(history.last_read).toTimestampString(adapter.dateFormat)
|
last_read.text = Date(history.last_read).toTimestampString(adapter.dateFormat)
|
||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
GlideApp.with(itemView.context).clear(cover)
|
cover.clear()
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
cover.loadAny(manga)
|
||||||
GlideApp.with(itemView.context)
|
|
||||||
.load(manga)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.centerCrop()
|
|
||||||
.into(cover)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,9 @@ package eu.kanade.tachiyomi.ui.recents
|
|||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.loadAny
|
||||||
import com.bumptech.glide.signature.ObjectKey
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaImpl
|
|
||||||
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.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
||||||
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
||||||
@ -72,10 +69,7 @@ class RecentMangaHolder(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if ((itemView.context as? Activity)?.isDestroyed != true) {
|
if ((itemView.context as? Activity)?.isDestroyed != true) {
|
||||||
GlideApp.with(itemView.context).load(item.mch.manga).diskCacheStrategy(
|
cover_thumbnail.loadAny(item.mch.manga)
|
||||||
DiskCacheStrategy.RESOURCE
|
|
||||||
).signature(ObjectKey(MangaImpl.getLastCoverFetch(item.mch.manga.id!!).toString()))
|
|
||||||
.into(cover_thumbnail)
|
|
||||||
}
|
}
|
||||||
notifyStatus(
|
notifyStatus(
|
||||||
if (adapter.isSelected(adapterPosition)) Download.CHECKED else item.status,
|
if (adapter.isSelected(adapterPosition)) Download.CHECKED else item.status,
|
||||||
|
@ -63,6 +63,16 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
|
|
||||||
onClick { clearChapterCache() }
|
onClick { clearChapterCache() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
preference {
|
||||||
|
titleRes = R.string.clean_up_cached_covers
|
||||||
|
summary = context.getString(R.string.delete_old_covers_in_library_used_, coverCache.getChapterCacheSize())
|
||||||
|
|
||||||
|
onClick {
|
||||||
|
context.toast(R.string.starting_cleanup)
|
||||||
|
coverCache.deleteOldCovers()
|
||||||
|
}
|
||||||
|
}
|
||||||
preference {
|
preference {
|
||||||
titleRes = R.string.clear_cookies
|
titleRes = R.string.clear_cookies
|
||||||
|
|
||||||
@ -101,16 +111,6 @@ class SettingsAdvancedController : SettingsController() {
|
|||||||
onClick { cleanupDownloads() }
|
onClick { cleanupDownloads() }
|
||||||
}
|
}
|
||||||
|
|
||||||
preference {
|
|
||||||
titleRes = R.string.clean_up_cached_covers
|
|
||||||
|
|
||||||
summaryRes = R.string.delete_old_covers_in_library
|
|
||||||
|
|
||||||
onClick {
|
|
||||||
context.toast(R.string.starting_cleanup)
|
|
||||||
coverCache.deleteOldCovers()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager?
|
val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager?
|
||||||
if (pm != null) preference {
|
if (pm != null) preference {
|
||||||
|
@ -3,17 +3,18 @@ package eu.kanade.tachiyomi.ui.source.browse
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.Glide
|
import coil.Coil
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.api.clear
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.request.LoadRequest
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
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.ui.library.LibraryCategoryAdapter
|
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.CoverViewTarget
|
||||||
import kotlinx.android.synthetic.main.manga_grid_item.*
|
import kotlinx.android.synthetic.main.manga_grid_item.*
|
||||||
|
import kotlinx.android.synthetic.main.manga_grid_item.cover_thumbnail
|
||||||
|
import kotlinx.android.synthetic.main.manga_grid_item.title
|
||||||
import kotlinx.android.synthetic.main.unread_download_badge.*
|
import kotlinx.android.synthetic.main.unread_download_badge.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,16 +59,12 @@ class BrowseSourceGridHolder(
|
|||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
if ((view.context as? Activity)?.isDestroyed == true) return
|
if ((view.context as? Activity)?.isDestroyed == true) return
|
||||||
if (manga.thumbnail_url == null)
|
if (manga.thumbnail_url == null) {
|
||||||
Glide.with(view.context).clear(cover_thumbnail)
|
cover_thumbnail.clear()
|
||||||
else {
|
} else {
|
||||||
GlideApp.with(view.context)
|
val id = manga.id ?: return
|
||||||
.load(manga)
|
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail, progress)).build()
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
Coil.imageLoader(view.context).execute(request)
|
||||||
.centerCrop()
|
|
||||||
.placeholder(android.R.color.transparent)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,16 @@ package eu.kanade.tachiyomi.ui.source.browse
|
|||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.Coil
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.api.clear
|
||||||
|
import coil.request.LoadRequest
|
||||||
|
import coil.transform.RoundedCornersTransformation
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
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.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.CoverViewTarget
|
||||||
import kotlinx.android.synthetic.main.manga_list_item.*
|
import kotlinx.android.synthetic.main.manga_list_item.*
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -42,17 +43,14 @@ class BrowseSourceListHolder(private val view: View, adapter: FlexibleAdapter<IF
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
if (manga.thumbnail_url.isNullOrEmpty()) {
|
// Update the cover.
|
||||||
GlideApp.with(view.context).clear(contentView)
|
if (manga.thumbnail_url == null) {
|
||||||
|
cover_thumbnail.clear()
|
||||||
} else {
|
} else {
|
||||||
GlideApp.with(view.context)
|
val id = manga.id ?: return
|
||||||
.load(manga)
|
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
.transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f)).build()
|
||||||
.dontAnimate()
|
Coil.imageLoader(view.context).execute(request)
|
||||||
.centerCrop()
|
|
||||||
.placeholder(android.R.color.transparent)
|
|
||||||
.transition(DrawableTransitionOptions.withCrossFade())
|
|
||||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -284,7 +284,7 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun confirmDeletion(manga: Manga) {
|
fun confirmDeletion(manga: Manga) {
|
||||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
coverCache.deleteFromCache(manga)
|
||||||
val downloadManager: DownloadManager = Injekt.get()
|
val downloadManager: DownloadManager = Injekt.get()
|
||||||
downloadManager.deleteManga(manga, source)
|
downloadManager.deleteManga(manga, source)
|
||||||
db.resetMangaInfo(manga).executeAsBlocking()
|
db.resetMangaInfo(manga).executeAsBlocking()
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.ui.source.global_search
|
package eu.kanade.tachiyomi.ui.source.global_search
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.Coil
|
||||||
|
import coil.api.clear
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.LoadRequest
|
||||||
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.ui.base.holder.BaseFlexibleViewHolder
|
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.CoverViewTarget
|
||||||
import kotlinx.android.synthetic.main.source_global_search_controller_card_item.*
|
import kotlinx.android.synthetic.main.source_global_search_controller_card_item.*
|
||||||
|
|
||||||
class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
||||||
@ -36,15 +38,13 @@ class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun setImage(manga: Manga) {
|
fun setImage(manga: Manga) {
|
||||||
GlideApp.with(itemView.context).clear(itemImage)
|
itemImage.clear()
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
GlideApp.with(itemView.context)
|
val request = LoadRequest.Builder(itemView.context).data(manga)
|
||||||
.load(manga)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
|
||||||
.centerCrop()
|
|
||||||
.skipMemoryCache(true)
|
|
||||||
.placeholder(android.R.color.transparent)
|
.placeholder(android.R.color.transparent)
|
||||||
.into(StateImageViewTarget(itemImage, progress))
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.target(CoverViewTarget(itemImage, progress)).build()
|
||||||
|
Coil.imageLoader(itemView.context).execute(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,32 @@
|
|||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.ImageView
|
||||||
|
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
||||||
|
import coil.target.ImageViewTarget
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
|
|
||||||
|
class CoverViewTarget(view: ImageView, val progress: View? = null) : ImageViewTarget(view) {
|
||||||
|
|
||||||
|
override fun onError(error: Drawable?) {
|
||||||
|
progress?.gone()
|
||||||
|
view.scaleType = ImageView.ScaleType.CENTER
|
||||||
|
val vector = VectorDrawableCompat.create(view.context.resources, R.drawable.ic_broken_image_grey_24dp, null)
|
||||||
|
vector?.setTint(view.context.getResourceColor(android.R.attr.textColorSecondary))
|
||||||
|
view.setImageDrawable(vector)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStart(placeholder: Drawable?) {
|
||||||
|
progress?.visible()
|
||||||
|
super.onStart(placeholder)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(result: Drawable) {
|
||||||
|
progress?.gone()
|
||||||
|
super.onSuccess(result)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,23 @@
|
|||||||
|
package eu.kanade.tachiyomi.widget
|
||||||
|
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import coil.target.ImageViewTarget
|
||||||
|
import eu.kanade.tachiyomi.util.view.gone
|
||||||
|
import eu.kanade.tachiyomi.util.view.visible
|
||||||
|
|
||||||
|
class GifViewTarget(view: ImageView, private val progressBar: View?, private val decodeErrorLayout: ViewGroup?) : ImageViewTarget(view) {
|
||||||
|
|
||||||
|
override fun onError(error: Drawable?) {
|
||||||
|
progressBar?.gone()
|
||||||
|
decodeErrorLayout?.visible()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onSuccess(result: Drawable) {
|
||||||
|
progressBar?.gone()
|
||||||
|
decodeErrorLayout?.gone()
|
||||||
|
super.onSuccess(result)
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.widget
|
|
||||||
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageView
|
|
||||||
import android.widget.ImageView.ScaleType
|
|
||||||
import androidx.vectordrawable.graphics.drawable.VectorDrawableCompat
|
|
||||||
import com.bumptech.glide.request.target.ImageViewTarget
|
|
||||||
import com.bumptech.glide.request.transition.Transition
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
import eu.kanade.tachiyomi.util.view.gone
|
|
||||||
import eu.kanade.tachiyomi.util.view.visible
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A glide target to display an image with an optional view to show while loading and a configurable
|
|
||||||
* error drawable.
|
|
||||||
*
|
|
||||||
* @param view the view where the image will be loaded
|
|
||||||
* @param progress an optional view to show when the image is loading.
|
|
||||||
* @param errorDrawableRes the error drawable resource to show.
|
|
||||||
* @param errorScaleType the scale type for the error drawable, [ScaleType.CENTER] by default.
|
|
||||||
*/
|
|
||||||
class StateImageViewTarget(
|
|
||||||
view: ImageView,
|
|
||||||
val progress: View? = null,
|
|
||||||
val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp,
|
|
||||||
val errorScaleType: ScaleType = ScaleType.CENTER
|
|
||||||
) :
|
|
||||||
|
|
||||||
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(errorDrawable: Drawable?) {
|
|
||||||
progress?.gone()
|
|
||||||
view.scaleType = errorScaleType
|
|
||||||
|
|
||||||
val vector = VectorDrawableCompat.create(view.context.resources, errorDrawableRes, null)
|
|
||||||
vector?.setTint(view.context.getResourceColor(android.R.attr.textColorSecondary))
|
|
||||||
view.setImageDrawable(vector)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
|
||||||
progress?.gone()
|
|
||||||
super.onLoadCleared(placeholder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
||||||
progress?.gone()
|
|
||||||
view.scaleType = imageScaleType
|
|
||||||
super.onResourceReady(resource, transition)
|
|
||||||
this.resource = resource
|
|
||||||
}
|
|
||||||
}
|
|
@ -34,9 +34,8 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:adjustViewBounds="true"
|
android:adjustViewBounds="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
android:background="?android:attr/colorBackground"
|
android:background="?android:attr/colorBackground"
|
||||||
android:maxHeight="250dp"
|
|
||||||
tools:background="?android:attr/colorBackground"
|
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@mipmap/ic_launcher" />
|
tools:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
@ -540,8 +540,8 @@
|
|||||||
<item quantity="other">Cleanup done. Removed %d folders</item>
|
<item quantity="other">Cleanup done. Removed %d folders</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
<string name="clean_up_cached_covers">Clean up cached covers</string>
|
<string name="clean_up_cached_covers">Clean up cached covers</string>
|
||||||
<string name="delete_old_covers_in_library">Delete old and unused cached covers of
|
<string name="delete_old_covers_in_library_used_">Delete old and unused cached covers of
|
||||||
manga in your library that has been updated</string>
|
manga in your library that has been updated.\nCurrently using: %1$s</string>
|
||||||
|
|
||||||
<!-- About section -->
|
<!-- About section -->
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user