Share logic for saving page/cover (#6787)

* Use MediaStore on newer Android Q or newer

* Use flow instead of Observable

* Review comment fixes

* Use suspended function instead of flow
This commit is contained in:
Andreas
2022-03-19 21:46:23 +01:00
committed by GitHub
parent ddb856edc7
commit 1163aa4e4e
10 changed files with 265 additions and 143 deletions

View File

@@ -6,8 +6,6 @@ import android.content.Intent
import android.net.Uri
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.storage.getUriCompat
import java.io.File
/**
* Class that manages [PendingIntent] of activity's
@@ -32,9 +30,8 @@ object NotificationHandler {
* @param context context of application
* @param file file containing image
*/
internal fun openImagePendingActivity(context: Context, file: File): PendingIntent {
internal fun openImagePendingActivity(context: Context, uri: Uri): PendingIntent {
val intent = Intent(Intent.ACTION_VIEW).apply {
val uri = file.getUriCompat(context)
setDataAndType(uri, "image/*")
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
}

View File

@@ -0,0 +1,143 @@
package eu.kanade.tachiyomi.data.saver
import android.annotation.SuppressLint
import android.content.ContentValues
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.ImageUtil
import okio.IOException
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.InputStream
class ImageSaver(
val context: Context
) {
@SuppressLint("InlinedApi")
suspend fun save(image: Image): Uri {
val data = image.data
val type = ImageUtil.findImageType(data) ?: throw Exception("Not an image")
val filename = DiskUtil.buildValidFilename("${image.name}.$type")
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return save(data(), image.location.directory(context), filename)
}
if (image.location !is Location.Pictures) {
return save(data(), image.location.directory(context), filename)
}
val pictureDir =
MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY)
val contentValues = ContentValues().apply {
put(MediaStore.Images.Media.DISPLAY_NAME, image.name)
put(
MediaStore.Images.Media.RELATIVE_PATH,
"${Environment.DIRECTORY_PICTURES}/${context.getString(R.string.app_name)}/" +
(image.location as Location.Pictures).relativePath
)
}
val picture = context.contentResolver.insert(
pictureDir,
contentValues
) ?: throw IOException("Couldn't create file")
data().use { input ->
@Suppress("BlockingMethodInNonBlockingContext")
context.contentResolver.openOutputStream(picture, "w").use { output ->
input.copyTo(output!!)
}
}
return picture
}
private fun save(inputStream: InputStream, directory: File, filename: String): Uri {
directory.mkdirs()
val destFile = File(directory, filename)
inputStream.use { input ->
destFile.outputStream().use { output ->
input.copyTo(output)
}
}
return destFile.getUriCompat(context)
}
}
sealed class Image(
open val name: String,
open val location: Location
) {
data class Cover(
val bitmap: Bitmap,
override val name: String,
override val location: Location
) : Image(name, location)
data class Page(
val inputStream: () -> InputStream,
override val name: String,
override val location: Location
) : Image(name, location)
val data: () -> InputStream
get() {
return when (this) {
is Cover -> {
{
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos)
ByteArrayInputStream(baos.toByteArray())
}
}
is Page -> inputStream
}
}
}
sealed class Location {
data class Pictures private constructor(val relativePath: String) : Location() {
companion object {
fun create(relativePath: String = ""): Pictures {
return Pictures(relativePath)
}
}
}
object Cache : Location()
fun directory(context: Context): File {
return when (this) {
Cache -> context.cacheImageDir
is Pictures -> {
val file = File(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES),
context.getString(R.string.app_name)
)
if (relativePath.isNotEmpty()) {
return File(
file,
relativePath
)
}
file
}
}
}
}