mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-17 16:09:16 +01:00
Add share and save cover actions (closes #3011)
This commit is contained in:
parent
9b77dd9a2b
commit
281a3911f6
@ -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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
ChangeMangaCoverDialog(this, manga).showDialog(router)
|
} 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) {
|
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.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.
|
||||||
*
|
*
|
||||||
|
@ -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) }
|
||||||
|
@ -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
|
||||||
*
|
*
|
||||||
|
@ -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>
|
||||||
|
|
||||||
<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
|
||||||
|
android:id="@+id/action_edit_cover"
|
||||||
|
android:title="@string/action_edit" />
|
||||||
|
</menu>
|
||||||
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/action_edit_cover"
|
android:id="@+id/action_edit_categories"
|
||||||
android:title="@string/action_edit_cover"
|
android:title="@string/action_edit_categories"
|
||||||
app:showAsAction="never" />
|
app:showAsAction="never" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user