diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt index 06c8f6f2a..7fc1b3cc1 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt @@ -24,6 +24,7 @@ import android.view.animation.AnimationUtils import android.widget.RelativeLayout import android.widget.SeekBar import android.widget.Toast +import androidx.annotation.ColorInt import androidx.core.view.ViewCompat import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible @@ -1201,15 +1202,20 @@ class ReaderActivity : BaseRxActivity() * actions to perform is shown. */ fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) { - // EXH --> + // SY --> try { - // EXH <-- - ReaderPageSheet(this, page, extraPage).show() - // EXH --> + val viewer = viewer as? PagerViewer + ReaderPageSheet( + this, + page, + extraPage, + (viewer !is R2LPagerViewer) xor (viewer?.config?.invertDoublePages ?: false), + viewer?.config?.pageCanvasColor + ).show() } catch (e: WindowManager.BadTokenException) { xLogE("Caught and ignoring reader page sheet launch exception!", e) } - // EXH <-- + // SY <-- } /** @@ -1245,17 +1251,31 @@ class ReaderActivity : BaseRxActivity() presenter.shareImage(page) } + // SY --> + fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { + presenter.shareImages(firstPage, secondPage, isLTR, bg) + } + // SY <-- + /** * Called from the presenter when a page is ready to be shared. It shows Android's default * sharing tool. */ - fun onShareImageResult(file: File, page: ReaderPage) { + fun onShareImageResult(file: File, page: ReaderPage /* SY --> */, secondPage: ReaderPage? = null /* SY <-- */) { val manga = presenter.manga ?: return val chapter = page.chapter.chapter + // SY --> + val text = if (secondPage != null) { + getString(R.string.share_pages_info, manga.title, chapter.name, if (resources.isLTR) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}") + } else { + getString(R.string.share_page_info, manga.title, chapter.name, page.number) + } + // SY <-- + val uri = file.getUriCompat(this) val intent = Intent(Intent.ACTION_SEND).apply { - putExtra(Intent.EXTRA_TEXT, getString(R.string.share_page_info, manga.title, chapter.name, page.number)) + putExtra(Intent.EXTRA_TEXT, /* SY --> */ text /* SY <-- */) putExtra(Intent.EXTRA_STREAM, uri) clipData = ClipData.newRawUri(null, uri) flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION @@ -1272,6 +1292,12 @@ class ReaderActivity : BaseRxActivity() presenter.saveImage(page) } + // SY --> + fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { + presenter.saveImages(firstPage, secondPage, isLTR, bg) + } + // SY <-- + /** * Called from the presenter when a page is saved or fails. It shows a message or logs the * event depending on the [result]. diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt index 2aa27701d..00a951caa 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderPageSheet.kt @@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader import android.view.LayoutInflater import android.view.View +import androidx.core.view.isVisible import com.afollestad.materialdialogs.MaterialDialog import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding @@ -15,7 +16,9 @@ import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog class ReaderPageSheet( private val activity: ReaderActivity, private val page: ReaderPage, - private val extraPage: ReaderPage? = null + private val extraPage: ReaderPage? = null, + private val isLTR: Boolean = false, + private val bg: Int? = null ) : BaseBottomSheetDialog(activity) { private lateinit var binding: ReaderPageSheetBinding @@ -23,9 +26,27 @@ class ReaderPageSheet( override fun createView(inflater: LayoutInflater): View { binding = ReaderPageSheetBinding.inflate(activity.layoutInflater, null, false) - binding.setAsCoverLayout.setOnClickListener { setAsCover() } - binding.shareLayout.setOnClickListener { share() } - binding.saveLayout.setOnClickListener { save() } + binding.setAsCoverLayout.setOnClickListener { setAsCover(page) } + binding.shareLayout.setOnClickListener { share(page) } + binding.saveLayout.setOnClickListener { save(page) } + + if (extraPage != null) { + binding.setAsCoverItem.setText(R.string.action_set_first_page_cover) + binding.shareItem.setText(R.string.action_share_first_page) + binding.saveItem.setText(R.string.action_save_first_page) + + binding.setAsCoverLayoutExtra.isVisible = true + binding.setAsCoverLayoutExtra.setOnClickListener { setAsCover(extraPage) } + binding.shareLayoutExtra.isVisible = true + binding.shareLayoutExtra.setOnClickListener { share(extraPage) } + binding.saveLayoutExtra.isVisible = true + binding.saveLayoutExtra.setOnClickListener { save(extraPage) } + + binding.shareLayoutCombined.isVisible = true + binding.shareLayoutCombined.setOnClickListener { shareCombined() } + binding.saveLayoutCombined.isVisible = true + binding.saveLayoutCombined.setOnClickListener { saveCombined() } + } return binding.root } @@ -33,7 +54,7 @@ class ReaderPageSheet( /** * Sets the image of this page as the cover of the manga. */ - private fun setAsCover() { + private fun setAsCover(page: ReaderPage) { if (page.status != Page.READY) return MaterialDialog(activity) @@ -49,16 +70,26 @@ class ReaderPageSheet( /** * Shares the image of this page with external apps. */ - private fun share() { + private fun share(page: ReaderPage) { activity.shareImage(page) dismiss() } + fun shareCombined() { + activity.shareImages(page, extraPage!!, isLTR, bg!!) + dismiss() + } + /** * Saves the image of this page on external storage. */ - private fun save() { + private fun save(page: ReaderPage) { activity.saveImage(page) dismiss() } + + fun saveCombined() { + activity.saveImages(page, extraPage!!, isLTR, bg!!) + dismiss() + } } 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 92559f913..37228d09e 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,8 +2,10 @@ package eu.kanade.tachiyomi.ui.reader import android.app.Application import android.content.Context +import android.graphics.BitmapFactory import android.os.Bundle import android.os.Environment +import androidx.annotation.ColorInt import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.data.cache.CoverCache @@ -724,6 +726,70 @@ class ReaderPresenter( ) } + // SY --> + fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { + if (firstPage.status != Page.READY) return + if (secondPage.status != Page.READY) return + val manga = manga ?: return + val context = Injekt.get() + + val notifier = SaveImageNotifier(context) + notifier.onClear() + + // Pictures directory. + val destDir = File( + Environment.getExternalStorageDirectory().absolutePath + + File.separator + Environment.DIRECTORY_PICTURES + + File.separator + context.getString(R.string.app_name) + ) + + // Copy file in background. + Observable.fromCallable { saveImages(firstPage, secondPage, isLTR, bg, destDir, manga) } + .doOnNext { file -> + DiskUtil.scanMedia(context, file) + notifier.onComplete(file) + } + .doOnError { notifier.onError(it.message) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, file -> view.onSaveImageResult(SaveImageResult.Success(file)) }, + { view, error -> view.onSaveImageResult(SaveImageResult.Error(error)) } + ) + } + + private fun saveImages(page1: ReaderPage, page2: ReaderPage, isLTR: Boolean, @ColorInt bg: Int, directory: File, manga: Manga): File { + val stream1 = page1.stream!! + ImageUtil.findImageType(stream1) ?: throw Exception("Not an image") + val stream2 = page2.stream!! + ImageUtil.findImageType(stream2) ?: throw Exception("Not an image") + val imageBytes = stream1().readBytes() + val imageBitmap = BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + + val imageBytes2 = stream2().readBytes() + val imageBitmap2 = BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size) + + val stream = ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, bg) + directory.mkdirs() + + val chapter = page1.chapter.chapter + + // Build destination file. + val filenameSuffix = " - ${page1.number}-${page2.number}.jpg" + val filename = DiskUtil.buildValidFilename( + "${manga.title} - ${chapter.name}".takeBytes(MAX_FILE_NAME_BYTES - filenameSuffix.byteSize()) + ) + filenameSuffix + + val destFile = File(directory, filename) + stream.use { input -> + destFile.outputStream().use { output -> + input.copyTo(output) + } + } + return destFile + } + // SY <-- + /** * Shares the image of this [page] and notifies the UI with the path of the file to share. * The image must be first copied to the internal partition because there are many possible @@ -748,6 +814,26 @@ class ReaderPresenter( ) } + // SY --> + fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) { + if (firstPage.status != Page.READY) return + if (secondPage.status != Page.READY) return + val manga = manga ?: return + val context = Injekt.get() + + val destDir = File(context.cacheDir, "shared_image") + + Observable.fromCallable { destDir.deleteRecursively() } // Keep only the last shared file + .map { saveImages(firstPage, secondPage, isLTR, bg, destDir, manga) } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribeFirst( + { view, file -> view.onShareImageResult(file, firstPage, secondPage) }, + { _, _ -> /* Empty */ } + ) + } + // SY <-- + /** * Sets the image of this [page] as cover and notifies the UI of the result. */ diff --git a/app/src/main/res/layout/reader_page_sheet.xml b/app/src/main/res/layout/reader_page_sheet.xml index 83c3ef373..a301a42c7 100644 --- a/app/src/main/res/layout/reader_page_sheet.xml +++ b/app/src/main/res/layout/reader_page_sheet.xml @@ -23,6 +23,7 @@ app:tint="?attr/colorOnBackground" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings_sy.xml b/app/src/main/res/values/strings_sy.xml index 1c41a8f0c..a6c496aaf 100644 --- a/app/src/main/res/values/strings_sy.xml +++ b/app/src/main/res/values/strings_sy.xml @@ -278,6 +278,17 @@ Crop borders Pager Crop borders Continuous Vertical Crop borders Webtoon + Set first page as cover + Set second page as cover + Save first page + Save second page + Share first page + Share second page + Save combined page + Share combined page + + + %1$s: %2$s, pages %3$s Reading webtoon style