From fcd6fe5d8a132c1f3773e23777d93b4d73e063ce Mon Sep 17 00:00:00 2001 From: E3FxGaming <8276268+E3FxGaming@users.noreply.github.com> Date: Sun, 18 Jul 2021 19:17:31 +0200 Subject: [PATCH] Fix Cover sharing and saving (#5335) * Fix Cover sharing and saving The newly added manga cover sharing only worked with manga saved to the library (due to the implemented CoverCache only recording covers of library manga). The changes made with this commit fixes that behaviour by implementing a fallback: the cover can now also be retrieved from the Coil memoryCache. * Removal of coil MemoryKey usage No longer uses the coil memory key, instead starts a new Coil request for the cover retrieval. * Removed try-/catch-wrapper and added context-passing useCoverAsBitmap lost its try-/catch-wrapper and doesn't call for the context anymore. Instead shareCover and saveCover now pass their activity as context to useCoverAsBitmap. * Added missing parameter description for useCoverAsBitmap --- .../tachiyomi/ui/manga/MangaController.kt | 41 +++++++-- .../tachiyomi/ui/manga/MangaPresenter.kt | 90 +++++++++++++++---- .../tachiyomi/data/database/CategoryTest.kt | 2 +- 3 files changed, 112 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 8abb8edc2..d63d5e70b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt @@ -3,7 +3,10 @@ package eu.kanade.tachiyomi.ui.manga import android.animation.Animator import android.animation.AnimatorListenerAdapter import android.app.Activity +import android.content.Context import android.content.Intent +import android.graphics.Bitmap +import android.graphics.drawable.BitmapDrawable import android.os.Bundle import android.view.LayoutInflater import android.view.Menu @@ -20,6 +23,8 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import coil.imageLoader +import coil.request.ImageRequest import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeType import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton @@ -661,12 +666,35 @@ class MangaController : } } + /** + * Fetches the cover with Coil, turns it into Bitmap and does something with it (asynchronous) + * @param context The context for building and executing the ImageRequest + * @param coverHandler A function that describes what should be done with the Bitmap + */ + private fun useCoverAsBitmap(context: Context, coverHandler: (Bitmap) -> Unit) { + val req = ImageRequest.Builder(context) + .data(manga) + .target { result -> + val coverBitmap = (result as BitmapDrawable).bitmap + coverHandler(coverBitmap) + } + .build() + context.imageLoader.enqueue(req) + } + fun shareCover() { try { val activity = activity!! - val cover = presenter.shareCover(activity) - val uri = cover.getUriCompat(activity) - startActivity(Intent.createChooser(uri.toShareIntent(), activity.getString(R.string.action_share))) + useCoverAsBitmap(activity) { coverBitmap -> + val cover = presenter.shareCover(activity, coverBitmap) + val uri = cover.getUriCompat(activity) + startActivity( + Intent.createChooser( + uri.toShareIntent(), + activity.getString(R.string.action_share) + ) + ) + } } catch (e: Exception) { Timber.e(e) activity?.toast(R.string.error_sharing_cover) @@ -675,8 +703,11 @@ class MangaController : fun saveCover() { try { - presenter.saveCover(activity!!) - activity?.toast(R.string.cover_saved) + val activity = activity!! + useCoverAsBitmap(activity) { coverBitmap -> + presenter.saveCover(activity, coverBitmap) + activity.toast(R.string.cover_saved) + } } catch (e: Exception) { Timber.e(e) activity?.toast(R.string.error_saving_cover) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 4a873a7ce..116a51d7f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -1,8 +1,12 @@ package eu.kanade.tachiyomi.ui.manga import android.content.Context +import android.graphics.Bitmap +import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle +import coil.imageLoader +import coil.memory.MemoryCache import com.jakewharton.rxrelay.PublishRelay import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.database.DatabaseHelper @@ -280,29 +284,85 @@ class MangaPresenter( moveMangaToCategories(manga, listOfNotNull(category)) } - fun shareCover(context: Context): File { - return saveCover(getTempShareDir(context)) + /** + * Get the manga cover as a Bitmap, either from the CoverCache (only works for library manga) + * or from the Coil ImageLoader cache. + * + * @param context the context used to get the Coil ImageLoader + * @param memoryCacheKey Coil MemoryCache.Key that points to the cover Bitmap cache location + * @return manga cover as Bitmap + */ + fun getCoverBitmap(context: Context, memoryCacheKey: MemoryCache.Key?): Bitmap { + var resultBitmap = coverBitmapFromCoverCache() + if (resultBitmap == null && memoryCacheKey != null) { + resultBitmap = coverBitmapFromImageLoader(context, memoryCacheKey) + } + + return resultBitmap ?: throw Exception("Cover not in cache") } - fun saveCover(context: Context) { - saveCover(getPicturesDir(context)) + /** + * Attempt manga cover retrieval from the CoverCache. + * + * @return cover as Bitmap or null if CoverCache does not contain cover for manga + */ + private fun coverBitmapFromCoverCache(): Bitmap? { + val cover = coverCache.getCoverFile(manga) + return if (cover != null) { + BitmapFactory.decodeFile(cover.path) + } else { + null + } } - private fun saveCover(directory: File): File { - val cover = coverCache.getCoverFile(manga) ?: throw Exception("Cover url was null") - if (!cover.exists()) throw Exception("Cover not in cache") - val type = ImageUtil.findImageType(cover.inputStream()) - ?: throw Exception("Not an image") + /** + * Attempt manga cover retrieval from the Coil ImageLoader memoryCache. + * + * @param context the context used to get the Coil ImageLoader + * @param memoryCacheKey Coil MemoryCache.Key that points to the cover Bitmap cache location + * @return cover as Bitmap or null if there is no thumbnail cached with the memoryCacheKey + */ + private fun coverBitmapFromImageLoader(context: Context, memoryCacheKey: MemoryCache.Key): Bitmap? { + return context.imageLoader.memoryCache[memoryCacheKey] + } + /** + * Save manga cover Bitmap to temporary share directory. + * + * @param context for the temporary share directory + * @param coverBitmap the cover to save (as Bitmap) + * @return cover File in temporary share directory + */ + fun shareCover(context: Context, coverBitmap: Bitmap): File { + return saveCover(getTempShareDir(context), coverBitmap) + } + + /** + * Save manga cover to pictures directory of the device. + * + * @param context for the pictures directory of the user + * @param coverBitmap the cover to save (as Bitmap) + * @return cover File in pictures directory + */ + fun saveCover(context: Context, coverBitmap: Bitmap) { + saveCover(getPicturesDir(context), coverBitmap) + } + + /** + * Save a manga cover Bitmap to a new File in a given directory. + * Overwrites file if it already exists. + * + * @param directory The directory in which the new file will be created + * @param coverBitmap The manga cover to save + * @return the newly created File + */ + private fun saveCover(directory: File, coverBitmap: Bitmap): File { directory.mkdirs() - - val filename = DiskUtil.buildValidFilename("${manga.title}.${type.extension}") + val filename = DiskUtil.buildValidFilename("${manga.title}.${ImageUtil.ImageType.PNG}") val destFile = File(directory, filename) - cover.inputStream().use { input -> - destFile.outputStream().use { output -> - input.copyTo(output) - } + destFile.outputStream().use { desFileOutputStream -> + coverBitmap.compress(Bitmap.CompressFormat.PNG, 100, desFileOutputStream) } return destFile } diff --git a/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt b/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt index 5e5ab0ba3..c24baa1c6 100644 --- a/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt +++ b/app/src/test/java/eu/kanade/tachiyomi/data/database/CategoryTest.kt @@ -75,7 +75,7 @@ class CategoryTest { assertThat(c.id).isNotZero // Add a manga to a category - val m = db.getMangas().executeAsBlocking()[0] + val m = db.getLibraryMangas().executeAsBlocking()[0] val mc = MangaCategory.create(m, c) db.insertMangaCategory(mc).executeAsBlocking()