From 1163aa4e4ecfe6f430e7f7c8323c0f64c73d7dbd Mon Sep 17 00:00:00 2001 From: Andreas Date: Sat, 19 Mar 2022 21:46:23 +0100 Subject: [PATCH] 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 --- .../java/eu/kanade/tachiyomi/AppModule.kt | 3 + .../data/notification/NotificationHandler.kt | 5 +- .../kanade/tachiyomi/data/saver/ImageSaver.kt | 143 ++++++++++++++++++ .../tachiyomi/ui/manga/MangaController.kt | 41 +++-- .../tachiyomi/ui/manga/MangaPresenter.kt | 51 ++----- .../tachiyomi/ui/reader/ReaderActivity.kt | 19 +-- .../tachiyomi/ui/reader/ReaderPresenter.kt | 116 +++++++------- .../tachiyomi/ui/reader/SaveImageNotifier.kt | 16 +- .../tachiyomi/util/storage/FileExtensions.kt | 11 +- .../tachiyomi/util/system/IntentExtensions.kt | 3 +- 10 files changed, 265 insertions(+), 143 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt b/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt index 053d4c333b..39c42dd645 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/AppModule.kt +++ b/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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt index 710e08fff1..937744fec2 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationHandler.kt +++ b/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 } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/saver/ImageSaver.kt new file mode 100644 index 0000000000..7ef07da6b0 --- /dev/null +++ b/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 + } + } + } +} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt index 215b3e6cb0..d4d37da6fe 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaController.kt +++ b/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) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 243c606c22..0516370f60 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/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) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 9e8a1b6d7d..ead8a3c024 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/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() * 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))) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt index ce6acd2bc9..fe6aa21aab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPresenter.kt +++ b/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() + 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() + val context = Injekt.get() 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() + val destDir = context.cacheImageDir - 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 */ } - ) + 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() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt index 98e8443040..66f4e160e9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/SaveImageNotifier.kt +++ b/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() diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt index ae26a5fd98..b939eb3016 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/storage/FileExtensions.kt +++ b/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 diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt index 5b159a1c33..ca5794479f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt +++ b/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)