Double page spread
(cherry picked from commit 7832d1abe1fdcdb962f388e5a86dd3fcecad6712)
This commit is contained in:
parent
3192140421
commit
3053bf9d5d
@ -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 <--
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -0,0 +1,9 @@
|
||||
<!-- drawable/book_open_variant.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="@android:color/black">
|
||||
<path android:fillColor="#000" android:pathData="M17.5 14.33C18.29 14.33 19.13 14.41 20 14.57V16.07C19.38 15.91 18.54 15.83 17.5 15.83C15.6 15.83 14.11 16.16 13 16.82V15.13C14.17 14.6 15.67 14.33 17.5 14.33M13 12.46C14.29 11.93 15.79 11.67 17.5 11.67C18.29 11.67 19.13 11.74 20 11.9V13.4C19.38 13.24 18.54 13.16 17.5 13.16C15.6 13.16 14.11 13.5 13 14.15M17.5 10.5C15.6 10.5 14.11 10.82 13 11.5V9.84C14.23 9.28 15.73 9 17.5 9C18.29 9 19.13 9.08 20 9.23V10.78C19.26 10.59 18.41 10.5 17.5 10.5M21 18.5V7C19.96 6.67 18.79 6.5 17.5 6.5C15.45 6.5 13.62 7 12 8V19.5C13.62 18.5 15.45 18 17.5 18C18.69 18 19.86 18.16 21 18.5M17.5 4.5C19.85 4.5 21.69 5 23 6V20.56C23 20.68 22.95 20.8 22.84 20.91C22.73 21 22.61 21.08 22.5 21.08C22.39 21.08 22.31 21.06 22.25 21.03C20.97 20.34 19.38 20 17.5 20C15.45 20 13.62 20.5 12 21.5C10.66 20.5 8.83 20 6.5 20C4.84 20 3.25 20.36 1.75 21.07C1.72 21.08 1.68 21.08 1.63 21.1C1.59 21.11 1.55 21.12 1.5 21.12C1.39 21.12 1.27 21.08 1.16 21C1.05 20.89 1 20.78 1 20.65V6C2.34 5 4.18 4.5 6.5 4.5C8.83 4.5 10.66 5 12 6C13.34 5 15.17 4.5 17.5 4.5Z" />
|
||||
</vector>
|
@ -0,0 +1,9 @@
|
||||
<!-- drawable/page_next_outline.xml -->
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="24dp"
|
||||
android:width="24dp"
|
||||
android:tint="@android:color/black"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path android:fillColor="#000" android:pathData="M22,3H5A2,2 0 0,0 3,5V9H5V5H22V19H5V15H3V19A2,2 0 0,0 5,21H22A2,2 0 0,0 24,19V5A2,2 0 0,0 22,3M7,15V13H0V11H7V9L11,12L7,15M20,13H13V11H20V13M20,9H13V7H20V9M17,17H13V15H17V17Z" />
|
||||
</vector>
|
@ -440,13 +440,41 @@
|
||||
android:background="@drawable/ripple_regular"
|
||||
android:contentDescription="@string/pref_rotation_type"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toStartOf="@id/action_settings"
|
||||
app:layout_constraintEnd_toStartOf="@id/double_page"
|
||||
app:layout_constraintStart_toEndOf="@+id/action_crop_borders"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:srcCompat="@drawable/ic_screen_rotation_24dp"
|
||||
app:tint="?attr/colorOnPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/double_page"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ripple_regular"
|
||||
android:contentDescription="@string/page_layout"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toStartOf="@id/shift_page_button"
|
||||
app:layout_constraintStart_toEndOf="@+id/action_rotation"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:srcCompat="@drawable/ic_book_open_variant_24dp"
|
||||
app:tint="?attr/colorOnPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/shift_page_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/ripple_regular"
|
||||
android:contentDescription="@string/shift_double_pages"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toStartOf="@id/action_settings"
|
||||
app:layout_constraintStart_toEndOf="@+id/double_page"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:srcCompat="@drawable/ic_page_next_outline_24dp"
|
||||
app:tint="?attr/colorOnPrimary" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/action_settings"
|
||||
android:layout_width="wrap_content"
|
||||
@ -455,7 +483,7 @@
|
||||
android:contentDescription="@string/action_settings"
|
||||
android:padding="@dimen/screen_edge_margin"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/action_rotation"
|
||||
app:layout_constraintStart_toEndOf="@id/shift_page_button"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintHorizontal_chainStyle="spread"
|
||||
app:srcCompat="@drawable/ic_settings_24dp"
|
||||
|
@ -43,6 +43,13 @@
|
||||
android:entries="@array/zoom_start"
|
||||
app:title="@string/pref_zoom_start" />
|
||||
|
||||
<eu.kanade.tachiyomi.widget.MaterialSpinnerView
|
||||
android:id="@+id/page_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:entries="@array/page_layouts"
|
||||
app:title="@string/page_layout" />
|
||||
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial
|
||||
android:id="@+id/crop_borders"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -15,4 +15,10 @@
|
||||
<item>@string/dropped</item>
|
||||
<item>@string/repeating</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="page_layouts">
|
||||
<item>@string/single_page</item>
|
||||
<item>@string/double_pages</item>
|
||||
<item>@string/automatic_orientation</item>
|
||||
</string-array>
|
||||
</resources>
|
@ -279,6 +279,15 @@
|
||||
<!-- Auto Webtoon Mode -->
|
||||
<string name="eh_auto_webtoon_snack">Reading webtoon style</string>
|
||||
|
||||
<!-- Double page spread -->
|
||||
<string name="page_layout">Page layout</string>
|
||||
<string name="shift_double_pages">Shift one page over</string>
|
||||
<string name="double_pages">Double pages</string>
|
||||
<string name="single_page">Single page</string>
|
||||
<string name="automatic_orientation">Automatic (based on orientation)</string>
|
||||
<string name="automatic_can_still_switch">While using automatic page layout, you can still switch between layouts while reading without overriding this setting</string>
|
||||
<string name="invert_double_pages">Invert double pages</string>
|
||||
|
||||
<!-- Manga Page -->
|
||||
<!-- Manga Info -->
|
||||
<string name="az_recommends">See Recommendations</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user