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 8abb8edc22..d63d5e70b8 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 4a873a7cea..116a51d7f1 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 5e5ab0ba34..c24baa1c63 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()