mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-10 01:35:09 +01:00
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:
parent
67a76da475
commit
cce6fdb765
@ -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))
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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())
|
||||
|
@ -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,47 +34,51 @@ 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)
|
||||
if (!manga.favorite) {
|
||||
val (response, body) = awaitGetCall(manga)
|
||||
|
||||
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)
|
||||
|
||||
val tmpFile = File(coverFile.absolutePath + "_tmp")
|
||||
|
||||
val response = call.execute()
|
||||
val body = checkNotNull(response.body) { "Null response source" }
|
||||
|
||||
body.source().use { input ->
|
||||
tmpFile.sink().buffer().use { output ->
|
||||
output.writeAll(input)
|
||||
@ -79,6 +88,42 @@ class MangaFetcher() : Fetcher<Manga> {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
return fileLoader(File(manga.thumbnail_url!!.substringAfter("file://")))
|
||||
@ -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)
|
||||
|
@ -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)
|
||||
val request = GetRequest.Builder(this@LibraryUpdateService).data(manga)
|
||||
.networkCachePolicy(CachePolicy.DISABLED)
|
||||
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
|
||||
.target { drawable -> setLargeIcon((drawable as BitmapDrawable).bitmap) }.build()
|
||||
.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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user