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
This commit is contained in:
E3FxGaming 2021-07-18 19:17:31 +02:00 committed by GitHub
parent a69a833716
commit fcd6fe5d8a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 112 additions and 21 deletions

View file

@ -3,7 +3,10 @@ package eu.kanade.tachiyomi.ui.manga
import android.animation.Animator import android.animation.Animator
import android.animation.AnimatorListenerAdapter import android.animation.AnimatorListenerAdapter
import android.app.Activity import android.app.Activity
import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.Menu import android.view.Menu
@ -20,6 +23,8 @@ import androidx.core.view.isVisible
import androidx.recyclerview.widget.ConcatAdapter import androidx.recyclerview.widget.ConcatAdapter
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import coil.imageLoader
import coil.request.ImageRequest
import com.bluelinelabs.conductor.ControllerChangeHandler import com.bluelinelabs.conductor.ControllerChangeHandler
import com.bluelinelabs.conductor.ControllerChangeType import com.bluelinelabs.conductor.ControllerChangeType
import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton 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() { fun shareCover() {
try { try {
val activity = activity!! val activity = activity!!
val cover = presenter.shareCover(activity) useCoverAsBitmap(activity) { coverBitmap ->
val uri = cover.getUriCompat(activity) val cover = presenter.shareCover(activity, coverBitmap)
startActivity(Intent.createChooser(uri.toShareIntent(), activity.getString(R.string.action_share))) val uri = cover.getUriCompat(activity)
startActivity(
Intent.createChooser(
uri.toShareIntent(),
activity.getString(R.string.action_share)
)
)
}
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
activity?.toast(R.string.error_sharing_cover) activity?.toast(R.string.error_sharing_cover)
@ -675,8 +703,11 @@ class MangaController :
fun saveCover() { fun saveCover() {
try { try {
presenter.saveCover(activity!!) val activity = activity!!
activity?.toast(R.string.cover_saved) useCoverAsBitmap(activity) { coverBitmap ->
presenter.saveCover(activity, coverBitmap)
activity.toast(R.string.cover_saved)
}
} catch (e: Exception) { } catch (e: Exception) {
Timber.e(e) Timber.e(e)
activity?.toast(R.string.error_saving_cover) activity?.toast(R.string.error_saving_cover)

View file

@ -1,8 +1,12 @@
package eu.kanade.tachiyomi.ui.manga package eu.kanade.tachiyomi.ui.manga
import android.content.Context import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import coil.imageLoader
import coil.memory.MemoryCache
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
@ -280,29 +284,85 @@ class MangaPresenter(
moveMangaToCategories(manga, listOfNotNull(category)) 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") * Attempt manga cover retrieval from the Coil ImageLoader memoryCache.
if (!cover.exists()) throw Exception("Cover not in cache") *
val type = ImageUtil.findImageType(cover.inputStream()) * @param context the context used to get the Coil ImageLoader
?: throw Exception("Not an image") * @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() directory.mkdirs()
val filename = DiskUtil.buildValidFilename("${manga.title}.${ImageUtil.ImageType.PNG}")
val filename = DiskUtil.buildValidFilename("${manga.title}.${type.extension}")
val destFile = File(directory, filename) val destFile = File(directory, filename)
cover.inputStream().use { input -> destFile.outputStream().use { desFileOutputStream ->
destFile.outputStream().use { output -> coverBitmap.compress(Bitmap.CompressFormat.PNG, 100, desFileOutputStream)
input.copyTo(output)
}
} }
return destFile return destFile
} }

View file

@ -75,7 +75,7 @@ class CategoryTest {
assertThat(c.id).isNotZero assertThat(c.id).isNotZero
// Add a manga to a category // Add a manga to a category
val m = db.getMangas().executeAsBlocking()[0] val m = db.getLibraryMangas().executeAsBlocking()[0]
val mc = MangaCategory.create(m, c) val mc = MangaCategory.create(m, c)
db.insertMangaCategory(mc).executeAsBlocking() db.insertMangaCategory(mc).executeAsBlocking()