Эх сурвалжийг харах

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
Andreas 3 жил өмнө
parent
commit
1163aa4e4e

+ 3 - 0
app/src/main/java/eu/kanade/tachiyomi/AppModule.kt

@@ -7,6 +7,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
 import eu.kanade.tachiyomi.data.database.DatabaseHelper
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.saver.ImageSaver
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
 import eu.kanade.tachiyomi.extension.ExtensionManager
@@ -46,6 +47,8 @@ class AppModule(val app: Application) : InjektModule {
 
         addSingletonFactory { DelayedTrackingStore(app) }
 
+        addSingletonFactory { ImageSaver(app) }
+
         // Asynchronously init expensive components for a faster cold start
         ContextCompat.getMainExecutor(app).execute {
             get<PreferencesHelper>()

+ 1 - 4
app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt

@@ -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
         }

+ 143 - 0
app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt

@@ -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
+            }
+        }
+    }
+}

+ 32 - 9
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt

@@ -44,6 +44,8 @@ import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.download.DownloadService
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.saver.Image
+import eu.kanade.tachiyomi.data.saver.Location
 import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 import eu.kanade.tachiyomi.data.track.TrackService
 import eu.kanade.tachiyomi.data.track.model.TrackSearch
@@ -85,7 +87,7 @@ import eu.kanade.tachiyomi.ui.webview.WebViewActivity
 import eu.kanade.tachiyomi.util.chapter.NoChaptersException
 import eu.kanade.tachiyomi.util.hasCustomCover
 import eu.kanade.tachiyomi.util.lang.launchIO
-import eu.kanade.tachiyomi.util.storage.getUriCompat
+import eu.kanade.tachiyomi.util.lang.launchUI
 import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toShareIntent
 import eu.kanade.tachiyomi.util.system.toast
@@ -775,26 +777,47 @@ class MangaController :
 
     fun shareCover() {
         try {
+            val manga = manga!!
             val activity = activity!!
             useCoverAsBitmap(activity) { coverBitmap ->
-                val cover = presenter.shareCover(activity, coverBitmap)
-                val uri = cover.getUriCompat(activity)
-                startActivity(uri.toShareIntent(activity))
+                viewScope.launchIO {
+                    val uri = presenter.saveImage(
+                        image = Image.Cover(
+                            bitmap = coverBitmap,
+                            name = manga.title,
+                            location = Location.Cache
+                        )
+                    )
+                    launchUI {
+                        startActivity(uri.toShareIntent(activity))
+                    }
+                }
             }
-        } catch (e: Exception) {
+        } catch (e: Throwable) {
             logcat(LogPriority.ERROR, e)
-            activity?.toast(R.string.error_sharing_cover)
+            activity?.toast(R.string.error_saving_cover)
         }
     }
 
     fun saveCover() {
         try {
+            val manga = manga!!
             val activity = activity!!
             useCoverAsBitmap(activity) { coverBitmap ->
-                presenter.saveCover(activity, coverBitmap)
-                activity.toast(R.string.cover_saved)
+                viewScope.launchIO {
+                    presenter.saveImage(
+                        image = Image.Cover(
+                            bitmap = coverBitmap,
+                            name = manga.title,
+                            location = Location.Pictures.create()
+                        )
+                    )
+                    launchUI {
+                        activity.toast(R.string.cover_saved)
+                    }
+                }
             }
-        } catch (e: Exception) {
+        } catch (e: Throwable) {
             logcat(LogPriority.ERROR, e)
             activity?.toast(R.string.error_saving_cover)
         }

+ 10 - 41
app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt

@@ -19,6 +19,8 @@ import eu.kanade.tachiyomi.data.database.models.toMangaInfo
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.download.model.Download
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.saver.Image
+import eu.kanade.tachiyomi.data.saver.ImageSaver
 import eu.kanade.tachiyomi.data.track.EnhancedTrackService
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.TrackService
@@ -39,10 +41,6 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
 import eu.kanade.tachiyomi.util.prepUpdateCover
 import eu.kanade.tachiyomi.util.removeCovers
 import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
-import eu.kanade.tachiyomi.util.storage.DiskUtil
-import eu.kanade.tachiyomi.util.storage.getPicturesDir
-import eu.kanade.tachiyomi.util.storage.getTempShareDir
-import eu.kanade.tachiyomi.util.system.ImageUtil
 import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.updateCoverLastModified
@@ -58,7 +56,7 @@ import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
-import java.io.File
+import uy.kohesive.injekt.injectLazy
 import java.util.Date
 
 class MangaPresenter(
@@ -110,6 +108,8 @@ class MangaPresenter(
 
     private val loggedServices by lazy { trackManager.services.filter { it.isLogged } }
 
+    private val imageSaver: ImageSaver by injectLazy()
+
     private var trackSubscription: Subscription? = null
     private var searchTrackerJob: Job? = null
     private var refreshTrackersJob: Job? = null
@@ -338,44 +338,13 @@ class MangaPresenter(
     }
 
     /**
-     * Save manga cover Bitmap to temporary share directory.
+     * Save manga cover Bitmap to picture or 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
+     * @param image the image with specified location
+     * @return flow Flow which emits the Uri which specifies where the image is saved when
      */
-    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}.${ImageUtil.ImageType.PNG}")
-
-        val destFile = File(directory, filename)
-        destFile.outputStream().use { desFileOutputStream ->
-            coverBitmap.compress(Bitmap.CompressFormat.PNG, 100, desFileOutputStream)
-        }
-        return destFile
+    suspend fun saveImage(image: Image): Uri {
+        return imageSaver.save(image)
     }
 
     /**

+ 7 - 12
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt

@@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.ui.reader
 import android.annotation.SuppressLint
 import android.annotation.TargetApi
 import android.app.ProgressDialog
-import android.content.ClipData
 import android.content.Context
 import android.content.Intent
 import android.content.res.ColorStateList
@@ -13,6 +12,7 @@ import android.graphics.ColorMatrix
 import android.graphics.ColorMatrixColorFilter
 import android.graphics.Paint
 import android.graphics.drawable.RippleDrawable
+import android.net.Uri
 import android.os.Build
 import android.os.Bundle
 import android.view.Gravity
@@ -69,13 +69,13 @@ import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
 import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
 import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
 import eu.kanade.tachiyomi.util.preference.toggle
-import eu.kanade.tachiyomi.util.storage.getUriCompat
 import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
 import eu.kanade.tachiyomi.util.system.createReaderThemeContext
 import eu.kanade.tachiyomi.util.system.getThemeColor
 import eu.kanade.tachiyomi.util.system.hasDisplayCutout
 import eu.kanade.tachiyomi.util.system.isNightMode
 import eu.kanade.tachiyomi.util.system.logcat
+import eu.kanade.tachiyomi.util.system.toShareIntent
 import eu.kanade.tachiyomi.util.system.toast
 import eu.kanade.tachiyomi.util.view.copy
 import eu.kanade.tachiyomi.util.view.popupMenu
@@ -89,7 +89,6 @@ import kotlinx.coroutines.flow.sample
 import logcat.LogPriority
 import nucleus.factory.RequiresPresenter
 import uy.kohesive.injekt.injectLazy
-import java.io.File
 import kotlin.math.abs
 import kotlin.math.max
 
@@ -830,18 +829,14 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
      * Called from the presenter when a page is ready to be shared. It shows Android's default
      * sharing tool.
      */
-    fun onShareImageResult(file: File, page: ReaderPage) {
+    fun onShareImageResult(uri: Uri, page: ReaderPage) {
         val manga = presenter.manga ?: return
         val chapter = page.chapter.chapter
 
-        val uri = file.getUriCompat(this)
-        val intent = Intent(Intent.ACTION_SEND).apply {
-            putExtra(Intent.EXTRA_TEXT, getString(R.string.share_page_info, manga.title, chapter.name, page.number))
-            putExtra(Intent.EXTRA_STREAM, uri)
-            clipData = ClipData.newRawUri(null, uri)
-            flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
-            type = "image/*"
-        }
+        val intent = uri.toShareIntent(
+            context = applicationContext,
+            message = getString(R.string.share_page_info, manga.title, chapter.name, page.number)
+        )
         startActivity(Intent.createChooser(intent, getString(R.string.action_share)))
     }
 

+ 58 - 60
app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt

@@ -1,6 +1,7 @@
 package eu.kanade.tachiyomi.ui.reader
 
 import android.app.Application
+import android.net.Uri
 import android.os.Bundle
 import com.jakewharton.rxrelay.BehaviorRelay
 import eu.kanade.tachiyomi.R
@@ -10,6 +11,9 @@ import eu.kanade.tachiyomi.data.database.models.History
 import eu.kanade.tachiyomi.data.database.models.Manga
 import eu.kanade.tachiyomi.data.download.DownloadManager
 import eu.kanade.tachiyomi.data.preference.PreferencesHelper
+import eu.kanade.tachiyomi.data.saver.Image
+import eu.kanade.tachiyomi.data.saver.ImageSaver
+import eu.kanade.tachiyomi.data.saver.Location
 import eu.kanade.tachiyomi.data.track.TrackManager
 import eu.kanade.tachiyomi.data.track.job.DelayedTrackingStore
 import eu.kanade.tachiyomi.data.track.job.DelayedTrackingUpdateJob
@@ -28,11 +32,10 @@ import eu.kanade.tachiyomi.util.chapter.getChapterSort
 import eu.kanade.tachiyomi.util.isLocal
 import eu.kanade.tachiyomi.util.lang.byteSize
 import eu.kanade.tachiyomi.util.lang.launchIO
+import eu.kanade.tachiyomi.util.lang.launchUI
 import eu.kanade.tachiyomi.util.lang.takeBytes
 import eu.kanade.tachiyomi.util.storage.DiskUtil
-import eu.kanade.tachiyomi.util.storage.getPicturesDir
-import eu.kanade.tachiyomi.util.storage.getTempShareDir
-import eu.kanade.tachiyomi.util.system.ImageUtil
+import eu.kanade.tachiyomi.util.storage.cacheImageDir
 import eu.kanade.tachiyomi.util.system.isOnline
 import eu.kanade.tachiyomi.util.system.logcat
 import eu.kanade.tachiyomi.util.updateCoverLastModified
@@ -45,7 +48,7 @@ import rx.android.schedulers.AndroidSchedulers
 import rx.schedulers.Schedulers
 import uy.kohesive.injekt.Injekt
 import uy.kohesive.injekt.api.get
-import java.io.File
+import uy.kohesive.injekt.injectLazy
 import java.util.Date
 import java.util.concurrent.TimeUnit
 
@@ -92,6 +95,8 @@ class ReaderPresenter(
      */
     private val isLoadingAdjacentChapterRelay = BehaviorRelay.create<Boolean>()
 
+    private val imageSaver: ImageSaver by injectLazy()
+
     /**
      * Chapter list for the active manga. It's retrieved lazily and should be accessed for the first
      * time in a background thread to avoid blocking the UI.
@@ -560,32 +565,6 @@ class ReaderPresenter(
             })
     }
 
-    /**
-     * Saves the image of this [page] in the given [directory] and returns the file location.
-     */
-    private fun saveImage(page: ReaderPage, directory: File, manga: Manga): File {
-        val stream = page.stream!!
-        val type = ImageUtil.findImageType(stream) ?: throw Exception("Not an image")
-
-        directory.mkdirs()
-
-        val chapter = page.chapter.chapter
-
-        // Build destination file.
-        val filenameSuffix = " - ${page.number}.${type.extension}"
-        val filename = DiskUtil.buildValidFilename(
-            "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
-        ) + filenameSuffix
-
-        val destFile = File(directory, filename)
-        stream().use { input ->
-            destFile.outputStream().use { output ->
-                input.copyTo(output)
-            }
-        }
-        return destFile
-    }
-
     /**
      * Saves the image of this [page] on the pictures directory and notifies the UI of the result.
      * There's also a notification to allow sharing the image somewhere else or deleting it.
@@ -593,32 +572,42 @@ class ReaderPresenter(
     fun saveImage(page: ReaderPage) {
         if (page.status != Page.READY) return
         val manga = manga ?: return
-        val context = Injekt.get<Application>()
 
+        val context = Injekt.get<Application>()
         val notifier = SaveImageNotifier(context)
         notifier.onClear()
 
+        // Generate filename
+        val chapter = page.chapter.chapter
+        val filenameSuffix = " - ${page.number}"
+        val filename = DiskUtil.buildValidFilename(
+            "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize())
+        ) + filenameSuffix
+
         // Pictures directory.
-        val baseDir = getPicturesDir(context).absolutePath
-        val destDir = if (preferences.folderPerManga()) {
-            File(baseDir + File.separator + DiskUtil.buildValidFilename(manga.title))
-        } else {
-            File(baseDir)
-        }
+        val relativePath = if (preferences.folderPerManga()) DiskUtil.buildValidFilename(manga.title) else ""
 
         // Copy file in background.
-        Observable.fromCallable { saveImage(page, destDir, manga) }
-            .doOnNext { file ->
-                DiskUtil.scanMedia(context, file)
-                notifier.onComplete(file)
+
+        try {
+            presenterScope.launchIO {
+                val uri = imageSaver.save(
+                    image = Image.Page(
+                        inputStream = page.stream!!,
+                        name = filename,
+                        location = Location.Pictures.create(relativePath)
+                    )
+                )
+                launchUI {
+                    DiskUtil.scanMedia(context, uri)
+                    notifier.onComplete(uri)
+                    view!!.onSaveImageResult(SaveImageResult.Success(uri))
+                }
             }
-            .doOnError { notifier.onError(it.message) }
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribeFirst(
-                { view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) },
-                { view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) }
-            )
+        } catch (e: Throwable) {
+            notifier.onError(e.message)
+            view!!.onSaveImageResult(SaveImageResult.Error(e))
+        }
     }
 
     /**
@@ -631,18 +620,27 @@ class ReaderPresenter(
     fun shareImage(page: ReaderPage) {
         if (page.status != Page.READY) return
         val manga = manga ?: return
-        val context = Injekt.get<Application>()
 
-        val destDir = getTempShareDir(context)
-
-        Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file
-            .map { saveImage(page, destDir, manga) }
-            .subscribeOn(Schedulers.io())
-            .observeOn(AndroidSchedulers.mainThread())
-            .subscribeFirst(
-                { view, file -> view.onShareImageResult(file, page) },
-                { _, _ -> /* Empty */ }
-            )
+        val context = Injekt.get<Application>()
+        val destDir = context.cacheImageDir
+
+        try {
+            presenterScope.launchIO {
+                destDir.deleteRecursively()
+                val uri = imageSaver.save(
+                    image = Image.Page(
+                        inputStream = page.stream!!,
+                        name = manga.title,
+                        location = Location.Cache
+                    )
+                )
+                launchUI {
+                    view!!.onShareImageResult(uri, page)
+                }
+            }
+        } catch (e: Throwable) {
+            logcat(LogPriority.ERROR, e)
+        }
     }
 
     /**
@@ -691,7 +689,7 @@ class ReaderPresenter(
      * Results of the save image feature.
      */
     sealed class SaveImageResult {
-        class Success(val file: File) : SaveImageResult()
+        class Success(val uri: Uri) : SaveImageResult()
         class Error(val error: Throwable) : SaveImageResult()
     }
 

+ 8 - 8
app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.ui.reader
 import android.content.Context
 import android.graphics.Bitmap
 import android.graphics.drawable.BitmapDrawable
+import android.net.Uri
 import androidx.core.app.NotificationCompat
 import coil.imageLoader
 import coil.request.CachePolicy
@@ -13,7 +14,6 @@ import eu.kanade.tachiyomi.data.notification.NotificationReceiver
 import eu.kanade.tachiyomi.data.notification.Notifications
 import eu.kanade.tachiyomi.util.system.notificationBuilder
 import eu.kanade.tachiyomi.util.system.notificationManager
-import java.io.File
 
 /**
  * Class used to show BigPictureStyle notifications
@@ -36,14 +36,14 @@ class SaveImageNotifier(private val context: Context) {
      *
      * @param file image file containing downloaded page image.
      */
-    fun onComplete(file: File) {
+    fun onComplete(uri: Uri) {
         val request = ImageRequest.Builder(context)
-            .data(file)
+            .data(uri)
             .memoryCachePolicy(CachePolicy.DISABLED)
             .size(720, 1280)
             .target(
                 onSuccess = { result ->
-                    showCompleteNotification(file, (result as BitmapDrawable).bitmap)
+                    showCompleteNotification(uri, (result as BitmapDrawable).bitmap)
                 },
                 onError = {
                     onError(null)
@@ -53,7 +53,7 @@ class SaveImageNotifier(private val context: Context) {
         context.imageLoader.enqueue(request)
     }
 
-    private fun showCompleteNotification(file: File, image: Bitmap) {
+    private fun showCompleteNotification(uri: Uri, image: Bitmap) {
         with(notificationBuilder) {
             setContentTitle(context.getString(R.string.picture_saved))
             setSmallIcon(R.drawable.ic_photo_24dp)
@@ -64,18 +64,18 @@ class SaveImageNotifier(private val context: Context) {
             // Clear old actions if they exist
             clearActions()
 
-            setContentIntent(NotificationHandler.openImagePendingActivity(context, file))
+            setContentIntent(NotificationHandler.openImagePendingActivity(context, uri))
             // Share action
             addAction(
                 R.drawable.ic_share_24dp,
                 context.getString(R.string.action_share),
-                NotificationReceiver.shareImagePendingBroadcast(context, file.absolutePath, notificationId)
+                NotificationReceiver.shareImagePendingBroadcast(context, uri.path!!, notificationId)
             )
             // Delete action
             addAction(
                 R.drawable.ic_delete_24dp,
                 context.getString(R.string.action_delete),
-                NotificationReceiver.deleteImagePendingBroadcast(context, file.absolutePath, notificationId)
+                NotificationReceiver.deleteImagePendingBroadcast(context, uri.path!!, notificationId)
             )
 
             updateNotification()

+ 2 - 9
app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt

@@ -3,20 +3,13 @@ package eu.kanade.tachiyomi.util.storage
 import android.content.Context
 import android.net.Uri
 import android.os.Build
-import android.os.Environment
 import androidx.core.content.FileProvider
 import androidx.core.net.toUri
 import eu.kanade.tachiyomi.BuildConfig
-import eu.kanade.tachiyomi.R
 import java.io.File
 
-fun getTempShareDir(context: Context) = File(context.cacheDir, "shared_image")
-
-fun getPicturesDir(context: Context) = File(
-    Environment.getExternalStorageDirectory().absolutePath +
-        File.separator + Environment.DIRECTORY_PICTURES +
-        File.separator + context.getString(R.string.app_name)
-)
+val Context.cacheImageDir: File
+    get() = File(cacheDir, "shared_image")
 
 /**
  * Returns the uri of a file

+ 2 - 1
app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt

@@ -6,10 +6,11 @@ import android.content.Intent
 import android.net.Uri
 import eu.kanade.tachiyomi.R
 
-fun Uri.toShareIntent(context: Context, type: String = "image/*"): Intent {
+fun Uri.toShareIntent(context: Context, type: String = "image/*", message: String? = null): Intent {
     val uri = this
 
     val shareIntent = Intent(Intent.ACTION_SEND).apply {
+        if (message != null) putExtra(Intent.EXTRA_TEXT, message)
         putExtra(Intent.EXTRA_STREAM, uri)
         clipData = ClipData.newRawUri(null, uri)
         setType(type)