Coil updates

Non library manga goes into android's default cache
Manga Fetcher now respects network cache policy
Not showing error drawable on manga details
Fixed manga notification icon sometimes not showing
General cleanup
This commit is contained in:
Jay 2020-05-16 23:54:43 -04:00
parent 67a76da475
commit cce6fdb765
12 changed files with 118 additions and 74 deletions

View File

@ -13,7 +13,6 @@ 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
@ -36,8 +35,6 @@ class CoverCache(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))
}

View File

@ -21,7 +21,6 @@ class ByteArrayFetcher : Fetcher<ByteArray> {
size: Size,
options: Options
): FetchResult {
val source = ByteArrayInputStream(data).source().buffer()
return SourceResult(
source = ByteArrayInputStream(data).source().buffer(),
mimeType = "image/gif",

View File

@ -9,7 +9,6 @@ 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) {
@ -19,7 +18,6 @@ class CoilSetup(context: Context) {
.crossfade(true)
.allowRgb565(true)
.allowHardware(false)
.error(R.drawable.ic_broken_image_grey_24dp)
.componentRegistry {
if (Build.VERSION.SDK_INT >= 28) {
add(ImageDecoderDecoder())

View File

@ -1,5 +1,6 @@
package eu.kanade.tachiyomi.data.download.coil
import android.webkit.MimeTypeMap
import coil.bitmappool.BitmapPool
import coil.decode.DataSource
import coil.decode.Options
@ -10,11 +11,15 @@ 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 eu.kanade.tachiyomi.util.storage.DiskUtil
import okhttp3.CacheControl
import okhttp3.Call
import okhttp3.Request
import okhttp3.Response
import okhttp3.ResponseBody
import okio.buffer
import okio.sink
import okio.source
@ -29,55 +34,95 @@ class MangaFetcher() : Fetcher<Manga> {
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 fun key(data: Manga): String? {
if (data.thumbnail_url.isNullOrBlank()) return null
return if (!data.favorite) {
data.thumbnail_url!!
} else {
DiskUtil.hashKeyForDisk(data.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)
}
override suspend fun fetch(pool: BitmapPool, data: Manga, size: Size, options: Options): FetchResult {
val cover = data.thumbnail_url
return when (getResourceType(cover)) {
Type.File -> fileLoader(data)
Type.URL -> httpLoader(data, options)
Type.CUSTOM -> customLoader(data, options)
null -> error("Invalid image")
}
}
private fun customLoader(manga: Manga): FetchResult {
private suspend fun customLoader(manga: Manga, options: Options): 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)
return httpLoader(manga, options)
}
private fun httpLoader(manga: Manga): FetchResult {
private suspend fun httpLoader(manga: Manga, options: Options): FetchResult {
val coverFile = coverCache.getCoverFile(manga)
if (coverFile.exists()) {
return fileLoader(coverFile)
}
val call = getCall(manga)
val tmpFile = File(coverFile.absolutePath + "_tmp")
if (!manga.favorite) {
val (response, body) = awaitGetCall(manga)
val response = call.execute()
val body = checkNotNull(response.body) { "Null response source" }
return SourceResult(
source = body.source(),
mimeType = getMimeType(manga.thumbnail_url!!, body),
dataSource = if (response.cacheResponse != null) DataSource.DISK else DataSource.NETWORK
)
} else {
val (_, body) = awaitGetCall(manga, !options.networkCachePolicy.readEnabled)
body.source().use { input ->
tmpFile.sink().buffer().use { output ->
output.writeAll(input)
val tmpFile = File(coverFile.absolutePath + "_tmp")
body.source().use { input ->
tmpFile.sink().buffer().use { output ->
output.writeAll(input)
}
}
tmpFile.renameTo(coverFile)
return fileLoader(coverFile)
}
}
private suspend fun awaitGetCall(manga: Manga, onlyCache: Boolean = false): Pair<Response,
ResponseBody> {
val call = getCall(manga, onlyCache)
val response = call.await()
return response to checkNotNull(response.body) { "Null response source" }
}
/**
* "text/plain" is often used as a default/fallback MIME type.
* Attempt to guess a better MIME type from the file extension.
*/
private fun getMimeType(data: String, body: ResponseBody): String? {
val rawContentType = body.contentType()?.toString()
return if (rawContentType == null || rawContentType.startsWith("text/plain")) {
MimeTypeMap.getSingleton().getMimeTypeFromUrl(data) ?: rawContentType
} else {
rawContentType
}
}
/** Modified from [MimeTypeMap.getFileExtensionFromUrl] to be more permissive with special characters. */
private fun MimeTypeMap.getMimeTypeFromUrl(url: String?): String? {
if (url.isNullOrBlank()) {
return null
}
tmpFile.renameTo(coverFile)
return fileLoader(coverFile)
val extension = url
.substringBeforeLast('#') // Strip the fragment.
.substringBeforeLast('?') // Strip the query.
.substringAfterLast('/') // Get the last path segment.
.substringAfterLast('.', missingDelimiterValue = "") // Get the file extension.
return getMimeTypeFromExtension(extension)
}
private fun fileLoader(manga: Manga): FetchResult {
@ -92,18 +137,19 @@ class MangaFetcher() : Fetcher<Manga> {
)
}
private fun getCall(manga: Manga): Call {
private fun getCall(manga: Manga, onlyCache: Boolean): Call {
val source = sourceManager.get(manga.source) as? HttpSource
val client = source?.client ?: defaultClient
val newClient = client.newBuilder()
.cache(coverCache.cache)
.build()
val newClient = client.newBuilder().build()
val request = Request.Builder().url(manga.thumbnail_url!!).also {
if (source != null) {
it.headers(source.headers)
}
if (onlyCache) {
it.cacheControl(CacheControl.FORCE_CACHE)
}
}.build()
return newClient.newCall(request)

View File

@ -16,6 +16,7 @@ import androidx.core.app.NotificationManagerCompat
import androidx.core.content.ContextCompat
import coil.Coil
import coil.request.CachePolicy
import coil.request.GetRequest
import coil.request.LoadRequest
import coil.transform.CircleCropTransformation
import eu.kanade.tachiyomi.R
@ -603,7 +604,7 @@ class LibraryUpdateService(
*
* @param updates a list of manga with new updates.
*/
private fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
private suspend fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
val notifications = ArrayList<Pair<Notification, Int>>()
updates.forEach {
val manga = it.key
@ -613,11 +614,15 @@ class LibraryUpdateService(
setSmallIcon(R.drawable.ic_tachi)
try {
val request = LoadRequest.Builder(this@LibraryUpdateService).data(manga)
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
.target { drawable -> setLargeIcon((drawable as BitmapDrawable).bitmap) }.build()
val request = GetRequest.Builder(this@LibraryUpdateService).data(manga)
.networkCachePolicy(CachePolicy.DISABLED)
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
.build()
Coil.imageLoader(this@LibraryUpdateService).execute(request)
Coil.imageLoader(this@LibraryUpdateService)
.execute(request).drawable?.let { drawable ->
setLargeIcon((drawable as BitmapDrawable).bitmap)
}
} catch (e: Exception) {
}
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)

View File

@ -4,7 +4,6 @@ import android.view.View
import android.view.ViewGroup
import coil.api.clear
import coil.api.loadAny
import coil.transform.RoundedCornersTransformation
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.view.gone
@ -12,9 +11,7 @@ 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.*
/**
@ -82,9 +79,7 @@ class LibraryListHolder(
cover_thumbnail.clear()
} else {
val id = item.manga.id ?: return
cover_thumbnail.loadAny(item.manga) {
transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f))
}
cover_thumbnail.loadAny(item.manga)
}
}

View File

@ -39,8 +39,6 @@ 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
@ -308,7 +306,6 @@ class MangaDetailsController : BaseController,
/** Get the color of the manga cover*/
fun setPaletteColor() {
val view = view ?: return
coverColor = null
val request = LoadRequest.Builder(view.context).data(manga).allowHardware(false)
.target { drawable ->
@ -330,16 +327,12 @@ class MangaDetailsController : BaseController,
activity?.window?.statusBarColor = translucentColor
}
}
manga_cover_full.setImageDrawable(drawable)
getHeader()?.updateCover(manga!!)
}.build()
Coil.imageLoader(view.context).execute(request)
}
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) */
private fun setActionBar(forThis: Boolean) {
val activity = activity ?: return
@ -1258,7 +1251,6 @@ 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

View File

@ -748,7 +748,6 @@ class MangaDetailsPresenter(
fun forceUpdateCovers(deleteCache: Boolean = true) {
if (deleteCache) coverCache.deleteFromCache(manga)
controller.setPaletteColor()
controller.resetCovers()
}
fun editCoverWithStream(uri: Uri): Boolean {

View File

@ -9,8 +9,8 @@ import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import coil.api.clear
import coil.api.loadAny
import coil.request.CachePolicy
import com.google.android.material.button.MaterialButton
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga
@ -308,12 +308,19 @@ class MangaHeaderHolder(
}
}
fun updateCover(manga: Manga, forceUpdate: Boolean = false) {
if (!isCached(manga) && !forceUpdate) return
manga_cover.clear()
backdrop.clear()
manga_cover.loadAny(manga)
backdrop.loadAny(manga)
fun updateCover(manga: Manga) {
if (!manga.initialized) return
val drawable = adapter.controller.manga_cover_full?.drawable
manga_cover.loadAny(manga, builder = {
placeholder(drawable)
error(drawable)
if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED)
})
backdrop.loadAny(manga, builder = {
placeholder(drawable)
error(drawable)
if (manga.favorite) networkCachePolicy(CachePolicy.DISABLED)
})
}
private fun isCached(manga: Manga): Boolean {

View File

@ -13,8 +13,6 @@ import eu.kanade.tachiyomi.ui.library.LibraryCategoryAdapter
import eu.kanade.tachiyomi.util.view.gone
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.*
/**
@ -63,7 +61,8 @@ class BrowseSourceGridHolder(
cover_thumbnail.clear()
} else {
val id = manga.id ?: return
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail, progress)).build()
val request = LoadRequest.Builder(view.context).data(manga)
.target(CoverViewTarget(cover_thumbnail, progress)).build()
Coil.imageLoader(view.context).execute(request)
}
}

View File

@ -5,7 +5,6 @@ import androidx.recyclerview.widget.RecyclerView
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
@ -48,8 +47,8 @@ class BrowseSourceListHolder(private val view: View, adapter: FlexibleAdapter<IF
cover_thumbnail.clear()
} else {
val id = manga.id ?: return
val request = LoadRequest.Builder(view.context).data(manga).target(CoverViewTarget(cover_thumbnail))
.transformations(RoundedCornersTransformation(2f, 2f, 2f, 2f)).build()
val request = LoadRequest.Builder(view.context).data(manga)
.target(CoverViewTarget(cover_thumbnail)).build()
Coil.imageLoader(view.context).execute(request)
}
}

View File

@ -10,23 +10,31 @@ 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) {
class CoverViewTarget(
view: ImageView,
val progress: View? = null,
val scaleType: ImageView.ScaleType = ImageView.ScaleType.CENTER_CROP
) : 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)
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()
view.scaleType = scaleType
super.onStart(placeholder)
}
override fun onSuccess(result: Drawable) {
progress?.gone()
view.scaleType = scaleType
super.onSuccess(result)
}
}