mirror of
https://github.com/mihonapp/mihon.git
synced 2024-11-30 04:24:53 -05:00
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:
parent
a69a833716
commit
fcd6fe5d8a
3 changed files with 112 additions and 21 deletions
|
@ -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 cover = presenter.shareCover(activity, coverBitmap)
|
||||||
val uri = cover.getUriCompat(activity)
|
val uri = cover.getUriCompat(activity)
|
||||||
startActivity(Intent.createChooser(uri.toShareIntent(), activity.getString(R.string.action_share)))
|
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)
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCover(context: Context) {
|
return resultBitmap ?: throw Exception("Cover not in cache")
|
||||||
saveCover(getPicturesDir(context))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun saveCover(directory: File): File {
|
/**
|
||||||
val cover = coverCache.getCoverFile(manga) ?: throw Exception("Cover url was null")
|
* Attempt manga cover retrieval from the CoverCache.
|
||||||
if (!cover.exists()) throw Exception("Cover not in cache")
|
*
|
||||||
val type = ImageUtil.findImageType(cover.inputStream())
|
* @return cover as Bitmap or null if CoverCache does not contain cover for manga
|
||||||
?: throw Exception("Not an image")
|
*/
|
||||||
|
private fun coverBitmapFromCoverCache(): Bitmap? {
|
||||||
|
val cover = coverCache.getCoverFile(manga)
|
||||||
|
return if (cover != null) {
|
||||||
|
BitmapFactory.decodeFile(cover.path)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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()
|
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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue