replace subscription with job in PagerPageHolder

also more fixes to the auto shifting
This commit is contained in:
Jays2Kings 2023-02-28 02:27:46 -05:00
parent d93f9d6a45
commit 6872616db5
4 changed files with 109 additions and 115 deletions

View file

@ -208,6 +208,8 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
private var indexChapterToShift: Long? = null private var indexChapterToShift: Long? = null
private var lastCropRes = 0 private var lastCropRes = 0
var manuallyShiftedPages = false
private set
val isSplitScreen: Boolean val isSplitScreen: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInMultiWindowMode
@ -593,6 +595,7 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
when (item.itemId) { when (item.itemId) {
R.id.action_shift_double_page -> { R.id.action_shift_double_page -> {
shiftDoublePages() shiftDoublePages()
manuallyShiftedPages = true
} }
else -> return super.onOptionsItemSelected(item) else -> return super.onOptionsItemSelected(item)
} }
@ -600,12 +603,13 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
} }
fun shiftDoublePages(forceShift: Boolean? = null, page: ReaderPage? = null) { fun shiftDoublePages(forceShift: Boolean? = null, page: ReaderPage? = null) {
(viewer as? PagerViewer)?.config?.let { config -> (viewer as? PagerViewer)?.let { pViewer ->
if (forceShift == config.shiftDoublePage) return if (forceShift == pViewer.config.shiftDoublePage) return
config.shiftDoublePage = !config.shiftDoublePage if (page != null && pViewer.getShiftedPage() == page) return
pViewer.config.shiftDoublePage = !pViewer.config.shiftDoublePage
viewModel.state.value.viewerChapters?.let { viewModel.state.value.viewerChapters?.let {
(viewer as? PagerViewer)?.updateShifting(page) pViewer.updateShifting(page)
(viewer as? PagerViewer)?.setChaptersDoubleShift(it) pViewer.setChaptersDoubleShift(it)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
} }
@ -821,6 +825,7 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
binding.chaptersSheet.shiftPageButton.setOnClickListener { binding.chaptersSheet.shiftPageButton.setOnClickListener {
shiftDoublePages() shiftDoublePages()
manuallyShiftedPages = true
} }
binding.readerNav.leftChapter.setOnClickListener { loadAdjacentChapter(false) } binding.readerNav.leftChapter.setOnClickListener { loadAdjacentChapter(false) }
@ -1303,6 +1308,9 @@ class ReaderActivity : BaseActivity<ReaderActivityBinding>() {
binding.chaptersSheet.root.sheetBehavior.isCollapsed() -> View.VISIBLE binding.chaptersSheet.root.sheetBehavior.isCollapsed() -> View.VISIBLE
else -> View.INVISIBLE else -> View.INVISIBLE
} }
if (lastShiftDoubleState == null) {
manuallyShiftedPages = false
}
lastShiftDoubleState = null lastShiftDoubleState = null
viewer?.setChapters(viewerChapters) viewer?.setChapters(viewerChapters)
intentPageNumber?.let { moveToPageIndex(it) } intentPageNumber?.let { moveToPageIndex(it) }

View file

@ -33,22 +33,21 @@ import eu.kanade.tachiyomi.util.system.bottomCutoutInset
import eu.kanade.tachiyomi.util.system.dpToPx import eu.kanade.tachiyomi.util.system.dpToPx
import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.getResourceColor
import eu.kanade.tachiyomi.util.system.isInNightMode import eu.kanade.tachiyomi.util.system.isInNightMode
import eu.kanade.tachiyomi.util.system.launchIO
import eu.kanade.tachiyomi.util.system.launchUI import eu.kanade.tachiyomi.util.system.launchUI
import eu.kanade.tachiyomi.util.system.topCutoutInset import eu.kanade.tachiyomi.util.system.topCutoutInset
import eu.kanade.tachiyomi.util.system.withIOContext
import eu.kanade.tachiyomi.util.system.withUIContext
import eu.kanade.tachiyomi.util.view.backgroundColor import eu.kanade.tachiyomi.util.view.backgroundColor
import eu.kanade.tachiyomi.util.view.isVisibleOnScreen import eu.kanade.tachiyomi.util.view.isVisibleOnScreen
import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.ViewPagerAdapter
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers.Default import kotlinx.coroutines.Dispatchers.Default
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.MainScope import kotlinx.coroutines.MainScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import timber.log.Timber import timber.log.Timber
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.InputStream import java.io.InputStream
@ -117,10 +116,10 @@ class PagerPageHolder(
private var extraProgressJob: Job? = null private var extraProgressJob: Job? = null
/** /**
* Subscription used to read the header of the image. This is needed in order to instantiate * Job used to read the header of the image. This is needed in order to instantiate
* the appropiate image view depending if the image is animated (GIF). * the appropriate image view depending if the image is animated (GIF).
*/ */
private var readImageHeaderSubscription: Subscription? = null private var readImageHeaderJob: Job? = null
private var status = Page.State.READY private var status = Page.State.READY
private var extraStatus = Page.State.READY private var extraStatus = Page.State.READY
@ -197,7 +196,7 @@ class PagerPageHolder(
cancelLoadJob(1) cancelLoadJob(1)
cancelProgressJob(2) cancelProgressJob(2)
cancelLoadJob(2) cancelLoadJob(2)
unsubscribeReadImageHeader() cancelReadImageHeader()
(pageView as? SubsamplingScaleImageView)?.setOnImageEventListener(null) (pageView as? SubsamplingScaleImageView)?.setOnImageEventListener(null)
} }
@ -442,9 +441,9 @@ class PagerPageHolder(
/** /**
* Unsubscribes from the read image header subscription. * Unsubscribes from the read image header subscription.
*/ */
private fun unsubscribeReadImageHeader() { private fun cancelReadImageHeader() {
readImageHeaderSubscription?.unsubscribe() readImageHeaderJob?.cancel()
readImageHeaderSubscription = null readImageHeaderJob = null
} }
/** /**
@ -487,40 +486,35 @@ class PagerPageHolder(
retryButton?.isVisible = false retryButton?.isVisible = false
decodeErrorLayout?.isVisible = false decodeErrorLayout?.isVisible = false
unsubscribeReadImageHeader() cancelReadImageHeader()
val streamFn = page.stream ?: return val streamFn = page.stream ?: return
val streamFn2 = extraPage?.stream val streamFn2 = extraPage?.stream
var openStream: InputStream? = null var openStream: InputStream? = null
readImageHeaderSubscription = Observable readImageHeaderJob = scope.launchIO {
.fromCallable { try {
val stream = streamFn().buffered(16) val stream = streamFn().buffered(16)
val stream2 = streamFn2?.invoke()?.buffered(16) val stream2 = streamFn2?.invoke()?.buffered(16)
openStream = this@PagerPageHolder.mergeOrSplitPages(stream, stream2) openStream = this@PagerPageHolder.mergeOrSplitPages(stream, stream2)
ImageUtil.isAnimatedAndSupported(stream) || val isAnimated = ImageUtil.isAnimatedAndSupported(stream) ||
if (stream2 != null) ImageUtil.isAnimatedAndSupported(stream2) else false (stream2?.let { ImageUtil.isAnimatedAndSupported(stream2) } ?: false)
} withUIContext {
.subscribeOn(Schedulers.io()) if (!isAnimated) {
.observeOn(AndroidSchedulers.mainThread()) if (viewer.config.readerTheme >= 2) {
.doOnNext { isAnimated -> val bgType = getBGType(viewer.config.readerTheme, context)
if (!isAnimated) { if (page.bg != null && page.bgType == bgType) {
if (viewer.config.readerTheme >= 2) { setImage(openStream!!, false, imageConfig)
if (page.bg != null && pageView?.background = page.bg
page.bgType == getBGType(viewer.config.readerTheme, context) + item.hashCode() }
) { // if the user switches to automatic when pages are already cached, the bg needs to be loaded
setImage(openStream!!, false, imageConfig) else {
pageView?.background = page.bg val bytesArray = openStream!!.readBytes()
} val bytesStream = bytesArray.inputStream()
// if the user switches to automatic when pages are already cached, the bg needs to be loaded setImage(bytesStream, false, imageConfig)
else { closeStreams(bytesStream)
val bytesArray = openStream!!.readBytes()
val bytesStream = bytesArray.inputStream()
setImage(bytesStream, false, imageConfig)
bytesStream.close()
scope.launchUI {
try { try {
pageView?.background = setBG(bytesArray) pageView?.background = setBG(bytesArray)
} catch (e: Exception) { } catch (e: Exception) {
@ -528,36 +522,26 @@ class PagerPageHolder(
pageView?.background = ColorDrawable(Color.WHITE) pageView?.background = ColorDrawable(Color.WHITE)
} finally { } finally {
page.bg = pageView?.background page.bg = pageView?.background
page.bgType = getBGType( page.bgType = bgType
viewer.config.readerTheme,
context,
) + item.hashCode()
} }
} }
} else {
setImage(openStream!!, false, imageConfig)
} }
} else { } else {
setImage(openStream!!, false, imageConfig) setImage(openStream!!, true, imageConfig)
} if (viewer.config.readerTheme >= 2 && page.bg != null) {
} else { pageView?.background = page.bg
setImage(openStream!!, true, imageConfig) }
if (viewer.config.readerTheme >= 2 && page.bg != null) {
pageView?.background = page.bg
} }
} }
} } catch (_: Exception) {
// Keep the Rx stream alive to close the input stream only when unsubscribed
.flatMap { Observable.never<Unit>() }
.doOnUnsubscribe {
try { try {
openStream?.close() openStream?.let { closeStreams(it) }
} catch (_: Exception) {} } catch (_: Exception) {
}
} }
.doOnError { }
try {
openStream?.close()
} catch (_: Exception) {}
}
.subscribe({}, {})
} }
private val imageConfig: Config private val imageConfig: Config
@ -716,18 +700,16 @@ class PagerPageHolder(
return decodeLayout return decodeLayout
} }
private fun mergeOrSplitPages(imageStream: InputStream, imageStream2: InputStream?): InputStream { private suspend fun mergeOrSplitPages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
if (ImageUtil.isAnimatedAndSupported(imageStream)) { if (ImageUtil.isAnimatedAndSupported(imageStream)) {
imageStream.reset() withContext(Dispatchers.IO) { imageStream.reset() }
if (page.longPage == null) { if (page.longPage == null) {
page.longPage = true page.longPage = true
if (viewer.config.splitPages || imageStream2 != null) { if (viewer.config.splitPages || imageStream2 != null) {
splitDoublePages() splitDoublePages()
} }
} }
scope.launchUI { withUIContext { progressBar.completeAndFadeOut() }
progressBar.completeAndFadeOut()
}
return imageStream return imageStream
} }
if (page.longPage == true && viewer.config.splitPages) { if (page.longPage == true && viewer.config.splitPages) {
@ -735,7 +717,7 @@ class PagerPageHolder(
val imageBitmap = try { val imageBitmap = try {
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
} catch (e: Exception) { } catch (e: Exception) {
imageStream.close() closeStreams(imageStream)
Timber.e("Cannot split page ${e.message}") Timber.e("Cannot split page ${e.message}")
return imageBytes.inputStream() return imageBytes.inputStream()
} }
@ -756,7 +738,7 @@ class PagerPageHolder(
val imageBitmap = try { val imageBitmap = try {
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
} catch (e: Exception) { } catch (e: Exception) {
imageStream.close() closeStreams(imageStream)
page.longPage = true page.longPage = true
splitDoublePages() splitDoublePages()
Timber.e("Cannot split page ${e.message}") Timber.e("Cannot split page ${e.message}")
@ -765,7 +747,7 @@ class PagerPageHolder(
val height = imageBitmap.height val height = imageBitmap.height
val width = imageBitmap.width val width = imageBitmap.width
return if (height < width) { return if (height < width) {
imageStream.close() closeStreams(imageStream)
page.longPage = true page.longPage = true
splitDoublePages() splitDoublePages()
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages) val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
@ -790,8 +772,7 @@ class PagerPageHolder(
val imageBitmap = try { val imageBitmap = try {
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
} catch (e: Exception) { } catch (e: Exception) {
imageStream2.close() closeStreams(imageStream, imageStream2)
imageStream.close()
page.fullPage = true page.fullPage = true
splitDoublePages() splitDoublePages()
Timber.e("Cannot combine pages ${e.message}") Timber.e("Cannot combine pages ${e.message}")
@ -802,8 +783,7 @@ class PagerPageHolder(
val width = imageBitmap.width val width = imageBitmap.width
if (height < width) { if (height < width) {
imageStream2.close() closeStreams(imageStream, imageStream2)
imageStream.close()
val oldValue = page.fullPage val oldValue = page.fullPage
page.fullPage = true page.fullPage = true
delayPageUpdate { delayPageUpdate {
@ -827,32 +807,37 @@ class PagerPageHolder(
shiftDoublePages(false) shiftDoublePages(false)
return supportHingeIfThere(imageBytes.inputStream()) return supportHingeIfThere(imageBytes.inputStream())
} else if ((page.isEndPage == true) && } else if ((page.isEndPage == true) &&
(if (page.index == 2) !viewer.activity.isFirstPageFull() else true) (if (page.index == 2) !viewer.activity.isFirstPageFull() else true) &&
extraPage?.isEndPage != true
) { ) {
shiftDoublePages(true) shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream()) return supportHingeIfThere(imageBytes.inputStream())
} }
} else if (!viewer.activity.manuallyShiftedPages && page.index == 0 && page.isEndPage == true) {
// if for some reason the first page should be by itself but its not, fix that
shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream())
} }
val imageBytes2 = imageStream2.readBytes() val imageBytes2 = imageStream2.readBytes()
val imageBitmap2 = try { val imageBitmap2 = try {
BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size) BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size)
} catch (e: Exception) { } catch (e: Exception) {
imageStream2.close() closeStreams(imageStream, imageStream2)
imageStream.close()
extraPage?.fullPage = true extraPage?.fullPage = true
page.isolatedPage = true page.isolatedPage = true
splitDoublePages() splitDoublePages()
Timber.e("Cannot combine pages ${e.message}") Timber.e("Cannot combine pages ${e.message}")
return supportHingeIfThere(imageBytes.inputStream()) return supportHingeIfThere(imageBytes.inputStream())
} }
scope.launchUI { progressBar.setProgress(97) } withUIContext { progressBar.setProgress(97) }
val height2 = imageBitmap2.height val height2 = imageBitmap2.height
val width2 = imageBitmap2.width val width2 = imageBitmap2.width
if (height2 < width2) { if (height2 < width2) {
imageStream2.close() closeStreams(imageStream, imageStream2)
imageStream.close()
extraPage?.fullPage = true extraPage?.fullPage = true
page.isolatedPage = true page.isolatedPage = true
splitDoublePages() splitDoublePages()
@ -864,13 +849,12 @@ class PagerPageHolder(
Color.BLACK Color.BLACK
} }
imageStream.close() closeStreams(imageStream, imageStream2)
imageStream2.close()
if (extraPage?.index == 1 && extraPage?.isStartPage == null && extraPage?.fullPage == null) { if (extraPage?.index == 1 && extraPage?.isStartPage == null && extraPage?.fullPage == null) {
extraPage?.isStartPage = ImageUtil.isPagePadded(imageBitmap, rightSide = isLTR) extraPage?.isStartPage = ImageUtil.isPagePadded(imageBitmap, rightSide = isLTR)
if (extraPage?.isStartPage == true) { if (extraPage?.isStartPage == true) {
shiftDoublePages(true) shiftDoublePages(true)
extraPage = null
return supportHingeIfThere(imageBytes.inputStream()) return supportHingeIfThere(imageBytes.inputStream())
} }
} }
@ -885,13 +869,13 @@ class PagerPageHolder(
} }
} }
private fun supportHingeIfThere(imageStream: InputStream): InputStream { private suspend fun supportHingeIfThere(imageStream: InputStream): InputStream {
if (viewer.config.hingeGapSize > 0 && !ImageUtil.isAnimatedAndSupported(imageStream)) { if (viewer.config.hingeGapSize > 0 && !ImageUtil.isAnimatedAndSupported(imageStream)) {
val imageBytes = imageStream.readBytes() val imageBytes = imageStream.readBytes()
val imageBitmap = try { val imageBitmap = try {
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
} catch (e: Exception) { } catch (e: Exception) {
imageStream.close() closeStreams(imageStream)
val wasNotFullPage = page.fullPage != true val wasNotFullPage = page.fullPage != true
page.fullPage = true page.fullPage = true
if (wasNotFullPage) { if (wasNotFullPage) {
@ -917,17 +901,23 @@ class PagerPageHolder(
return imageStream return imageStream
} }
private fun shiftDoublePages(shift: Boolean) { private suspend fun closeStreams(stream1: InputStream?, stream2: InputStream? = null) {
withContext(Dispatchers.IO) {
stream1?.close()
stream2?.close()
}
}
private suspend fun shiftDoublePages(shift: Boolean) {
delayPageUpdate { viewer.activity.shiftDoublePages(shift) } delayPageUpdate { viewer.activity.shiftDoublePages(shift) }
} }
private fun splitDoublePages() { private suspend fun splitDoublePages() {
delayPageUpdate { viewer.splitDoublePages(page) } delayPageUpdate { viewer.splitDoublePages(page) }
} }
private fun delayPageUpdate(callback: () -> Unit) { private suspend fun delayPageUpdate(callback: () -> Unit) {
scope.launchUI { withUIContext {
delay(100)
callback() callback()
if (extraPage?.fullPage == true || page.fullPage == true) { if (extraPage?.fullPage == true || page.fullPage == true) {
extraPage = null extraPage = null
@ -935,13 +925,11 @@ class PagerPageHolder(
} }
} }
companion object { private fun getBGType(readerTheme: Int, context: Context): Int {
fun getBGType(readerTheme: Int, context: Context): Int { return if (readerTheme == 3) {
return if (readerTheme == 3) { if (context.isInNightMode()) 2 else 1
if (context.isInNightMode()) 2 else 1 } else {
} else { 0 + (context.resources.configuration?.orientation ?: 0) * 10
0 + (context.resources.configuration?.orientation ?: 0) * 10 } + item.hashCode()
}
}
} }
} }

View file

@ -167,12 +167,8 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
(oldCurrent?.first as? ReaderPage)?.firstHalf == false (oldCurrent?.first as? ReaderPage)?.firstHalf == false
} else { } else {
oldCurrent?.second == current || oldCurrent?.second == current ||
(current.index + 1) < ( (current.index + 1) <
( (((oldCurrent?.second ?: oldCurrent?.first) as? ReaderPage)?.index ?: 0)
oldCurrent?.second
?: oldCurrent?.first
) as? ReaderPage
)?.index ?: 0
}, },
) )

View file

@ -647,27 +647,29 @@ object ImageUtil {
val right = width - left val right = width - left
val paddedSide = if (rightSide) right else left val paddedSide = if (rightSide) right else left
val unPaddedSide = if (!rightSide) right else left val unPaddedSide = if (!rightSide) right else left
return (1 until 30).all { return (1 until 30).count {
// if all of a side is padded (the left page usually has a white padding on the right when scanned) // if all of a side is padded (the left page usually has a white padding on the right when scanned)
getPixel(paddedSide, (height * (it / 30f)).roundToInt()).isWhiteOrDark(checkWhite) getPixel(paddedSide, (height * (it / 30f)).roundToInt()).isWhiteOrDark(checkWhite)
} && !(1 until 50).all { } >= 27 && !(1 until 50).all {
// and if all of the other side isn't padded // and if all of the other side isn't padded
getPixel(unPaddedSide, (height * (it / 50f)).roundToInt()).isWhiteOrDark(checkWhite) getPixel(unPaddedSide, (height * (it / 50f)).roundToInt()).isWhiteOrDark(checkWhite)
} }
} }
private fun Bitmap.isOneSideMorePadded(rightSide: Boolean, checkWhite: Boolean): Boolean { private fun Bitmap.isOneSideMorePadded(rightSide: Boolean, checkWhite: Boolean): Boolean {
val middle = height / 2 val middle = (height * 0.475).roundToInt()
val paddedSide: (Int) -> Int = { if (rightSide) width - it * 2 else it * 2 } val middle2 = (height * 0.525).roundToInt()
val unPaddedSide: (Int) -> Int = { if (!rightSide) width - it * 2 else it * 2 } val widthFactor = max(1, (width / 400f).roundToInt())
// val pixels = IntArray(100) val paddedSide: (Int) -> Int = { if (!rightSide) width - it * widthFactor else it * widthFactor }
// getPixels(pixels, 0, 2, paddedSide(0), 0) val unPaddedSide: (Int) -> Int = { if (rightSide) width - it * widthFactor else it * widthFactor }
return run stop@{ return run stop@{
(1 until 100).any { (1 until 37).any {
if (!getPixel(paddedSide(it), middle).isWhiteOrDark(checkWhite)) return@stop false if (!getPixel(paddedSide(it), middle).isWhiteOrDark(checkWhite)) return@stop false
!getPixel(unPaddedSide(it), middle).isWhiteOrDark(checkWhite) if (!getPixel(paddedSide(it), middle2).isWhiteOrDark(checkWhite)) return@stop false
!getPixel(unPaddedSide(it), middle).isWhiteOrDark(checkWhite) ||
!getPixel(unPaddedSide(it), middle2).isWhiteOrDark(checkWhite)
} }
} // && getPixels() }
} }
private fun Int.isWhiteOrDark(checkWhite: Boolean): Boolean = private fun Int.isWhiteOrDark(checkWhite: Boolean): Boolean =