mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-03 23:58:55 +01:00 
			
		
		
		
	Add share and save cover actions (closes #3011)
This commit is contained in:
		@@ -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())
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -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.
 | 
			
		||||
     *
 | 
			
		||||
 
 | 
			
		||||
@@ -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) }
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user