mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-11-09 19:55:11 +01:00
Option to save/share combined double pages
This commit is contained in:
parent
443887c89a
commit
2ec4db3c10
@ -12,6 +12,7 @@ import android.widget.ImageView
|
|||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.annotation.DrawableRes
|
import androidx.annotation.DrawableRes
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.core.widget.TextViewCompat
|
import androidx.core.widget.TextViewCompat
|
||||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||||
@ -52,9 +53,19 @@ open class MaterialMenuSheet(
|
|||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.isInNightMode() && !activity.window.decorView.rootWindowInsets.hasSideNavBar()) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && !context.isInNightMode() && !activity.window.decorView.rootWindowInsets.hasSideNavBar()) {
|
||||||
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
window?.decorView?.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
|
||||||
}
|
}
|
||||||
maxHeight?.let {
|
if (maxHeight != null) {
|
||||||
binding.menuScrollView.maxHeight = it + activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
|
binding.menuScrollView.maxHeight = maxHeight + activity.window.decorView.rootWindowInsets.systemWindowInsetBottom
|
||||||
binding.menuScrollView.requestLayout()
|
binding.menuScrollView.requestLayout()
|
||||||
|
} else {
|
||||||
|
binding.titleLayout.viewTreeObserver.addOnGlobalLayoutListener {
|
||||||
|
binding.menuScrollView.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
||||||
|
val fullHeight = activity.window.decorView.height
|
||||||
|
val insets = activity.window.decorView.rootWindowInsets
|
||||||
|
matchConstraintMaxHeight =
|
||||||
|
fullHeight - (insets?.systemWindowInsetTop ?: 0) -
|
||||||
|
binding.titleLayout.height - 26.dpToPx
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.divider.visibleIf(showDivider)
|
binding.divider.visibleIf(showDivider)
|
||||||
|
@ -9,11 +9,14 @@ class MaxHeightScrollView @JvmOverloads constructor(context: Context, attrs: Att
|
|||||||
var maxHeight = -1
|
var maxHeight = -1
|
||||||
|
|
||||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||||
val heightS = if (maxHeight > 0) {
|
var heightS = if (maxHeight > 0) {
|
||||||
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
|
MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
|
||||||
} else {
|
} else {
|
||||||
heightMeasureSpec
|
heightMeasureSpec
|
||||||
}
|
}
|
||||||
|
if (maxHeight < height + (rootWindowInsets?.systemWindowInsetBottom ?: 0)) {
|
||||||
|
heightS = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.AT_MOST)
|
||||||
|
}
|
||||||
super.onMeasure(widthMeasureSpec, heightS)
|
super.onMeasure(widthMeasureSpec, heightS)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -968,6 +968,16 @@ class ReaderActivity :
|
|||||||
2,
|
2,
|
||||||
R.drawable.ic_photo_24dp,
|
R.drawable.ic_photo_24dp,
|
||||||
R.string.set_first_page_as_cover
|
R.string.set_first_page_as_cover
|
||||||
|
),
|
||||||
|
MaterialMenuSheet.MenuSheetItem(
|
||||||
|
6,
|
||||||
|
R.drawable.ic_share_all_outline_24dp,
|
||||||
|
R.string.share_combined_pages
|
||||||
|
),
|
||||||
|
MaterialMenuSheet.MenuSheetItem(
|
||||||
|
7,
|
||||||
|
R.drawable.ic_save_all_outline_24dp,
|
||||||
|
R.string.save_combined_pages
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@ -997,6 +1007,22 @@ class ReaderActivity :
|
|||||||
3 -> extraPage?.let { shareImage(it) }
|
3 -> extraPage?.let { shareImage(it) }
|
||||||
4 -> extraPage?.let { saveImage(it) }
|
4 -> extraPage?.let { saveImage(it) }
|
||||||
5 -> extraPage?.let { showSetCoverPrompt(it) }
|
5 -> extraPage?.let { showSetCoverPrompt(it) }
|
||||||
|
6, 7 -> extraPage?.let { secondPage ->
|
||||||
|
(viewer as? PagerViewer)?.let { viewer ->
|
||||||
|
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
|
||||||
|
val bg =
|
||||||
|
if (viewer.config.readerTheme >= 2 || viewer.config.readerTheme == 0) {
|
||||||
|
Color.WHITE
|
||||||
|
} else {
|
||||||
|
Color.BLACK
|
||||||
|
}
|
||||||
|
if (item == 6) {
|
||||||
|
presenter.shareImages(page, secondPage, isLTR, bg)
|
||||||
|
} else {
|
||||||
|
presenter.saveImages(page, secondPage, isLTR, bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
}.show()
|
}.show()
|
||||||
@ -1051,17 +1077,22 @@ class ReaderActivity :
|
|||||||
* 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, secondPage: ReaderPage? = null) {
|
||||||
val manga = presenter.manga ?: return
|
val manga = presenter.manga ?: return
|
||||||
val chapter = page.chapter.chapter
|
val chapter = page.chapter.chapter
|
||||||
|
|
||||||
val decimalFormat =
|
val decimalFormat =
|
||||||
DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
DecimalFormat("#.###", DecimalFormatSymbols().apply { decimalSeparator = '.' })
|
||||||
|
|
||||||
|
val pageNumber = if (secondPage != null) {
|
||||||
|
getString(R.string.pages_, if (resources.isLTR) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}")
|
||||||
|
} else {
|
||||||
|
getString(R.string.page_, page.number)
|
||||||
|
}
|
||||||
val text = "${manga.title}: ${getString(
|
val text = "${manga.title}: ${getString(
|
||||||
R.string.chapter_,
|
R.string.chapter_,
|
||||||
decimalFormat.format(chapter.chapter_number)
|
decimalFormat.format(chapter.chapter_number)
|
||||||
)}, ${getString(R.string.page_, page.number)}"
|
)}, $pageNumber"
|
||||||
|
|
||||||
val stream = file.getUriCompat(this)
|
val stream = file.getUriCompat(this)
|
||||||
val intent = Intent(Intent.ACTION_SEND).apply {
|
val intent = Intent(Intent.ACTION_SEND).apply {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.ui.reader
|
package eu.kanade.tachiyomi.ui.reader
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
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
|
||||||
@ -32,8 +34,11 @@ import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
|||||||
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
import eu.kanade.tachiyomi.util.storage.DiskUtil
|
||||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
|
import eu.kanade.tachiyomi.util.system.withUIContext
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import rx.Completable
|
import rx.Completable
|
||||||
@ -117,6 +122,8 @@ class ReaderPresenter(
|
|||||||
|
|
||||||
var chapterItems = emptyList<ReaderChapterItem>()
|
var chapterItems = emptyList<ReaderChapterItem>()
|
||||||
|
|
||||||
|
private var scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the presenter is created. It retrieves the saved active chapter if the process
|
* Called when the presenter is created. It retrieves the saved active chapter if the process
|
||||||
* was restored.
|
* was restored.
|
||||||
@ -610,6 +617,40 @@ class ReaderPresenter(
|
|||||||
return destFile
|
return destFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the image of this [page] in the given [directory] and returns the file location.
|
||||||
|
*/
|
||||||
|
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 filename = DiskUtil.buildValidFilename(
|
||||||
|
"${manga.title} - ${chapter.name}".take(225)
|
||||||
|
) + " - ${page1.number}-${page2.number}.jpg"
|
||||||
|
|
||||||
|
val destFile = File(directory, filename)
|
||||||
|
stream.use { input ->
|
||||||
|
destFile.outputStream().use { output ->
|
||||||
|
input.copyTo(output)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
stream.close()
|
||||||
|
return destFile
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the image of this [page] on the pictures directory and notifies the UI of the result.
|
* Saves the image of this [page] on the pictures directory and notifies the UI of the result.
|
||||||
* There's also a notification to allow sharing the image somewhere else or deleting it.
|
* There's also a notification to allow sharing the image somewhere else or deleting it.
|
||||||
@ -644,6 +685,34 @@ class ReaderPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun saveImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||||
|
scope.launch {
|
||||||
|
if (firstPage.status != Page.READY) return@launch
|
||||||
|
if (secondPage.status != Page.READY) return@launch
|
||||||
|
val manga = manga ?: return@launch
|
||||||
|
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 + "Tachiyomi"
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
val file = saveImages(firstPage, secondPage, isLTR, bg, destDir, manga)
|
||||||
|
DiskUtil.scanMedia(context, file)
|
||||||
|
notifier.onComplete(file)
|
||||||
|
withUIContext { view?.onSaveImageResult(SaveImageResult.Success(file)) }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
withUIContext { view?.onSaveImageResult(SaveImageResult.Error(e)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -668,6 +737,25 @@ class ReaderPresenter(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun shareImages(firstPage: ReaderPage, secondPage: ReaderPage, isLTR: Boolean, @ColorInt bg: Int) {
|
||||||
|
scope.launch {
|
||||||
|
if (firstPage.status != Page.READY) return@launch
|
||||||
|
if (secondPage.status != Page.READY) return@launch
|
||||||
|
val manga = manga ?: return@launch
|
||||||
|
val context = Injekt.get<Application>()
|
||||||
|
|
||||||
|
val destDir = File(context.cacheDir, "shared_image")
|
||||||
|
destDir.deleteRecursively()
|
||||||
|
try {
|
||||||
|
val file = saveImages(firstPage, secondPage, isLTR, bg, destDir, manga)
|
||||||
|
withUIContext {
|
||||||
|
view?.onShareImageResult(file, firstPage, secondPage)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
*/
|
*/
|
||||||
|
@ -3,12 +3,9 @@ package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.Bitmap
|
|
||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.Canvas
|
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.graphics.PointF
|
import android.graphics.PointF
|
||||||
import android.graphics.Rect
|
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.view.GestureDetector
|
import android.view.GestureDetector
|
||||||
import android.view.Gravity
|
import android.view.Gravity
|
||||||
@ -54,11 +51,8 @@ import rx.Subscription
|
|||||||
import rx.android.schedulers.AndroidSchedulers
|
import rx.android.schedulers.AndroidSchedulers
|
||||||
import rx.schedulers.Schedulers
|
import rx.schedulers.Schedulers
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.ByteArrayInputStream
|
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -683,36 +677,24 @@ class PagerPageHolder(
|
|||||||
skipExtra = true
|
skipExtra = true
|
||||||
return imageBytes.inputStream()
|
return imageBytes.inputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
val maxHeight = max(height, height2)
|
|
||||||
|
|
||||||
val result = Bitmap.createBitmap(width + width2, max(height, height2), Bitmap.Config.ARGB_8888)
|
|
||||||
val canvas = Canvas(result)
|
|
||||||
canvas.drawColor(if (viewer.config.readerTheme >= 2 || viewer.config.readerTheme == 0) Color.WHITE else Color.BLACK)
|
|
||||||
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
|
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
|
||||||
val upperPart = Rect(
|
val bg = if (viewer.config.readerTheme >= 2 || viewer.config.readerTheme == 0) {
|
||||||
if (isLTR) 0 else width2,
|
Color.WHITE
|
||||||
(maxHeight - imageBitmap.height) / 2,
|
} else {
|
||||||
(if (isLTR) 0 else width2) + imageBitmap.width,
|
Color.BLACK
|
||||||
imageBitmap.height + (maxHeight - imageBitmap.height) / 2
|
}
|
||||||
)
|
|
||||||
canvas.drawBitmap(imageBitmap, imageBitmap.rect, upperPart, null)
|
|
||||||
scope?.launchUI { progressBar.setProgress(98) }
|
|
||||||
val bottomPart = Rect(
|
|
||||||
if (!isLTR) 0 else width,
|
|
||||||
(maxHeight - imageBitmap2.height) / 2,
|
|
||||||
(if (!isLTR) 0 else width) + imageBitmap2.width,
|
|
||||||
imageBitmap2.height + (maxHeight - imageBitmap2.height) / 2
|
|
||||||
)
|
|
||||||
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
|
|
||||||
scope?.launchUI { progressBar.setProgress(99) }
|
|
||||||
|
|
||||||
val output = ByteArrayOutputStream()
|
|
||||||
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
|
||||||
imageStream.close()
|
imageStream.close()
|
||||||
imageStream2.close()
|
imageStream2.close()
|
||||||
scope?.launchUI { progressBar.completeAndFadeOut() }
|
return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, bg) {
|
||||||
return ByteArrayInputStream(output.toByteArray())
|
scope?.launchUI {
|
||||||
|
if (it == 100) {
|
||||||
|
progressBar.completeAndFadeOut()
|
||||||
|
} else {
|
||||||
|
progressBar.setProgress(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun splitDoublePages() {
|
private fun splitDoublePages() {
|
||||||
@ -734,9 +716,6 @@ class PagerPageHolder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val Bitmap.rect: Rect
|
|
||||||
get() = Rect(0, 0, width, height)
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun getBGType(readerTheme: Int, context: Context): Int {
|
fun getBGType(readerTheme: Int, context: Context): Int {
|
||||||
return if (readerTheme == 3) {
|
return if (readerTheme == 3) {
|
||||||
|
@ -3,14 +3,20 @@ package eu.kanade.tachiyomi.util.system
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.graphics.Rect
|
||||||
import android.graphics.drawable.ColorDrawable
|
import android.graphics.drawable.ColorDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.GradientDrawable
|
import android.graphics.drawable.GradientDrawable
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
|
import java.io.ByteArrayInputStream
|
||||||
|
import java.io.ByteArrayOutputStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
object ImageUtil {
|
object ImageUtil {
|
||||||
|
|
||||||
@ -244,6 +250,47 @@ object ImageUtil {
|
|||||||
return ColorDrawable(backgroundColor)
|
return ColorDrawable(backgroundColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun mergeBitmaps(
|
||||||
|
imageBitmap: Bitmap,
|
||||||
|
imageBitmap2: Bitmap,
|
||||||
|
isLTR: Boolean,
|
||||||
|
@ColorInt background: Int = Color.WHITE,
|
||||||
|
progressCallback: ((Int) -> Unit)? = null
|
||||||
|
): ByteArrayInputStream {
|
||||||
|
val height = imageBitmap.height
|
||||||
|
val width = imageBitmap.width
|
||||||
|
val height2 = imageBitmap2.height
|
||||||
|
val width2 = imageBitmap2.width
|
||||||
|
val maxHeight = max(height, height2)
|
||||||
|
val result = Bitmap.createBitmap(width + width2, max(height, height2), Bitmap.Config.ARGB_8888)
|
||||||
|
val canvas = Canvas(result)
|
||||||
|
canvas.drawColor(background)
|
||||||
|
val upperPart = Rect(
|
||||||
|
if (isLTR) 0 else width2,
|
||||||
|
(maxHeight - imageBitmap.height) / 2,
|
||||||
|
(if (isLTR) 0 else width2) + imageBitmap.width,
|
||||||
|
imageBitmap.height + (maxHeight - imageBitmap.height) / 2
|
||||||
|
)
|
||||||
|
canvas.drawBitmap(imageBitmap, imageBitmap.rect, upperPart, null)
|
||||||
|
progressCallback?.invoke(98)
|
||||||
|
val bottomPart = Rect(
|
||||||
|
if (!isLTR) 0 else width,
|
||||||
|
(maxHeight - imageBitmap2.height) / 2,
|
||||||
|
(if (!isLTR) 0 else width) + imageBitmap2.width,
|
||||||
|
imageBitmap2.height + (maxHeight - imageBitmap2.height) / 2
|
||||||
|
)
|
||||||
|
canvas.drawBitmap(imageBitmap2, imageBitmap2.rect, bottomPart, null)
|
||||||
|
progressCallback?.invoke(99)
|
||||||
|
|
||||||
|
val output = ByteArrayOutputStream()
|
||||||
|
result.compress(Bitmap.CompressFormat.JPEG, 100, output)
|
||||||
|
progressCallback?.invoke(100)
|
||||||
|
return ByteArrayInputStream(output.toByteArray())
|
||||||
|
}
|
||||||
|
|
||||||
|
private val Bitmap.rect: Rect
|
||||||
|
get() = Rect(0, 0, width, height)
|
||||||
|
|
||||||
fun Boolean.toInt() = if (this) 1 else 0
|
fun Boolean.toInt() = if (this) 1 else 0
|
||||||
private fun isDark(color: Int): Boolean {
|
private fun isDark(color: Int): Boolean {
|
||||||
return Color.red(color) < 40 && Color.blue(color) < 40 && Color.green(color) < 40 &&
|
return Color.red(color) < 40 && Color.blue(color) < 40 && Color.green(color) < 40 &&
|
||||||
|
9
app/src/main/res/drawable/ic_save_all_outline_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_save_all_outline_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!-- drawable/content_save_all_outline.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/actionBarTintColor">
|
||||||
|
<path android:fillColor="#000" android:pathData="M1 7H3V21H17V23H3C1.9 23 1 22.11 1 21V7M19 1H7C5.89 1 5 1.9 5 3V17C5 18.1 5.89 19 7 19H21C22.1 19 23 18.1 23 17V5L19 1M21 17H7V3H18.17L21 5.83V17M14 10C12.34 10 11 11.34 11 13S12.34 16 14 16 17 14.66 17 13 15.66 10 14 10M8 4H17V8H8V4Z" />
|
||||||
|
</vector>
|
9
app/src/main/res/drawable/ic_share_all_outline_24dp.xml
Normal file
9
app/src/main/res/drawable/ic_share_all_outline_24dp.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<!-- drawable/share_all_outline.xml -->
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:height="24dp"
|
||||||
|
android:width="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24"
|
||||||
|
android:tint="?attr/actionBarTintColor">
|
||||||
|
<path android:fillColor="#000" android:pathData="M13 9.8V10.7L11.3 10.9C8.7 11.3 6.8 12.3 5.4 13.6C7.1 13.1 8.9 12.8 11 12.8H13V14.1L15.2 12L13 9.8M11 5L18 12L11 19V14.9C6 14.9 2.5 16.5 0 20C1 15 4 10 11 9M17 8V5L24 12L17 19V16L21 12" />
|
||||||
|
</vector>
|
@ -284,6 +284,7 @@
|
|||||||
<string name="set_as_default_for_all">Set as default for all</string>
|
<string name="set_as_default_for_all">Set as default for all</string>
|
||||||
<string name="cover_updated">Cover updated</string>
|
<string name="cover_updated">Cover updated</string>
|
||||||
<string name="page_">Page %1$d</string>
|
<string name="page_">Page %1$d</string>
|
||||||
|
<string name="pages_">Pages %1$s</string>
|
||||||
<string name="next_chapter_not_found">Next chapter not found</string>
|
<string name="next_chapter_not_found">Next chapter not found</string>
|
||||||
<string name="decode_image_error">The image could not be decoded</string>
|
<string name="decode_image_error">The image could not be decoded</string>
|
||||||
<string name="use_image_as_cover">Use this image as cover art?</string>
|
<string name="use_image_as_cover">Use this image as cover art?</string>
|
||||||
@ -311,6 +312,9 @@
|
|||||||
<string name="share_second_page">Share second page</string>
|
<string name="share_second_page">Share second page</string>
|
||||||
<string name="save_second_page">Save second page</string>
|
<string name="save_second_page">Save second page</string>
|
||||||
|
|
||||||
|
<string name="share_combined_pages">Share combined pages</string>
|
||||||
|
<string name="save_combined_pages">Save combined pages</string>
|
||||||
|
|
||||||
<!-- Reader settings -->
|
<!-- Reader settings -->
|
||||||
<string name="fullscreen">Fullscreen</string>
|
<string name="fullscreen">Fullscreen</string>
|
||||||
<string name="animate_page_transitions">Animate page transitions</string>
|
<string name="animate_page_transitions">Animate page transitions</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user