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 flowpull/6800/head
parent
ddb856edc7
commit
1163aa4e4e
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue