Migrate reader slider and next/prev buttons to Compose

This commit is contained in:
arkon 2023-05-03 15:54:37 -04:00
parent 3c79777e66
commit 9a10656bf0
13 changed files with 179 additions and 259 deletions

View File

@ -0,0 +1,121 @@
package eu.kanade.presentation.reader
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.hapticfeedback.HapticFeedbackType
import androidx.compose.ui.platform.LocalHapticFeedback
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.res.stringResource
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,
onNextChapter: () -> Unit,
enabledNext: Boolean,
onPreviousChapter: () -> Unit,
enabledPrevious: Boolean,
currentPage: Int,
totalPages: Int,
onSliderValueChange: (Int) -> Unit,
) {
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,
) {
Text(text = currentPage.toString())
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),
)
}
}
}
}
}

View File

@ -6,18 +6,15 @@ import android.app.ProgressDialog
import android.app.assist.AssistContent
import android.content.Context
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
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.view.Gravity
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
import android.view.Menu
import android.view.MenuItem
@ -37,17 +34,16 @@ 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.lifecycleScope
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
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.presentation.reader.ChapterNavigator
import eu.kanade.presentation.reader.PageIndicatorText
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
@ -66,14 +62,12 @@ import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
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.R2LPagerViewer
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.isNightMode
import eu.kanade.tachiyomi.util.system.toShareIntent
@ -101,7 +95,6 @@ import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.injectLazy
import kotlin.math.abs
import kotlin.math.max
class ReaderActivity : BaseActivity() {
@ -113,9 +106,6 @@ class ReaderActivity : BaseActivity() {
addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
}
}
private const val ENABLED_BUTTON_IMAGE_ALPHA = 255
private const val DISABLED_BUTTON_IMAGE_ALPHA = 64
}
private val readerPreferences: ReaderPreferences by injectLazy()
@ -128,12 +118,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.
*/
@ -255,8 +239,7 @@ class ReaderActivity : BaseActivity() {
*/
override fun onDestroy() {
super.onDestroy()
viewer?.destroy()
viewer = null
viewModel.state.value.viewer?.destroy()
config = null
menuToggleToast?.cancel()
readingModeToast?.cancel()
@ -365,7 +348,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)
}
@ -374,7 +357,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)
}
@ -416,46 +399,30 @@ class ReaderActivity : BaseActivity() {
PageIndicatorText(
currentPage = state.currentPage,
totalPages = state.viewerChapters?.currChapter?.pages?.size ?: -1,
totalPages = state.totalPages,
)
}
// Init listeners on bottom menu
binding.pageSlider.addOnSliderTouchListener(
object : Slider.OnSliderTouchListener {
override fun onStartTrackingTouch(slider: Slider) {
isScrollingThroughPages = true
}
binding.readerNav.setComposeContent {
val state by viewModel.state.collectAsState()
override fun onStopTrackingTouch(slider: Slider) {
isScrollingThroughPages = false
}
},
)
binding.pageSlider.addOnChangeListener { slider, value, fromUser ->
if (viewer != null && fromUser) {
isScrollingThroughPages = true
moveToPageIndex(value.toInt())
slider.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY)
}
}
binding.leftChapter.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer) {
loadNextChapter()
} else {
loadPreviousChapter()
}
}
}
binding.rightChapter.setOnClickListener {
if (viewer != null) {
if (viewer is R2LPagerViewer) {
loadPreviousChapter()
} else {
loadNextChapter()
}
}
if (state.viewer == null) return@setComposeContent
val isRtl = state.viewer is R2LPagerViewer
ChapterNavigator(
isRtl = isRtl,
onNextChapter = ::loadNextChapter,
enabledNext = state.viewerChapters?.nextChapter != null,
onPreviousChapter = ::loadPreviousChapter,
enabledPrevious = state.viewerChapters?.prevChapter != null,
currentPage = state.currentPage,
totalPages = state.totalPages,
onSliderValueChange = {
isScrollingThroughPages = true
moveToPageIndex(it)
},
)
}
initBottomShortcuts()
@ -466,18 +433,6 @@ class ReaderActivity : BaseActivity() {
}
binding.toolbarBottom.background = toolbarBackground.copy(this@ReaderActivity)
binding.readerSeekbar.background = toolbarBackground.copy(this@ReaderActivity)?.apply {
setCornerSize(999F)
}
listOf(binding.leftChapter, binding.rightChapter).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,
@ -671,7 +626,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)
@ -693,7 +648,7 @@ class ReaderActivity : BaseActivity() {
prevViewer.destroy()
binding.viewerContainer.removeAllViews()
}
viewer = newViewer
viewModel.onViewerLoaded(newViewer)
updateViewerInset(readerPreferences.fullscreen().get())
binding.viewerContainer.addView(newViewer.getView())
@ -703,15 +658,6 @@ class ReaderActivity : BaseActivity() {
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)
}
val loadingIndicatorContext = createReaderThemeContext()
loadingIndicator = ReaderProgressIndicator(loadingIndicatorContext).apply {
updateLayoutParams<FrameLayout.LayoutParams> {
@ -751,26 +697,9 @@ class ReaderActivity : BaseActivity() {
*/
private fun setChapters(viewerChapters: ViewerChapters) {
binding.readerContainer.removeView(loadingIndicator)
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
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
} 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
}
// Invalidate menu to show proper chapter bookmark state
invalidateOptionsMenu()
@ -812,7 +741,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)
@ -847,21 +776,6 @@ class ReaderActivity : BaseActivity() {
@SuppressLint("SetTextI18n")
fun onPageSelected(page: ReaderPage) {
viewModel.onPageSelected(page)
val pages = page.chapter.pages ?: return
// Set page numbers
if (viewer !is R2LPagerViewer) {
binding.leftPageText.text = "${page.number}"
binding.rightPageText.text = "${pages.size}"
} else {
binding.rightPageText.text = "${page.number}"
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()
}
/**
@ -989,7 +903,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()

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

@ -33,6 +33,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
@ -396,6 +397,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
@ -881,7 +890,15 @@ class ReaderViewModel(
val viewerChapters: ViewerChapters? = null,
val isLoadingAdjacentChapter: Boolean = false,
val currentPage: Int = -1,
)
/**
* Viewer used to display the pages (pager, webtoon, ...).
*/
val viewer: Viewer? = null,
) {
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

@ -17,7 +17,7 @@ import eu.kanade.tachiyomi.ui.reader.model.ChapterTransition
import eu.kanade.tachiyomi.ui.reader.model.InsertPage
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
@ -26,10 +26,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

@ -18,7 +18,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
@ -30,9 +30,9 @@ import kotlin.math.max
import kotlin.math.min
/**
* 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) : BaseViewer {
class WebtoonViewer(val activity: ReaderActivity, val isContinuous: Boolean = true) : 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

@ -59,82 +59,12 @@
android:layout_gravity="bottom"
android:orientation="vertical">
<LinearLayout
<androidx.compose.ui.platform.ComposeView
android:id="@+id/reader_nav"
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"