Browse Source

Add share and save cover actions (closes #3011)

arkon 3 years ago
parent
commit
281a3911f6

+ 3 - 19
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())
     }
 
     /**

+ 30 - 10
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,22 +645,37 @@ class MangaController :
         }
     }
 
-    private fun handleChangeCover() {
+    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)
+        }
+    }
+
+    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)) {
-            showEditCoverDialog(manga)
+            ChangeMangaCoverDialog(this, manga).showDialog(router)
         } else {
             openMangaCoverPicker(manga)
         }
     }
 
-    /**
-     * Edit custom cover for selected manga.
-     */
-    private fun showEditCoverDialog(manga: Manga) {
-        ChangeMangaCoverDialog(this, manga).showDialog(router)
-    }
-
     override fun openMangaCoverPicker(manga: Manga) {
         if (manga.favorite) {
             val intent = Intent(Intent.ACTION_GET_CONTENT).apply {

+ 32 - 0
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.
      *

+ 4 - 5
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<Application>()
 
-        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) }

+ 10 - 0
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
  *

+ 15 - 0
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
+    }
+}

+ 17 - 5
app/src/main/res/menu/manga.xml

@@ -38,13 +38,25 @@
     </item>
 
     <item
-        android:id="@+id/action_edit_categories"
-        android:title="@string/action_edit_categories"
-        app:showAsAction="never" />
+        android:id="@+id/cover_group"
+        android:title="@string/manga_cover"
+        app:showAsAction="never">
+        <menu>
+            <item
+                android:id="@+id/action_share_cover"
+                android:title="@string/action_share" />
+            <item
+                android:id="@+id/action_save_cover"
+                android:title="@string/action_save" />
+            <item
+                android:id="@+id/action_edit_cover"
+                android:title="@string/action_edit" />
+        </menu>
+    </item>
 
     <item
-        android:id="@+id/action_edit_cover"
-        android:title="@string/action_edit_cover"
+        android:id="@+id/action_edit_categories"
+        android:title="@string/action_edit_categories"
         app:showAsAction="never" />
 
     <item

+ 4 - 0
app/src/main/res/values/strings.xml

@@ -558,6 +558,10 @@
     <string name="download_custom">Custom</string>
     <string name="download_all">All</string>
     <string name="download_unread">Unread</string>
+    <string name="manga_cover">Cover</string>
+    <string name="cover_saved">Cover saved</string>
+    <string name="error_saving_cover">Error saving cover</string>
+    <string name="error_sharing_cover">Error sharing cover</string>
     <string name="confirm_delete_chapters">Are you sure you want to delete the selected chapters?</string>
     <string name="invalid_download_dir">Invalid download location</string>
     <string name="chapter_settings">Chapter settings</string>