From 281a3911f69b96050bae4b232af94ca3671b92ae Mon Sep 17 00:00:00 2001 From: arkon Date: Tue, 1 Jun 2021 18:36:06 -0400 Subject: [PATCH] Add share and save cover actions (closes #3011) --- .../data/notification/NotificationReceiver.kt | 22 ++-------- .../tachiyomi/ui/manga/MangaController.kt | 44 ++++++++++++++----- .../tachiyomi/ui/manga/MangaPresenter.kt | 32 ++++++++++++++ .../tachiyomi/ui/reader/ReaderPresenter.kt | 9 ++-- .../tachiyomi/util/storage/FileExtensions.kt | 10 +++++ .../tachiyomi/util/system/IntentExtensions.kt | 15 +++++++ app/src/main/res/menu/manga.xml | 22 +++++++--- app/src/main/res/values/strings.xml | 4 ++ 8 files changed, 117 insertions(+), 41 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index ea3a073d78..53b3be48d9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.notification import android.app.PendingIntent import android.content.BroadcastReceiver -import android.content.ClipData import android.content.Context import android.content.Intent import android.net.Uri @@ -25,6 +24,7 @@ import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.notificationManager +import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get @@ -130,16 +130,8 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification */ private fun shareImage(context: Context, path: String, notificationId: Int) { - val intent = Intent(Intent.ACTION_SEND).apply { - val uri = File(path).getUriCompat(context) - putExtra(Intent.EXTRA_STREAM, uri) - clipData = ClipData.newRawUri(null, uri) - type = "image/*" - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } dismissNotification(context, notificationId) - // Launch share activity - context.startActivity(intent) + context.startActivity(File(path).getUriCompat(context).toShareIntent()) } /** @@ -150,16 +142,8 @@ class NotificationReceiver : BroadcastReceiver() { * @param notificationId id of notification */ private fun shareFile(context: Context, uri: Uri, fileMimeType: String, notificationId: Int) { - val sendIntent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_STREAM, uri) - clipData = ClipData.newRawUri(null, uri) - type = fileMimeType - flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION - } - // Dismiss notification dismissNotification(context, notificationId) - // Launch share activity - context.startActivity(sendIntent) + context.startActivity(uri.toShareIntent()) } /** 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 2cb7cc1eb6..7d17283b4d 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 @@ -76,7 +76,9 @@ 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.lang.launchUI +import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.system.getResourceColor +import eu.kanade.tachiyomi.util.system.toShareIntent import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.view.getCoordinates import eu.kanade.tachiyomi.util.view.shrinkOnScroll @@ -400,8 +402,11 @@ class MangaController : R.id.download_custom, R.id.download_unread, R.id.download_all -> downloadChapters(item.itemId) + R.id.action_share_cover -> shareCover() + R.id.action_save_cover -> saveCover() + R.id.action_edit_cover -> changeCover() + R.id.action_edit_categories -> onCategoriesClick() - R.id.action_edit_cover -> handleChangeCover() R.id.action_migrate -> migrateManga() } return super.onOptionsItemSelected(item) @@ -640,20 +645,35 @@ class MangaController : } } - private fun handleChangeCover() { - val manga = manga ?: return - if (manga.hasCustomCover(coverCache)) { - showEditCoverDialog(manga) - } else { - openMangaCoverPicker(manga) + private fun shareCover() { + try { + val activity = activity!! + val cover = presenter.shareCover(activity) + val uri = cover.getUriCompat(activity) + startActivity(Intent.createChooser(uri.toShareIntent(), activity.getString(R.string.action_share))) + } catch (e: Exception) { + Timber.e(e) + activity?.toast(R.string.error_sharing_cover) } } - /** - * Edit custom cover for selected manga. - */ - private fun showEditCoverDialog(manga: Manga) { - ChangeMangaCoverDialog(this, manga).showDialog(router) + private fun saveCover() { + try { + presenter.saveCover(activity!!) + activity?.toast(R.string.cover_saved) + } catch (e: Exception) { + Timber.e(e) + activity?.toast(R.string.error_saving_cover) + } + } + + private fun changeCover() { + val manga = manga ?: return + if (manga.hasCustomCover(coverCache)) { + ChangeMangaCoverDialog(this, manga).showDialog(router) + } else { + openMangaCoverPicker(manga) + } } override fun openMangaCoverPicker(manga: Manga) { 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 cffd365f2c..9ef77d1365 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 @@ -35,6 +35,10 @@ 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.toast import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State @@ -49,6 +53,7 @@ import rx.schedulers.Schedulers import timber.log.Timber import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get +import java.io.File import java.util.Date class MangaPresenter( @@ -275,6 +280,33 @@ class MangaPresenter( moveMangaToCategories(manga, listOfNotNull(category)) } + fun shareCover(context: Context): File { + return saveCover(getTempShareDir(context)) + } + + fun saveCover(context: Context) { + saveCover(getPicturesDir(context)) + } + + private fun saveCover(directory: File): File { + val cover = coverCache.getCoverFile(manga) ?: throw Exception("Cover url was null") + if (!cover.exists()) throw Exception("Cover not in cache") + val type = ImageUtil.findImageType(cover.inputStream()) + ?: throw Exception("Not an image") + + directory.mkdirs() + + val filename = DiskUtil.buildValidFilename("${manga.title}.${type.extension}") + + val destFile = File(directory, filename) + cover.inputStream().use { input -> + destFile.outputStream().use { output -> + input.copyTo(output) + } + } + return destFile + } + /** * Update cover with local file. * 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 fa471c3145..e10d8924e9 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 @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader import android.app.Application import android.os.Bundle -import android.os.Environment import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache @@ -29,6 +28,8 @@ import eu.kanade.tachiyomi.util.lang.byteSize import eu.kanade.tachiyomi.util.lang.launchIO 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.updateCoverLastModified import kotlinx.coroutines.async @@ -592,9 +593,7 @@ class ReaderPresenter( notifier.onClear() // Pictures directory. - val baseDir = Environment.getExternalStorageDirectory().absolutePath + - File.separator + Environment.DIRECTORY_PICTURES + - File.separator + context.getString(R.string.app_name) + val baseDir = getPicturesDir(context).absolutePath val destDir = if (preferences.folderPerManga()) { File(baseDir + File.separator + manga.title) } else { @@ -628,7 +627,7 @@ class ReaderPresenter( val manga = manga ?: return val context = Injekt.get() - val destDir = File(context.cacheDir, "shared_image") + val destDir = getTempShareDir(context) Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file .map { saveImage(page, destDir, manga) } 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 6c7265773b..ae26a5fd98 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,11 +3,21 @@ 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) +) + /** * 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 new file mode 100644 index 0000000000..f5a3abf8f7 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/system/IntentExtensions.kt @@ -0,0 +1,15 @@ +package eu.kanade.tachiyomi.util.system + +import android.content.ClipData +import android.content.Intent +import android.net.Uri + +fun Uri.toShareIntent(): Intent { + val uri = this + return Intent(Intent.ACTION_SEND).apply { + putExtra(Intent.EXTRA_STREAM, uri) + clipData = ClipData.newRawUri(null, uri) + type = "image/*" + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION + } +} diff --git a/app/src/main/res/menu/manga.xml b/app/src/main/res/menu/manga.xml index 8bbd392050..d71888c39a 100644 --- a/app/src/main/res/menu/manga.xml +++ b/app/src/main/res/menu/manga.xml @@ -38,13 +38,25 @@ + android:id="@+id/cover_group" + android:title="@string/manga_cover" + app:showAsAction="never"> + + + + + + Custom All Unread + Cover + Cover saved + Error saving cover + Error sharing cover Are you sure you want to delete the selected chapters? Invalid download location Chapter settings