Double page spread
(cherry picked from commit 7832d1abe1fdcdb962f388e5a86dd3fcecad6712)
This commit is contained in:
@@ -374,4 +374,8 @@ object PreferenceKeys {
|
||||
const val hideUpdatesButton = "pref_hide_updates_button"
|
||||
|
||||
const val hideHistoryButton = "pref_hide_history_button"
|
||||
|
||||
const val pageLayout = "page_layout"
|
||||
|
||||
const val invertDoublePages = "invert_double_pages"
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.track.anilist.Anilist
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||
import eu.kanade.tachiyomi.widget.ExtendedNavigationView
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
@@ -489,4 +490,8 @@ class PreferencesHelper(val context: Context) {
|
||||
fun hideUpdatesButton() = flowPrefs.getBoolean(Keys.hideUpdatesButton, false)
|
||||
|
||||
fun hideHistoryButton() = flowPrefs.getBoolean(Keys.hideHistoryButton, false)
|
||||
|
||||
fun pageLayout() = flowPrefs.getInt(Keys.pageLayout, PagerConfig.PageLayout.AUTOMATIC)
|
||||
|
||||
fun invertDoublePages() = flowPrefs.getBoolean(Keys.invertDoublePages, false)
|
||||
}
|
||||
|
||||
@@ -58,6 +58,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsSheet
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
|
||||
@@ -66,6 +67,7 @@ import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.GLUtil
|
||||
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
|
||||
import eu.kanade.tachiyomi.util.system.isLTR
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import eu.kanade.tachiyomi.util.view.defaultBar
|
||||
import eu.kanade.tachiyomi.util.view.hideBar
|
||||
@@ -114,6 +116,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||
}
|
||||
}
|
||||
|
||||
const val SHIFT_DOUBLE_PAGES = "shiftingDoublePages"
|
||||
const val SHIFTED_PAGE_INDEX = "shiftedPageIndex"
|
||||
const val SHIFTED_CHAP_INDEX = "shiftedChapterIndex"
|
||||
}
|
||||
|
||||
private val preferences: PreferencesHelper by injectLazy()
|
||||
@@ -143,6 +149,10 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
private val autoScrollFlow = MutableSharedFlow<Unit>()
|
||||
private var autoScrollJob: Job? = null
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
private var lastShiftDoubleState: Boolean? = null
|
||||
private var indexPageToShift: Int? = null
|
||||
private var indexChapterToShift: Long? = null
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
@@ -192,6 +202,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
// --> EH
|
||||
ehUtilsVisible = savedInstanceState.getBoolean(::ehUtilsVisible.name)
|
||||
// <-- EH
|
||||
// SY -->
|
||||
lastShiftDoubleState = savedInstanceState.get(SHIFT_DOUBLE_PAGES) as? Boolean
|
||||
indexPageToShift = savedInstanceState.get(SHIFTED_PAGE_INDEX) as? Int
|
||||
indexChapterToShift = savedInstanceState.get(SHIFTED_CHAP_INDEX) as? Long
|
||||
// SY <--
|
||||
}
|
||||
|
||||
config = ReaderConfig()
|
||||
@@ -262,6 +277,18 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
// EXH -->
|
||||
outState.putBoolean(::ehUtilsVisible.name, ehUtilsVisible)
|
||||
// EXH <--
|
||||
// SY -->
|
||||
(viewer as? PagerViewer)?.let { pViewer ->
|
||||
val config = pViewer.config
|
||||
outState.putBoolean(SHIFT_DOUBLE_PAGES, config.shiftDoublePage)
|
||||
if (config.shiftDoublePage && config.doublePages) {
|
||||
pViewer.getShiftedPage()?.let {
|
||||
outState.putInt(SHIFTED_PAGE_INDEX, it.index)
|
||||
outState.putLong(SHIFTED_CHAP_INDEX, it.chapter.chapter.id ?: 0L)
|
||||
}
|
||||
}
|
||||
}
|
||||
// SY <--
|
||||
if (!isChangingConfigurations) {
|
||||
presenter.onSaveInstanceStateNonConfigurationChange()
|
||||
}
|
||||
@@ -543,6 +570,30 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.doublePage) {
|
||||
setTooltip(R.string.page_layout)
|
||||
|
||||
setOnClickListener {
|
||||
if (preferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) {
|
||||
(viewer as? PagerViewer)?.config?.let { config ->
|
||||
config.doublePages = !config.doublePages
|
||||
reloadChapters(config.doublePages, true)
|
||||
}
|
||||
updateBottomButtons()
|
||||
} else {
|
||||
preferences.pageLayout().set(1 - preferences.pageLayout().get())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
with(binding.shiftPageButton) {
|
||||
setTooltip(R.string.shift_double_pages)
|
||||
|
||||
setOnClickListener {
|
||||
shiftDoublePages()
|
||||
}
|
||||
}
|
||||
|
||||
binding.expandEhButton.clicks()
|
||||
.onEach {
|
||||
ehUtilsVisible = !ehUtilsVisible
|
||||
@@ -723,7 +774,8 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
actionReadingMode.isVisible = ReaderBottomButton.ReadingMode.isIn(enabledButtons)
|
||||
actionRotation.isVisible =
|
||||
ReaderBottomButton.Rotation.isIn(enabledButtons)
|
||||
// doublePage.isVisible = viewer is PagerViewer && ReaderBottomButton.PageLayout.isIn(enabledButtons)
|
||||
doublePage.isVisible =
|
||||
viewer is PagerViewer && ReaderBottomButton.PageLayout.isIn(enabledButtons) && !preferences.dualPageSplitPaged().get()
|
||||
actionCropBorders.isVisible =
|
||||
if (viewer is PagerViewer) {
|
||||
ReaderBottomButton.CropBordersPager.isIn(enabledButtons)
|
||||
@@ -739,8 +791,44 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
ReaderBottomButton.WebView.isIn(enabledButtons)
|
||||
actionChapterList.isVisible =
|
||||
ReaderBottomButton.ViewChapters.isIn(enabledButtons)
|
||||
// shiftPageButton.isVisible = ((viewer as? PagerViewer)?.config?.doublePages ?: false) && ReaderBottomButton.ShiftDoublePage.isIn(enabledButtons)
|
||||
// binding.toolbar.menu.findItem(R.id.action_shift_double_page)?.isVisible = ((viewer as? PagerViewer)?.config?.doublePages ?: false) && !ReaderBottomButton.ShiftDoublePage.isIn(enabledButtons)
|
||||
shiftPageButton.isVisible = (viewer as? PagerViewer)?.config?.doublePages ?: false
|
||||
}
|
||||
}
|
||||
|
||||
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
|
||||
val pViewer = viewer as? PagerViewer ?: return
|
||||
pViewer.updateShifting()
|
||||
if (!force && pViewer.config.autoDoublePages) {
|
||||
setDoublePageMode(pViewer)
|
||||
} else {
|
||||
pViewer.config.doublePages = doublePages
|
||||
}
|
||||
val currentChapter = presenter.getCurrentChapter()
|
||||
if (doublePages) {
|
||||
// If we're moving from singe to double, we want the current page to be the first page
|
||||
pViewer.config.shiftDoublePage = (
|
||||
binding.pageSeekbar.progress +
|
||||
(currentChapter?.pages?.take(binding.pageSeekbar.progress)?.count { it.fullPage || it.isolatedPage } ?: 0)
|
||||
) % 2 != 0
|
||||
}
|
||||
presenter.viewerChaptersRelay.value?.let {
|
||||
pViewer.setChaptersDoubleShift(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setDoublePageMode(viewer: PagerViewer) {
|
||||
val currentOrientation = resources.configuration.orientation
|
||||
viewer.config.doublePages = currentOrientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
}
|
||||
|
||||
private fun shiftDoublePages() {
|
||||
(viewer as? PagerViewer)?.config?.let { config ->
|
||||
config.shiftDoublePage = !config.shiftDoublePage
|
||||
presenter.viewerChaptersRelay.value?.let {
|
||||
(viewer as? PagerViewer)?.updateShifting()
|
||||
(viewer as? PagerViewer)?.setChaptersDoubleShift(it)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
// EXH <--
|
||||
@@ -905,6 +993,13 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
binding.viewerContainer.addView(newViewer.getView())
|
||||
|
||||
// SY -->
|
||||
if (newViewer is PagerViewer) {
|
||||
if (preferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) {
|
||||
setDoublePageMode(newViewer)
|
||||
}
|
||||
lastShiftDoubleState?.let { newViewer.config.shiftDoublePage = it }
|
||||
}
|
||||
|
||||
val defaultReaderType = manga.defaultReaderType(manga.mangaType(sourceName = sourceManager.get(manga.source)?.name))
|
||||
if (preferences.useAutoWebtoon().get() && manga.readingModeType == ReadingModeType.DEFAULT.flagValue && defaultReaderType != null && defaultReaderType == ReadingModeType.WEBTOON.prefValue) {
|
||||
readingModeToast?.cancel()
|
||||
@@ -980,6 +1075,25 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
*/
|
||||
fun setChapters(viewerChapters: ViewerChapters) {
|
||||
binding.pleaseWait.isVisible = false
|
||||
// SY -->
|
||||
if (indexChapterToShift != null && indexPageToShift != null) {
|
||||
viewerChapters.currChapter.pages?.find { it.index == indexPageToShift && it.chapter.chapter.id == indexChapterToShift }?.let {
|
||||
(viewer as? PagerViewer)?.updateShifting(it)
|
||||
}
|
||||
indexChapterToShift = null
|
||||
indexPageToShift = null
|
||||
} else if (lastShiftDoubleState != null) {
|
||||
val currentChapter = viewerChapters.currChapter
|
||||
(viewer as? PagerViewer)?.config?.shiftDoublePage = (
|
||||
currentChapter.requestedPage +
|
||||
(
|
||||
currentChapter.pages?.take(currentChapter.requestedPage)
|
||||
?.count { it.fullPage || it.isolatedPage } ?: 0
|
||||
)
|
||||
) % 2 != 0
|
||||
}
|
||||
// SY <--
|
||||
|
||||
viewer?.setChapters(viewerChapters)
|
||||
binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
|
||||
|
||||
@@ -1045,25 +1159,31 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* bottom menu and delegates the change to the presenter.
|
||||
*/
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun onPageSelected(page: ReaderPage) {
|
||||
fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) {
|
||||
val newChapter = presenter.onPageSelected(page)
|
||||
val pages = page.chapter.pages ?: return
|
||||
|
||||
val currentPage = if (hasExtraPage) {
|
||||
if (resources.isLTR) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}"
|
||||
} else {
|
||||
"${page.number}"
|
||||
}
|
||||
|
||||
// Set bottom page number
|
||||
binding.pageNumber.text = "${page.number}/${pages.size}"
|
||||
binding.pageNumber.text = "$currentPage/${pages.size}"
|
||||
// binding.pageText.text = "${page.number}/${pages.size}"
|
||||
|
||||
// Set seekbar page number
|
||||
if (viewer !is R2LPagerViewer) {
|
||||
binding.leftPageText.text = "${page.number}"
|
||||
binding.leftPageText.text = currentPage
|
||||
binding.rightPageText.text = "${pages.size}"
|
||||
} else {
|
||||
binding.rightPageText.text = "${page.number}"
|
||||
binding.rightPageText.text = currentPage
|
||||
binding.leftPageText.text = "${pages.size}"
|
||||
}
|
||||
|
||||
// SY -->
|
||||
binding.abovePageText.text = "${page.number}"
|
||||
binding.abovePageText.text = currentPage
|
||||
binding.belowPageText.text = "${pages.size}"
|
||||
// SY <--
|
||||
|
||||
@@ -1080,11 +1200,11 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
* Called from the viewer whenever a [page] is long clicked. A bottom sheet with a list of
|
||||
* actions to perform is shown.
|
||||
*/
|
||||
fun onPageLongTap(page: ReaderPage) {
|
||||
fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) {
|
||||
// EXH -->
|
||||
try {
|
||||
// EXH <--
|
||||
ReaderPageSheet(this, page).show()
|
||||
ReaderPageSheet(this, page, extraPage).show()
|
||||
// EXH -->
|
||||
} catch (e: WindowManager.BadTokenException) {
|
||||
xLogE("Caught and ignoring reader page sheet launch exception!", e)
|
||||
@@ -1256,6 +1376,27 @@ class ReaderActivity : BaseRxActivity<ReaderActivityBinding, ReaderPresenter>()
|
||||
preferences.grayscale().asFlow()
|
||||
.onEach { setGrayscale(it) }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
preferences.pageLayout().asFlow()
|
||||
.drop(1)
|
||||
.onEach { updateBottomButtons() }
|
||||
.launchIn(lifecycleScope)
|
||||
|
||||
preferences.dualPageSplitPaged().asFlow()
|
||||
.drop(1)
|
||||
.onEach {
|
||||
if (viewer !is PagerViewer) return@onEach
|
||||
updateBottomButtons()
|
||||
reloadChapters(
|
||||
!it && when (preferences.pageLayout().get()) {
|
||||
PagerConfig.PageLayout.DOUBLE_PAGES -> true
|
||||
PagerConfig.PageLayout.AUTOMATIC -> resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
|
||||
else -> false
|
||||
},
|
||||
true
|
||||
)
|
||||
}
|
||||
.launchIn(lifecycleScope)
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -14,7 +14,8 @@ import eu.kanade.tachiyomi.widget.sheet.BaseBottomSheetDialog
|
||||
*/
|
||||
class ReaderPageSheet(
|
||||
private val activity: ReaderActivity,
|
||||
private val page: ReaderPage
|
||||
private val page: ReaderPage,
|
||||
private val extraPage: ReaderPage? = null
|
||||
) : BaseBottomSheetDialog(activity) {
|
||||
|
||||
private lateinit var binding: ReaderPageSheetBinding
|
||||
|
||||
@@ -11,10 +11,21 @@ open class ReaderPage(
|
||||
// SY -->
|
||||
var bg: Drawable? = null,
|
||||
var bgType: Int? = null,
|
||||
/** Value to check if this page is used to as if it was too wide */
|
||||
var shiftedPage: Boolean = false,
|
||||
/** Value to check if a page is can be doubled up, but can't because the next page is too wide */
|
||||
var isolatedPage: Boolean = false,
|
||||
// SY <--
|
||||
var stream: (() -> InputStream)? = null
|
||||
|
||||
) : Page(index, url, imageUrl, null) {
|
||||
|
||||
open lateinit var chapter: ReaderChapter
|
||||
|
||||
/** Value to check if a page is too wide to be doubled up */
|
||||
var fullPage: Boolean = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) shiftedPage = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,8 +11,7 @@ enum class ReaderBottomButton(val value: String, @StringRes val stringRes: Int)
|
||||
CropBordersPager("cbp", R.string.pref_crop_borders_pager),
|
||||
CropBordersContinuesVertical("cbc", R.string.pref_crop_borders_continuous_vertical),
|
||||
CropBordersWebtoon("cbw", R.string.pref_crop_borders_webtoon),
|
||||
// PageLayout("pl", R.string.page_layout),
|
||||
// ShiftDoublePage("sdp", R.string.shift_double_pages)
|
||||
PageLayout("pl", R.string.page_layout),
|
||||
;
|
||||
|
||||
fun isIn(buttons: Collection<String>) = value in buttons
|
||||
@@ -22,7 +21,8 @@ enum class ReaderBottomButton(val value: String, @StringRes val stringRes: Int)
|
||||
ViewChapters,
|
||||
WebView,
|
||||
CropBordersPager,
|
||||
CropBordersContinuesVertical
|
||||
CropBordersContinuesVertical,
|
||||
PageLayout
|
||||
).map { it.value }.toSet()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
|
||||
|
||||
// SY -->
|
||||
binding.pagerPrefsGroup.pageTransitionsPager.bindToPreference(preferences.pageTransitionsPager())
|
||||
binding.pagerPrefsGroup.pageLayout.bindToPreference(preferences.pageLayout())
|
||||
// SY <--
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||
|
||||
import android.graphics.Color
|
||||
import androidx.annotation.ColorInt
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerConfig
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation
|
||||
@@ -28,6 +30,8 @@ class PagerConfig(
|
||||
|
||||
var dualPageSplitChangedListener: ((Boolean) -> Unit)? = null
|
||||
|
||||
var reloadChapterListener: ((Boolean) -> Unit)? = null
|
||||
|
||||
var imageScaleType = 1
|
||||
private set
|
||||
|
||||
@@ -39,6 +43,23 @@ class PagerConfig(
|
||||
|
||||
// SY -->
|
||||
var usePageTransitions = false
|
||||
|
||||
var shiftDoublePage = false
|
||||
|
||||
var doublePages = preferences.pageLayout().get() == PageLayout.DOUBLE_PAGES && !preferences.dualPageSplitPaged().get()
|
||||
set(value) {
|
||||
field = value
|
||||
if (!value) {
|
||||
shiftDoublePage = false
|
||||
}
|
||||
}
|
||||
|
||||
var invertDoublePages = false
|
||||
|
||||
var autoDoublePages = preferences.pageLayout().get() == PageLayout.AUTOMATIC
|
||||
|
||||
@ColorInt
|
||||
var pageCanvasColor = Color.WHITE
|
||||
// SY <--
|
||||
|
||||
init {
|
||||
@@ -79,6 +100,33 @@ class PagerConfig(
|
||||
// SY -->
|
||||
preferences.pageTransitionsPager()
|
||||
.register({ usePageTransitions = it }, { imagePropertyChangedListener?.invoke() })
|
||||
preferences.readerTheme()
|
||||
.register(
|
||||
{
|
||||
themeToColor(it)
|
||||
},
|
||||
{
|
||||
themeToColor(it)
|
||||
reloadChapterListener?.invoke(doublePages)
|
||||
}
|
||||
)
|
||||
preferences.pageLayout()
|
||||
.register(
|
||||
{
|
||||
autoDoublePages = it == PageLayout.AUTOMATIC
|
||||
if (!autoDoublePages) {
|
||||
doublePages = it == PageLayout.DOUBLE_PAGES && dualPageSplit == false
|
||||
}
|
||||
},
|
||||
{
|
||||
autoDoublePages = it == PageLayout.AUTOMATIC
|
||||
if (!autoDoublePages) {
|
||||
doublePages = it == PageLayout.DOUBLE_PAGES && dualPageSplit == false
|
||||
}
|
||||
reloadChapterListener?.invoke(doublePages)
|
||||
}
|
||||
)
|
||||
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -126,4 +174,18 @@ class PagerConfig(
|
||||
enum class ZoomType {
|
||||
Left, Center, Right
|
||||
}
|
||||
|
||||
object PageLayout {
|
||||
const val SINGLE_PAGE = 0
|
||||
const val DOUBLE_PAGES = 1
|
||||
const val AUTOMATIC = 2
|
||||
}
|
||||
|
||||
fun themeToColor(theme: Int) {
|
||||
pageCanvasColor = when (theme) {
|
||||
1 -> Color.BLACK
|
||||
2 -> 0x202125
|
||||
else -> Color.WHITE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package eu.kanade.tachiyomi.ui.reader.viewer.pager
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.PointF
|
||||
import android.graphics.drawable.Animatable
|
||||
import android.view.GestureDetector
|
||||
@@ -27,16 +28,22 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressBar
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig.ZoomType
|
||||
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.system.ImageUtil
|
||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import java.io.InputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
/**
|
||||
* View of the ViewPager that contains a page of a chapter.
|
||||
@@ -44,14 +51,15 @@ import java.util.concurrent.TimeUnit
|
||||
@SuppressLint("ViewConstructor")
|
||||
class PagerPageHolder(
|
||||
val viewer: PagerViewer,
|
||||
val page: ReaderPage
|
||||
val page: ReaderPage,
|
||||
private var extraPage: ReaderPage? = null
|
||||
) : FrameLayout(viewer.activity), ViewPagerAdapter.PositionableView {
|
||||
|
||||
/**
|
||||
* Item that identifies this view. Needed by the adapter to not recreate views.
|
||||
*/
|
||||
override val item
|
||||
get() = page
|
||||
get() = page to extraPage
|
||||
|
||||
/**
|
||||
* Loading progress bar to indicate the current progress.
|
||||
@@ -88,14 +96,34 @@ class PagerPageHolder(
|
||||
*/
|
||||
private var progressSubscription: Subscription? = null
|
||||
|
||||
/**
|
||||
* Subscription for status changes of the page.
|
||||
*/
|
||||
private var extraStatusSubscription: Subscription? = null
|
||||
|
||||
/**
|
||||
* Subscription for progress changes of the page.
|
||||
*/
|
||||
private var extraProgressSubscription: Subscription? = null
|
||||
|
||||
/**
|
||||
* Subscription 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).
|
||||
*/
|
||||
private var readImageHeaderSubscription: Subscription? = null
|
||||
|
||||
// SY -->
|
||||
var status: Int = 0
|
||||
var extraStatus: Int = 0
|
||||
var progress: Int = 0
|
||||
var extraProgress: Int = 0
|
||||
private var skipExtra = false
|
||||
var scope: CoroutineScope? = null
|
||||
// SY <--
|
||||
|
||||
init {
|
||||
addView(progressBar)
|
||||
scope = CoroutineScope(Job() + Dispatchers.Default)
|
||||
observeStatus()
|
||||
}
|
||||
|
||||
@@ -105,8 +133,10 @@ class PagerPageHolder(
|
||||
@SuppressLint("ClickableViewAccessibility")
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
unsubscribeProgress()
|
||||
unsubscribeStatus()
|
||||
unsubscribeProgress(1)
|
||||
unsubscribeProgress(2)
|
||||
unsubscribeStatus(1)
|
||||
unsubscribeStatus(2)
|
||||
unsubscribeReadImageHeader()
|
||||
subsamplingImageView?.setOnImageEventListener(null)
|
||||
}
|
||||
@@ -122,7 +152,19 @@ class PagerPageHolder(
|
||||
val loader = page.chapter.pageLoader ?: return
|
||||
statusSubscription = loader.getPage(page)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { processStatus(it) }
|
||||
.subscribe {
|
||||
status = it
|
||||
processStatus(it)
|
||||
}
|
||||
|
||||
val extraPage = extraPage ?: return
|
||||
val loader2 = extraPage.chapter.pageLoader ?: return
|
||||
extraStatusSubscription = loader2.getPage(extraPage)
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe {
|
||||
extraStatus = it
|
||||
processStatus2(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -139,6 +181,20 @@ class PagerPageHolder(
|
||||
.subscribe { value -> progressBar.setProgress(value) }
|
||||
}
|
||||
|
||||
private fun observeProgress2() {
|
||||
extraProgressSubscription?.unsubscribe()
|
||||
val extraPage = extraPage ?: return
|
||||
extraProgressSubscription = Observable.interval(100, TimeUnit.MILLISECONDS)
|
||||
.map { extraPage.progress }
|
||||
.distinctUntilChanged()
|
||||
.onBackpressureLatest()
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe { value ->
|
||||
extraProgress = value
|
||||
progressBar.setProgress(((progress + extraProgress) / 2 * 0.95f).roundToInt())
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the status of the page changes.
|
||||
*
|
||||
@@ -153,12 +209,40 @@ class PagerPageHolder(
|
||||
setDownloading()
|
||||
}
|
||||
Page.READY -> {
|
||||
setImage()
|
||||
unsubscribeProgress()
|
||||
if (extraStatus == Page.READY || extraPage == null) {
|
||||
setImage()
|
||||
}
|
||||
unsubscribeProgress(1)
|
||||
}
|
||||
Page.ERROR -> {
|
||||
setError()
|
||||
unsubscribeProgress()
|
||||
unsubscribeProgress(1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the status of the page changes.
|
||||
*
|
||||
* @param status the new status of the page.
|
||||
*/
|
||||
private fun processStatus2(status: Int) {
|
||||
when (status) {
|
||||
Page.QUEUE -> setQueued()
|
||||
Page.LOAD_PAGE -> setLoading()
|
||||
Page.DOWNLOAD_IMAGE -> {
|
||||
observeProgress2()
|
||||
setDownloading()
|
||||
}
|
||||
Page.READY -> {
|
||||
if (this.status == Page.READY) {
|
||||
setImage()
|
||||
}
|
||||
unsubscribeProgress(2)
|
||||
}
|
||||
Page.ERROR -> {
|
||||
setError()
|
||||
unsubscribeProgress(2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -166,17 +250,19 @@ class PagerPageHolder(
|
||||
/**
|
||||
* Unsubscribes from the status subscription.
|
||||
*/
|
||||
private fun unsubscribeStatus() {
|
||||
statusSubscription?.unsubscribe()
|
||||
statusSubscription = null
|
||||
private fun unsubscribeStatus(page: Int) {
|
||||
val subscription = if (page == 1) statusSubscription else extraStatusSubscription
|
||||
subscription?.unsubscribe()
|
||||
if (page == 1) statusSubscription = null else extraStatusSubscription = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribes from the progress subscription.
|
||||
*/
|
||||
private fun unsubscribeProgress() {
|
||||
progressSubscription?.unsubscribe()
|
||||
progressSubscription = null
|
||||
private fun unsubscribeProgress(page: Int) {
|
||||
val subscription = if (page == 1) progressSubscription else extraProgressSubscription
|
||||
subscription?.unsubscribe()
|
||||
if (page == 1) progressSubscription = null else extraProgressSubscription = null
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -219,18 +305,31 @@ class PagerPageHolder(
|
||||
*/
|
||||
private fun setImage() {
|
||||
progressBar.isVisible = true
|
||||
progressBar.completeAndFadeOut()
|
||||
progressBar.isVisible = true
|
||||
if (extraPage == null) {
|
||||
progressBar.completeAndFadeOut()
|
||||
} else {
|
||||
progressBar.setProgress(95)
|
||||
}
|
||||
retryButton?.isVisible = false
|
||||
decodeErrorLayout?.isVisible = false
|
||||
|
||||
unsubscribeReadImageHeader()
|
||||
val streamFn = page.stream ?: return
|
||||
val streamFn2 = extraPage?.stream
|
||||
|
||||
var openStream: InputStream? = null
|
||||
readImageHeaderSubscription = Observable
|
||||
.fromCallable {
|
||||
val stream = streamFn().buffered(16)
|
||||
openStream = process(item, stream)
|
||||
// SY -->
|
||||
val stream2 = if (extraPage != null) streamFn2?.invoke()?.buffered(16) else null
|
||||
openStream = if (viewer.config.dualPageSplit) {
|
||||
process(item.first, stream)
|
||||
} else {
|
||||
mergePages(stream, stream2)
|
||||
}
|
||||
// SY <--
|
||||
|
||||
ImageUtil.findImageType(stream) == ImageUtil.ImageType.GIF
|
||||
}
|
||||
@@ -273,6 +372,81 @@ class PagerPageHolder(
|
||||
return splitInHalf(imageStream)
|
||||
}
|
||||
|
||||
private fun mergePages(imageStream: InputStream, imageStream2: InputStream?): InputStream {
|
||||
imageStream2 ?: return imageStream
|
||||
if (page.fullPage) return imageStream
|
||||
if (ImageUtil.findImageType(imageStream) == ImageUtil.ImageType.GIF) {
|
||||
page.fullPage = true
|
||||
skipExtra = true
|
||||
return imageStream
|
||||
} else if (ImageUtil.findImageType(imageStream2) == ImageUtil.ImageType.GIF) {
|
||||
page.isolatedPage = true
|
||||
extraPage?.fullPage = true
|
||||
skipExtra = true
|
||||
return imageStream
|
||||
}
|
||||
val imageBytes = imageStream.readBytes()
|
||||
val imageBitmap = try {
|
||||
BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
|
||||
} catch (e: Exception) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
page.fullPage = true
|
||||
skipExtra = true
|
||||
Timber.e("Cannot combine pages ${e.message}")
|
||||
return imageBytes.inputStream()
|
||||
}
|
||||
scope?.launchUI { progressBar.setProgress(96) }
|
||||
val height = imageBitmap.height
|
||||
val width = imageBitmap.width
|
||||
|
||||
if (height < width) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
page.fullPage = true
|
||||
skipExtra = true
|
||||
return imageBytes.inputStream()
|
||||
}
|
||||
|
||||
val imageBytes2 = imageStream2.readBytes()
|
||||
val imageBitmap2 = try {
|
||||
BitmapFactory.decodeByteArray(imageBytes2, 0, imageBytes2.size)
|
||||
} catch (e: Exception) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
extraPage?.fullPage = true
|
||||
skipExtra = true
|
||||
page.isolatedPage = true
|
||||
Timber.e("Cannot combine pages ${e.message}")
|
||||
return imageBytes.inputStream()
|
||||
}
|
||||
scope?.launchUI { progressBar.setProgress(97) }
|
||||
val height2 = imageBitmap2.height
|
||||
val width2 = imageBitmap2.width
|
||||
|
||||
if (height2 < width2) {
|
||||
imageStream2.close()
|
||||
imageStream.close()
|
||||
extraPage?.fullPage = true
|
||||
page.isolatedPage = true
|
||||
skipExtra = true
|
||||
return imageBytes.inputStream()
|
||||
}
|
||||
val isLTR = (viewer !is R2LPagerViewer).xor(viewer.config.invertDoublePages)
|
||||
|
||||
imageStream.close()
|
||||
imageStream2.close()
|
||||
return ImageUtil.mergeBitmaps(imageBitmap, imageBitmap2, isLTR, viewer.config.pageCanvasColor) {
|
||||
scope?.launchUI {
|
||||
if (it == 100) {
|
||||
progressBar.completeAndFadeOut()
|
||||
} else {
|
||||
progressBar.setProgress(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun splitInHalf(imageStream: InputStream): InputStream {
|
||||
var side = when {
|
||||
viewer is L2RPagerViewer && page is InsertPage -> ImageUtil.Side.RIGHT
|
||||
|
||||
@@ -72,6 +72,16 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
}
|
||||
}
|
||||
|
||||
private var pagerListener = object : ViewPager.SimpleOnPageChangeListener() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
onPageChange(position)
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
isIdle = state == ViewPager.SCROLL_STATE_IDLE
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
pager.isVisible = false // Don't layout the pager yet
|
||||
pager.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
|
||||
@@ -79,15 +89,9 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
pager.id = R.id.reader_pager
|
||||
pager.adapter = adapter
|
||||
pager.addOnPageChangeListener(
|
||||
object : ViewPager.SimpleOnPageChangeListener() {
|
||||
override fun onPageSelected(position: Int) {
|
||||
onPageChange(position)
|
||||
}
|
||||
|
||||
override fun onPageScrollStateChanged(state: Int) {
|
||||
isIdle = state == ViewPager.SCROLL_STATE_IDLE
|
||||
}
|
||||
}
|
||||
// SY -->
|
||||
pagerListener
|
||||
// SY <--
|
||||
)
|
||||
pager.tapListener = f@{ event ->
|
||||
if (!config.tappingEnabled) {
|
||||
@@ -107,9 +111,11 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
}
|
||||
pager.longTapListener = f@{
|
||||
if (activity.menuVisible || config.longTapEnabled) {
|
||||
val item = adapter.items.getOrNull(pager.currentItem)
|
||||
if (item is ReaderPage) {
|
||||
activity.onPageLongTap(item)
|
||||
val item = adapter.joinedItems.getOrNull(pager.currentItem)
|
||||
val firstPage = item?.first as? ReaderPage
|
||||
val secondPage = item?.second as? ReaderPage
|
||||
if (firstPage is ReaderPage) {
|
||||
activity.onPageLongTap(firstPage, secondPage)
|
||||
return@f true
|
||||
}
|
||||
}
|
||||
@@ -122,6 +128,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
}
|
||||
}
|
||||
|
||||
config.reloadChapterListener = {
|
||||
activity.reloadChapters(it)
|
||||
}
|
||||
|
||||
config.imagePropertyChangedListener = {
|
||||
refreshAdapter()
|
||||
}
|
||||
@@ -153,13 +163,13 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
* Called when a new page (either a [ReaderPage] or [ChapterTransition]) is marked as active
|
||||
*/
|
||||
private fun onPageChange(position: Int) {
|
||||
val page = adapter.items.getOrNull(position)
|
||||
val page = adapter.joinedItems.getOrNull(position)
|
||||
if (page != null && currentPage != page) {
|
||||
val allowPreload = checkAllowPreload(page as? ReaderPage)
|
||||
currentPage = page
|
||||
when (page) {
|
||||
is ReaderPage -> onReaderPageSelected(page, allowPreload)
|
||||
is ChapterTransition -> onTransitionSelected(page)
|
||||
val allowPreload = checkAllowPreload(page.first as? ReaderPage)
|
||||
currentPage = page.first
|
||||
when (val aPage = page.first) {
|
||||
is ReaderPage -> onReaderPageSelected(aPage, allowPreload, page.second != null)
|
||||
is ChapterTransition -> onTransitionSelected(aPage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,10 +197,10 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
* Called when a [ReaderPage] is marked as active. It notifies the
|
||||
* activity of the change and requests the preload of the next chapter if this is the last page.
|
||||
*/
|
||||
private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean) {
|
||||
private fun onReaderPageSelected(page: ReaderPage, allowPreload: Boolean, hasExtraPage: Boolean) {
|
||||
val pages = page.chapter.pages ?: return
|
||||
Timber.d("onReaderPageSelected: ${page.number}/${pages.size}")
|
||||
activity.onPageSelected(page)
|
||||
activity.onPageSelected(page, hasExtraPage)
|
||||
|
||||
// Skip preload on inserts it causes unwanted page jumping
|
||||
if (page is InsertPage) {
|
||||
@@ -240,7 +250,7 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
*/
|
||||
private fun setChaptersInternal(chapters: ViewerChapters) {
|
||||
Timber.d("setChaptersInternal")
|
||||
val forceTransition = config.alwaysShowChapterTransition || adapter.items.getOrNull(pager.currentItem) is ChapterTransition
|
||||
val forceTransition = config.alwaysShowChapterTransition || adapter.joinedItems.getOrNull(pager.currentItem)?.first is ChapterTransition
|
||||
adapter.setChapters(chapters, forceTransition)
|
||||
|
||||
// Layout the pager once a chapter is being set
|
||||
@@ -257,13 +267,21 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
*/
|
||||
override fun moveToPage(page: ReaderPage) {
|
||||
Timber.d("moveToPage ${page.number}")
|
||||
val position = adapter.items.indexOf(page)
|
||||
val position = adapter.joinedItems.indexOfFirst { it.first == page || it.second == page }
|
||||
if (position != -1) {
|
||||
val currentPosition = pager.currentItem
|
||||
pager.setCurrentItem(position, true)
|
||||
// manually call onPageChange since ViewPager listener is not triggered in this case
|
||||
if (currentPosition == position) {
|
||||
onPageChange(position)
|
||||
} else {
|
||||
// Call this since with double shift onPageChange wont get called (it shouldn't)
|
||||
// Instead just update the page count in ui
|
||||
val joinedItem = adapter.joinedItems.firstOrNull { it.first == page || it.second == page }
|
||||
activity.onPageSelected(
|
||||
joinedItem?.first as? ReaderPage ?: page,
|
||||
joinedItem?.second != null
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Timber.d("Page $page not found in adapter")
|
||||
@@ -399,4 +417,22 @@ abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
|
||||
private fun cleanupPageSplit() {
|
||||
adapter.cleanupPageSplit()
|
||||
}
|
||||
|
||||
// SY -->
|
||||
fun setChaptersDoubleShift(chapters: ViewerChapters) {
|
||||
// Remove Listener since we're about to change the size of the items
|
||||
// If we don't the size change could put us on a new chapter
|
||||
pager.removeOnPageChangeListener(pagerListener)
|
||||
setChaptersInternal(chapters)
|
||||
pager.addOnPageChangeListener(pagerListener)
|
||||
// Since we removed the listener while shifting, call page change to update the ui
|
||||
onPageChange(pager.currentItem)
|
||||
}
|
||||
|
||||
fun updateShifting(page: ReaderPage? = null) {
|
||||
adapter.pageToShift = page ?: adapter.joinedItems.getOrNull(pager.currentItem)?.first as? ReaderPage
|
||||
}
|
||||
|
||||
fun getShiftedPage(): ReaderPage? = adapter.pageToShift
|
||||
// SY <--
|
||||
}
|
||||
|
||||
+175
-19
@@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.hasMissingChapters
|
||||
import eu.kanade.tachiyomi.widget.ViewPagerAdapter
|
||||
import timber.log.Timber
|
||||
import kotlin.math.max
|
||||
|
||||
/**
|
||||
* Pager adapter used by this [viewer] to where [ViewerChapters] updates are posted.
|
||||
@@ -17,11 +18,16 @@ import timber.log.Timber
|
||||
class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
|
||||
/**
|
||||
* List of currently set items.
|
||||
* Paired list of currently set items.
|
||||
*/
|
||||
var items: MutableList<Any> = mutableListOf()
|
||||
var joinedItems: MutableList<Pair<Any, Any?>> = mutableListOf()
|
||||
private set
|
||||
|
||||
/**
|
||||
* Single list of items
|
||||
*/
|
||||
private var subItems: MutableList<Any> = mutableListOf()
|
||||
|
||||
/**
|
||||
* Holds preprocessed items so they don't get removed when changing chapter
|
||||
*/
|
||||
@@ -32,6 +38,15 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
|
||||
var currentChapter: ReaderChapter? = null
|
||||
|
||||
// SY -->
|
||||
/** Page used to start the shifted pages */
|
||||
var pageToShift: ReaderPage? = null
|
||||
|
||||
/** Varibles used to check if config of the pages have changed */
|
||||
private var shifted = viewer.config.shiftDoublePage
|
||||
private var doubledUp = viewer.config.doublePages
|
||||
// SY <--
|
||||
|
||||
/**
|
||||
* Updates this adapter with the given [chapters]. It handles setting a few pages of the
|
||||
* next/previous chapter to allow seamless transitions and inverting the pages if the viewer
|
||||
@@ -102,15 +117,20 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
}
|
||||
|
||||
// Resets double-page splits, else insert pages get misplaced
|
||||
items.filterIsInstance<InsertPage>().also { items.removeAll(it) }
|
||||
|
||||
if (viewer is R2LPagerViewer) {
|
||||
newItems.reverse()
|
||||
}
|
||||
subItems.filterIsInstance<InsertPage>().also { subItems.removeAll(it) }
|
||||
|
||||
preprocessed = mutableMapOf()
|
||||
items = newItems
|
||||
notifyDataSetChanged()
|
||||
subItems = newItems.toMutableList()
|
||||
|
||||
var useSecondPage = false
|
||||
if (shifted != viewer.config.shiftDoublePage || (doubledUp != viewer.config.doublePages && doubledUp)) {
|
||||
if (shifted && (doubledUp == viewer.config.doublePages)) {
|
||||
useSecondPage = true
|
||||
}
|
||||
shifted = viewer.config.shiftDoublePage
|
||||
}
|
||||
doubledUp = viewer.config.doublePages
|
||||
setJoinedItems(useSecondPage)
|
||||
|
||||
// Will skip insert page otherwise
|
||||
if (insertPageLastPage != null) {
|
||||
@@ -122,15 +142,17 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
* Returns the amount of items of the adapter.
|
||||
*/
|
||||
override fun getCount(): Int {
|
||||
return items.size
|
||||
return joinedItems.size
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new view for the item at the given [position].
|
||||
*/
|
||||
override fun createView(container: ViewGroup, position: Int): View {
|
||||
return when (val item = items[position]) {
|
||||
is ReaderPage -> PagerPageHolder(viewer, item)
|
||||
val item = joinedItems[position].first
|
||||
val item2 = joinedItems[position].second
|
||||
return when (item) {
|
||||
is ReaderPage -> PagerPageHolder(viewer, item, item2 as? ReaderPage)
|
||||
is ChapterTransition -> PagerTransitionHolder(viewer, item)
|
||||
else -> throw NotImplementedError("Holder for ${item.javaClass} not implemented")
|
||||
}
|
||||
@@ -141,7 +163,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
*/
|
||||
override fun getItemPosition(view: Any): Int {
|
||||
if (view is PositionableView) {
|
||||
val position = items.indexOf(view.item)
|
||||
val position = joinedItems.indexOf(view.item)
|
||||
if (position != -1) {
|
||||
return position
|
||||
} else {
|
||||
@@ -154,7 +176,7 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
fun onPageSplit(currentPage: Any?, newPage: InsertPage) {
|
||||
if (currentPage !is ReaderPage) return
|
||||
|
||||
val currentIndex = items.indexOf(currentPage)
|
||||
val currentIndex = joinedItems.indexOfFirst { it.first == currentPage }
|
||||
|
||||
// Put aside preprocessed pages for next chapter so they don't get removed when changing chapter
|
||||
if (currentPage.chapter.chapter.id != currentChapter?.chapter?.id) {
|
||||
@@ -169,23 +191,157 @@ class PagerViewerAdapter(private val viewer: PagerViewer) : ViewPagerAdapter() {
|
||||
}
|
||||
|
||||
// It will enter a endless cycle of insert pages
|
||||
if (viewer is R2LPagerViewer && placeAtIndex - 1 >= 0 && items[placeAtIndex - 1] is InsertPage) {
|
||||
if (viewer is R2LPagerViewer && placeAtIndex - 1 >= 0 && joinedItems[placeAtIndex - 1].first is InsertPage) {
|
||||
return
|
||||
}
|
||||
|
||||
// Same here it will enter a endless cycle of insert pages
|
||||
if (items[placeAtIndex] is InsertPage) {
|
||||
if (joinedItems[placeAtIndex].first is InsertPage) {
|
||||
return
|
||||
}
|
||||
|
||||
items.add(placeAtIndex, newPage)
|
||||
joinedItems.add(placeAtIndex, newPage to null)
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun cleanupPageSplit() {
|
||||
val insertPages = items.filterIsInstance(InsertPage::class.java)
|
||||
items.removeAll(insertPages)
|
||||
val insertPages = joinedItems.filter { it.first is InsertPage }
|
||||
joinedItems.removeAll(insertPages)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
// SY -->
|
||||
|
||||
private fun setJoinedItems(useSecondPage: Boolean = false) {
|
||||
val oldCurrent = joinedItems.getOrNull(viewer.pager.currentItem)
|
||||
if (!viewer.config.doublePages) {
|
||||
// If not in double mode, set up items like before
|
||||
subItems.forEach {
|
||||
(it as? ReaderPage)?.shiftedPage = false
|
||||
}
|
||||
this.joinedItems = subItems.map { Pair<Any, Any?>(it, null) }.toMutableList()
|
||||
if (viewer is R2LPagerViewer) {
|
||||
joinedItems.reverse()
|
||||
}
|
||||
} else {
|
||||
val pagedItems = mutableListOf<MutableList<ReaderPage?>>()
|
||||
val otherItems = mutableListOf<Any>()
|
||||
pagedItems.add(mutableListOf())
|
||||
// Step 1: segment the pages and transition pages
|
||||
subItems.forEach {
|
||||
if (it is ReaderPage) {
|
||||
pagedItems.last().add(it)
|
||||
} else {
|
||||
otherItems.add(it)
|
||||
pagedItems.add(mutableListOf())
|
||||
}
|
||||
}
|
||||
var pagedIndex = 0
|
||||
val subJoinedItems = mutableListOf<Pair<Any, Any?>>()
|
||||
// Step 2: run through each set of pages
|
||||
pagedItems.forEach { items ->
|
||||
|
||||
items.forEach {
|
||||
it?.shiftedPage = false
|
||||
}
|
||||
// Step 3: If pages have been shifted,
|
||||
if (viewer.config.shiftDoublePage) {
|
||||
run loop@{
|
||||
var index = items.indexOf(pageToShift)
|
||||
if (pageToShift?.fullPage == true) {
|
||||
index = max(0, index - 1)
|
||||
}
|
||||
// Go from the current page and work your way back to the first page,
|
||||
// or the first page that's a full page.
|
||||
// This is done in case user tries to shift a page after a full page
|
||||
val fullPageBeforeIndex = max(
|
||||
0,
|
||||
(
|
||||
if (index > -1) (
|
||||
items.take(index).indexOfLast { it?.fullPage == true }
|
||||
) else -1
|
||||
)
|
||||
)
|
||||
// Add a shifted page to the first place there isnt a full page
|
||||
(fullPageBeforeIndex until items.size).forEach {
|
||||
if (items[it]?.fullPage == false) {
|
||||
items[it]?.shiftedPage = true
|
||||
return@loop
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4: Add blanks for chunking
|
||||
var itemIndex = 0
|
||||
while (itemIndex < items.size) {
|
||||
items[itemIndex]?.isolatedPage = false
|
||||
if (items[itemIndex]?.fullPage == true || items[itemIndex]?.shiftedPage == true) {
|
||||
// Add a 'blank' page after each full page. It will be used when chunked to solo a page
|
||||
items.add(itemIndex + 1, null)
|
||||
if (items[itemIndex]?.fullPage == true && itemIndex > 0 &&
|
||||
items[itemIndex - 1] != null && (itemIndex - 1) % 2 == 0
|
||||
) {
|
||||
// If a page is a full page, check if the previous page needs to be isolated
|
||||
// we should check if it's an even or odd page, since even pages need shifting
|
||||
// For example if Page 1 is full, Page 0 needs to be isolated
|
||||
// No need to take account shifted pages, because null additions should
|
||||
// always have an odd index in the list
|
||||
items[itemIndex - 1]?.isolatedPage = true
|
||||
items.add(itemIndex, null)
|
||||
itemIndex++
|
||||
}
|
||||
itemIndex++
|
||||
}
|
||||
itemIndex++
|
||||
}
|
||||
|
||||
// Step 5: chunk em
|
||||
if (items.isNotEmpty()) {
|
||||
subJoinedItems.addAll(
|
||||
items.chunked(2).map { Pair(it.first()!!, it.getOrNull(1)) }
|
||||
)
|
||||
}
|
||||
otherItems.getOrNull(pagedIndex)?.let {
|
||||
subJoinedItems.add(Pair(it, null))
|
||||
pagedIndex++
|
||||
}
|
||||
}
|
||||
if (viewer is R2LPagerViewer) {
|
||||
subJoinedItems.reverse()
|
||||
}
|
||||
|
||||
this.joinedItems = subJoinedItems
|
||||
}
|
||||
notifyDataSetChanged()
|
||||
|
||||
// Step 6: Move back to our previous page or transition page
|
||||
// The listener is likely off around now, but either way when shifting or doubling,
|
||||
// we need to set the page back correctly
|
||||
// We will however shift to the first page of the new chapter if the last page we were are
|
||||
// on is not in the new chapter that has loaded
|
||||
val newPage =
|
||||
when {
|
||||
(oldCurrent?.first as? ReaderPage)?.chapter != currentChapter &&
|
||||
(oldCurrent?.first as? ChapterTransition)?.from != currentChapter -> subItems.find { (it as? ReaderPage)?.chapter == currentChapter }
|
||||
useSecondPage -> (oldCurrent?.second ?: oldCurrent?.first)
|
||||
else -> oldCurrent?.first ?: return
|
||||
}
|
||||
var index = joinedItems.indexOfFirst { it.first == newPage || it.second == newPage }
|
||||
if (newPage is ChapterTransition && index == -1) {
|
||||
val newerPage = if (newPage is ChapterTransition.Next) {
|
||||
joinedItems.filter {
|
||||
(it.first as? ReaderPage)?.chapter == newPage.to
|
||||
}.minByOrNull { (it.first as? ReaderPage)?.index ?: Int.MAX_VALUE }?.first
|
||||
} else {
|
||||
joinedItems.filter {
|
||||
(it.first as? ReaderPage)?.chapter == newPage.to
|
||||
}.maxByOrNull { (it.first as? ReaderPage)?.index ?: Int.MIN_VALUE }?.first
|
||||
}
|
||||
index = joinedItems.indexOfFirst { it.first == newerPage || it.second == newerPage }
|
||||
}
|
||||
viewer.pager.setCurrentItem(index, false)
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.ui.base.controller.DialogController
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
|
||||
import eu.kanade.tachiyomi.util.preference.defaultValue
|
||||
import eu.kanade.tachiyomi.util.preference.entriesRes
|
||||
import eu.kanade.tachiyomi.util.preference.intListPreference
|
||||
@@ -515,6 +516,24 @@ class SettingsReaderController : SettingsController() {
|
||||
ReaderBottomButtonsDialog().showDialog(router)
|
||||
}
|
||||
}
|
||||
intListPreference {
|
||||
key = Keys.pageLayout
|
||||
titleRes = R.string.page_layout
|
||||
summaryRes = R.string.automatic_can_still_switch
|
||||
entriesRes = arrayOf(
|
||||
R.string.single_page,
|
||||
R.string.double_pages,
|
||||
R.string.automatic_orientation
|
||||
)
|
||||
entryValues = arrayOf("0", "1", "2")
|
||||
defaultValue = 2
|
||||
}
|
||||
switchPreference {
|
||||
key = Keys.invertDoublePages
|
||||
titleRes = R.string.invert_double_pages
|
||||
defaultValue = false
|
||||
preferences.pageLayout().asImmediateFlow { isVisible = it != PagerConfig.PageLayout.SINGLE_PAGE }
|
||||
}
|
||||
}
|
||||
// EXH <--
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import android.graphics.Rect
|
||||
import android.graphics.drawable.ColorDrawable
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.GradientDrawable
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.graphics.alpha
|
||||
import androidx.core.graphics.blue
|
||||
import androidx.core.graphics.createBitmap
|
||||
@@ -20,6 +21,7 @@ import java.io.ByteArrayOutputStream
|
||||
import java.io.InputStream
|
||||
import java.net.URLConnection
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
|
||||
object ImageUtil {
|
||||
|
||||
@@ -381,4 +383,45 @@ object ImageUtil {
|
||||
|
||||
private fun Int.isWhite(): Boolean =
|
||||
red + blue + green > 740
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user