mirror of
https://github.com/aniyomiorg/aniyomi.git
synced 2024-11-27 16:26:29 +03:00
Use Coil (#4870)
* Use Coil * Remove coil-transformations lib * Add MangaCoverFetcher * Remove Glide * MangaCoverFetcher: Allow skipping custom cover usage * Adjust coil caching policy for some non-library items * Allow coil to use RGB565 only on low ram devices * Fix image loading progress view not showing a * Increase coil crossfade duration Same as default glide duration * Add back request clearing
This commit is contained in:
parent
7d23fd8ef5
commit
93e6136795
39 changed files with 492 additions and 797 deletions
|
@ -198,10 +198,9 @@ dependencies {
|
||||||
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
implementation("com.github.inorichi.injekt:injekt-core:65b0440")
|
||||||
|
|
||||||
// Image library
|
// Image library
|
||||||
val glideVersion = "4.12.0"
|
val coilVersion = "1.2.0"
|
||||||
implementation("com.github.bumptech.glide:glide:$glideVersion")
|
implementation("io.coil-kt:coil:$coilVersion")
|
||||||
implementation("com.github.bumptech.glide:okhttp3-integration:$glideVersion")
|
implementation("io.coil-kt:coil-gif:$coilVersion")
|
||||||
kapt("com.github.bumptech.glide:compiler:$glideVersion")
|
|
||||||
|
|
||||||
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:547d9c0")
|
implementation("com.github.tachiyomiorg:subsampling-scale-image-view:547d9c0")
|
||||||
|
|
||||||
|
@ -278,7 +277,8 @@ tasks {
|
||||||
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
|
"-Xuse-experimental=kotlinx.coroutines.FlowPreview",
|
||||||
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
"-Xuse-experimental=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
||||||
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
|
"-Xuse-experimental=kotlinx.coroutines.InternalCoroutinesApi",
|
||||||
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi"
|
"-Xuse-experimental=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
"-Xuse-experimental=coil.annotation.ExperimentalCoilApi",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,25 @@
|
||||||
package eu.kanade.tachiyomi
|
package eu.kanade.tachiyomi
|
||||||
|
|
||||||
|
import android.app.ActivityManager
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import androidx.core.content.getSystemService
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.LifecycleObserver
|
import androidx.lifecycle.LifecycleObserver
|
||||||
import androidx.lifecycle.OnLifecycleEvent
|
import androidx.lifecycle.OnLifecycleEvent
|
||||||
import androidx.lifecycle.ProcessLifecycleOwner
|
import androidx.lifecycle.ProcessLifecycleOwner
|
||||||
import androidx.multidex.MultiDex
|
import androidx.multidex.MultiDex
|
||||||
|
import coil.ImageLoader
|
||||||
|
import coil.ImageLoaderFactory
|
||||||
|
import coil.decode.GifDecoder
|
||||||
|
import coil.decode.ImageDecoderDecoder
|
||||||
|
import eu.kanade.tachiyomi.data.coil.ByteBufferFetcher
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
import eu.kanade.tachiyomi.ui.security.SecureActivityDelegate
|
||||||
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
import eu.kanade.tachiyomi.util.system.LocaleHelper
|
||||||
import org.acra.ACRA
|
import org.acra.ACRA
|
||||||
|
@ -20,6 +29,7 @@ import org.acra.sender.HttpSender
|
||||||
import org.conscrypt.Conscrypt
|
import org.conscrypt.Conscrypt
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.security.Security
|
import java.security.Security
|
||||||
|
|
||||||
|
@ -31,7 +41,7 @@ import java.security.Security
|
||||||
uri = BuildConfig.ACRA_URI,
|
uri = BuildConfig.ACRA_URI,
|
||||||
httpMethod = HttpSender.Method.PUT
|
httpMethod = HttpSender.Method.PUT
|
||||||
)
|
)
|
||||||
open class App : Application(), LifecycleObserver {
|
open class App : Application(), LifecycleObserver, ImageLoaderFactory {
|
||||||
|
|
||||||
private val preferences: PreferencesHelper by injectLazy()
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
@ -67,6 +77,23 @@ open class App : Application(), LifecycleObserver {
|
||||||
LocaleHelper.updateConfiguration(this, newConfig, true)
|
LocaleHelper.updateConfiguration(this, newConfig, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun newImageLoader(): ImageLoader {
|
||||||
|
return ImageLoader.Builder(this).apply {
|
||||||
|
componentRegistry {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
add(ImageDecoderDecoder(this@App))
|
||||||
|
} else {
|
||||||
|
add(GifDecoder())
|
||||||
|
}
|
||||||
|
add(ByteBufferFetcher())
|
||||||
|
add(MangaCoverFetcher())
|
||||||
|
}
|
||||||
|
okHttpClient(Injekt.get<NetworkHelper>().coilClient)
|
||||||
|
crossfade(300)
|
||||||
|
allowRgb565(getSystemService<ActivityManager>()!!.isLowRamDevice)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
|
||||||
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
fun onAppBackgrounded() {
|
fun onAppBackgrounded() {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.data.cache
|
package eu.kanade.tachiyomi.data.cache
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import coil.imageLoader
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -99,6 +100,13 @@ class CoverCache(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear coil's memory cache.
|
||||||
|
*/
|
||||||
|
fun clearMemoryCache() {
|
||||||
|
context.imageLoader.memoryCache.clear()
|
||||||
|
}
|
||||||
|
|
||||||
private fun getCacheDir(dir: String): File {
|
private fun getCacheDir(dir: String): File {
|
||||||
return context.getExternalFilesDir(dir)
|
return context.getExternalFilesDir(dir)
|
||||||
?: File(context.filesDir, dir).also { it.mkdirs() }
|
?: File(context.filesDir, dir).also { it.mkdirs() }
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
package eu.kanade.tachiyomi.data.coil
|
||||||
|
|
||||||
|
import coil.bitmap.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
|
||||||
|
import java.nio.ByteBuffer
|
||||||
|
|
||||||
|
class ByteBufferFetcher : Fetcher<ByteBuffer> {
|
||||||
|
override suspend fun fetch(pool: BitmapPool, data: ByteBuffer, size: Size, options: Options): FetchResult {
|
||||||
|
return SourceResult(
|
||||||
|
source = ByteArrayInputStream(data.array()).source().buffer(),
|
||||||
|
mimeType = null,
|
||||||
|
dataSource = DataSource.MEMORY
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun key(data: ByteBuffer): String? = null
|
||||||
|
}
|
|
@ -0,0 +1,172 @@
|
||||||
|
package eu.kanade.tachiyomi.data.coil
|
||||||
|
|
||||||
|
import coil.bitmap.BitmapPool
|
||||||
|
import coil.decode.DataSource
|
||||||
|
import coil.decode.Options
|
||||||
|
import coil.fetch.FetchResult
|
||||||
|
import coil.fetch.Fetcher
|
||||||
|
import coil.fetch.SourceResult
|
||||||
|
import coil.network.HttpException
|
||||||
|
import coil.request.get
|
||||||
|
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.network.await
|
||||||
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import okhttp3.CacheControl
|
||||||
|
import okhttp3.Call
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import okhttp3.ResponseBody
|
||||||
|
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
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coil component that fetches [Manga] cover while using the cached file in disk when available.
|
||||||
|
*
|
||||||
|
* Available request parameter:
|
||||||
|
* - [USE_CUSTOM_COVER]: Use custom cover if set by user, default is true
|
||||||
|
*/
|
||||||
|
class MangaCoverFetcher : Fetcher<Manga> {
|
||||||
|
private val coverCache: CoverCache by injectLazy()
|
||||||
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
|
private val defaultClient = Injekt.get<NetworkHelper>().coilClient
|
||||||
|
|
||||||
|
override fun key(data: Manga): String? {
|
||||||
|
if (data.thumbnail_url.isNullOrBlank()) return null
|
||||||
|
return data.thumbnail_url!!
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult {
|
||||||
|
// Use custom cover if exists
|
||||||
|
val useCustomCover = options.parameters[USE_CUSTOM_COVER] as? Boolean ?: true
|
||||||
|
val customCoverFile = coverCache.getCustomCoverFile(data)
|
||||||
|
if (useCustomCover && customCoverFile.exists()) {
|
||||||
|
return fileLoader(customCoverFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
val cover = data.thumbnail_url
|
||||||
|
return when (getResourceType(cover)) {
|
||||||
|
Type.URL -> httpLoader(data, options)
|
||||||
|
Type.File -> fileLoader(data)
|
||||||
|
null -> error("Invalid image")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult {
|
||||||
|
val coverFile = coverCache.getCoverFile(manga) ?: error("No cover specified")
|
||||||
|
|
||||||
|
// Use previously cached cover if exist
|
||||||
|
if (coverFile.exists() && options.diskCachePolicy.readEnabled) {
|
||||||
|
if (!manga.favorite) {
|
||||||
|
coverFile.setLastModified(Date().time)
|
||||||
|
}
|
||||||
|
return fileLoader(coverFile)
|
||||||
|
}
|
||||||
|
|
||||||
|
val (response, body) = awaitGetCall(manga, options)
|
||||||
|
if (!response.isSuccessful) {
|
||||||
|
body.close()
|
||||||
|
throw HttpException(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write to disk for future use
|
||||||
|
if (options.diskCachePolicy.writeEnabled) {
|
||||||
|
response.peekBody(Long.MAX_VALUE).source().use { input ->
|
||||||
|
val tmpFile = File(coverFile.absolutePath + "_tmp")
|
||||||
|
tmpFile.parentFile?.mkdirs()
|
||||||
|
tmpFile.sink().buffer().use { output ->
|
||||||
|
output.writeAll(input)
|
||||||
|
}
|
||||||
|
if (coverFile.exists()) {
|
||||||
|
coverFile.delete()
|
||||||
|
}
|
||||||
|
tmpFile.renameTo(coverFile)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return SourceResult(
|
||||||
|
source = body.source(),
|
||||||
|
mimeType = "image/*",
|
||||||
|
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun awaitGetCall(manga: Manga, options: Options): Pair<Response, ResponseBody> {
|
||||||
|
val call = getCall(manga, options)
|
||||||
|
val response = call.await()
|
||||||
|
return response to checkNotNull(response.body) { "Null response source" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCall(manga: Manga, options: Options): Call {
|
||||||
|
val source = sourceManager.get(manga.source) as? HttpSource
|
||||||
|
val client = source?.client ?: defaultClient
|
||||||
|
|
||||||
|
val newClient = client.newBuilder().build()
|
||||||
|
|
||||||
|
val request = Request.Builder().url(manga.thumbnail_url!!).also {
|
||||||
|
if (source != null) {
|
||||||
|
it.headers(source.headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val networkRead = options.networkCachePolicy.readEnabled
|
||||||
|
val diskRead = options.diskCachePolicy.readEnabled
|
||||||
|
when {
|
||||||
|
!networkRead && diskRead -> {
|
||||||
|
it.cacheControl(CacheControl.FORCE_CACHE)
|
||||||
|
}
|
||||||
|
networkRead && !diskRead -> if (options.diskCachePolicy.writeEnabled) {
|
||||||
|
it.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
|
} else {
|
||||||
|
it.cacheControl(CACHE_CONTROL_FORCE_NETWORK_NO_CACHE)
|
||||||
|
}
|
||||||
|
!networkRead && !diskRead -> {
|
||||||
|
// This causes the request to fail with a 504 Unsatisfiable Request.
|
||||||
|
it.cacheControl(CACHE_CONTROL_NO_NETWORK_NO_CACHE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
return newClient.newCall(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 getResourceType(cover: String?): Type? {
|
||||||
|
return when {
|
||||||
|
cover.isNullOrEmpty() -> null
|
||||||
|
cover.startsWith("http") || cover.startsWith("Custom-", true) -> Type.URL
|
||||||
|
cover.startsWith("/") || cover.startsWith("file://") -> Type.File
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private enum class Type {
|
||||||
|
File, URL
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val USE_CUSTOM_COVER = "use_custom_cover"
|
||||||
|
|
||||||
|
private val CACHE_CONTROL_FORCE_NETWORK_NO_CACHE = CacheControl.Builder().noCache().noStore().build()
|
||||||
|
private val CACHE_CONTROL_NO_NETWORK_NO_CACHE = CacheControl.Builder().noCache().onlyIfCached().build()
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,60 +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 timber.log.Timber
|
|
||||||
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 filePath: String = "") : DataFetcher<InputStream> {
|
|
||||||
|
|
||||||
private var data: InputStream? = null
|
|
||||||
|
|
||||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
|
||||||
loadFromFile(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun loadFromFile(callback: DataFetcher.DataCallback<in InputStream>) {
|
|
||||||
loadFromFile(File(filePath), callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun loadFromFile(file: File, callback: DataFetcher.DataCallback<in InputStream>) {
|
|
||||||
try {
|
|
||||||
data = FileInputStream(file)
|
|
||||||
} catch (e: FileNotFoundException) {
|
|
||||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
|
||||||
Timber.d(e, "Failed to open file")
|
|
||||||
}
|
|
||||||
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,25 +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.cache.CoverCache
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import java.io.File
|
|
||||||
import java.io.InputStream
|
|
||||||
import java.lang.Exception
|
|
||||||
|
|
||||||
open class LibraryMangaCustomCoverFetcher(
|
|
||||||
private val manga: Manga,
|
|
||||||
private val coverCache: CoverCache
|
|
||||||
) : FileFetcher() {
|
|
||||||
|
|
||||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
|
||||||
getCustomCoverFile()?.let {
|
|
||||||
loadFromFile(it, callback)
|
|
||||||
} ?: callback.onLoadFailed(Exception("Custom cover file not found"))
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun getCustomCoverFile(): File? {
|
|
||||||
return coverCache.getCustomCoverFile(manga).takeIf { it.exists() }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,86 +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.cache.CoverCache
|
|
||||||
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 coverCache: CoverCache
|
|
||||||
) : LibraryMangaCustomCoverFetcher(manga, coverCache) {
|
|
||||||
|
|
||||||
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {
|
|
||||||
getCustomCoverFile()?.let {
|
|
||||||
loadFromFile(it, callback)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val cover = coverCache.getCoverFile(manga)
|
|
||||||
if (cover == null) {
|
|
||||||
callback.onLoadFailed(Exception("Null thumbnail url"))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!cover.exists()) {
|
|
||||||
networkFetcher.loadData(
|
|
||||||
priority,
|
|
||||||
object : DataFetcher.DataCallback<InputStream> {
|
|
||||||
override fun onDataReady(data: InputStream?) {
|
|
||||||
if (data != null) {
|
|
||||||
val tmpFile = File(cover.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(cover)
|
|
||||||
loadFromFile(cover, 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(cover, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cleanup() {
|
|
||||||
super.cleanup()
|
|
||||||
networkFetcher.cleanup()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancel() {
|
|
||||||
super.cancel()
|
|
||||||
networkFetcher.cancel()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import com.bumptech.glide.load.Key
|
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
data class MangaThumbnail(val manga: Manga, val coverLastModified: Long) : Key {
|
|
||||||
val key = manga.url + coverLastModified
|
|
||||||
|
|
||||||
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
|
|
||||||
messageDigest.update(key.toByteArray(Key.CHARSET))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Manga.toMangaThumbnail() = MangaThumbnail(this, cover_last_modified)
|
|
|
@ -1,134 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
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 eu.kanade.tachiyomi.util.isLocal
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
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 MangaThumbnailModelLoader : ModelLoader<MangaThumbnail, 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
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Map where request headers are stored for a source.
|
|
||||||
*/
|
|
||||||
private val cachedHeaders = hashMapOf<Long, LazyHeaders>()
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory class for creating [MangaThumbnailModelLoader] instances.
|
|
||||||
*/
|
|
||||||
class Factory : ModelLoaderFactory<MangaThumbnail, InputStream> {
|
|
||||||
|
|
||||||
override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<MangaThumbnail, InputStream> {
|
|
||||||
return MangaThumbnailModelLoader()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun teardown() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun handles(model: MangaThumbnail): Boolean {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a fetcher for the given manga or null if the url is empty.
|
|
||||||
*
|
|
||||||
* @param mangaThumbnail 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(
|
|
||||||
mangaThumbnail: MangaThumbnail,
|
|
||||||
width: Int,
|
|
||||||
height: Int,
|
|
||||||
options: Options
|
|
||||||
): ModelLoader.LoadData<InputStream>? {
|
|
||||||
val manga = mangaThumbnail.manga
|
|
||||||
val url = manga.thumbnail_url
|
|
||||||
|
|
||||||
if (url.isNullOrEmpty()) {
|
|
||||||
return if (!manga.favorite || manga.isLocal()) {
|
|
||||||
null
|
|
||||||
} else {
|
|
||||||
ModelLoader.LoadData(mangaThumbnail, LibraryMangaCustomCoverFetcher(manga, coverCache))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
val libraryFetcher = LibraryMangaUrlFetcher(networkFetcher, manga, coverCache)
|
|
||||||
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
|
||||||
return ModelLoader.LoadData(mangaThumbnail, libraryFetcher)
|
|
||||||
} else {
|
|
||||||
// Return an instance of the fetcher providing the needed elements.
|
|
||||||
return ModelLoader.LoadData(mangaThumbnail, FileFetcher(url.removePrefix("file://")))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the request headers for a source copying its OkHttp headers and caching them.
|
|
||||||
*
|
|
||||||
* @param manga the model.
|
|
||||||
*/
|
|
||||||
private 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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,55 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.glide
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import com.bumptech.glide.Glide
|
|
||||||
import com.bumptech.glide.GlideBuilder
|
|
||||||
import com.bumptech.glide.Registry
|
|
||||||
import com.bumptech.glide.annotation.GlideModule
|
|
||||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader
|
|
||||||
import com.bumptech.glide.load.DecodeFormat
|
|
||||||
import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
|
|
||||||
import com.bumptech.glide.load.model.GlideUrl
|
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
||||||
import com.bumptech.glide.module.AppGlideModule
|
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
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))
|
|
||||||
builder.setDefaultTransitionOptions(
|
|
||||||
Drawable::class.java,
|
|
||||||
DrawableTransitionOptions.withCrossFade()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
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(
|
|
||||||
MangaThumbnail::class.java,
|
|
||||||
InputStream::class.java,
|
|
||||||
MangaThumbnailModelLoader.Factory()
|
|
||||||
)
|
|
||||||
registry.append(
|
|
||||||
InputStream::class.java,
|
|
||||||
InputStream::class.java,
|
|
||||||
PassthroughModelLoader.Factory()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -6,19 +6,22 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import com.bumptech.glide.Glide
|
import coil.imageLoader
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
|
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
@ -165,14 +168,17 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
// Per-manga notification
|
// Per-manga notification
|
||||||
if (!preferences.hideNotificationContent()) {
|
if (!preferences.hideNotificationContent()) {
|
||||||
updates.forEach { (manga, chapters) ->
|
launchUI {
|
||||||
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
|
updates.forEach { (manga, chapters) ->
|
||||||
|
notify(manga.id.hashCode(), createNewChaptersNotification(manga, chapters))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
|
private suspend fun createNewChaptersNotification(manga: Manga, chapters: Array<Chapter>): Notification {
|
||||||
|
val icon = getMangaIcon(manga)
|
||||||
return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
return context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
setContentTitle(manga.title)
|
setContentTitle(manga.title)
|
||||||
|
|
||||||
|
@ -182,7 +188,6 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
|
||||||
val icon = getMangaIcon(manga)
|
|
||||||
if (icon != null) {
|
if (icon != null) {
|
||||||
setLargeIcon(icon)
|
setLargeIcon(icon)
|
||||||
}
|
}
|
||||||
|
@ -226,23 +231,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||||
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMangaIcon(manga: Manga): Bitmap? {
|
private suspend fun getMangaIcon(manga: Manga): Bitmap? {
|
||||||
return try {
|
val request = ImageRequest.Builder(context)
|
||||||
Glide.with(context)
|
.data(manga)
|
||||||
.asBitmap()
|
.transformations(CircleCropTransformation())
|
||||||
.load(manga.toMangaThumbnail())
|
.size(NOTIF_ICON_SIZE)
|
||||||
.dontTransform()
|
.build()
|
||||||
.centerCrop()
|
val drawable = context.imageLoader.execute(request).drawable
|
||||||
.circleCrop()
|
return (drawable as? BitmapDrawable)?.bitmap
|
||||||
.override(
|
|
||||||
NOTIF_ICON_SIZE,
|
|
||||||
NOTIF_ICON_SIZE
|
|
||||||
)
|
|
||||||
.submit()
|
|
||||||
.get()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
private fun getNewChaptersDescription(chapters: Array<Chapter>): String {
|
||||||
|
|
|
@ -386,6 +386,7 @@ class LibraryUpdateService(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
coverCache.clearMemoryCache()
|
||||||
notifier.cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import coil.util.CoilUtils
|
||||||
import eu.kanade.tachiyomi.BuildConfig
|
import eu.kanade.tachiyomi.BuildConfig
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
|
@ -20,28 +21,32 @@ class NetworkHelper(context: Context) {
|
||||||
|
|
||||||
val cookieManager = AndroidCookieJar()
|
val cookieManager = AndroidCookieJar()
|
||||||
|
|
||||||
val client by lazy {
|
private val baseClientBuilder: OkHttpClient.Builder
|
||||||
val builder = OkHttpClient.Builder()
|
get() {
|
||||||
.cookieJar(cookieManager)
|
val builder = OkHttpClient.Builder()
|
||||||
.cache(Cache(cacheDir, cacheSize))
|
.cookieJar(cookieManager)
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.addInterceptor(UserAgentInterceptor())
|
.addInterceptor(UserAgentInterceptor())
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
val httpLoggingInterceptor = HttpLoggingInterceptor().apply {
|
||||||
level = HttpLoggingInterceptor.Level.HEADERS
|
level = HttpLoggingInterceptor.Level.HEADERS
|
||||||
|
}
|
||||||
|
builder.addInterceptor(httpLoggingInterceptor)
|
||||||
}
|
}
|
||||||
builder.addInterceptor(httpLoggingInterceptor)
|
|
||||||
|
when (preferences.dohProvider()) {
|
||||||
|
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
||||||
|
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
when (preferences.dohProvider()) {
|
val client by lazy { baseClientBuilder.cache(Cache(cacheDir, cacheSize)).build() }
|
||||||
PREF_DOH_CLOUDFLARE -> builder.dohCloudflare()
|
|
||||||
PREF_DOH_GOOGLE -> builder.dohGoogle()
|
|
||||||
}
|
|
||||||
|
|
||||||
builder.build()
|
val coilClient by lazy { baseClientBuilder.cache(CoilUtils.createDefaultCache(context)).build() }
|
||||||
}
|
|
||||||
|
|
||||||
val cloudflareClient by lazy {
|
val cloudflareClient by lazy {
|
||||||
client.newBuilder()
|
client.newBuilder()
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.extension
|
package eu.kanade.tachiyomi.ui.browse.extension
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
|
import coil.clear
|
||||||
|
import coil.load
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
|
import eu.kanade.tachiyomi.databinding.ExtensionCardItemBinding
|
||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
|
@ -41,11 +42,9 @@ class ExtensionHolder(view: View, val adapter: ExtensionAdapter) :
|
||||||
else -> ""
|
else -> ""
|
||||||
}.toUpperCase()
|
}.toUpperCase()
|
||||||
|
|
||||||
GlideApp.with(itemView.context).clear(binding.image)
|
binding.image.clear()
|
||||||
if (extension is Extension.Available) {
|
if (extension is Extension.Available) {
|
||||||
GlideApp.with(itemView.context)
|
binding.image.load(extension.iconUrl)
|
||||||
.load(extension.iconUrl)
|
|
||||||
.into(binding.image)
|
|
||||||
} else {
|
} else {
|
||||||
extension.getApplicationIcon(itemView.context)?.let { binding.image.setImageDrawable(it) }
|
extension.getApplicationIcon(itemView.context)?.let { binding.image.setImageDrawable(it) }
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,11 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.migration.manga
|
package eu.kanade.tachiyomi.ui.browse.migration.manga
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import coil.loadAny
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
||||||
|
|
||||||
class MigrationMangaHolder(
|
class MigrationMangaHolder(
|
||||||
|
@ -28,15 +25,10 @@ class MigrationMangaHolder(
|
||||||
binding.title.text = item.manga.title
|
binding.title.text = item.manga.title
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
GlideApp.with(itemView.context).clear(binding.thumbnail)
|
val radius = itemView.context.resources.getDimension(R.dimen.card_radius)
|
||||||
|
binding.thumbnail.clear()
|
||||||
val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
binding.thumbnail.loadAny(item.manga) {
|
||||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
transformations(RoundedCornersTransformation(radius))
|
||||||
GlideApp.with(itemView.context)
|
}
|
||||||
.load(item.manga.toMangaThumbnail())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.apply(requestOptions)
|
|
||||||
.dontAnimate()
|
|
||||||
.into(binding.thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.transition.CrossfadeTransition
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
|
|
||||||
|
@ -42,14 +45,18 @@ class SourceComfortableGridHolder(private val view: View, private val adapter: F
|
||||||
// For rounded corners
|
// For rounded corners
|
||||||
binding.card.clipToOutline = true
|
binding.card.clipToOutline = true
|
||||||
|
|
||||||
GlideApp.with(view.context).clear(binding.thumbnail)
|
binding.thumbnail.clear()
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
GlideApp.with(view.context)
|
val crossfadeDuration = view.context.imageLoader.defaults.transition.let {
|
||||||
.load(manga.toMangaThumbnail())
|
if (it is CrossfadeTransition) it.durationMillis else 0
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
}
|
||||||
.centerCrop()
|
val request = ImageRequest.Builder(view.context)
|
||||||
.placeholder(android.R.color.transparent)
|
.data(manga)
|
||||||
.into(StateImageViewTarget(binding.thumbnail, binding.progress))
|
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
|
||||||
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration))
|
||||||
|
.build()
|
||||||
|
itemView.context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.transition.CrossfadeTransition
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
|
|
||||||
|
@ -42,14 +45,18 @@ open class SourceGridHolder(private val view: View, private val adapter: Flexibl
|
||||||
// For rounded corners
|
// For rounded corners
|
||||||
binding.card.clipToOutline = true
|
binding.card.clipToOutline = true
|
||||||
|
|
||||||
GlideApp.with(view.context).clear(binding.thumbnail)
|
binding.thumbnail.clear()
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
GlideApp.with(view.context)
|
val crossfadeDuration = view.context.imageLoader.defaults.transition.let {
|
||||||
.load(manga.toMangaThumbnail())
|
if (it is CrossfadeTransition) it.durationMillis else 0
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
}
|
||||||
.centerCrop()
|
val request = ImageRequest.Builder(view.context)
|
||||||
.placeholder(android.R.color.transparent)
|
.data(manga)
|
||||||
.into(StateImageViewTarget(binding.thumbnail, binding.progress))
|
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
|
||||||
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.target(StateImageViewTarget(binding.thumbnail, binding.progress, crossfadeDuration))
|
||||||
|
.build()
|
||||||
|
itemView.context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,14 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.source.browse
|
package eu.kanade.tachiyomi.ui.browse.source.browse
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import coil.loadAny
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import coil.request.CachePolicy
|
||||||
import com.bumptech.glide.request.RequestOptions
|
import coil.transform.RoundedCornersTransformation
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
import eu.kanade.tachiyomi.util.system.getResourceColor
|
||||||
|
|
||||||
|
@ -46,18 +45,14 @@ class SourceListHolder(private val view: View, adapter: FlexibleAdapter<*>) :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setImage(manga: Manga) {
|
override fun setImage(manga: Manga) {
|
||||||
GlideApp.with(view.context).clear(binding.thumbnail)
|
binding.thumbnail.clear()
|
||||||
|
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
val radius = view.context.resources.getDimension(R.dimen.card_radius)
|
||||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
binding.thumbnail.loadAny(manga) {
|
||||||
GlideApp.with(view.context)
|
setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
|
||||||
.load(manga.toMangaThumbnail())
|
transformations(RoundedCornersTransformation(radius))
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.apply(requestOptions)
|
}
|
||||||
.dontAnimate()
|
|
||||||
.placeholder(android.R.color.transparent)
|
|
||||||
.into(binding.thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
|
import coil.transition.CrossfadeTransition
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
|
import eu.kanade.tachiyomi.data.coil.MangaCoverFetcher
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
|
import eu.kanade.tachiyomi.databinding.GlobalSearchControllerCardItemBinding
|
||||||
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
import eu.kanade.tachiyomi.widget.StateImageViewTarget
|
||||||
|
|
||||||
|
@ -42,15 +45,18 @@ class GlobalSearchCardHolder(view: View, adapter: GlobalSearchCardAdapter) :
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setImage(manga: Manga) {
|
fun setImage(manga: Manga) {
|
||||||
GlideApp.with(itemView.context).clear(binding.cover)
|
binding.cover.clear()
|
||||||
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
if (!manga.thumbnail_url.isNullOrEmpty()) {
|
||||||
GlideApp.with(itemView.context)
|
val crossfadeDuration = itemView.context.imageLoader.defaults.transition.let {
|
||||||
.load(manga.toMangaThumbnail())
|
if (it is CrossfadeTransition) it.durationMillis else 0
|
||||||
.diskCacheStrategy(DiskCacheStrategy.DATA)
|
}
|
||||||
.centerCrop()
|
val request = ImageRequest.Builder(itemView.context)
|
||||||
.skipMemoryCache(true)
|
.data(manga)
|
||||||
.placeholder(android.R.color.transparent)
|
.setParameter(MangaCoverFetcher.USE_CUSTOM_COVER, false)
|
||||||
.into(StateImageViewTarget(binding.cover, binding.progress))
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.target(StateImageViewTarget(binding.cover, binding.progress, crossfadeDuration))
|
||||||
|
.build()
|
||||||
|
itemView.context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,11 +3,10 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.loadAny
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.davidea.flexibleadapter.items.IFlexible
|
import eu.davidea.flexibleadapter.items.IFlexible
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceComfortableGridItemBinding
|
||||||
import eu.kanade.tachiyomi.util.isLocal
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
|
|
||||||
|
@ -57,12 +56,7 @@ class LibraryComfortableGridHolder(
|
||||||
binding.card.clipToOutline = true
|
binding.card.clipToOutline = true
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
GlideApp.with(view.context).clear(binding.thumbnail)
|
binding.thumbnail.clear()
|
||||||
GlideApp.with(view.context)
|
binding.thumbnail.loadAny(item.manga)
|
||||||
.load(item.manga.toMangaThumbnail())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.centerCrop()
|
|
||||||
.dontAnimate()
|
|
||||||
.into(binding.thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,10 +2,9 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.loadAny
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceCompactGridItemBinding
|
||||||
import eu.kanade.tachiyomi.util.isLocal
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
|
|
||||||
|
@ -55,12 +54,7 @@ open class LibraryCompactGridHolder(
|
||||||
binding.card.clipToOutline = true
|
binding.card.clipToOutline = true
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
GlideApp.with(view.context).clear(binding.thumbnail)
|
binding.thumbnail.clear()
|
||||||
GlideApp.with(view.context)
|
binding.thumbnail.loadAny(item.manga)
|
||||||
.load(item.manga.toMangaThumbnail())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.centerCrop()
|
|
||||||
.dontAnimate()
|
|
||||||
.into(binding.thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,14 +2,11 @@ package eu.kanade.tachiyomi.ui.library
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import coil.loadAny
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import eu.davidea.flexibleadapter.FlexibleAdapter
|
import eu.davidea.flexibleadapter.FlexibleAdapter
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
import eu.kanade.tachiyomi.databinding.SourceListItemBinding
|
||||||
import eu.kanade.tachiyomi.util.isLocal
|
import eu.kanade.tachiyomi.util.isLocal
|
||||||
|
|
||||||
|
@ -62,15 +59,10 @@ class LibraryListHolder(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the cover.
|
// Update the cover.
|
||||||
GlideApp.with(itemView.context).clear(binding.thumbnail)
|
val radius = view.context.resources.getDimension(R.dimen.card_radius)
|
||||||
|
binding.thumbnail.clear()
|
||||||
val radius = view.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
binding.thumbnail.loadAny(item.manga) {
|
||||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
transformations(RoundedCornersTransformation(radius))
|
||||||
GlideApp.with(itemView.context)
|
}
|
||||||
.load(item.manga.toMangaThumbnail())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.apply(requestOptions)
|
|
||||||
.dontAnimate()
|
|
||||||
.into(binding.thumbnail)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -284,6 +284,7 @@ class MangaPresenter(
|
||||||
} else if (manga.favorite) {
|
} else if (manga.favorite) {
|
||||||
coverCache.setCustomCoverToCache(manga, it)
|
coverCache.setCustomCoverToCache(manga, it)
|
||||||
manga.updateCoverLastModified(db)
|
manga.updateCoverLastModified(db)
|
||||||
|
coverCache.clearMemoryCache()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -300,6 +301,7 @@ class MangaPresenter(
|
||||||
.fromCallable {
|
.fromCallable {
|
||||||
coverCache.deleteCustomCover(manga)
|
coverCache.deleteCustomCover(manga)
|
||||||
manga.updateCoverLastModified(db)
|
manga.updateCoverLastModified(db)
|
||||||
|
coverCache.clearMemoryCache()
|
||||||
}
|
}
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
|
|
@ -5,12 +5,9 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.loadAny
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.MangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.data.track.TrackManager
|
import eu.kanade.tachiyomi.data.track.TrackManager
|
||||||
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
|
import eu.kanade.tachiyomi.databinding.MangaInfoHeaderBinding
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
|
@ -44,7 +41,6 @@ class MangaInfoHeaderAdapter(
|
||||||
private lateinit var binding: MangaInfoHeaderBinding
|
private lateinit var binding: MangaInfoHeaderBinding
|
||||||
|
|
||||||
private var initialLoad: Boolean = true
|
private var initialLoad: Boolean = true
|
||||||
private var currentMangaThumbnail: MangaThumbnail? = null
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): HeaderViewHolder {
|
||||||
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
binding = MangaInfoHeaderBinding.inflate(LayoutInflater.from(parent.context), parent, false)
|
||||||
|
@ -246,17 +242,8 @@ class MangaInfoHeaderAdapter(
|
||||||
setFavoriteButtonState(manga.favorite)
|
setFavoriteButtonState(manga.favorite)
|
||||||
|
|
||||||
// Set cover if changed.
|
// Set cover if changed.
|
||||||
val mangaThumbnail = manga.toMangaThumbnail()
|
listOf(binding.mangaCover, binding.backdrop).forEach {
|
||||||
if (mangaThumbnail != currentMangaThumbnail) {
|
it.loadAny(manga)
|
||||||
currentMangaThumbnail = mangaThumbnail
|
|
||||||
listOf(binding.mangaCover, binding.backdrop)
|
|
||||||
.forEach {
|
|
||||||
GlideApp.with(view.context)
|
|
||||||
.load(mangaThumbnail)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.centerCrop()
|
|
||||||
.into(it)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Manga info section
|
// Manga info section
|
||||||
|
|
|
@ -5,9 +5,9 @@ import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
|
import coil.load
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
import eu.kanade.tachiyomi.data.track.model.TrackSearch
|
||||||
import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding
|
import eu.kanade.tachiyomi.databinding.TrackSearchItemBinding
|
||||||
import eu.kanade.tachiyomi.util.view.inflate
|
import eu.kanade.tachiyomi.util.view.inflate
|
||||||
|
@ -46,13 +46,9 @@ class TrackSearchAdapter(context: Context) :
|
||||||
fun onSetValues(track: TrackSearch) {
|
fun onSetValues(track: TrackSearch) {
|
||||||
binding.trackSearchTitle.text = track.title
|
binding.trackSearchTitle.text = track.title
|
||||||
binding.trackSearchSummary.text = track.summary
|
binding.trackSearchSummary.text = track.summary
|
||||||
GlideApp.with(view.context).clear(binding.trackSearchCover)
|
binding.trackSearchCover.clear()
|
||||||
if (track.cover_url.isNotEmpty()) {
|
if (track.cover_url.isNotEmpty()) {
|
||||||
GlideApp.with(view.context)
|
binding.trackSearchCover.load(track.cover_url)
|
||||||
.load(track.cover_url)
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.centerCrop()
|
|
||||||
.into(binding.trackSearchCover)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val hasStatus = track.publishing_status.isNotBlank()
|
val hasStatus = track.publishing_status.isNotBlank()
|
||||||
|
|
|
@ -624,6 +624,7 @@ class ReaderPresenter(
|
||||||
if (manga.favorite) {
|
if (manga.favorite) {
|
||||||
coverCache.setCustomCoverToCache(manga, stream())
|
coverCache.setCustomCoverToCache(manga, stream())
|
||||||
manga.updateCoverLastModified(db)
|
manga.updateCoverLastModified(db)
|
||||||
|
coverCache.clearMemoryCache()
|
||||||
SetAsCoverResult.Success
|
SetAsCoverResult.Success
|
||||||
} else {
|
} else {
|
||||||
SetAsCoverResult.AddToLibraryFirst
|
SetAsCoverResult.AddToLibraryFirst
|
||||||
|
|
|
@ -2,10 +2,12 @@ package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.imageLoader
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.ImageRequest
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
@ -30,25 +32,25 @@ class SaveImageNotifier(private val context: Context) {
|
||||||
get() = Notifications.ID_DOWNLOAD_IMAGE
|
get() = Notifications.ID_DOWNLOAD_IMAGE
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when image download/copy is complete. This method must be called in a background
|
* Called when image download/copy is complete.
|
||||||
* thread.
|
|
||||||
*
|
*
|
||||||
* @param file image file containing downloaded page image.
|
* @param file image file containing downloaded page image.
|
||||||
*/
|
*/
|
||||||
fun onComplete(file: File) {
|
fun onComplete(file: File) {
|
||||||
val bitmap = GlideApp.with(context)
|
val request = ImageRequest.Builder(context)
|
||||||
.asBitmap()
|
.data(file)
|
||||||
.load(file)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.size(720, 1280)
|
||||||
.skipMemoryCache(true)
|
.target(
|
||||||
.submit(720, 1280)
|
onSuccess = { result ->
|
||||||
.get()
|
showCompleteNotification(file, (result as BitmapDrawable).bitmap)
|
||||||
|
},
|
||||||
if (bitmap != null) {
|
onError = {
|
||||||
showCompleteNotification(file, bitmap)
|
onError(null)
|
||||||
} else {
|
}
|
||||||
onError(null)
|
)
|
||||||
}
|
.build()
|
||||||
|
context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showCompleteNotification(file: File, image: Bitmap) {
|
private fun showCompleteNotification(file: File, image: Bitmap) {
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Animatable
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
|
@ -14,19 +14,13 @@ import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.DataSource
|
import coil.imageLoader
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.request.CachePolicy
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import coil.request.ImageRequest
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.bumptech.glide.request.transition.NoTransition
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import com.github.chrisbanes.photoview.PhotoView
|
import com.github.chrisbanes.photoview.PhotoView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
|
@ -41,6 +35,7 @@ import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -480,38 +475,24 @@ class PagerPageHolder(
|
||||||
* Extension method to set a [stream] into this ImageView.
|
* Extension method to set a [stream] into this ImageView.
|
||||||
*/
|
*/
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
private fun ImageView.setImage(stream: InputStream) {
|
||||||
GlideApp.with(this)
|
val request = ImageRequest.Builder(context)
|
||||||
.load(stream)
|
.data(ByteBuffer.wrap(stream.readBytes()))
|
||||||
.skipMemoryCache(true)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
.target(
|
||||||
.listener(
|
onSuccess = { result ->
|
||||||
object : RequestListener<Drawable> {
|
if (result is Animatable) {
|
||||||
override fun onLoadFailed(
|
result.start()
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
onImageDecodeError()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
if (resource is GifDrawable) {
|
|
||||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
|
||||||
}
|
|
||||||
onImageDecoded()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
setImageDrawable(result)
|
||||||
|
onImageDecoded()
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
onImageDecodeError()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.into(this)
|
.crossfade(false)
|
||||||
|
.build()
|
||||||
|
context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.webtoon
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Animatable
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||||
|
@ -14,18 +14,13 @@ import android.widget.TextView
|
||||||
import androidx.appcompat.widget.AppCompatButton
|
import androidx.appcompat.widget.AppCompatButton
|
||||||
import androidx.appcompat.widget.AppCompatImageView
|
import androidx.appcompat.widget.AppCompatImageView
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.DataSource
|
import coil.clear
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.imageLoader
|
||||||
import com.bumptech.glide.load.engine.GlideException
|
import coil.request.CachePolicy
|
||||||
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions
|
import coil.request.ImageRequest
|
||||||
import com.bumptech.glide.load.resource.gif.GifDrawable
|
|
||||||
import com.bumptech.glide.request.RequestListener
|
|
||||||
import com.bumptech.glide.request.target.Target
|
|
||||||
import com.bumptech.glide.request.transition.NoTransition
|
|
||||||
import com.davemorrissey.labs.subscaleview.ImageSource
|
import com.davemorrissey.labs.subscaleview.ImageSource
|
||||||
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||||
|
@ -37,6 +32,7 @@ import rx.Subscription
|
||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
import java.nio.ByteBuffer
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -146,7 +142,7 @@ class WebtoonPageHolder(
|
||||||
removeDecodeErrorLayout()
|
removeDecodeErrorLayout()
|
||||||
subsamplingImageView?.recycle()
|
subsamplingImageView?.recycle()
|
||||||
subsamplingImageView?.isVisible = false
|
subsamplingImageView?.isVisible = false
|
||||||
imageView?.let { GlideApp.with(frame).clear(it) }
|
imageView?.clear()
|
||||||
imageView?.isVisible = false
|
imageView?.isVisible = false
|
||||||
progressBar.setProgress(0)
|
progressBar.setProgress(0)
|
||||||
}
|
}
|
||||||
|
@ -512,38 +508,24 @@ class WebtoonPageHolder(
|
||||||
* Extension method to set a [stream] into this ImageView.
|
* Extension method to set a [stream] into this ImageView.
|
||||||
*/
|
*/
|
||||||
private fun ImageView.setImage(stream: InputStream) {
|
private fun ImageView.setImage(stream: InputStream) {
|
||||||
GlideApp.with(this)
|
val request = ImageRequest.Builder(context)
|
||||||
.load(stream)
|
.data(ByteBuffer.wrap(stream.readBytes()))
|
||||||
.skipMemoryCache(true)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
.diskCacheStrategy(DiskCacheStrategy.NONE)
|
.diskCachePolicy(CachePolicy.DISABLED)
|
||||||
.transition(DrawableTransitionOptions.with(NoTransition.getFactory()))
|
.target(
|
||||||
.listener(
|
onSuccess = { result ->
|
||||||
object : RequestListener<Drawable> {
|
if (result is Animatable) {
|
||||||
override fun onLoadFailed(
|
result.start()
|
||||||
e: GlideException?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
onImageDecodeError()
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(
|
|
||||||
resource: Drawable?,
|
|
||||||
model: Any?,
|
|
||||||
target: Target<Drawable>?,
|
|
||||||
dataSource: DataSource?,
|
|
||||||
isFirstResource: Boolean
|
|
||||||
): Boolean {
|
|
||||||
if (resource is GifDrawable) {
|
|
||||||
resource.setLoopCount(GifDrawable.LOOP_INTRINSIC)
|
|
||||||
}
|
|
||||||
onImageDecoded()
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
setImageDrawable(result)
|
||||||
|
onImageDecoded()
|
||||||
|
},
|
||||||
|
onError = {
|
||||||
|
onImageDecodeError()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.into(this)
|
.crossfade(false)
|
||||||
|
.build()
|
||||||
|
context.imageLoader.enqueue(request)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,12 @@
|
||||||
package eu.kanade.tachiyomi.ui.recent.history
|
package eu.kanade.tachiyomi.ui.recent.history
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import coil.loadAny
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import eu.davidea.viewholders.FlexibleViewHolder
|
import eu.davidea.viewholders.FlexibleViewHolder
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.HistoryItemBinding
|
import eu.kanade.tachiyomi.databinding.HistoryItemBinding
|
||||||
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
import eu.kanade.tachiyomi.util.lang.toTimestampString
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
@ -68,15 +65,11 @@ class HistoryHolder(
|
||||||
binding.mangaSubtitle.text = Date(history.last_read).toTimestampString()
|
binding.mangaSubtitle.text = Date(history.last_read).toTimestampString()
|
||||||
}
|
}
|
||||||
|
|
||||||
val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
|
||||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
|
||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
GlideApp.with(itemView.context).clear(binding.cover)
|
val radius = itemView.context.resources.getDimension(R.dimen.card_radius)
|
||||||
GlideApp.with(itemView.context)
|
binding.cover.clear()
|
||||||
.load(manga.toMangaThumbnail())
|
binding.cover.loadAny(item.manga) {
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
transformations(RoundedCornersTransformation(radius))
|
||||||
.apply(requestOptions)
|
}
|
||||||
.into(binding.cover)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,13 +2,10 @@ package eu.kanade.tachiyomi.ui.recent.updates
|
||||||
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.load.engine.DiskCacheStrategy
|
import coil.clear
|
||||||
import com.bumptech.glide.load.resource.bitmap.CenterCrop
|
import coil.loadAny
|
||||||
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
|
import coil.transform.RoundedCornersTransformation
|
||||||
import com.bumptech.glide.request.RequestOptions
|
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.glide.GlideApp
|
|
||||||
import eu.kanade.tachiyomi.data.glide.toMangaThumbnail
|
|
||||||
import eu.kanade.tachiyomi.databinding.UpdatesItemBinding
|
import eu.kanade.tachiyomi.databinding.UpdatesItemBinding
|
||||||
import eu.kanade.tachiyomi.source.LocalSource
|
import eu.kanade.tachiyomi.source.LocalSource
|
||||||
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder
|
import eu.kanade.tachiyomi.ui.manga.chapter.base.BaseChapterHolder
|
||||||
|
@ -58,15 +55,10 @@ class UpdatesHolder(private val view: View, private val adapter: UpdatesAdapter)
|
||||||
binding.download.setState(item.status, item.progress)
|
binding.download.setState(item.status, item.progress)
|
||||||
|
|
||||||
// Set cover
|
// Set cover
|
||||||
GlideApp.with(itemView.context).clear(binding.mangaCover)
|
val radius = itemView.context.resources.getDimension(R.dimen.card_radius)
|
||||||
|
binding.mangaCover.clear()
|
||||||
val radius = itemView.context.resources.getDimensionPixelSize(R.dimen.card_radius)
|
binding.mangaCover.loadAny(item.manga) {
|
||||||
val requestOptions = RequestOptions().transform(CenterCrop(), RoundedCorners(radius))
|
transformations(RoundedCornersTransformation(radius))
|
||||||
GlideApp.with(itemView.context)
|
}
|
||||||
.load(item.manga.toMangaThumbnail())
|
|
||||||
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
|
|
||||||
.apply(requestOptions)
|
|
||||||
.dontAnimate()
|
|
||||||
.into(binding.mangaCover)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,61 +3,41 @@ package eu.kanade.tachiyomi.widget
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
import android.widget.ImageView.ScaleType
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import com.bumptech.glide.request.target.ImageViewTarget
|
import coil.drawable.CrossfadeDrawable
|
||||||
import com.bumptech.glide.request.transition.Transition
|
import coil.target.ImageViewTarget
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.util.system.getResourceColor
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A glide target to display an image with an optional view to show while loading and a configurable
|
* A Coil target to display an image with an optional view to show while loading.
|
||||||
* error drawable.
|
|
||||||
*
|
*
|
||||||
* @param view the view where the image will be loaded
|
* @param target the view where the image will be loaded
|
||||||
* @param progress an optional view to show when the image is loading.
|
* @param progress the view to show when the image is loading.
|
||||||
* @param errorDrawableRes the error drawable resource to show.
|
* @param crossfadeDuration duration in millisecond to crossfade the result drawable
|
||||||
* @param errorScaleType the scale type for the error drawable, [ScaleType.CENTER] by default.
|
|
||||||
*/
|
*/
|
||||||
class StateImageViewTarget(
|
class StateImageViewTarget(
|
||||||
view: ImageView,
|
private val target: ImageView,
|
||||||
val progress: View? = null,
|
private val progress: View,
|
||||||
private val errorDrawableRes: Int = R.drawable.ic_broken_image_grey_24dp,
|
private val crossfadeDuration: Int = 0
|
||||||
private val errorScaleType: ScaleType = ScaleType.CENTER
|
) : ImageViewTarget(target) {
|
||||||
) : ImageViewTarget<Drawable>(view) {
|
override fun onStart(placeholder: Drawable?) {
|
||||||
|
progress.isVisible = true
|
||||||
private var resource: Drawable? = null
|
|
||||||
|
|
||||||
private val imageScaleType = view.scaleType
|
|
||||||
|
|
||||||
override fun setResource(resource: Drawable?) {
|
|
||||||
view.setImageDrawable(resource)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadStarted(placeholder: Drawable?) {
|
override fun onSuccess(result: Drawable) {
|
||||||
progress?.isVisible = true
|
progress.isVisible = false
|
||||||
super.onLoadStarted(placeholder)
|
if (crossfadeDuration > 0) {
|
||||||
|
val crossfadeResult = CrossfadeDrawable(target.drawable, result, durationMillis = crossfadeDuration)
|
||||||
|
target.setImageDrawable(crossfadeResult)
|
||||||
|
crossfadeResult.start()
|
||||||
|
} else {
|
||||||
|
target.setImageDrawable(result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadFailed(errorDrawable: Drawable?) {
|
override fun onError(error: Drawable?) {
|
||||||
progress?.isVisible = false
|
progress.isVisible = false
|
||||||
view.scaleType = errorScaleType
|
if (error != null) {
|
||||||
|
target.setImageDrawable(error)
|
||||||
val vector = AppCompatResources.getDrawable(view.context, errorDrawableRes)
|
}
|
||||||
vector?.setTint(view.context.getResourceColor(R.attr.colorOnBackground, 0.38f))
|
|
||||||
view.setImageDrawable(vector)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLoadCleared(placeholder: Drawable?) {
|
|
||||||
progress?.isVisible = false
|
|
||||||
super.onLoadCleared(placeholder)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
|
||||||
progress?.isVisible = false
|
|
||||||
view.scaleType = imageScaleType
|
|
||||||
super.onResourceReady(resource, transition)
|
|
||||||
this.resource = resource
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_marginBottom="44dp"
|
android:layout_marginBottom="44dp"
|
||||||
android:alpha="0.2"
|
android:alpha="0.2"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -50,6 +51,7 @@
|
||||||
android:background="@drawable/rounded_rectangle"
|
android:background="@drawable/rounded_rectangle"
|
||||||
android:contentDescription="@string/description_cover"
|
android:contentDescription="@string/description_cover"
|
||||||
android:maxWidth="100dp"
|
android:maxWidth="100dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
tools:src="@mipmap/ic_launcher" />
|
tools:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@mipmap/ic_launcher" />
|
tools:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/colorSurface"
|
android:background="?attr/colorSurface"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
tools:ignore="ContentDescription"
|
tools:ignore="ContentDescription"
|
||||||
tools:src="@mipmap/ic_launcher" />
|
tools:src="@mipmap/ic_launcher" />
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
android:layout_height="@dimen/material_component_lists_single_line_with_avatar_height"
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
android:layout_marginStart="16dp"
|
android:layout_marginStart="16dp"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintDimensionRatio="h,1:1"
|
app:layout_constraintDimensionRatio="h,1:1"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
|
Loading…
Reference in a new issue