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.RelativeLayout
import android.widget.SeekBar import android.widget.SeekBar
import android.widget.Toast import android.widget.Toast
import androidx.annotation.ColorInt
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
@ -1201,15 +1202,20 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
* actions to perform is shown. * actions to perform is shown.
*/ */
fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) { fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) {
// EXH --> // SY -->
try { try {
// EXH <-- val viewer = viewer as? PagerViewer
ReaderPageSheet(this, page, extraPage).show() ReaderPageSheet(
// EXH --> this,
page,
extraPage,
(viewer !is R2LPagerViewer) xor (viewer?.config?.invertDoublePages ?: false),
viewer?.config?.pageCanvasColor
).show()
} catch (e: WindowManager.BadTokenException) { } catch (e: WindowManager.BadTokenException) {
xLogE("Caught and ignoring reader page sheet launch exception!", e) xLogE("Caught and ignoring reader page sheet launch exception!", e)
} }
// EXH <-- // SY <--
} }
/** /**
@ -1245,17 +1251,31 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
presenter.shareImage(page) 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 * Called from the presenter when a page is ready to be shared. It shows Android's default
* sharing tool. * 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 manga = presenter.manga ?: return
val chapter = page.chapter.chapter 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 uri = file.getUriCompat(this)
val intent = Intent(Intent.ACTION_SEND).apply { 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) putExtra(Intent.EXTRA_STREAM, uri)
clipData = ClipData.newRawUri(null, uri) clipData = ClipData.newRawUri(null, uri)
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION 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) 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 * Called from the presenter when a page is saved or fails. It shows a message or logs the
* event depending on the [result]. * event depending on the [result].

View File

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.ui.reader
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import androidx.core.view.isVisible
import com.afollestad.materialdialogs.MaterialDialog import com.afollestad.materialdialogs.MaterialDialog
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding import eu.kanade.tachiyomi.databinding.ReaderPageSheetBinding
@ -15,7 +16,9 @@ import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
class ReaderPageSheet( class ReaderPageSheet(
private val activity: ReaderActivity, private val activity: ReaderActivity,
private val page: ReaderPage, 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) { ) : BaseBottomSheetDialog(activity) {
private lateinit var binding: ReaderPageSheetBinding private lateinit var binding: ReaderPageSheetBinding
@ -23,9 +26,27 @@ class ReaderPageSheet(
override fun createView(inflater: LayoutInflater): View { override fun createView(inflater: LayoutInflater): View {
binding = ReaderPageSheetBinding.inflate(activity.layoutInflater, null, false) binding = ReaderPageSheetBinding.inflate(activity.layoutInflater, null, false)
binding.setAsCoverLayout.setOnClickListener { setAsCover() } binding.setAsCoverLayout.setOnClickListener { setAsCover(page) }
binding.shareLayout.setOnClickListener { share() } binding.shareLayout.setOnClickListener { share(page) }
binding.saveLayout.setOnClickListener { save() } 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 return binding.root
} }
@ -33,7 +54,7 @@ class ReaderPageSheet(
/** /**
* Sets the image of this page as the cover of the manga. * 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 if (page.status != Page.READY) return
MaterialDialog(activity) MaterialDialog(activity)
@ -49,16 +70,26 @@ class ReaderPageSheet(
/** /**
* Shares the image of this page with external apps. * Shares the image of this page with external apps.
*/ */
private fun share() { private fun share(page: ReaderPage) {
activity.shareImage(page) activity.shareImage(page)
dismiss() dismiss()
} }
fun shareCombined() {
activity.shareImages(page, extraPage!!, isLTR, bg!!)
dismiss()
}
/** /**
* Saves the image of this page on external storage. * Saves the image of this page on external storage.
*/ */
private fun save() { private fun save(page: ReaderPage) {
activity.saveImage(page) activity.saveImage(page)
dismiss() 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.app.Application
import android.content.Context import android.content.Context
import android.graphics.BitmapFactory
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import androidx.annotation.ColorInt
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
@ -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. * 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 * 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. * 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" /> app:tint="?attr/colorOnBackground" />
<TextView <TextView
android:id="@+id/set_as_cover_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
@ -31,6 +32,33 @@
</LinearLayout> </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 <LinearLayout
android:id="@+id/share_layout" android:id="@+id/share_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -49,6 +77,7 @@
app:tint="?attr/colorOnBackground" /> app:tint="?attr/colorOnBackground" />
<TextView <TextView
android:id="@+id/share_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
@ -57,6 +86,60 @@
</LinearLayout> </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 <LinearLayout
android:id="@+id/save_layout" android:id="@+id/save_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -75,6 +158,7 @@
app:tint="?attr/colorOnBackground" /> app:tint="?attr/colorOnBackground" />
<TextView <TextView
android:id="@+id/save_item"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" android:layout_marginStart="32dp"
@ -83,4 +167,61 @@
</LinearLayout> </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> </LinearLayout>

View File

@ -278,6 +278,17 @@
<string name="pref_crop_borders_pager">Crop borders Pager</string> <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_continuous_vertical">Crop borders Continuous Vertical</string>
<string name="pref_crop_borders_webtoon">Crop borders Webtoon</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 --> <!-- Auto Webtoon Mode -->
<string name="eh_auto_webtoon_snack">Reading webtoon style</string> <string name="eh_auto_webtoon_snack">Reading webtoon style</string>