diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt index 88d620165e..b884490805 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/CoverCache.kt @@ -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)) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/ByteArrayFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/ByteArrayFetcher.kt index 84bab7f640..6cdc4f5b72 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/ByteArrayFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/ByteArrayFetcher.kt @@ -21,7 +21,6 @@ class ByteArrayFetcher : Fetcher { size: Size, options: Options ): FetchResult { - val source = ByteArrayInputStream(data).source().buffer() return SourceResult( source = ByteArrayInputStream(data).source().buffer(), mimeType = "image/gif", diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/CoilSetup.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/CoilSetup.kt index 6ccb44b1e3..b6bdcb206e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/CoilSetup.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/CoilSetup.kt @@ -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()) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/MangaFetcher.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/MangaFetcher.kt index ac0f86cda5..7c469d8490 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/MangaFetcher.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/coil/MangaFetcher.kt @@ -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 { private val sourceManager: SourceManager by injectLazy() private val defaultClient = Injekt.get().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 { + 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 { ) } - 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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index bbd883dad3..8bcc845c63 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -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>) { + private suspend fun showResultNotification(updates: Map>) { val notifications = ArrayList>() 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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt index 748e79144c..44eb38672c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryListHolder.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt index af7c81cccf..f21c2f448b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsController.kt @@ -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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt index d4204cfd7a..fa6f3adae4 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaDetailsPresenter.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt index cec007dbc0..6b49dac3fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaHeaderHolder.kt @@ -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 { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt index f13dedb2fc..b8d5da170b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceGridHolder.kt @@ -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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt index 0b0ad79027..9d309afafe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/source/browse/BrowseSourceListHolder.kt @@ -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