Double page spread share/save options
This commit is contained in:
parent
143d0d2518
commit
481f600056
@ -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].
|
||||
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
Loading…
x
Reference in New Issue
Block a user