mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-23 10:21: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 {
|
||||
setSourceCompatibility(1.8)
|
||||
setTargetCompatibility(1.8)
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
@ -141,6 +141,21 @@ dependencies {
|
||||
debugImplementation ("com.github.ChuckerTeam.Chucker:library:$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
|
||||
val retrofitVersion = "2.7.2"
|
||||
implementation("com.squareup.retrofit2:retrofit:$retrofitVersion")
|
||||
@ -184,10 +199,10 @@ dependencies {
|
||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||
|
||||
// Image library
|
||||
val glideVersion = "4.11.0"
|
||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
||||
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
|
||||
kapt("com.github.bumptech.glide:compiler:$glideVersion")
|
||||
val coilVersion = "0.10.1"
|
||||
implementation("io.coil-kt:coil:$coilVersion")
|
||||
implementation("io.coil-kt:coil-gif:$coilVersion")
|
||||
implementation("io.coil-kt:coil-svg:$coilVersion")
|
||||
|
||||
// Logging
|
||||
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 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
|
||||
-dontwarn sun.misc.**
|
||||
|
@ -8,6 +8,7 @@ import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.lifecycle.OnLifecycleEvent
|
||||
import androidx.lifecycle.ProcessLifecycleOwner
|
||||
import androidx.multidex.MultiDex
|
||||
import eu.kanade.tachiyomi.data.download.coil.CoilSetup
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
@ -18,6 +19,7 @@ import org.acra.annotation.ReportsCrashes
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.InjektScope
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import uy.kohesive.injekt.registry.default.DefaultRegistrar
|
||||
|
||||
@ -37,6 +39,7 @@ open class App : Application(), LifecycleObserver {
|
||||
Injekt = InjektScope(DefaultRegistrar())
|
||||
Injekt.importModule(AppModule(this))
|
||||
|
||||
CoilSetup(this)
|
||||
setupAcra()
|
||||
setupNotificationChannels()
|
||||
|
||||
|
@ -2,8 +2,10 @@ package eu.kanade.tachiyomi.data.cache
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.Formatter
|
||||
import coil.Coil
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.system.executeOnIO
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
@ -11,6 +13,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Cache
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
@ -20,13 +23,12 @@ import java.io.InputStream
|
||||
/**
|
||||
* Class used to create cover cache.
|
||||
* 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.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @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.
|
||||
@ -34,14 +36,18 @@ class CoverCache(private val context: Context) {
|
||||
private val cacheDir = context.getExternalFilesDir("covers")
|
||||
?: 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() {
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val db = Injekt.get<DatabaseHelper>()
|
||||
var deletedSize = 0L
|
||||
val urls = db.getLibraryMangas().executeOnIO().mapNotNull {
|
||||
it.thumbnail_url?.let { url ->
|
||||
return@mapNotNull DiskUtil.hashKeyForDisk(url)
|
||||
}
|
||||
it.thumbnail_url?.let { url -> return@mapNotNull it.key() }
|
||||
null
|
||||
}
|
||||
val files = cacheDir.listFiles()?.iterator() ?: return@launch
|
||||
@ -68,8 +74,8 @@ class CoverCache(private val context: Context) {
|
||||
* @param thumbnailUrl the thumbnail url.
|
||||
* @return cover image.
|
||||
*/
|
||||
fun getCoverFile(thumbnailUrl: String): File {
|
||||
return File(cacheDir, DiskUtil.hashKeyForDisk(thumbnailUrl))
|
||||
fun getCoverFile(manga: Manga): File {
|
||||
return File(cacheDir, manga.key())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -80,26 +86,26 @@ class CoverCache(private val context: Context) {
|
||||
* @throws IOException if there's any error.
|
||||
*/
|
||||
@Throws(IOException::class)
|
||||
fun copyToCache(thumbnailUrl: String, inputStream: InputStream) {
|
||||
fun copyToCache(manga: Manga, inputStream: InputStream) {
|
||||
// Get destination file.
|
||||
val destFile = getCoverFile(thumbnailUrl)
|
||||
val destFile = getCoverFile(manga)
|
||||
|
||||
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.
|
||||
* @return status of deletion.
|
||||
*/
|
||||
fun deleteFromCache(thumbnailUrl: String?): Boolean {
|
||||
fun deleteFromCache(manga: Manga, deleteMemoryCache: Boolean = true) {
|
||||
// Check if url is empty.
|
||||
if (thumbnailUrl.isNullOrEmpty())
|
||||
return false
|
||||
if (manga.thumbnail_url.isNullOrEmpty()) return
|
||||
|
||||
// Remove file.
|
||||
val file = getCoverFile(thumbnailUrl)
|
||||
return file.exists() && file.delete()
|
||||
// Remove file
|
||||
val file = getCoverFile(manga)
|
||||
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.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
|
||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
import kotlin.random.Random
|
||||
|
||||
interface Manga : SManga {
|
||||
|
||||
@ -52,13 +54,15 @@ interface Manga : SManga {
|
||||
fun showChapterTitle(defaultShow: Boolean): Boolean = chapter_flags and DISPLAY_MASK == DISPLAY_NUMBER
|
||||
|
||||
fun mangaType(context: Context): String {
|
||||
return context.getString(when (mangaType()) {
|
||||
return context.getString(
|
||||
when (mangaType()) {
|
||||
TYPE_WEBTOON -> R.string.webtoon
|
||||
TYPE_MANHWA -> R.string.manhwa
|
||||
TYPE_MANHUA -> R.string.manhua
|
||||
TYPE_COMIC -> R.string.comic
|
||||
else -> R.string.manga
|
||||
}).toLowerCase(Locale.getDefault())
|
||||
}
|
||||
).toLowerCase(Locale.getDefault())
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,14 +101,16 @@ interface Manga : SManga {
|
||||
return if (currentTags?.any
|
||||
{ tag ->
|
||||
tag == "long strip" || tag == "manhwa" || tag.contains("webtoon")
|
||||
} == true || isWebtoonSource(sourceName))
|
||||
} == true || isWebtoonSource(sourceName)
|
||||
)
|
||||
ReaderActivity.WEBTOON
|
||||
else if (currentTags?.any
|
||||
{ tag ->
|
||||
tag == "chinese" || tag == "manhua" ||
|
||||
tag.startsWith("english") || tag == "comic"
|
||||
} == true || (isComicSource(sourceName) && !sourceName.contains("tapastic", true)) ||
|
||||
sourceName.contains("manhua", true))
|
||||
sourceName.contains("manhua", true)
|
||||
)
|
||||
ReaderActivity.LEFT_TO_RIGHT
|
||||
else 0
|
||||
}
|
||||
@ -139,6 +145,19 @@ interface Manga : SManga {
|
||||
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
|
||||
var displayMode: Int
|
||||
get() = chapter_flags and DISPLAY_MASK
|
||||
|
@ -67,14 +67,4 @@ open class MangaImpl : Manga {
|
||||
if (::url.isInitialized) return url.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.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
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.NotificationManagerCompat
|
||||
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.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||
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.DownloadService
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
@ -56,7 +59,6 @@ import timber.log.Timber
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.ArrayList
|
||||
import java.util.Date
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
@ -530,10 +532,16 @@ class LibraryUpdateService(
|
||||
val thumbnailUrl = manga.thumbnail_url
|
||||
manga.copyFrom(networkManga)
|
||||
manga.initialized = true
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
// load new covers in background
|
||||
if (!manga.hasCustomCover()) {
|
||||
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) {
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
try {
|
||||
val icon = GlideApp.with(this@LibraryUpdateService)
|
||||
.asBitmap().load(manga).dontTransform().centerCrop().circleCrop()
|
||||
.override(256, 256).submit().get()
|
||||
setLargeIcon(icon)
|
||||
|
||||
val request = LoadRequest.Builder(this@LibraryUpdateService).data(manga)
|
||||
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
|
||||
.target { drawable -> setLargeIcon((drawable as BitmapDrawable).bitmap) }.build()
|
||||
|
||||
Coil.imageLoader(this@LibraryUpdateService).execute(request)
|
||||
} catch (e: Exception) {
|
||||
}
|
||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
||||
|
@ -47,8 +47,8 @@ class LocalSource(private val context: Context) : CatalogueSource {
|
||||
return null
|
||||
}
|
||||
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()
|
||||
input.use {
|
||||
cover.outputStream().use {
|
||||
|
@ -4,15 +4,18 @@ import android.content.res.ColorStateList
|
||||
import android.graphics.Color
|
||||
import android.view.View
|
||||
import androidx.core.content.ContextCompat
|
||||
import coil.api.clear
|
||||
import coil.api.load
|
||||
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.InstallStep
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
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.source_global_search_controller_card_item.*
|
||||
|
||||
class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||
BaseFlexibleViewHolder(view, adapter) {
|
||||
@ -35,11 +38,12 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||
itemView.context.getString(R.string.untrusted).toUpperCase()
|
||||
}
|
||||
|
||||
GlideApp.with(itemView.context).clear(edit_button)
|
||||
edit_button.clear()
|
||||
|
||||
if (extension is Extension.Available) {
|
||||
GlideApp.with(itemView.context)
|
||||
.load(extension.iconUrl)
|
||||
.into(edit_button)
|
||||
edit_button.load(extension.iconUrl) {
|
||||
target(CoverViewTarget(edit_button, progress))
|
||||
}
|
||||
} else {
|
||||
extension.getApplicationIcon(itemView.context)?.let { edit_button.setImageDrawable(it) }
|
||||
}
|
||||
|
@ -4,11 +4,11 @@ import android.app.Activity
|
||||
import android.view.Gravity
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import coil.api.clear
|
||||
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.MangaImpl
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
import kotlinx.android.synthetic.main.manga_grid_item.*
|
||||
@ -65,29 +65,26 @@ class LibraryGridHolder(
|
||||
setReadingButton(item)
|
||||
|
||||
// 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 {
|
||||
val id = item.manga.id ?: return
|
||||
if (cover_thumbnail.height == 0) {
|
||||
val oldPos = adapterPosition
|
||||
adapter.recyclerView.post {
|
||||
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
|
||||
GlideApp.with(adapter.recyclerView.context).load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
|
||||
.apply {
|
||||
if (fixedSize) centerCrop()
|
||||
else override(cover_thumbnail.maxHeight)
|
||||
cover_thumbnail.loadAny(manga) {
|
||||
if (!fixedSize) {
|
||||
precision(Precision.INEXACT)
|
||||
scale(Scale.FIT)
|
||||
}
|
||||
}
|
||||
.into(cover_thumbnail)
|
||||
}
|
||||
|
||||
private fun playButtonClicked() {
|
||||
|
@ -2,19 +2,19 @@ package eu.kanade.tachiyomi.ui.library
|
||||
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import coil.api.clear
|
||||
import coil.api.loadAny
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
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.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.updateLayoutParams
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import eu.kanade.tachiyomi.util.view.visibleIf
|
||||
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.recently_read_item.*
|
||||
import kotlinx.android.synthetic.main.unread_download_badge.*
|
||||
|
||||
/**
|
||||
@ -78,15 +78,13 @@ class LibraryListHolder(
|
||||
}
|
||||
|
||||
// Update the cover.
|
||||
if (item.manga.thumbnail_url == null) Glide.with(view.context).clear(cover_thumbnail)
|
||||
else {
|
||||
if (item.manga.thumbnail_url == null) {
|
||||
cover_thumbnail.clear()
|
||||
} else {
|
||||
val id = item.manga.id ?: return
|
||||
|
||||
GlideApp.with(view.context).load(item.manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(id).toString()))
|
||||
.centerCrop()
|
||||
.into(cover_thumbnail)
|
||||
cover_thumbnail.loadAny(item.manga) {
|
||||
transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,7 +641,7 @@ class LibraryPresenter(
|
||||
val mangaToDelete = mangas.distinctBy { it.id }
|
||||
mangaToDelete.forEach { manga ->
|
||||
db.resetMangaInfo(manga).executeOnIO()
|
||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
||||
coverCache.deleteFromCache(manga)
|
||||
val source = sourceManager.get(manga.source) as? HttpSource
|
||||
if (source != null)
|
||||
downloadManager.deleteManga(manga, source)
|
||||
|
@ -4,15 +4,12 @@ import android.app.Dialog
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import coil.api.loadAny
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
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.data.database.DatabaseHelper
|
||||
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.ui.base.controller.DialogController
|
||||
import kotlinx.android.synthetic.main.edit_manga_dialog.view.*
|
||||
@ -60,13 +57,7 @@ class EditMangaDialog : DialogController {
|
||||
}
|
||||
|
||||
fun onViewCreated(view: View) {
|
||||
GlideApp.with(view.context)
|
||||
.asDrawable()
|
||||
.load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
||||
.dontAnimate()
|
||||
.into(view.manga_cover)
|
||||
view.manga_cover.loadAny(manga)
|
||||
val isLocal = manga.source == LocalSource.ID
|
||||
|
||||
if (isLocal) {
|
||||
@ -93,7 +84,7 @@ class EditMangaDialog : DialogController {
|
||||
}
|
||||
|
||||
fun updateCover(uri: Uri) {
|
||||
GlideApp.with(dialogView!!.context).load(uri).into(dialogView!!.manga_cover)
|
||||
dialogView!!.manga_cover.loadAny(uri)
|
||||
customCoverUri = uri
|
||||
}
|
||||
|
||||
|
@ -11,11 +11,9 @@ import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Color
|
||||
import android.graphics.Rect
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
@ -24,7 +22,6 @@ import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewPropertyAnimator
|
||||
import android.view.WindowInsets
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
@ -41,16 +38,16 @@ import androidx.transition.ChangeBounds
|
||||
import androidx.transition.ChangeImageTransform
|
||||
import androidx.transition.TransitionManager
|
||||
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.checkbox.checkBoxPrompt
|
||||
import com.afollestad.materialdialogs.checkbox.isCheckPromptChecked
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.bluelinelabs.conductor.ControllerChangeHandler
|
||||
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.Snackbar
|
||||
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.Chapter
|
||||
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.model.Download
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
@ -165,13 +160,10 @@ class MangaDetailsController : BaseController,
|
||||
var toolbarIsColored = false
|
||||
private var snack: Snackbar? = null
|
||||
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
|
||||
var coverDrawable: Drawable? = null
|
||||
private var trackingBottomSheet: TrackingBottomSheet? = null
|
||||
private var startingDLChapterPos: Int? = null
|
||||
private var editMangaDialog: EditMangaDialog? = null
|
||||
var refreshTracker: Int? = null
|
||||
private var textAnim: ViewPropertyAnimator? = null
|
||||
private var scrollAnim: ViewPropertyAnimator? = null
|
||||
var chapterPopupMenu: Pair<Int, PopupMenu>? = null
|
||||
|
||||
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() {
|
||||
val view = view ?: return
|
||||
coverColor = null
|
||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga!!.id!!).toString()))
|
||||
.into(object : CustomTarget<Drawable>() {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable>?
|
||||
) {
|
||||
coverDrawable = resource
|
||||
val bitmapCover = resource as? BitmapDrawable ?: return
|
||||
Palette.from(bitmapCover.bitmap).generate {
|
||||
|
||||
val request = LoadRequest.Builder(view.context).data(manga).allowHardware(false)
|
||||
.target { drawable ->
|
||||
val bitmap = (drawable as BitmapDrawable).bitmap
|
||||
// Generate the Palette on a background thread.
|
||||
Palette.from(bitmap).generate {
|
||||
if (recycler == null || it == null) return@generate
|
||||
val colorBack = view.context.getResourceColor(
|
||||
android.R.attr.colorBackground
|
||||
)
|
||||
val backDropColor = if (!view.context.isInNightMode()) {
|
||||
it.getLightVibrantColor(colorBack)
|
||||
} else {
|
||||
it.getDarkVibrantColor(colorBack)
|
||||
}
|
||||
// this makes the color more consistent regardless of theme
|
||||
val backDropColor = ColorUtils.blendARGB(it.getVibrantColor(colorBack), colorBack, .35f)
|
||||
|
||||
coverColor = backDropColor
|
||||
getHeader()?.setBackDrop(backDropColor)
|
||||
if (toolbarIsColored) {
|
||||
@ -344,11 +330,14 @@ class MangaDetailsController : BaseController,
|
||||
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) */
|
||||
@ -404,12 +393,15 @@ class MangaDetailsController : BaseController,
|
||||
super.onActivityResumed(activity)
|
||||
presenter.isLockedFromSearch = SecureActivityDelegate.shouldBeLocked()
|
||||
presenter.headerItem.isLocked = presenter.isLockedFromSearch
|
||||
manga!!.thumbnail_url = presenter.refreshMangaFromDb().thumbnail_url
|
||||
presenter.fetchChapters(refreshTracker == null)
|
||||
if (refreshTracker != null) {
|
||||
trackingBottomSheet?.refreshItem(refreshTracker ?: 0)
|
||||
presenter.refreshTracking()
|
||||
refreshTracker = null
|
||||
}
|
||||
// reset the covers and palette cause user might have set a custom cover
|
||||
presenter.forceUpdateCovers(false)
|
||||
val isCurrentController = router?.backstack?.lastOrNull()?.controller() ==
|
||||
this
|
||||
if (isCurrentController) {
|
||||
@ -774,7 +766,7 @@ class MangaDetailsController : BaseController,
|
||||
), waitForPositiveButton = false, selection = { _, index, _ ->
|
||||
when (index) {
|
||||
0 -> changeCover()
|
||||
else -> presenter.clearCover()
|
||||
else -> presenter.clearCustomCover()
|
||||
}
|
||||
}).show()
|
||||
} else {
|
||||
@ -810,20 +802,16 @@ class MangaDetailsController : BaseController,
|
||||
//endregion
|
||||
|
||||
override fun prepareToShareManga() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && coverDrawable != null)
|
||||
GlideApp.with(activity!!).asBitmap().load(presenter.manga).into(object :
|
||||
CustomTarget<Bitmap>() {
|
||||
override fun onResourceReady(resource: Bitmap, transition: Transition<in Bitmap>?) {
|
||||
presenter.shareManga(resource)
|
||||
}
|
||||
|
||||
override fun onLoadCleared(placeholder: Drawable?) {}
|
||||
|
||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val request = LoadRequest.Builder(activity!!).data(manga).target(onError = {
|
||||
shareManga()
|
||||
}, onSuccess = {
|
||||
presenter.shareManga((it as BitmapDrawable).bitmap)
|
||||
}).build()
|
||||
Coil.imageLoader(activity!!).execute(request)
|
||||
} else {
|
||||
shareManga()
|
||||
}
|
||||
})
|
||||
else shareManga()
|
||||
}
|
||||
|
||||
fun shareManga(cover: File? = null) {
|
||||
@ -1257,7 +1245,6 @@ class MangaDetailsController : BaseController,
|
||||
if (editMangaDialog != null) editMangaDialog?.updateCover(uri)
|
||||
else {
|
||||
presenter.editCoverWithStream(uri)
|
||||
setPaletteColor()
|
||||
}
|
||||
} catch (error: IOException) {
|
||||
activity.toast(R.string.failed_to_update_cover)
|
||||
@ -1271,10 +1258,9 @@ class MangaDetailsController : BaseController,
|
||||
currentAnimator?.cancel()
|
||||
|
||||
// Load the high-resolution "zoomed-in" image.
|
||||
manga_cover_full?.loadAny(manga)
|
||||
val expandedImageView = manga_cover_full ?: return
|
||||
val fullBackdrop = full_backdrop
|
||||
val image = coverDrawable ?: return
|
||||
expandedImageView.setImageDrawable(image)
|
||||
|
||||
// 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
|
||||
|
@ -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.Manga
|
||||
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.download.DownloadManager
|
||||
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.fetchMangaDetailsAsync
|
||||
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.track.TrackItem
|
||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||
@ -368,6 +368,12 @@ class MangaDetailsPresenter(
|
||||
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) */
|
||||
fun refreshAll() {
|
||||
if (controller.isNotOnline() && manga.source != LocalSource.ID) return
|
||||
@ -398,13 +404,16 @@ class MangaDetailsPresenter(
|
||||
if (networkManga != null) {
|
||||
manga.copyFrom(networkManga)
|
||||
manga.initialized = true
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
if (thumbnailUrl != networkManga.thumbnail_url && !manga.hasCustomCover()) {
|
||||
coverCache.deleteFromCache(thumbnailUrl)
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
withContext(Dispatchers.Main) { controller.setPaletteColor() }
|
||||
|
||||
if (shouldUpdateCover(thumbnailUrl, networkManga)) {
|
||||
coverCache.deleteFromCache(manga, false)
|
||||
manga.thumbnail_url = networkManga.thumbnail_url
|
||||
withContext(Dispatchers.Main) {
|
||||
forceUpdateCovers()
|
||||
}
|
||||
}
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
}
|
||||
val finChapters = chapters.await()
|
||||
if (finChapters.isNotEmpty()) {
|
||||
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.
|
||||
*/
|
||||
@ -588,7 +610,9 @@ class MangaDetailsPresenter(
|
||||
manga.favorite = !manga.favorite
|
||||
|
||||
when (manga.favorite) {
|
||||
true -> manga.date_added = Date().time
|
||||
true -> {
|
||||
manga.date_added = Date().time
|
||||
}
|
||||
false -> manga.date_added = 0
|
||||
}
|
||||
|
||||
@ -639,7 +663,7 @@ class MangaDetailsPresenter(
|
||||
}
|
||||
|
||||
fun confirmDeletion() {
|
||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
||||
coverCache.deleteFromCache(manga)
|
||||
db.resetMangaInfo(manga).executeAsBlocking()
|
||||
downloadManager.deleteManga(manga, source)
|
||||
asyncUpdateMangaAndChapters(true)
|
||||
@ -707,36 +731,41 @@ class MangaDetailsPresenter(
|
||||
db.updateMangaInfo(manga).executeAsBlocking()
|
||||
}
|
||||
if (uri != null) editCoverWithStream(uri)
|
||||
controller.updateHeader()
|
||||
}
|
||||
|
||||
fun clearCover() {
|
||||
/**
|
||||
* Remvoe custom cover
|
||||
*/
|
||||
fun clearCustomCover() {
|
||||
if (manga.hasCustomCover()) {
|
||||
coverCache.deleteFromCache(manga.thumbnail_url!!)
|
||||
manga.thumbnail_url = manga.thumbnail_url?.removePrefix("Custom-")
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.removeCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
controller.updateHeader()
|
||||
controller.setPaletteColor()
|
||||
forceUpdateCovers()
|
||||
}
|
||||
}
|
||||
|
||||
fun forceUpdateCovers(deleteCache: Boolean = true) {
|
||||
if (deleteCache) coverCache.deleteFromCache(manga)
|
||||
controller.setPaletteColor()
|
||||
controller.resetCovers()
|
||||
}
|
||||
|
||||
fun editCoverWithStream(uri: Uri): Boolean {
|
||||
val inputStream =
|
||||
downloadManager.context.contentResolver.openInputStream(uri) ?: return false
|
||||
if (manga.source == LocalSource.ID) {
|
||||
LocalSource.updateCover(downloadManager.context, manga, inputStream)
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
forceUpdateCovers()
|
||||
return true
|
||||
}
|
||||
|
||||
if (manga.favorite) {
|
||||
if (!manga.hasCustomCover()) {
|
||||
manga.thumbnail_url = "Custom-${manga.thumbnail_url ?: manga.id!!}"
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.setCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
}
|
||||
coverCache.copyToCache(manga.thumbnail_url!!, inputStream)
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
coverCache.copyToCache(manga, inputStream)
|
||||
forceUpdateCovers(false)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
@ -9,14 +9,11 @@ import android.view.ViewGroup
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import coil.api.clear
|
||||
import coil.api.loadAny
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.model.SManga
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
@ -287,11 +284,15 @@ class MangaHeaderHolder(
|
||||
val presenter = adapter.delegate.mangaPresenter()
|
||||
val tracked = presenter.isTracked()
|
||||
with(track_button) {
|
||||
text = itemView.context.getString(if (tracked) R.string.tracked
|
||||
else R.string.tracking)
|
||||
text = itemView.context.getString(
|
||||
if (tracked) R.string.tracked
|
||||
else R.string.tracking
|
||||
)
|
||||
|
||||
icon = ContextCompat.getDrawable(itemView.context, if (tracked) R.drawable
|
||||
.ic_check_white_24dp else R.drawable.ic_sync_black_24dp)
|
||||
icon = ContextCompat.getDrawable(
|
||||
itemView.context, if (tracked) R.drawable
|
||||
.ic_check_white_24dp else R.drawable.ic_sync_black_24dp
|
||||
)
|
||||
checked(tracked)
|
||||
}
|
||||
}
|
||||
@ -307,22 +308,18 @@ class MangaHeaderHolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateCover(manga: Manga) {
|
||||
if (!isCached(manga)) return
|
||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString()))
|
||||
.into(manga_cover)
|
||||
GlideApp.with(view.context).load(manga).diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.signature(ObjectKey(MangaImpl.getLastCoverFetch(manga.id!!).toString())).centerCrop()
|
||||
.transition(DrawableTransitionOptions.withCrossFade()).into(backdrop)
|
||||
fun updateCover(manga: Manga, forceUpdate: Boolean = false) {
|
||||
if (!isCached(manga) && !forceUpdate) return
|
||||
manga_cover.clear()
|
||||
backdrop.clear()
|
||||
manga_cover.loadAny(manga)
|
||||
backdrop.loadAny(manga)
|
||||
}
|
||||
|
||||
private fun isCached(manga: Manga): Boolean {
|
||||
if (manga.source == LocalSource.ID) return true
|
||||
val coverCache = adapter.delegate.mangaPresenter().coverCache
|
||||
manga.thumbnail_url?.let {
|
||||
return if (manga.favorite) coverCache.getCoverFile(it).exists()
|
||||
else true
|
||||
return adapter.delegate.mangaPresenter().coverCache.getCoverFile(manga).exists()
|
||||
}
|
||||
return manga.initialized
|
||||
}
|
||||
|
@ -4,10 +4,9 @@ import android.content.Context
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import coil.api.clear
|
||||
import coil.api.load
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.inflate
|
||||
@ -46,12 +45,9 @@ class TrackSearchAdapter(context: Context) :
|
||||
fun onSetValues(track: TrackSearch) {
|
||||
view.track_search_title.text = track.title
|
||||
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()) {
|
||||
GlideApp.with(view.context).load(track.cover_url)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerCrop()
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(view.track_search_cover)
|
||||
view.track_search_cover.load(track.cover_url)
|
||||
}
|
||||
|
||||
if (track.publishing_status.isNullOrBlank()) {
|
||||
|
@ -2,10 +2,10 @@ package eu.kanade.tachiyomi.ui.migration
|
||||
|
||||
import android.view.View
|
||||
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.items.IFlexible
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import kotlinx.android.synthetic.main.manga_list_item.*
|
||||
|
||||
@ -20,9 +20,7 @@ class MangaHolder(
|
||||
subtitle.text = item.manga.author?.trim()
|
||||
|
||||
// Update the cover.
|
||||
GlideApp.with(itemView.context).clear(cover_thumbnail)
|
||||
GlideApp.with(itemView.context).load(item.manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).centerCrop().dontAnimate()
|
||||
.into(cover_thumbnail)
|
||||
cover_thumbnail.clear()
|
||||
cover_thumbnail.loadAny(item.manga)
|
||||
}
|
||||
}
|
||||
|
@ -3,12 +3,11 @@ package eu.kanade.tachiyomi.ui.migration.manga.process
|
||||
import android.view.View
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import coil.Coil
|
||||
import coil.request.LoadRequest
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
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.visible
|
||||
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.migration_process_item.*
|
||||
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) {
|
||||
(layoutParams as ConstraintLayout.LayoutParams).verticalBias = 1f
|
||||
progress.gone()
|
||||
GlideApp.with(view.context.applicationContext).load(manga).apply {
|
||||
diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
if (isTo) {
|
||||
transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
||||
} else
|
||||
into(cover_thumbnail)
|
||||
}
|
||||
|
||||
val request = LoadRequest.Builder(view.context).data(manga)
|
||||
.target(CoverViewTarget(cover_thumbnail, progress)).build()
|
||||
Coil.imageLoader(view.context).execute(request)
|
||||
|
||||
compact_title.visible()
|
||||
gradient.visible()
|
||||
@ -164,11 +159,15 @@ class MigrationProcessHolder(
|
||||
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
||||
|
||||
if (latestChapter > 0f) {
|
||||
subtitle.text = context.getString(R.string.latest_,
|
||||
DecimalFormat("#.#").format(latestChapter))
|
||||
subtitle.text = context.getString(
|
||||
R.string.latest_,
|
||||
DecimalFormat("#.#").format(latestChapter)
|
||||
)
|
||||
} else {
|
||||
subtitle.text = context.getString(R.string.latest_,
|
||||
context.getString(R.string.unknown))
|
||||
subtitle.text = context.getString(
|
||||
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.History
|
||||
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.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
@ -536,18 +535,17 @@ class ReaderPresenter(
|
||||
.fromCallable {
|
||||
if (manga.source == LocalSource.ID) {
|
||||
val context = Injekt.get<Application>()
|
||||
coverCache.deleteFromCache(manga)
|
||||
LocalSource.updateCover(context, manga, stream())
|
||||
R.string.cover_updated
|
||||
SetAsCoverResult.Success
|
||||
} 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.hasCustomCover()) {
|
||||
manga.thumbnail_url = "Custom-${manga.thumbnail_url ?: manga.id!!}"
|
||||
coverCache.deleteFromCache(manga)
|
||||
manga.setCustomThumbnailUrl()
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
}
|
||||
coverCache.copyToCache(manga.thumbnail_url!!, stream())
|
||||
MangaImpl.setLastCoverFetch(manga.id!!, Date().time)
|
||||
coverCache.copyToCache(manga, stream())
|
||||
SetAsCoverResult.Success
|
||||
} else {
|
||||
SetAsCoverResult.AddToLibraryFirst
|
||||
|
@ -2,11 +2,13 @@ package eu.kanade.tachiyomi.ui.reader
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.drawable.BitmapDrawable
|
||||
import androidx.core.app.NotificationCompat
|
||||
import 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.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||
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.
|
||||
*/
|
||||
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) {
|
||||
showCompleteNotification(file, bitmap)
|
||||
} else {
|
||||
onError(null)
|
||||
}
|
||||
}).build()
|
||||
Coil.imageLoader(context).execute(request)
|
||||
}
|
||||
|
||||
private fun showCompleteNotification(file: File, image: Bitmap) {
|
||||
|
@ -18,19 +18,12 @@ import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.NoTransition
|
||||
import coil.api.loadAny
|
||||
import coil.request.CachePolicy
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import com.github.chrisbanes.photoview.PhotoView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
@ -43,6 +36,7 @@ import eu.kanade.tachiyomi.util.system.isInNightMode
|
||||
import eu.kanade.tachiyomi.util.system.launchUI
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import eu.kanade.tachiyomi.widget.GifViewTarget
|
||||
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||
import kotlinx.coroutines.Dispatchers.Default
|
||||
import kotlinx.coroutines.withContext
|
||||
@ -487,37 +481,11 @@ class PagerPageHolder(
|
||||
* Extension method to set a [stream] into this ImageView.
|
||||
*/
|
||||
private fun ImageView.setImage(stream: InputStream) {
|
||||
GlideApp.with(this)
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
this.loadAny(stream.readBytes()) {
|
||||
memoryCachePolicy(CachePolicy.DISABLED)
|
||||
diskCachePolicy(CachePolicy.DISABLED)
|
||||
target(GifViewTarget(this@setImage, progressBar, decodeErrorLayout))
|
||||
}
|
||||
|
||||
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 {
|
||||
|
@ -4,7 +4,6 @@ import android.annotation.SuppressLint
|
||||
import android.content.Intent
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.net.Uri
|
||||
import android.view.Gravity
|
||||
import android.view.ViewGroup
|
||||
@ -16,18 +15,12 @@ import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.widget.AppCompatButton
|
||||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import com.bumptech.glide.load.DataSource
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.engine.GlideException
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
||||
import com.bumptech.glide.request.RequestListener
|
||||
import com.bumptech.glide.request.target.Target
|
||||
import com.bumptech.glide.request.transition.NoTransition
|
||||
import coil.api.clear
|
||||
import coil.api.loadAny
|
||||
import coil.request.CachePolicy
|
||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||
@ -36,6 +29,7 @@ import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.util.view.gone
|
||||
import eu.kanade.tachiyomi.util.view.updatePaddingRelative
|
||||
import eu.kanade.tachiyomi.util.view.visible
|
||||
import eu.kanade.tachiyomi.widget.GifViewTarget
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -149,7 +143,7 @@ class WebtoonPageHolder(
|
||||
removeDecodeErrorLayout()
|
||||
subsamplingImageView?.recycle()
|
||||
subsamplingImageView?.gone()
|
||||
imageView?.let { GlideApp.with(frame).clear(it) }
|
||||
imageView?.clear()
|
||||
imageView?.gone()
|
||||
progressBar.setProgress(0)
|
||||
}
|
||||
@ -491,36 +485,10 @@ class WebtoonPageHolder(
|
||||
* Extension method to set a [stream] into this ImageView.
|
||||
*/
|
||||
private fun ImageView.setImage(stream: InputStream) {
|
||||
GlideApp.with(this)
|
||||
.load(stream)
|
||||
.skipMemoryCache(true)
|
||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
||||
.listener(object : RequestListener<Drawable> {
|
||||
override fun onLoadFailed(
|
||||
e: GlideException?,
|
||||
model: Any?,
|
||||
target: Target<Drawable>?,
|
||||
isFirstResource: Boolean
|
||||
): Boolean {
|
||||
onImageDecodeError()
|
||||
return false
|
||||
this.loadAny(stream.readBytes()) {
|
||||
memoryCachePolicy(CachePolicy.DISABLED)
|
||||
diskCachePolicy(CachePolicy.DISABLED)
|
||||
target(GifViewTarget(this@setImage, progressBar, decodeErrorLayout))
|
||||
}
|
||||
|
||||
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.view.View
|
||||
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.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||
@ -71,10 +72,9 @@ class RecentChapterHolder(private val view: View, private val adapter: RecentCha
|
||||
|
||||
// Set cover
|
||||
if ((view.context as? Activity)?.isDestroyed != true) {
|
||||
GlideApp.with(itemView.context).clear(manga_cover)
|
||||
if (!item.manga.thumbnail_url.isNullOrEmpty()) {
|
||||
GlideApp.with(itemView.context).load(item.manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE).circleCrop().into(manga_cover)
|
||||
manga_cover.clear()
|
||||
manga_cover.loadAny(item.manga) {
|
||||
transformations(CircleCropTransformation())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
package eu.kanade.tachiyomi.ui.recently_read
|
||||
|
||||
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.data.database.models.MangaChapterHistory
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||
import kotlinx.android.synthetic.main.recently_read_item.*
|
||||
@ -59,13 +59,7 @@ class RecentlyReadHolder(
|
||||
last_read.text = Date(history.last_read).toTimestampString(adapter.dateFormat)
|
||||
|
||||
// Set cover
|
||||
GlideApp.with(itemView.context).clear(cover)
|
||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||
GlideApp.with(itemView.context)
|
||||
.load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
||||
.centerCrop()
|
||||
.into(cover)
|
||||
}
|
||||
cover.clear()
|
||||
cover.loadAny(manga)
|
||||
}
|
||||
}
|
||||
|
@ -2,12 +2,9 @@ package eu.kanade.tachiyomi.ui.recents
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.signature.ObjectKey
|
||||
import coil.api.loadAny
|
||||
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.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.source.LocalSource
|
||||
import eu.kanade.tachiyomi.ui.manga.chapter.BaseChapterHolder
|
||||
import eu.kanade.tachiyomi.util.chapter.ChapterUtil
|
||||
@ -72,10 +69,7 @@ class RecentMangaHolder(
|
||||
)
|
||||
}
|
||||
if ((itemView.context as? Activity)?.isDestroyed != true) {
|
||||
GlideApp.with(itemView.context).load(item.mch.manga).diskCacheStrategy(
|
||||
DiskCacheStrategy.RESOURCE
|
||||
).signature(ObjectKey(MangaImpl.getLastCoverFetch(item.mch.manga.id!!).toString()))
|
||||
.into(cover_thumbnail)
|
||||
cover_thumbnail.loadAny(item.mch.manga)
|
||||
}
|
||||
notifyStatus(
|
||||
if (adapter.isSelected(adapterPosition)) Download.CHECKED else item.status,
|
||||
|
@ -63,6 +63,16 @@ class SettingsAdvancedController : SettingsController() {
|
||||
|
||||
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 {
|
||||
titleRes = R.string.clear_cookies
|
||||
|
||||
@ -101,16 +111,6 @@ class SettingsAdvancedController : SettingsController() {
|
||||
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) {
|
||||
val pm = context.getSystemService(Context.POWER_SERVICE) as? PowerManager?
|
||||
if (pm != null) preference {
|
||||
|
@ -3,17 +3,18 @@ package eu.kanade.tachiyomi.ui.source.browse
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.Glide
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import coil.Coil
|
||||
import coil.api.clear
|
||||
import coil.request.LoadRequest
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
|
||||
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.cover_thumbnail
|
||||
import kotlinx.android.synthetic.main.manga_grid_item.title
|
||||
import kotlinx.android.synthetic.main.unread_download_badge.*
|
||||
|
||||
/**
|
||||
@ -58,16 +59,12 @@ class BrowseSourceGridHolder(
|
||||
|
||||
override fun setImage(manga: Manga) {
|
||||
if ((view.context as? Activity)?.isDestroyed == true) return
|
||||
if (manga.thumbnail_url == null)
|
||||
Glide.with(view.context).clear(cover_thumbnail)
|
||||
else {
|
||||
GlideApp.with(view.context)
|
||||
.load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.centerCrop()
|
||||
.placeholder(android.R.color.transparent)
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
||||
if (manga.thumbnail_url == null) {
|
||||
cover_thumbnail.clear()
|
||||
} else {
|
||||
val id = manga.id ?: return
|
||||
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail, progress)).build()
|
||||
Coil.imageLoader(view.context).execute(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,15 +2,16 @@ package eu.kanade.tachiyomi.ui.source.browse
|
||||
|
||||
import android.view.View
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
||||
import coil.Coil
|
||||
import coil.api.clear
|
||||
import coil.request.LoadRequest
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||
import eu.davidea.flexibleadapter.items.IFlexible
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
||||
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.*
|
||||
|
||||
/**
|
||||
@ -42,17 +43,14 @@ class BrowseSourceListHolder(private val view: View, adapter: FlexibleAdapter<IF
|
||||
}
|
||||
|
||||
override fun setImage(manga: Manga) {
|
||||
if (manga.thumbnail_url.isNullOrEmpty()) {
|
||||
GlideApp.with(view.context).clear(contentView)
|
||||
// Update the cover.
|
||||
if (manga.thumbnail_url == null) {
|
||||
cover_thumbnail.clear()
|
||||
} else {
|
||||
GlideApp.with(view.context)
|
||||
.load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.dontAnimate()
|
||||
.centerCrop()
|
||||
.placeholder(android.R.color.transparent)
|
||||
.transition(DrawableTransitionOptions.withCrossFade())
|
||||
.into(StateImageViewTarget(cover_thumbnail, progress))
|
||||
val id = manga.id ?: return
|
||||
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail))
|
||||
.transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f)).build()
|
||||
Coil.imageLoader(view.context).execute(request)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ open class BrowseSourcePresenter(
|
||||
}
|
||||
|
||||
fun confirmDeletion(manga: Manga) {
|
||||
coverCache.deleteFromCache(manga.thumbnail_url)
|
||||
coverCache.deleteFromCache(manga)
|
||||
val downloadManager: DownloadManager = Injekt.get()
|
||||
downloadManager.deleteManga(manga, source)
|
||||
db.resetMangaInfo(manga).executeAsBlocking()
|
||||
|
@ -1,12 +1,14 @@
|
||||
package eu.kanade.tachiyomi.ui.source.global_search
|
||||
|
||||
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.glide.GlideApp
|
||||
import eu.kanade.tachiyomi.ui.base.holder.BaseFlexibleViewHolder
|
||||
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.*
|
||||
|
||||
class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
||||
@ -36,15 +38,13 @@ class GlobalSearchMangaHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
||||
}
|
||||
|
||||
fun setImage(manga: Manga) {
|
||||
GlideApp.with(itemView.context).clear(itemImage)
|
||||
itemImage.clear()
|
||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||
GlideApp.with(itemView.context)
|
||||
.load(manga)
|
||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
||||
.centerCrop()
|
||||
.skipMemoryCache(true)
|
||||
val request = LoadRequest.Builder(itemView.context).data(manga)
|
||||
.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_height="wrap_content"
|
||||
android:adjustViewBounds="true"
|
||||
android:scaleType="centerCrop"
|
||||
android:background="?android:attr/colorBackground"
|
||||
android:maxHeight="250dp"
|
||||
tools:background="?android:attr/colorBackground"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
|
@ -540,8 +540,8 @@
|
||||
<item quantity="other">Cleanup done. Removed %d folders</item>
|
||||
</plurals>
|
||||
<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
|
||||
manga in your library that has been updated</string>
|
||||
<string name="delete_old_covers_in_library_used_">Delete old and unused cached covers of
|
||||
manga in your library that has been updated.\nCurrently using: %1$s</string>
|
||||
|
||||
<!-- About section -->
|
||||
<string name="version">Version</string>
|
||||
|
Loading…
Reference in New Issue
Block a user