Double page spread share/save options

This commit is contained in:
Jobobby04 2021-05-23 17:30:43 -04:00
parent 143d0d2518
commit 481f600056
5 changed files with 309 additions and 14 deletions

View File

@ -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<ReaderActivityBinding, ReaderPresenter>()
* 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<ReaderActivityBinding, ReaderPresenter>()
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<ReaderActivityBinding, ReaderPresenter>()
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].

View File

@ -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()
}
}

View File

@ -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<Application>()
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<Application>()
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.
*/

View File

@ -23,6 +23,7 @@
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/set_as_cover_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
@ -31,6 +32,33 @@
</LinearLayout>
<LinearLayout
android:id="@+id/set_as_cover_layout_extra"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selectable_item_background"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/ic_photo_24dp"
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/set_as_cover_item_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/action_set_second_page_cover"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/share_layout"
android:layout_width="match_parent"
@ -49,6 +77,7 @@
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/share_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
@ -57,6 +86,60 @@
</LinearLayout>
<LinearLayout
android:id="@+id/share_layout_extra"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selectable_item_background"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/ic_share_24dp"
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/share_item_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/action_share_second_page"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/share_layout_combined"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selectable_item_background"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/ic_share_24dp"
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/share_item_combined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/action_share_combined_page"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/save_layout"
android:layout_width="match_parent"
@ -75,6 +158,7 @@
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/save_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
@ -83,4 +167,61 @@
</LinearLayout>
<LinearLayout
android:id="@+id/save_layout_extra"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selectable_item_background"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/ic_get_app_24dp"
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/save_item_extra"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/action_save_second_page"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
<LinearLayout
android:id="@+id/save_layout_combined"
android:layout_width="match_parent"
android:layout_height="56dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/selectable_item_background"
android:gravity="center"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:visibility="gone">
<ImageView
android:layout_width="24dp"
android:layout_height="24dp"
app:srcCompat="@drawable/ic_get_app_24dp"
app:tint="?attr/colorOnBackground" />
<TextView
android:id="@+id/save_item_combined"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:text="@string/action_save_combined_page"
android:textColor="?attr/colorOnBackground" />
</LinearLayout>
</LinearLayout>

View File

@ -278,6 +278,17 @@
<string name="pref_crop_borders_pager">Crop borders Pager</string>
<string name="pref_crop_borders_continuous_vertical">Crop borders Continuous Vertical</string>
<string name="pref_crop_borders_webtoon">Crop borders Webtoon</string>
<string name="action_set_first_page_cover">Set first page as cover</string>
<string name="action_set_second_page_cover">Set second page as cover</string>
<string name="action_save_first_page">Save first page</string>
<string name="action_save_second_page">Save second page</string>
<string name="action_share_first_page">Share first page</string>
<string name="action_share_second_page">Share second page</string>
<string name="action_save_combined_page">Save combined page</string>
<string name="action_share_combined_page">Share combined page</string>
<!-- Reader Sharing -->
<string name="share_pages_info">%1$s: %2$s, pages %3$s</string>
<!-- Auto Webtoon Mode -->
<string name="eh_auto_webtoon_snack">Reading webtoon style</string>