Add share and save cover actions (closes #3011)

This commit is contained in:
arkon 2021-06-01 18:36:06 -04:00
parent 9b77dd9a2b
commit 281a3911f6
8 changed files with 117 additions and 41 deletions

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.notification
import android.app.PendingIntent import android.app.PendingIntent
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.ClipData
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.net.Uri 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.DiskUtil
import eu.kanade.tachiyomi.util.storage.getUriCompat import eu.kanade.tachiyomi.util.storage.getUriCompat
import eu.kanade.tachiyomi.util.system.notificationManager import eu.kanade.tachiyomi.util.system.notificationManager
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
@ -130,16 +130,8 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification * @param notificationId id of notification
*/ */
private fun shareImage(context: Context, path: String, notificationId: Int) { 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) dismissNotification(context, notificationId)
// Launch share activity context.startActivity(File(path).getUriCompat(context).toShareIntent())
context.startActivity(intent)
} }
/** /**
@ -150,16 +142,8 @@ class NotificationReceiver : BroadcastReceiver() {
* @param notificationId id of notification * @param notificationId id of notification
*/ */
private fun shareFile(context: Context, uri: Uri, fileMimeType: String, notificationId: Int) { 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) dismissNotification(context, notificationId)
// Launch share activity context.startActivity(uri.toShareIntent())
context.startActivity(sendIntent)
} }
/** /**

View File

@ -76,7 +76,9 @@ import eu.kanade.tachiyomi.util.chapter.NoChaptersException
import eu.kanade.tachiyomi.util.hasCustomCover import eu.kanade.tachiyomi.util.hasCustomCover
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchUI 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.getResourceColor
import eu.kanade.tachiyomi.util.system.toShareIntent
import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.util.system.toast
import eu.kanade.tachiyomi.util.view.getCoordinates import eu.kanade.tachiyomi.util.view.getCoordinates
import eu.kanade.tachiyomi.util.view.shrinkOnScroll 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 R.id.download_custom, R.id.download_unread, R.id.download_all
-> downloadChapters(item.itemId) -> 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_categories -> onCategoriesClick()
R.id.action_edit_cover -> handleChangeCover()
R.id.action_migrate -> migrateManga() R.id.action_migrate -> migrateManga()
} }
return super.onOptionsItemSelected(item) return super.onOptionsItemSelected(item)
@ -640,20 +645,35 @@ class MangaController :
} }
} }
private fun handleChangeCover() { private fun shareCover() {
val manga = manga ?: return try {
if (manga.hasCustomCover(coverCache)) { val activity = activity!!
showEditCoverDialog(manga) val cover = presenter.shareCover(activity)
} else { val uri = cover.getUriCompat(activity)
openMangaCoverPicker(manga) 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() {
* Edit custom cover for selected manga. try {
*/ presenter.saveCover(activity!!)
private fun showEditCoverDialog(manga: Manga) { 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) ChangeMangaCoverDialog(this, manga).showDialog(router)
} else {
openMangaCoverPicker(manga)
}
} }
override fun openMangaCoverPicker(manga: Manga) { override fun openMangaCoverPicker(manga: Manga) {

View File

@ -35,6 +35,10 @@ import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters 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.system.toast
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State
@ -49,6 +53,7 @@ import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File
import java.util.Date import java.util.Date
class MangaPresenter( class MangaPresenter(
@ -275,6 +280,33 @@ class MangaPresenter(
moveMangaToCategories(manga, listOfNotNull(category)) 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. * Update cover with local file.
* *

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.ui.reader
import android.app.Application import android.app.Application
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import com.jakewharton.rxrelay.BehaviorRelay import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache 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.launchIO
import eu.kanade.tachiyomi.util.lang.takeBytes import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.storage.DiskUtil 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.ImageUtil
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
import kotlinx.coroutines.async import kotlinx.coroutines.async
@ -592,9 +593,7 @@ class ReaderPresenter(
notifier.onClear() notifier.onClear()
// Pictures directory. // Pictures directory.
val baseDir = Environment.getExternalStorageDirectory().absolutePath + val baseDir = getPicturesDir(context).absolutePath
File.separator + Environment.DIRECTORY_PICTURES +
File.separator + context.getString(R.string.app_name)
val destDir = if (preferences.folderPerManga()) { val destDir = if (preferences.folderPerManga()) {
File(baseDir + File.separator + manga.title) File(baseDir + File.separator + manga.title)
} else { } else {
@ -628,7 +627,7 @@ class ReaderPresenter(
val manga = manga ?: return val manga = manga ?: return
val context = Injekt.get<Application>() 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 Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file
.map { saveImage(page, destDir, manga) } .map { saveImage(page, destDir, manga) }

View File

@ -3,11 +3,21 @@ package eu.kanade.tachiyomi.util.storage
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.os.Build import android.os.Build
import android.os.Environment
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.core.net.toUri import androidx.core.net.toUri
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.R
import java.io.File 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 * Returns the uri of a file
* *

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

View File

@ -38,13 +38,25 @@
</item> </item>
<item <item
android:id="@+id/action_edit_categories" android:id="@+id/cover_group"
android:title="@string/action_edit_categories" android:title="@string/manga_cover"
app:showAsAction="never" /> 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 <item
android:id="@+id/action_edit_cover" android:id="@+id/action_edit_cover"
android:title="@string/action_edit_cover" android:title="@string/action_edit" />
</menu>
</item>
<item
android:id="@+id/action_edit_categories"
android:title="@string/action_edit_categories"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item

View File

@ -558,6 +558,10 @@
<string name="download_custom">Custom</string> <string name="download_custom">Custom</string>
<string name="download_all">All</string> <string name="download_all">All</string>
<string name="download_unread">Unread</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="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="invalid_download_dir">Invalid download location</string>
<string name="chapter_settings">Chapter settings</string> <string name="chapter_settings">Chapter settings</string>