Migrate reader slider and next/prev buttons to Compose

(cherry picked from commit 9a10656bf07a7dd35400fa6e42dd0e4889ddb177)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/viewer/webtoon/WebtoonViewer.kt
#	app/src/main/res/layout/reader_activity.xml
This commit is contained in:
arkon 2023-05-03 15:54:37 -04:00 committed by Jobobby04
parent c320daf832
commit 2d013c551d
13 changed files with 362 additions and 416 deletions

View File

@ -0,0 +1,255 @@
package eu.kanade.presentation.reader
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SkipNext
import androidx.compose.material.icons.outlined.SkipPrevious
import androidx.compose.material3.FilledIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.TransformOrigin
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.dp
import eu.kanade.presentation.util.isTabletUi
import eu.kanade.tachiyomi.R
@Composable
fun ChapterNavigator(
isRtl: Boolean,
isVerticalSlider: Boolean,
onNextChapter: () -> Unit,
enabledNext: Boolean,
onPreviousChapter: () -> Unit,
enabledPrevious: Boolean,
currentPage: Int,
// SY -->
currentPageText: String,
// SY <--
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
) {
// SY -->
if (isVerticalSlider) {
ChapterNavigatorVert(
onNextChapter = onNextChapter,
enabledNext = enabledNext,
onPreviousChapter = onPreviousChapter,
enabledPrevious = enabledPrevious,
currentPage = currentPage,
currentPageText = currentPageText,
totalPages = totalPages,
onSliderValueChange = onSliderValueChange,
)
return
}
// SY <--
val isTabletUi = isTabletUi()
val horizontalPadding = if (isTabletUi) 24.dp else 16.dp
val layoutDirection = if (isRtl) LayoutDirection.Rtl else LayoutDirection.Ltr
val backgroundColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
val haptic = LocalHapticFeedback.current
// We explicitly handle direction based on the reader viewer rather than the system direction
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = horizontalPadding),
verticalAlignment = Alignment.CenterVertically,
) {
val isLeftEnabled = if (isRtl) enabledNext else enabledPrevious
if (isLeftEnabled) {
FilledIconButton(
onClick = if (isRtl) onNextChapter else onPreviousChapter,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = backgroundColor,
),
) {
Icon(
imageVector = Icons.Outlined.SkipPrevious,
contentDescription = stringResource(if (isRtl) R.string.action_next_chapter else R.string.action_previous_chapter),
)
}
}
if (totalPages > 1) {
CompositionLocalProvider(LocalLayoutDirection provides layoutDirection) {
Row(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(24.dp))
.background(backgroundColor)
.padding(horizontal = 16.dp),
verticalAlignment = Alignment.CenterVertically,
) {
// SY -->
Text(text = currentPageText)
// SY <--
Slider(
modifier = Modifier
.weight(1f)
.padding(horizontal = 8.dp),
value = currentPage.toFloat(),
valueRange = 1f..totalPages.toFloat(),
steps = totalPages,
onValueChange = {
onSliderValueChange(it.toInt() - 1)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
},
)
Text(text = totalPages.toString())
}
}
} else {
Spacer(Modifier.weight(1f))
}
val isRightEnabled = if (isRtl) enabledPrevious else enabledNext
if (isRightEnabled) {
FilledIconButton(
onClick = if (isRtl) onPreviousChapter else onNextChapter,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = backgroundColor,
),
) {
Icon(
imageVector = Icons.Outlined.SkipNext,
contentDescription = stringResource(if (isRtl) R.string.action_previous_chapter else R.string.action_next_chapter),
)
}
}
}
}
}
@Composable
fun ChapterNavigatorVert(
onNextChapter: () -> Unit,
enabledNext: Boolean,
onPreviousChapter: () -> Unit,
enabledPrevious: Boolean,
currentPage: Int,
// SY -->
currentPageText: String,
// SY <--
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
) {
val isTabletUi = isTabletUi()
val verticalPadding = if (isTabletUi) 24.dp else 16.dp
val backgroundColor = MaterialTheme.colorScheme.surface.copy(alpha = 0.9f)
val haptic = LocalHapticFeedback.current
Column(
modifier = Modifier
.fillMaxHeight()
.padding(vertical = verticalPadding, horizontal = 8.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
if (enabledPrevious) {
FilledIconButton(
onClick = onPreviousChapter,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = backgroundColor,
),
) {
Icon(
imageVector = Icons.Outlined.SkipPrevious,
contentDescription = stringResource(R.string.action_previous_chapter),
modifier = Modifier.rotate(90f),
)
}
}
if (totalPages > 1) {
Column(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(24.dp))
.background(backgroundColor)
.padding(vertical = 16.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
// SY -->
Text(text = currentPageText)
// SY <--
Slider(
modifier = Modifier
.padding(vertical = 8.dp)
.graphicsLayer {
rotationZ = 90f
transformOrigin = TransformOrigin(0f, 0f)
}
.layout { measurable, constraints ->
val placeable = measurable.measure(
Constraints(
minWidth = constraints.minHeight,
maxWidth = constraints.maxHeight,
minHeight = constraints.minWidth,
maxHeight = constraints.maxWidth,
),
)
layout(placeable.height, placeable.width) {
placeable.place(0, -placeable.height)
}
}
.weight(1f),
value = currentPage.toFloat(),
valueRange = 1f..totalPages.toFloat(),
steps = totalPages,
onValueChange = {
onSliderValueChange(it.toInt() - 1)
haptic.performHapticFeedback(HapticFeedbackType.TextHandleMove)
},
)
Text(text = totalPages.toString())
}
} else {
Spacer(Modifier.weight(1f))
}
if (enabledNext) {
FilledIconButton(
onClick = onNextChapter,
colors = IconButtonDefaults.filledIconButtonColors(
containerColor = backgroundColor,
),
) {
Icon(
imageVector = Icons.Outlined.SkipNext,
contentDescription = stringResource(R.string.action_next_chapter),
modifier = Modifier.rotate(90f),
)
}
}
}
}

View File

@ -13,14 +13,12 @@ import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.graphics.drawable.RippleDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.Menu
import android.view.MotionEvent
@ -35,6 +33,7 @@ import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.ColorInt
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.core.graphics.ColorUtils
@ -43,7 +42,6 @@ import androidx.core.transition.doOnEnd
import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.Lifecycle
@ -52,12 +50,12 @@ import androidx.lifecycle.repeatOnLifecycle
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.slider.Slider
import com.google.android.material.transition.platform.MaterialContainerTransform
import dev.chrisbanes.insetter.applyInsetter
import eu.kanade.domain.base.BasePreferences
import eu.kanade.domain.manga.model.orientationType
import eu.kanade.domain.manga.model.readingModeType
import eu.kanade.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -80,7 +78,6 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReaderBottomButton
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
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.ReaderProgressIndicator
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerConfig
import eu.kanade.tachiyomi.ui.reader.viewer.pager.PagerViewer
@ -91,7 +88,6 @@ import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.preference.toggle
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
import eu.kanade.tachiyomi.util.system.createReaderThemeContext
import eu.kanade.tachiyomi.util.system.getThemeColor
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import eu.kanade.tachiyomi.util.system.isLTR
import eu.kanade.tachiyomi.util.system.isNightMode
@ -105,7 +101,6 @@ import eu.kanade.tachiyomi.widget.listener.SimpleAnimationListener
import exh.log.xLogE
import exh.source.isEhBasedSource
import exh.util.defaultReaderType
import exh.util.floor
import exh.util.mangaType
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.delay
@ -134,7 +129,6 @@ import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.source.service.SourceManager
import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
import kotlin.math.max
import kotlin.time.Duration.Companion.seconds
class ReaderActivity : BaseActivity() {
@ -155,9 +149,6 @@ class ReaderActivity : BaseActivity() {
const val SHIFT_DOUBLE_PAGES = "shiftingDoublePages"
const val SHIFTED_PAGE_INDEX = "shiftedPageIndex"
const val SHIFTED_CHAP_INDEX = "shiftedChapterIndex"
private const val ENABLED_BUTTON_IMAGE_ALPHA = 255
private const val DISABLED_BUTTON_IMAGE_ALPHA = 64
}
private val readerPreferences: ReaderPreferences by injectLazy()
@ -170,12 +161,6 @@ class ReaderActivity : BaseActivity() {
val hasCutout by lazy { hasDisplayCutout() }
/**
* Viewer used to display the pages (pager, webtoon, ...).
*/
var viewer: BaseViewer? = null
private set
/**
* Whether the menu is currently visible.
*/
@ -330,8 +315,7 @@ class ReaderActivity : BaseActivity() {
*/
override fun onDestroy() {
super.onDestroy()
viewer?.destroy()
viewer = null
viewModel.state.value.viewer?.destroy()
config = null
menuToggleToast?.cancel()
readingModeToast?.cancel()
@ -349,7 +333,7 @@ class ReaderActivity : BaseActivity() {
outState.putBoolean(::ehUtilsVisible.name, ehUtilsVisible)
// EXH <--
// SY -->
(viewer as? PagerViewer)?.let { pViewer ->
(viewModel.state.value.viewer as? PagerViewer)?.let { pViewer ->
val config = pViewer.config
outState.putBoolean(SHIFT_DOUBLE_PAGES, config.shiftDoublePage)
if (config.shiftDoublePage && config.doublePages) {
@ -455,7 +439,7 @@ class ReaderActivity : BaseActivity() {
* Dispatches a key event. If the viewer doesn't handle it, call the default implementation.
*/
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val handled = viewer?.handleKeyEvent(event) ?: false
val handled = viewModel.state.value.viewer?.handleKeyEvent(event) ?: false
return handled || super.dispatchKeyEvent(event)
}
@ -464,7 +448,7 @@ class ReaderActivity : BaseActivity() {
* implementation.
*/
override fun dispatchGenericMotionEvent(event: MotionEvent): Boolean {
val handled = viewer?.handleGenericMotionEvent(event) ?: false
val handled = viewModel.state.value.viewer?.handleGenericMotionEvent(event) ?: false
return handled || super.dispatchGenericMotionEvent(event)
}
@ -535,60 +519,47 @@ class ReaderActivity : BaseActivity() {
val state by viewModel.state.collectAsState()
PageIndicatorText(
currentPage = state.currentPage,
totalPages = state.viewerChapters?.currChapter?.pages?.size ?: -1,
// SY -->
currentPage = state.currentPageText,
// SY <--
totalPages = state.totalPages,
)
}
// SY -->
// Init listeners on bottom menu
val listener = object : Slider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: Slider) {
isScrollingThroughPages = true
}
val sliderContent: @Composable (Boolean) -> Unit = a@{ isVertical ->
val state by viewModel.state.collectAsState()
override fun onStopTrackingTouch(slider: Slider) {
isScrollingThroughPages = false
}
}
val onChangeListener = Slider.OnChangeListener { slider, value, fromUser ->
if (viewer != null && fromUser) {
isScrollingThroughPages = true
moveToPageIndex(value.toInt())
slider.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
}
}
listOf(binding.pageSlider, binding.pageSliderVert)
.forEach {
it.addOnSliderTouchListener(listener)
it.addOnChangeListener(onChangeListener)
}
// SY <--
// Extra menu buttons
if (state.viewer == null) return@a
val isRtl = state.viewer is R2LPagerViewer
ChapterNavigator(
isRtl = isRtl,
// SY -->
listOf(binding.leftChapter, binding.aboveChapter).forEach {
it.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer) {
loadNextChapter()
} else {
loadPreviousChapter()
}
}
}
}
listOf(binding.rightChapter, binding.belowChapter).forEach {
it.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer) {
loadPreviousChapter()
} else {
loadNextChapter()
}
isVerticalSlider = isVertical,
// SY <--
onNextChapter = ::loadNextChapter,
enabledNext = state.viewerChapters?.nextChapter != null,
onPreviousChapter = ::loadPreviousChapter,
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
// SY -->
currentPageText = state.currentPageText,
// SY <--
totalPages = state.totalPages,
onSliderValueChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
)
}
// Init listeners on bottom menu
binding.readerNavHorz.setComposeContent {
sliderContent(false)
}
binding.readerNavVert.setComposeContent {
sliderContent(true)
}
initBottomShortcuts()
@ -604,23 +575,6 @@ class ReaderActivity : BaseActivity() {
}
binding.toolbarBottom.background = toolbarBackground.copy(this@ReaderActivity)
binding.readerSeekbar.background = toolbarBackground.copy(this@ReaderActivity)?.apply {
setCornerSize(999F)
}
// SY -->
binding.readerSeekbarVert.background = toolbarBackground.copy(this@ReaderActivity)?.apply {
setCornerSize(999F)
}
// SY <--
listOf(binding.leftChapter, binding.rightChapter /* SY --> */, binding.belowChapter, binding.aboveChapter /* SY <-- */).forEach {
it.background = binding.readerSeekbar.background.copy(this)
it.foreground = RippleDrawable(
ColorStateList.valueOf(getThemeColor(android.R.attr.colorControlHighlight)),
null,
it.background,
)
}
val toolbarColor = ColorUtils.setAlphaComponent(
toolbarBackground.resolvedTintColor,
toolbarBackground.alpha,
@ -766,7 +720,7 @@ class ReaderActivity : BaseActivity() {
setOnClickListener {
if (readerPreferences.pageLayout().get() == PagerConfig.PageLayout.AUTOMATIC) {
(viewer as? PagerViewer)?.config?.let { config ->
(viewModel.state.value.viewer as? PagerViewer)?.config?.let { config ->
config.doublePages = !config.doublePages
reloadChapters(config.doublePages, true)
}
@ -821,7 +775,7 @@ class ReaderActivity : BaseActivity() {
val interval = parsed.seconds
while (true) {
if (!binding.readerMenu.isVisible) {
viewer.let { v ->
viewModel.state.value.viewer.let { v ->
when (v) {
is PagerViewer -> v.moveToNext()
is WebtoonViewer -> {
@ -903,7 +857,7 @@ class ReaderActivity : BaseActivity() {
}
binding.ehBoostPage.setOnClickListener {
viewer ?: return@setOnClickListener
viewModel.state.value.viewer ?: return@setOnClickListener
val curPage = exhCurrentpage() ?: run {
toast(R.string.eh_boost_page_invalid)
return@setOnClickListener
@ -936,11 +890,13 @@ class ReaderActivity : BaseActivity() {
}
private fun exhCurrentpage(): ReaderPage? {
val viewer = viewModel.state.value.viewer
val currentPage = (((viewer as? PagerViewer)?.currentPage ?: (viewer as? WebtoonViewer)?.currentPage) as? ReaderPage)?.index
return currentPage?.let { viewModel.state.value.viewerChapters?.currChapter?.pages?.getOrNull(it) }
}
fun updateBottomButtons() {
val viewer = viewModel.state.value.viewer
val enabledButtons = readerPreferences.readerBottomButtons().get()
with(binding) {
actionReadingMode.isVisible = ReaderBottomButton.ReadingMode.isIn(enabledButtons)
@ -968,23 +924,23 @@ class ReaderActivity : BaseActivity() {
}
fun reloadChapters(doublePages: Boolean, force: Boolean = false) {
val pViewer = viewer as? PagerViewer ?: return
pViewer.updateShifting()
if (!force && pViewer.config.autoDoublePages) {
setDoublePageMode(pViewer)
val viewer = viewModel.state.value.viewer as? PagerViewer ?: return
viewer.updateShifting()
if (!force && viewer.config.autoDoublePages) {
setDoublePageMode(viewer)
} else {
pViewer.config.doublePages = doublePages
viewer.config.doublePages = doublePages
}
val currentChapter = viewModel.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.pageSlider.value.floor() +
(currentChapter?.pages?.take(binding.pageSlider.value.floor())?.count { it.fullPage || it.isolatedPage } ?: 0)
val currentPage = viewModel.state.value.currentPage
viewer.config.shiftDoublePage = (
currentPage + (currentChapter?.pages?.take(currentPage)?.count { it.fullPage || it.isolatedPage } ?: 0)
) % 2 != 0
}
viewModel.state.value.viewerChapters?.let {
pViewer.setChaptersDoubleShift(it)
viewer.setChaptersDoubleShift(it)
}
}
@ -994,11 +950,12 @@ class ReaderActivity : BaseActivity() {
}
private fun shiftDoublePages() {
(viewer as? PagerViewer)?.config?.let { config ->
val viewer = viewModel.state.value.viewer as? PagerViewer ?: return
viewer.config.let { config ->
config.shiftDoublePage = !config.shiftDoublePage
viewModel.state.value.viewerChapters?.let {
(viewer as? PagerViewer)?.updateShifting()
(viewer as? PagerViewer)?.setChaptersDoubleShift(it)
viewer.updateShifting()
viewer.setChaptersDoubleShift(it)
invalidateOptionsMenu()
}
}
@ -1119,7 +1076,7 @@ class ReaderActivity : BaseActivity() {
* and the toolbar title.
*/
private fun setManga(manga: Manga) {
val prevViewer = viewer
val prevViewer = viewModel.state.value.viewer
val viewerMode = ReadingModeType.fromPreference(viewModel.getMangaReadingMode(resolveDefault = false))
binding.actionReadingMode.setImageResource(viewerMode.iconRes)
@ -1141,7 +1098,7 @@ class ReaderActivity : BaseActivity() {
prevViewer.destroy()
binding.viewerContainer.removeAllViews()
}
viewer = newViewer
viewModel.onViewerLoaded(newViewer)
updateViewerInset(readerPreferences.fullscreen().get())
binding.viewerContainer.addView(newViewer.getView())
@ -1174,7 +1131,7 @@ class ReaderActivity : BaseActivity() {
) ||
resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT
) &&
(viewer is WebtoonViewer || viewer is VerticalPagerViewer)
(viewModel.state.value.viewer is WebtoonViewer || viewModel.state.value.viewer is VerticalPagerViewer)
) {
binding.readerNavVert.isVisible = true
binding.readerNavHorz.isVisible = false
@ -1199,17 +1156,6 @@ class ReaderActivity : BaseActivity() {
// SY <--
supportActionBar?.title = manga.title
binding.pageSlider.isRTL = newViewer is R2LPagerViewer
if (newViewer is R2LPagerViewer) {
binding.leftChapter.setTooltip(R.string.action_next_chapter)
binding.rightChapter.setTooltip(R.string.action_previous_chapter)
} else {
binding.leftChapter.setTooltip(R.string.action_previous_chapter)
binding.rightChapter.setTooltip(R.string.action_next_chapter)
}
binding.aboveChapter.setTooltip(R.string.action_previous_chapter)
binding.belowChapter.setTooltip(R.string.action_next_chapter)
val loadingIndicatorContext = createReaderThemeContext()
loadingIndicator = ReaderProgressIndicator(loadingIndicatorContext).apply {
updateLayoutParams<FrameLayout.LayoutParams> {
@ -1252,13 +1198,13 @@ class ReaderActivity : BaseActivity() {
// SY -->
if (indexChapterToShift != null && indexPageToShift != null) {
viewerChapters.currChapter.pages?.find { it.index == indexPageToShift && it.chapter.chapter.id == indexChapterToShift }?.let {
(viewer as? PagerViewer)?.updateShifting(it)
(viewModel.state.value.viewer as? PagerViewer)?.updateShifting(it)
}
indexChapterToShift = null
indexPageToShift = null
} else if (lastShiftDoubleState != null) {
val currentChapter = viewerChapters.currChapter
(viewer as? PagerViewer)?.config?.shiftDoublePage = (
(viewModel.state.value.viewer as? PagerViewer)?.config?.shiftDoublePage = (
currentChapter.requestedPage +
(
currentChapter.pages?.take(currentChapter.requestedPage)
@ -1268,35 +1214,9 @@ class ReaderActivity : BaseActivity() {
}
// SY <--
viewer?.setChapters(viewerChapters)
viewModel.state.value.viewer?.setChapters(viewerChapters)
binding.toolbar.subtitle = viewerChapters.currChapter.chapter.name
val currentChapterPageCount = viewerChapters.currChapter.pages?.size ?: 1
binding.readerSeekbar.isInvisible = currentChapterPageCount == 1
binding.readerSeekbarVert.isInvisible = currentChapterPageCount == 1
val leftChapterObject = if (viewer is R2LPagerViewer) viewerChapters.nextChapter else viewerChapters.prevChapter
val rightChapterObject = if (viewer is R2LPagerViewer) viewerChapters.prevChapter else viewerChapters.nextChapter
if (leftChapterObject == null && rightChapterObject == null) {
binding.leftChapter.isVisible = false
binding.rightChapter.isVisible = false
binding.aboveChapter.isVisible = false
binding.belowChapter.isVisible = false
} else {
binding.leftChapter.isEnabled = leftChapterObject != null
binding.leftChapter.imageAlpha = if (leftChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
binding.rightChapter.isEnabled = rightChapterObject != null
binding.rightChapter.imageAlpha = if (rightChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
binding.aboveChapter.isEnabled = leftChapterObject != null
binding.aboveChapter.imageAlpha = if (leftChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
binding.belowChapter.isEnabled = rightChapterObject != null
binding.belowChapter.imageAlpha = if (rightChapterObject != null) ENABLED_BUTTON_IMAGE_ALPHA else DISABLED_BUTTON_IMAGE_ALPHA
}
// Invalidate menu to show proper chapter bookmark state
invalidateOptionsMenu()
@ -1338,7 +1258,7 @@ class ReaderActivity : BaseActivity() {
* page is not found.
*/
private fun moveToPageIndex(index: Int) {
val viewer = viewer ?: return
val viewer = viewModel.state.value.viewer ?: return
val currentChapter = viewModel.getCurrentChapter() ?: return
val page = currentChapter.pages?.getOrNull(index) ?: return
viewer.moveToPage(page)
@ -1374,38 +1294,13 @@ class ReaderActivity : BaseActivity() {
fun onPageSelected(page: ReaderPage, hasExtraPage: Boolean = false) {
// SY -->
val currentPage = if (hasExtraPage) {
val invertDoublePage = (viewer as? PagerViewer)?.config?.invertDoublePages ?: false
val invertDoublePage = (viewModel.state.value.viewer as? PagerViewer)?.config?.invertDoublePages ?: false
if (resources.isLTR xor invertDoublePage) "${page.number}-${page.number + 1}" else "${page.number + 1}-${page.number}"
} else {
"${page.number}"
}
viewModel.onPageSelected(page, hasExtraPage, currentPage)
// SY <--
val pages = page.chapter.pages ?: return
// Set page numbers
if (viewer !is R2LPagerViewer) {
binding.leftPageText.text = currentPage
binding.rightPageText.text = "${pages.size}"
} else {
binding.rightPageText.text = currentPage
binding.leftPageText.text = "${pages.size}"
}
// Set slider progress
binding.pageSlider.isEnabled = pages.size > 1
binding.pageSlider.valueTo = max(pages.lastIndex.toFloat(), 1f)
binding.pageSlider.value = page.index.toFloat()
// SY -->
binding.pageSliderVert.valueTo = max(pages.lastIndex.toFloat(), 1f)
binding.pageSliderVert.value = page.index.toFloat()
// SY <--
// SY -->
binding.abovePageText.text = currentPage
binding.belowPageText.text = "${pages.size}"
// SY <--
}
/**
@ -1415,7 +1310,7 @@ class ReaderActivity : BaseActivity() {
fun onPageLongTap(page: ReaderPage, extraPage: ReaderPage? = null) {
// SY -->
try {
val viewer = viewer as? PagerViewer
val viewer = viewModel.state.value.viewer as? PagerViewer
ReaderPageSheet(
this,
page,
@ -1566,7 +1461,7 @@ class ReaderActivity : BaseActivity() {
* Updates viewer inset depending on fullscreen reader preferences.
*/
fun updateViewerInset(fullscreen: Boolean) {
viewer?.getView()?.applyInsetter {
viewModel.state.value.viewer?.getView()?.applyInsetter {
if (!fullscreen) {
type(navigationBars = true, statusBars = true) {
padding()
@ -1671,7 +1566,7 @@ class ReaderActivity : BaseActivity() {
readerPreferences.dualPageSplitPaged().changes()
.drop(1)
.onEach {
if (viewer !is PagerViewer) return@onEach
if (viewModel.state.value.viewer !is PagerViewer) return@onEach
updateBottomButtons()
reloadChapters(
!it && when (readerPreferences.pageLayout().get()) {

View File

@ -1,30 +0,0 @@
package eu.kanade.tachiyomi.ui.reader
import android.content.Context
import android.util.AttributeSet
import com.google.android.material.slider.Slider
/**
* Slider to show current chapter progress.
*/
class ReaderSlider @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : Slider(context, attrs) {
init {
stepSize = 1f
setLabelFormatter { value ->
(value.toInt() + 1).toString()
}
}
/**
* Whether the slider should draw from right to left.
*/
var isRTL: Boolean
set(value) {
layoutDirection = if (value) LAYOUT_DIRECTION_RTL else LAYOUT_DIRECTION_LTR
}
get() = layoutDirection == LAYOUT_DIRECTION_RTL
}

View File

@ -39,6 +39,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
import eu.kanade.tachiyomi.util.chapter.removeDuplicates
import eu.kanade.tachiyomi.util.editCover
import eu.kanade.tachiyomi.util.lang.byteSize
@ -480,6 +481,14 @@ class ReaderViewModel(
eventChannel.trySend(Event.ReloadViewerChapters)
}
fun onViewerLoaded(viewer: Viewer?) {
mutableState.update {
it.copy(
viewer = viewer,
)
}
}
/**
* Called every time a page changes on the reader. Used to mark the flag of chapters being
* read, update tracking services, enqueue downloaded chapter deletion, and updating the active chapter if this
@ -498,8 +507,9 @@ class ReaderViewModel(
// Save last page read and mark as read if needed
mutableState.update {
it.copy(
currentPage = page.number,
// SY -->
currentPage = currentPage,
currentPageText = currentPage,
// SY <--
)
}
@ -1108,12 +1118,21 @@ class ReaderViewModel(
val manga: Manga? = null,
val viewerChapters: ViewerChapters? = null,
val isLoadingAdjacentChapter: Boolean = false,
val currentPage: Int = -1,
/**
* Viewer used to display the pages (pager, webtoon, ...).
*/
val viewer: Viewer? = null,
// SY -->
val currentPage: String = "",
val currentPageText: String = "",
val meta: RaisedSearchMetadata? = null,
val mergedManga: Map<Long, Manga>? = null,
// SY <--
)
) {
val totalPages: Int
get() = viewerChapters?.currChapter?.pages?.size ?: -1
}
sealed class Event {
object ReloadViewerChapters : Event()

View File

@ -34,7 +34,7 @@ class ReaderReadingModeSettings @JvmOverloads constructor(context: Context, attr
initGeneralPreferences()
when ((context as ReaderActivity).viewer) {
when ((context as ReaderActivity).viewModel.state.value.viewer) {
is PagerViewer -> initPagerPreferences()
is WebtoonViewer -> initWebtoonPreferences()
}

View File

@ -4,7 +4,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.StringRes
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.L2RPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
import eu.kanade.tachiyomi.ui.reader.viewer.pager.VerticalPagerViewer
@ -31,7 +31,7 @@ enum class ReadingModeType(val prefValue: Int, @StringRes val stringRes: Int, @D
fun fromSpinner(position: Int?) = values().find { value -> value.prefValue == position } ?: DEFAULT
fun toViewer(preference: Int?, activity: ReaderActivity): BaseViewer {
fun toViewer(preference: Int?, activity: ReaderActivity): Viewer {
return when (fromPreference(preference)) {
LEFT_TO_RIGHT -> L2RPagerViewer(activity)
RIGHT_TO_LEFT -> R2LPagerViewer(activity)

View File

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
/**
* Interface for implementing a viewer.
*/
interface BaseViewer {
interface Viewer {
/**
* Returns the view this viewer uses.

View File

@ -18,7 +18,7 @@ import eu.kanade.tachiyomi.ui.reader.model.InsertPage
import eu.kanade.tachiyomi.ui.reader.model.ReaderItem
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
@ -27,10 +27,10 @@ import uy.kohesive.injekt.injectLazy
import kotlin.math.min
/**
* Implementation of a [BaseViewer] to display pages with a [ViewPager].
* Implementation of a [Viewer] to display pages with a [ViewPager].
*/
@Suppress("LeakingThis")
abstract class PagerViewer(val activity: ReaderActivity) : BaseViewer {
abstract class PagerViewer(val activity: ReaderActivity) : Viewer {
val downloadManager: DownloadManager by injectLazy()

View File

@ -19,7 +19,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
import eu.kanade.tachiyomi.ui.reader.model.StencilPage
import eu.kanade.tachiyomi.ui.reader.model.ViewerChapters
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.viewer.BaseViewer
import eu.kanade.tachiyomi.ui.reader.viewer.Viewer
import eu.kanade.tachiyomi.ui.reader.viewer.ViewerNavigation.NavigationRegion
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.cancel
@ -32,9 +32,9 @@ import kotlin.math.min
import kotlin.time.Duration
/**
* Implementation of a [BaseViewer] to display pages with a [RecyclerView].
* Implementation of a [Viewer] to display pages with a [RecyclerView].
*/
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, private val tapByPage: Boolean = false) : BaseViewer {
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true, private val tapByPage: Boolean = false) : Viewer {
val downloadManager: DownloadManager by injectLazy()

View File

@ -12,7 +12,6 @@ import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.util.TypedValue
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.view.ContextThemeWrapper
@ -89,19 +88,6 @@ fun Context.hasPermission(permission: String) = PermissionChecker.checkSelfPermi
return color
}
@ColorInt fun Context.getThemeColor(attr: Int): Int {
val tv = TypedValue()
return if (this.theme.resolveAttribute(attr, tv, true)) {
if (tv.resourceId != 0) {
getColor(tv.resourceId)
} else {
tv.data
}
} else {
0
}
}
val Context.powerManager: PowerManager
get() = getSystemService()!!

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z" />
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/black"
android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z" />
</vector>

View File

@ -195,104 +195,13 @@
app:layout_constraintTop_toBottomOf="@id/above_guideline"
tools:ignore="NotSibling">
<LinearLayout
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_nav_vert"
android:layout_width="48dp"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:orientation="vertical"
android:gravity="center">
<ImageButton
android:id="@+id/above_chapter"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginTop="80dp"
android:layout_marginBottom="8dp"
android:contentDescription="@string/action_previous_chapter"
android:padding="@dimen/screen_edge_margin"
android:rotation="90"
app:srcCompat="@drawable/ic_skip_previous_24dp"
app:tint="?attr/colorOnSurface" />
<LinearLayout
android:id="@+id/reader_seekbar_vert"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/above_page_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:gravity="center"
android:textColor="?attr/colorOnSurface"
android:textSize="15sp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="1" />
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginTop="27dp"
android:layout_marginBottom="27dp"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintBottom_toTopOf="@id/below_page_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/above_page_text" >
<eu.kanade.tachiyomi.ui.reader.ReaderSlider
android:id="@+id/page_slider_vert"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rotation="90"
android:layout_gravity="center"
app:layout_constraintDimensionRatio="1:1" />
</FrameLayout>
<TextView
android:id="@+id/below_page_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="12dp"
android:gravity="center"
android:textColor="?attr/colorOnSurface"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
tools:text="15" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
<ImageButton
android:id="@+id/below_chapter"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_gravity="center"
android:layout_marginTop="8dp"
android:layout_marginBottom="80dp"
android:contentDescription="@string/action_next_chapter"
android:padding="@dimen/screen_edge_margin"
android:rotation="90"
app:srcCompat="@drawable/ic_skip_next_24dp"
app:tint="?attr/colorOnSurface" />
</LinearLayout>
android:gravity="center"/>
</RelativeLayout>
@ -314,82 +223,12 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_nav_horz"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="16dp"
android:layoutDirection="ltr"
android:orientation="horizontal">
<ImageButton
android:id="@+id/left_chapter"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginEnd="8dp"
android:contentDescription="@string/action_previous_chapter"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_previous_24dp"
app:tint="?attr/colorOnSurface" />
<LinearLayout
android:id="@+id/reader_seekbar"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:clickable="true"
android:paddingStart="8dp"
android:paddingEnd="8dp"
tools:ignore="KeyboardInaccessibleWidget">
<TextView
android:id="@+id/left_page_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="32dp"
android:textColor="?attr/colorOnSurface"
android:textSize="15sp"
tools:text="1" />
<!--
Wonky way of setting height due to issues with horizontally centering the thumb in Android 5.
See https://stackoverflow.com/questions/15701767/android-thumb-is-not-centered-in-seekbar
-->
<eu.kanade.tachiyomi.ui.reader.ReaderSlider
android:id="@+id/page_slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:maxHeight="?attr/actionBarSize"
android:minHeight="?attr/actionBarSize"
app:tickVisible="true"/>
<TextView
android:id="@+id/right_page_text"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:gravity="center"
android:minWidth="32dp"
android:textColor="?attr/colorOnSurface"
android:textSize="15sp"
tools:text="15" />
</LinearLayout>
<ImageButton
android:id="@+id/right_chapter"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginStart="8dp"
android:contentDescription="@string/action_next_chapter"
android:padding="@dimen/screen_edge_margin"
app:srcCompat="@drawable/ic_skip_next_24dp"
app:tint="?attr/colorOnSurface" />
</LinearLayout>
android:layoutDirection="ltr" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/toolbar_bottom"