Update reader actions bar

master
Koitharu 1 year ago
parent 5d91e20844
commit 09590cfab0
Signed by: Koitharu
GPG Key ID: 676DEE768C17A9D7

@ -170,7 +170,7 @@ private fun SpannableStringBuilder.appendTagsSummary(filter: MangaListFilter) {
} }
} }
fun MangaChapter.getLocalizedTitle(resources: Resources): String { fun MangaChapter.getLocalizedTitle(resources: Resources, index: Int = -1): String {
title?.let { title?.let {
if (it.isNotBlank()) { if (it.isNotBlank()) {
return it return it
@ -181,6 +181,12 @@ fun MangaChapter.getLocalizedTitle(resources: Resources): String {
return when { return when {
num != null && vol != null -> resources.getString(R.string.chapter_volume_number, vol, num) num != null && vol != null -> resources.getString(R.string.chapter_volume_number, vol, num)
num != null -> resources.getString(R.string.chapter_number, num) num != null -> resources.getString(R.string.chapter_number, num)
else -> resources.getString(R.string.unnamed_chapter) // TODO fallback to manga title + index index > 0 -> resources.getString(
R.string.chapters_time_pattern,
resources.getString(R.string.unnamed_chapter),
index.toString(),
)
else -> resources.getString(R.string.unnamed_chapter)
} }
} }

@ -33,7 +33,7 @@ data class MangaDetails(
val coverUrl: String? val coverUrl: String?
get() = manga.largeCoverUrl get() = manga.largeCoverUrl
.ifNullOrEmpty { manga.largeCoverUrl } .ifNullOrEmpty { manga.coverUrl }
.ifNullOrEmpty { localManga?.manga?.coverUrl } .ifNullOrEmpty { localManga?.manga?.coverUrl }
?.nullIfEmpty() ?.nullIfEmpty()

@ -69,7 +69,6 @@ data class ChapterListItem(
} }
} }
private fun buildDescription(): String { private fun buildDescription(): String {
val joiner = StringJoiner("") val joiner = StringJoiner("")
chapter.numberString()?.let { chapter.numberString()?.let {

@ -0,0 +1,239 @@
package org.koitharu.kotatsu.reader.ui
import android.content.Context
import android.content.SharedPreferences
import android.database.ContentObserver
import android.provider.Settings
import android.util.AttributeSet
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.Button
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.annotation.AttrRes
import androidx.annotation.StringRes
import androidx.core.view.ViewCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import com.google.android.material.slider.Slider
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderControl
import org.koitharu.kotatsu.core.util.ext.isRtl
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_PAGES
import org.koitharu.kotatsu.reader.ui.ReaderControlDelegate.OnInteractionListener
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class ReaderActionsView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
@AttrRes defStyleAttr: Int = 0,
) : LinearLayout(context, attrs, defStyleAttr),
View.OnClickListener,
SharedPreferences.OnSharedPreferenceChangeListener,
Slider.OnChangeListener,
Slider.OnSliderTouchListener {
@Inject
lateinit var settings: AppSettings
private val binding = LayoutReaderActionsBinding.inflate(LayoutInflater.from(context), this)
private val rotationObserver = object : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
updateRotationButton()
}
}
private var isSliderChanged = false
private var isSliderTracking = false
var isSliderEnabled: Boolean
get() = binding.slider.isEnabled
set(value) {
binding.slider.isEnabled = value
binding.slider.setThumbVisible(value)
}
var isNextEnabled: Boolean
get() = binding.buttonNext.isEnabled
set(value) {
binding.buttonNext.isEnabled = value
}
var isPrevEnabled: Boolean
get() = binding.buttonPrev.isEnabled
set(value) {
binding.buttonPrev.isEnabled = value
}
var listener: OnInteractionListener? = null
init {
orientation = HORIZONTAL
gravity = Gravity.CENTER_VERTICAL
binding.buttonNext.initAction()
binding.buttonPrev.initAction()
binding.buttonSave.initAction()
binding.buttonOptions.initAction()
binding.buttonScreenRotation.initAction()
binding.buttonPagesThumbs.initAction()
binding.slider.setLabelFormatter(PageLabelFormatter())
binding.slider.addOnChangeListener(this)
binding.slider.addOnSliderTouchListener(this)
updateControlsVisibility()
updatePagesSheetButton()
updateRotationButton()
}
override fun onAttachedToWindow() {
super.onAttachedToWindow()
settings.subscribe(this)
context.contentResolver.registerContentObserver(
Settings.System.CONTENT_URI, true, rotationObserver,
)
}
override fun onDetachedFromWindow() {
settings.unsubscribe(this)
context.contentResolver.unregisterContentObserver(rotationObserver)
super.onDetachedFromWindow()
}
override fun onClick(v: View) {
when (v.id) {
R.id.button_prev -> listener?.switchChapterBy(-1)
R.id.button_next -> listener?.switchChapterBy(1)
R.id.button_save -> listener?.onSavePageClick()
R.id.button_pages_thumbs -> AppRouter.from(this)?.showChapterPagesSheet()
R.id.button_screen_rotation -> listener?.toggleScreenOrientation()
R.id.button_options -> listener?.openMenu()
}
}
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (fromUser) {
if (isSliderTracking) {
isSliderChanged = true
} else {
listener?.switchPageTo(value.toInt())
}
}
}
override fun onStartTrackingTouch(slider: Slider) {
isSliderChanged = false
isSliderTracking = true
}
override fun onStopTrackingTouch(slider: Slider) {
isSliderTracking = false
if (isSliderChanged) {
listener?.switchPageTo(slider.value.toInt())
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) {
AppSettings.KEY_READER_CONTROLS -> updateControlsVisibility()
AppSettings.KEY_PAGES_TAB,
AppSettings.KEY_DETAILS_TAB,
AppSettings.KEY_DETAILS_LAST_TAB -> updatePagesSheetButton()
}
}
fun setSliderValue(value: Int, max: Int) {
binding.slider.valueTo = max.toFloat()
binding.slider.setValueRounded(value.toFloat())
}
fun setSliderReversed(reversed: Boolean) {
binding.slider.isRtl = reversed != isRtl
}
private fun updateControlsVisibility() {
val controls = settings.readerControls
binding.buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
binding.buttonNext.isVisible = ReaderControl.NEXT_CHAPTER in controls
binding.buttonPagesThumbs.isVisible = ReaderControl.PAGES_SHEET in controls
binding.buttonScreenRotation.isVisible = ReaderControl.SCREEN_ROTATION in controls
binding.buttonSave.isVisible = ReaderControl.SAVE_PAGE in controls
binding.slider.isVisible = ReaderControl.SLIDER in controls
adjustLayoutParams()
}
private fun updatePagesSheetButton() {
val isPagesMode = settings.defaultDetailsTab == TAB_PAGES
val button = binding.buttonPagesThumbs
button.setIconResource(
if (isPagesMode) R.drawable.ic_grid else R.drawable.ic_list,
)
button.setTitle(
if (isPagesMode) R.string.pages else R.string.chapters,
)
}
private fun adjustLayoutParams() {
val isSliderVisible = binding.slider.isVisible
repeat(childCount) { i ->
val child = getChildAt(i)
if (child is FrameLayout) {
child.updateLayoutParams<LayoutParams> {
width = if (isSliderVisible) LayoutParams.WRAP_CONTENT else 0
weight = if (isSliderVisible) 0f else 1f
}
}
}
}
private fun updateRotationButton() {
val button = binding.buttonScreenRotation
when {
!button.isVisible -> return
isAutoRotationEnabled() -> {
button.setTitle(R.string.lock_screen_rotation)
button.setIconResource(R.drawable.ic_screen_rotation_lock)
}
else -> {
button.setTitle(R.string.rotate_screen)
button.setIconResource(R.drawable.ic_screen_rotation)
}
}
}
private fun Button.initAction() {
setOnClickListener(this@ReaderActionsView)
ViewCompat.setTooltipText(this, contentDescription)
}
private fun Button.setTitle(@StringRes titleResId: Int) {
val text = resources.getString(titleResId)
contentDescription = text
ViewCompat.setTooltipText(this, text)
}
private fun isAutoRotationEnabled(): Boolean = Settings.System.getInt(
context.contentResolver,
Settings.System.ACCELEROMETER_ROTATION,
0,
) == 1
private fun Slider.setThumbVisible(visible: Boolean) {
thumbWidth = if (visible) {
resources.getDimensionPixelSize(materialR.dimen.m3_comp_slider_active_handle_width)
} else {
0
}
thumbHeight = if (visible) {
resources.getDimensionPixelSize(materialR.dimen.m3_comp_slider_active_handle_height)
} else {
0
}
}
}

@ -10,11 +10,10 @@ import android.view.Gravity
import android.view.KeyEvent import android.view.KeyEvent
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.ViewGroup.MarginLayoutParams import android.view.ViewGroup
import android.view.WindowManager import android.view.WindowManager
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.graphics.Insets import androidx.core.graphics.Insets
import androidx.core.view.MenuHost
import androidx.core.view.OnApplyWindowInsetsListener import androidx.core.view.OnApplyWindowInsetsListener
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
@ -35,7 +34,6 @@ import org.koitharu.kotatsu.core.exceptions.resolve.DialogErrorObserver
import org.koitharu.kotatsu.core.nav.AppRouter import org.koitharu.kotatsu.core.nav.AppRouter
import org.koitharu.kotatsu.core.nav.router import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderControl
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity import org.koitharu.kotatsu.core.ui.BaseFullscreenActivity
import org.koitharu.kotatsu.core.ui.util.MenuInvalidator import org.koitharu.kotatsu.core.ui.util.MenuInvalidator
@ -43,11 +41,9 @@ import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
import org.koitharu.kotatsu.core.util.IdlingDetector import org.koitharu.kotatsu.core.util.IdlingDetector
import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint import org.koitharu.kotatsu.core.util.ext.hasGlobalPoint
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
import org.koitharu.kotatsu.core.util.ext.isRtl
import org.koitharu.kotatsu.core.util.ext.observe import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.observeEvent import org.koitharu.kotatsu.core.util.ext.observeEvent
import org.koitharu.kotatsu.core.util.ext.postDelayed import org.koitharu.kotatsu.core.util.ext.postDelayed
import org.koitharu.kotatsu.core.util.ext.setValueRounded
import org.koitharu.kotatsu.core.util.ext.zipWithPrevious import org.koitharu.kotatsu.core.util.ext.zipWithPrevious
import org.koitharu.kotatsu.databinding.ActivityReaderBinding import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.details.ui.pager.pages.PagesSavedObserver import org.koitharu.kotatsu.details.ui.pager.pages.PagesSavedObserver
@ -100,9 +96,6 @@ class ReaderActivity :
scrollTimer.isEnabled = value scrollTimer.isEnabled = value
} }
private val secondaryMenuHost: MenuHost
get() = viewBinding.toolbarBottom ?: this
private lateinit var scrollTimer: ScrollTimer private lateinit var scrollTimer: ScrollTimer
private lateinit var pageSaveHelper: PageSaveHelper private lateinit var pageSaveHelper: PageSaveHelper
private lateinit var touchHelper: TapGridDispatcher private lateinit var touchHelper: TapGridDispatcher
@ -120,13 +113,11 @@ class ReaderActivity :
scrollTimer = scrollTimerFactory.create(this, this) scrollTimer = scrollTimerFactory.create(this, this)
pageSaveHelper = pageSaveHelperFactory.create(this) pageSaveHelper = pageSaveHelperFactory.create(this)
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this) controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
viewBinding.slider.setLabelFormatter(PageLabelFormatter())
viewBinding.zoomControl.listener = this viewBinding.zoomControl.listener = this
ReaderSliderListener(viewModel, this).attachToSlider(viewBinding.slider) viewBinding.actionsView.listener = this
idlingDetector.bindToLifecycle(this) idlingDetector.bindToLifecycle(this)
viewBinding.buttonPrev.setOnClickListener(controlDelegate)
viewBinding.buttonNext.setOnClickListener(controlDelegate)
ViewCompat.setOnApplyWindowInsetsListener(viewBinding.root, this) ViewCompat.setOnApplyWindowInsetsListener(viewBinding.root, this)
screenOrientationHelper.applySettings()
viewModel.onError.observeEvent( viewModel.onError.observeEvent(
this, this,
@ -150,17 +141,13 @@ class ReaderActivity :
viewModel.content.observe(this) { viewModel.content.observe(this) {
onLoadingStateChanged(viewModel.isLoading.value) onLoadingStateChanged(viewModel.isLoading.value)
} }
viewModel.readerControls.observe(this, ::onReaderControlsChanged)
viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn) viewModel.isKeepScreenOnEnabled.observe(this, this::setKeepScreenOn)
viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it } viewModel.isInfoBarTransparent.observe(this) { viewBinding.infoBar.drawBackground = !it }
viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged) viewModel.isInfoBarEnabled.observe(this, ::onReaderBarChanged)
viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this)) viewModel.isBookmarkAdded.observe(this, MenuInvalidator(this))
val bottomMenuInvalidator = MenuInvalidator(secondaryMenuHost)
viewModel.isPagesSheetEnabled.observe(this, bottomMenuInvalidator)
screenOrientationHelper.observeAutoOrientation().observe(this, bottomMenuInvalidator)
viewModel.onShowToast.observeEvent(this) { msgId -> viewModel.onShowToast.observeEvent(this) { msgId ->
Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT) Snackbar.make(viewBinding.container, msgId, Snackbar.LENGTH_SHORT)
.setAnchorView(viewBinding.appbarBottom) .setAnchorView(viewBinding.toolbarDocked)
.show() .show()
} }
viewModel.readerSettings.observe(this) { viewModel.readerSettings.observe(this) {
@ -169,10 +156,7 @@ class ReaderActivity :
viewModel.isZoomControlsEnabled.observe(this) { viewModel.isZoomControlsEnabled.observe(this) {
viewBinding.zoomControl.isVisible = it viewBinding.zoomControl.isVisible = it
} }
addMenuProvider(ReaderMenuTopProvider(viewModel)) addMenuProvider(ReaderMenuProvider(viewModel))
secondaryMenuHost.addMenuProvider(
ReaderMenuBottomProvider(this, readerManager, screenOrientationHelper, this, viewModel),
)
} }
override fun getParentActivityIntent(): Intent? { override fun getParentActivityIntent(): Intent? {
@ -215,7 +199,7 @@ class ReaderActivity :
if (viewBinding.appbarTop.isVisible) { if (viewBinding.appbarTop.isVisible) {
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable) lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
} }
viewBinding.slider.isRtl = mode == ReaderMode.REVERSED viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED)
} }
private fun onLoadingStateChanged(isLoading: Boolean) { private fun onLoadingStateChanged(isLoading: Boolean) {
@ -226,7 +210,6 @@ class ReaderActivity :
} else { } else {
viewBinding.toastView.hide() viewBinding.toastView.hide()
} }
secondaryMenuHost.invalidateMenu()
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -247,7 +230,7 @@ class ReaderActivity :
rawX >= viewBinding.root.width - gestureInsets.right || rawX >= viewBinding.root.width - gestureInsets.right ||
rawY >= viewBinding.root.height - gestureInsets.bottom || rawY >= viewBinding.root.height - gestureInsets.bottom ||
viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) || viewBinding.appbarTop.hasGlobalPoint(rawX, rawY) ||
viewBinding.appbarBottom?.hasGlobalPoint(rawX, rawY) == true viewBinding.toolbarDocked?.hasGlobalPoint(rawX, rawY) == true
) { ) {
false false
} else { } else {
@ -263,7 +246,7 @@ class ReaderActivity :
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return controlDelegate.onKeyDown(keyCode) || super.onKeyDown(keyCode, event) return controlDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event)
} }
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
@ -307,13 +290,6 @@ class ReaderActivity :
} }
} }
private fun onReaderControlsChanged(controls: Set<ReaderControl>) = with(viewBinding) {
buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
buttonNext.isVisible = ReaderControl.NEXT_CHAPTER in controls
slider.isVisible = ReaderControl.SLIDER in controls
secondaryMenuHost.invalidateMenu()
}
private fun setUiIsVisible(isUiVisible: Boolean) { private fun setUiIsVisible(isUiVisible: Boolean) {
if (viewBinding.appbarTop.isVisible != isUiVisible) { if (viewBinding.appbarTop.isVisible != isUiVisible) {
if (isAnimationsEnabled) { if (isAnimationsEnabled) {
@ -321,12 +297,12 @@ class ReaderActivity :
.setOrdering(TransitionSet.ORDERING_TOGETHER) .setOrdering(TransitionSet.ORDERING_TOGETHER)
.addTransition(Slide(Gravity.TOP).addTarget(viewBinding.appbarTop)) .addTransition(Slide(Gravity.TOP).addTarget(viewBinding.appbarTop))
.addTransition(Fade().addTarget(viewBinding.infoBar)) .addTransition(Fade().addTarget(viewBinding.infoBar))
.addTransition(Slide(Gravity.BOTTOM).addTarget(viewBinding.appbarBottom)) .addTransition(Slide(Gravity.BOTTOM).addTarget(viewBinding.toolbarDocked))
TransitionManager.beginDelayedTransition(viewBinding.root, transition) TransitionManager.beginDelayedTransition(viewBinding.root, transition)
} }
val isFullscreen = settings.isReaderFullscreenEnabled val isFullscreen = settings.isReaderFullscreenEnabled
viewBinding.appbarTop.isVisible = isUiVisible viewBinding.appbarTop.isVisible = isUiVisible
viewBinding.appbarBottom?.isVisible = isUiVisible viewBinding.toolbarDocked?.isVisible = isUiVisible
viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value) viewBinding.infoBar.isGone = isUiVisible || (!viewModel.isInfoBarEnabled.value)
viewBinding.infoBar.isTimeVisible = isFullscreen viewBinding.infoBar.isTimeVisible = isFullscreen
systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen) systemUiController.setSystemUiVisible(isUiVisible || !isFullscreen)
@ -341,10 +317,12 @@ class ReaderActivity :
right = systemBars.right, right = systemBars.right,
left = systemBars.left, left = systemBars.left,
) )
viewBinding.appbarBottom?.updateLayoutParams<MarginLayoutParams> { if (viewBinding.toolbarDocked != null) {
bottomMargin = systemBars.bottom + topMargin viewBinding.actionsView.updateLayoutParams<ViewGroup.MarginLayoutParams> {
rightMargin = systemBars.right + topMargin bottomMargin = systemBars.bottom
leftMargin = systemBars.left + topMargin rightMargin = systemBars.right
leftMargin = systemBars.left
}
} }
viewBinding.infoBar.updatePadding( viewBinding.infoBar.updatePadding(
top = systemBars.top, top = systemBars.top,
@ -385,6 +363,28 @@ class ReaderActivity :
viewModel.saveCurrentPage(pageSaveHelper) viewModel.saveCurrentPage(pageSaveHelper)
} }
override fun toggleScreenOrientation() {
if (screenOrientationHelper.toggleScreenOrientation()) {
Snackbar.make(
viewBinding.container,
if (screenOrientationHelper.isLocked) {
R.string.screen_rotation_locked
} else {
R.string.screen_rotation_unlocked
},
Snackbar.LENGTH_SHORT,
).setAnchorView(viewBinding.toolbarDocked)
.show()
}
}
override fun switchPageTo(index: Int) {
val pages = viewModel.getCurrentChapterPages()
val page = pages?.getOrNull(index) ?: return
val chapterId = viewModel.getCurrentState()?.chapterId ?: return
onPageSelected(ReaderPage(page, index, chapterId))
}
private fun onReaderBarChanged(isBarEnabled: Boolean) { private fun onReaderBarChanged(isBarEnabled: Boolean) {
viewBinding.infoBar.isVisible = isBarEnabled && viewBinding.appbarTop.isGone viewBinding.infoBar.isVisible = isBarEnabled && viewBinding.appbarTop.isGone
} }
@ -395,27 +395,29 @@ class ReaderActivity :
viewBinding.infoBar.update(uiState) viewBinding.infoBar.update(uiState)
if (uiState == null) { if (uiState == null) {
supportActionBar?.subtitle = null supportActionBar?.subtitle = null
viewBinding.layoutSlider.isVisible = false viewBinding.actionsView.setSliderValue(0, 1)
viewBinding.actionsView.isSliderEnabled = false
return return
} }
val chapterTitle = uiState.getChapterTitle(resources)
supportActionBar?.subtitle = when { supportActionBar?.subtitle = when {
uiState.incognito -> getString(R.string.incognito_mode) uiState.incognito -> getString(R.string.incognito_mode)
else -> uiState.chapterName else -> chapterTitle
} }
if (uiState.chapterName != previous?.chapterName && !uiState.chapterName.isNullOrEmpty()) { if (chapterTitle != previous?.getChapterTitle(resources) && chapterTitle.isNotEmpty()) {
viewBinding.toastView.showTemporary(uiState.chapterName, TOAST_DURATION) viewBinding.toastView.showTemporary(chapterTitle, TOAST_DURATION)
} }
if (uiState.isSliderAvailable()) { if (uiState.isSliderAvailable()) {
viewBinding.slider.valueTo = uiState.totalPages.toFloat() - 1 viewBinding.actionsView.setSliderValue(
viewBinding.slider.setValueRounded(uiState.currentPage.toFloat()) value = uiState.currentPage,
max = uiState.totalPages - 1,
)
} else { } else {
viewBinding.slider.valueTo = 1f viewBinding.actionsView.setSliderValue(0, 1)
viewBinding.slider.value = 0f
} }
viewBinding.slider.isEnabled = uiState.isSliderAvailable() viewBinding.actionsView.isSliderEnabled = uiState.isSliderAvailable()
viewBinding.buttonNext.isEnabled = uiState.hasNextChapter() viewBinding.actionsView.isNextEnabled = uiState.hasNextChapter()
viewBinding.buttonPrev.isEnabled = uiState.hasPreviousChapter() viewBinding.actionsView.isPrevEnabled = uiState.hasPreviousChapter()
viewBinding.layoutSlider.isVisible = true
} }
companion object { companion object {

@ -9,6 +9,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.reader.data.TapGridSettings import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea import org.koitharu.kotatsu.reader.domain.TapGridArea
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
import kotlin.math.sign
class ReaderControlDelegate( class ReaderControlDelegate(
resources: Resources, resources: Resources,
@ -43,77 +44,48 @@ class ReaderControlDelegate(
processAction(action) processAction(action)
} }
fun onKeyDown(keyCode: Int): Boolean = when (keyCode) { fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
when (keyCode) {
KeyEvent.KEYCODE_NAVIGATE_NEXT,
KeyEvent.KEYCODE_SPACE -> switchBy(1, event, false)
KeyEvent.KEYCODE_R -> { KeyEvent.KEYCODE_PAGE_DOWN -> switchBy(1, event, false)
listener.switchPageBy(1)
true
}
KeyEvent.KEYCODE_L -> {
listener.switchPageBy(-1) KeyEvent.KEYCODE_NAVIGATE_PREVIOUS -> switchBy(-1, event, false)
true KeyEvent.KEYCODE_PAGE_UP -> switchBy(-1, event, false)
}
KeyEvent.KEYCODE_R -> switchBy(1, null, false)
KeyEvent.KEYCODE_L -> switchBy(-1, null, false)
KeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) { KeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) {
listener.switchPageBy(-1) switchBy(-1, event, false)
true
} else { } else {
false return false
} }
KeyEvent.KEYCODE_VOLUME_DOWN -> if (settings.isReaderVolumeButtonsEnabled) { KeyEvent.KEYCODE_VOLUME_DOWN -> if (settings.isReaderVolumeButtonsEnabled) {
listener.switchPageBy(1) switchBy(1, event, false)
true
} else { } else {
false return false
}
KeyEvent.KEYCODE_SPACE,
KeyEvent.KEYCODE_PAGE_DOWN,
-> {
listener.switchPageBy(1)
true
} }
KeyEvent.KEYCODE_DPAD_RIGHT -> { KeyEvent.KEYCODE_DPAD_RIGHT -> switchByRelative(-1, event)
listener.switchPageBy(if (isReaderTapsReversed()) -1 else 1)
true
}
KeyEvent.KEYCODE_PAGE_UP, KeyEvent.KEYCODE_DPAD_LEFT -> switchByRelative(1, event)
-> {
listener.switchPageBy(-1)
true
}
KeyEvent.KEYCODE_DPAD_LEFT -> { KeyEvent.KEYCODE_DPAD_CENTER -> listener.toggleUiVisibility()
listener.switchPageBy(if (isReaderTapsReversed()) 1 else -1)
true
}
KeyEvent.KEYCODE_DPAD_CENTER -> {
listener.toggleUiVisibility()
true
}
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
KeyEvent.KEYCODE_DPAD_UP -> { KeyEvent.KEYCODE_DPAD_UP -> switchBy(-1, event, true)
if (!listener.scrollBy(-minScrollDelta, smooth = true)) {
listener.switchPageBy(-1)
}
true
}
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN, KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN -> { KeyEvent.KEYCODE_DPAD_DOWN -> switchBy(1, event, true)
if (!listener.scrollBy(minScrollDelta, smooth = true)) {
listener.switchPageBy(1)
}
true
}
else -> false else -> return false
}
return true
} }
fun onKeyUp(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean { fun onKeyUp(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean {
@ -136,12 +108,30 @@ class ReaderControlDelegate(
return settings.isReaderControlAlwaysLTR && listener.readerMode == ReaderMode.REVERSED return settings.isReaderControlAlwaysLTR && listener.readerMode == ReaderMode.REVERSED
} }
private fun switchBy(delta: Int, event: KeyEvent?, scroll: Boolean) {
if (event?.isCtrlPressed == true) {
listener.switchChapterBy(delta)
} else if (scroll) {
if (!listener.scrollBy(minScrollDelta * delta.sign, smooth = true)) {
listener.switchPageBy(delta)
}
} else {
listener.switchPageBy(delta)
}
}
private fun switchByRelative(delta: Int, event: KeyEvent?) {
return switchBy(if (isReaderTapsReversed()) -delta else delta, event, scroll = false)
}
interface OnInteractionListener { interface OnInteractionListener {
val readerMode: ReaderMode? val readerMode: ReaderMode?
fun switchPageBy(delta: Int) fun switchPageBy(delta: Int)
fun switchPageTo(index: Int)
fun switchChapterBy(delta: Int) fun switchChapterBy(delta: Int)
fun scrollBy(delta: Int, smooth: Boolean): Boolean fun scrollBy(delta: Int, smooth: Boolean): Boolean
@ -150,6 +140,10 @@ class ReaderControlDelegate(
fun openMenu() fun openMenu()
fun onSavePageClick()
fun toggleScreenOrientation()
fun isReaderResumed(): Boolean fun isReaderResumed(): Boolean
} }
} }

@ -1,111 +0,0 @@
package org.koitharu.kotatsu.reader.ui
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.widget.Toast
import androidx.core.view.MenuProvider
import androidx.fragment.app.FragmentActivity
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.nav.router
import org.koitharu.kotatsu.core.prefs.ReaderControl
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
class ReaderMenuBottomProvider(
private val activity: FragmentActivity,
private val readerManager: ReaderManager,
private val screenOrientationHelper: ScreenOrientationHelper,
private val configCallback: ReaderConfigSheet.Callback,
private val viewModel: ReaderViewModel,
) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_reader_bottom, menu)
onPrepareMenu(menu) // fix, not called in toolbar
}
override fun onPrepareMenu(menu: Menu) {
val readerControls = viewModel.readerControls.value
val hasPages = viewModel.content.value.pages.isNotEmpty()
val isPagesSheetEnabled = hasPages && ReaderControl.PAGES_SHEET in readerControls
menu.findItem(R.id.action_pages_thumbs).run {
isVisible = isPagesSheetEnabled
if (isPagesSheetEnabled) {
setIcon(if (viewModel.isPagesSheetEnabled.value) R.drawable.ic_grid else R.drawable.ic_list)
}
}
menu.findItem(R.id.action_screen_rotation).run {
isVisible = ReaderControl.SCREEN_ROTATION in readerControls
when {
!isVisible -> Unit
!screenOrientationHelper.isAutoRotationEnabled -> {
setTitle(R.string.rotate_screen)
setIcon(R.drawable.ic_screen_rotation)
}
else -> {
setTitle(R.string.lock_screen_rotation)
setIcon(R.drawable.ic_screen_rotation_lock)
}
}
}
menu.findItem(R.id.action_save_page)?.run {
isVisible = hasPages && ReaderControl.SAVE_PAGE in readerControls
}
}
override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
return when (menuItem.itemId) {
R.id.action_screen_rotation -> {
toggleScreenRotation()
true
}
R.id.action_save_page -> {
configCallback.onSavePageClick()
true
}
R.id.action_pages_thumbs -> {
activity.router.showChapterPagesSheet()
true
}
R.id.action_options -> {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return false
activity.router.showReaderConfigSheet(currentMode)
true
}
R.id.action_bookmark -> {
if (viewModel.isBookmarkAdded.value) {
viewModel.removeBookmark()
} else {
viewModel.addBookmark()
}
true
}
else -> false
}
}
private fun toggleScreenRotation() = with(screenOrientationHelper) {
if (isAutoRotationEnabled) {
val newValue = !isLocked
isLocked = newValue
Toast.makeText(
activity,
if (newValue) {
R.string.screen_rotation_locked
} else {
R.string.screen_rotation_unlocked
},
Toast.LENGTH_SHORT,
).show()
} else {
isLandscape = !isLandscape
}
}
}

@ -6,12 +6,12 @@ import android.view.MenuItem
import androidx.core.view.MenuProvider import androidx.core.view.MenuProvider
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
class ReaderMenuTopProvider( class ReaderMenuProvider(
private val viewModel: ReaderViewModel, private val viewModel: ReaderViewModel,
) : MenuProvider { ) : MenuProvider {
override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) { override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
menuInflater.inflate(R.menu.opt_reader_top, menu) menuInflater.inflate(R.menu.opt_reader, menu)
} }
override fun onPrepareMenu(menu: Menu) { override fun onPrepareMenu(menu: Menu) {

@ -1,47 +0,0 @@
package org.koitharu.kotatsu.reader.ui
import com.google.android.material.slider.Slider
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
class ReaderSliderListener(
private val viewModel: ReaderViewModel,
private val callback: ReaderNavigationCallback,
) : Slider.OnChangeListener, Slider.OnSliderTouchListener {
private var isChanged = false
private var isTracking = false
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
if (fromUser) {
if (isTracking) {
isChanged = true
} else {
switchPageToIndex(value.toInt())
}
}
}
override fun onStartTrackingTouch(slider: Slider) {
isChanged = false
isTracking = true
}
override fun onStopTrackingTouch(slider: Slider) {
isTracking = false
if (isChanged) {
switchPageToIndex(slider.value.toInt())
}
}
fun attachToSlider(slider: Slider) {
slider.addOnChangeListener(this)
slider.addOnSliderTouchListener(this)
}
private fun switchPageToIndex(index: Int) {
val pages = viewModel.getCurrentChapterPages()
val page = pages?.getOrNull(index) ?: return
val chapterId = viewModel.getCurrentState()?.chapterId ?: return
callback.onPageSelected(ReaderPage(page, index, chapterId))
}
}

@ -15,7 +15,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
@ -43,7 +42,6 @@ import org.koitharu.kotatsu.core.util.ext.requireValue
import org.koitharu.kotatsu.details.data.MangaDetails import org.koitharu.kotatsu.details.data.MangaDetails
import org.koitharu.kotatsu.details.domain.DetailsInteractor import org.koitharu.kotatsu.details.domain.DetailsInteractor
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesSheet.Companion.TAB_PAGES
import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel import org.koitharu.kotatsu.details.ui.pager.ChaptersPagesViewModel
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
import org.koitharu.kotatsu.history.data.HistoryRepository import org.koitharu.kotatsu.history.data.HistoryRepository
@ -120,8 +118,6 @@ class ReaderViewModel @Inject constructor(
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false) .stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
} }
val isPagesSheetEnabled = observeIsPagesSheetEnabled()
val content = MutableStateFlow(ReaderContent(emptyList(), null)) val content = MutableStateFlow(ReaderContent(emptyList(), null))
val pageAnimation = settings.observeAsStateFlow( val pageAnimation = settings.observeAsStateFlow(
@ -136,12 +132,6 @@ class ReaderViewModel @Inject constructor(
valueProducer = { isReaderBarEnabled }, valueProducer = { isReaderBarEnabled },
) )
val readerControls = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_READER_CONTROLS,
valueProducer = { readerControls },
)
val isInfoBarTransparent = settings.observeAsStateFlow( val isInfoBarTransparent = settings.observeAsStateFlow(
scope = viewModelScope + Dispatchers.Default, scope = viewModelScope + Dispatchers.Default,
key = AppSettings.KEY_READER_BAR_TRANSPARENT, key = AppSettings.KEY_READER_BAR_TRANSPARENT,
@ -449,9 +439,8 @@ class ReaderViewModel @Inject constructor(
val chapterIndex = m.chapters[chapter.branch]?.indexOfFirst { it.id == chapter.id } ?: -1 val chapterIndex = m.chapters[chapter.branch]?.indexOfFirst { it.id == chapter.id } ?: -1
val newState = ReaderUiState( val newState = ReaderUiState(
mangaName = m.toManga().title, mangaName = m.toManga().title,
branch = chapter.branch, chapter = chapter,
chapterName = chapter.name, chapterIndex = chapterIndex,
chapterNumber = chapterIndex + 1,
chaptersTotal = m.chapters[chapter.branch].sizeOrZero(), chaptersTotal = m.chapters[chapter.branch].sizeOrZero(),
totalPages = chaptersLoader.getPagesCount(chapter.id), totalPages = chaptersLoader.getPagesCount(chapter.id),
currentPage = state.page, currentPage = state.page,
@ -493,11 +482,6 @@ class ReaderViewModel @Inject constructor(
valueProducer = { isReaderZoomButtonsEnabled }, valueProducer = { isReaderZoomButtonsEnabled },
) )
private fun observeIsPagesSheetEnabled() = settings.observe()
.filter { it == AppSettings.KEY_PAGES_TAB || it == AppSettings.KEY_DETAILS_TAB || it == AppSettings.KEY_DETAILS_LAST_TAB }
.map { settings.defaultDetailsTab == TAB_PAGES }
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, settings.defaultDetailsTab == TAB_PAGES)
private suspend fun getStateFromIntent(manga: Manga): ReaderState { private suspend fun getStateFromIntent(manga: Manga): ReaderState {
val history = historyRepository.getOne(manga) val history = historyRepository.getOne(manga)
val preselectedBranch = selectedBranch.value val preselectedBranch = selectedBranch.value

@ -19,7 +19,7 @@ import javax.inject.Inject
@ActivityScoped @ActivityScoped
class ScreenOrientationHelper @Inject constructor( class ScreenOrientationHelper @Inject constructor(
private val activity: Activity, private val activity: Activity,
settings: AppSettings, private val settings: AppSettings,
) { ) {
val isAutoRotationEnabled: Boolean val isAutoRotationEnabled: Boolean
@ -49,7 +49,7 @@ class ScreenOrientationHelper @Inject constructor(
} }
} }
init { fun applySettings() {
if (activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) { if (activity.requestedOrientation == ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
// https://developer.android.com/reference/android/R.attr.html#screenOrientation // https://developer.android.com/reference/android/R.attr.html#screenOrientation
activity.requestedOrientation = settings.readerScreenOrientation activity.requestedOrientation = settings.readerScreenOrientation
@ -72,4 +72,13 @@ class ScreenOrientationHelper @Inject constructor(
emit(isAutoRotationEnabled) emit(isAutoRotationEnabled)
}.distinctUntilChanged() }.distinctUntilChanged()
.conflate() .conflate()
fun toggleScreenOrientation(): Boolean = if (isAutoRotationEnabled) {
val newValue = !isLocked
isLocked = newValue
true
} else {
isLandscape = !isLandscape
false
}
} }

@ -1,10 +1,13 @@
package org.koitharu.kotatsu.reader.ui.pager package org.koitharu.kotatsu.reader.ui.pager
import android.content.res.Resources
import org.koitharu.kotatsu.core.model.getLocalizedTitle
import org.koitharu.kotatsu.parsers.model.MangaChapter
data class ReaderUiState( data class ReaderUiState(
val mangaName: String?, val mangaName: String?,
val branch: String?, val chapter: MangaChapter,
val chapterName: String?, val chapterIndex: Int,
val chapterNumber: Int,
val chaptersTotal: Int, val chaptersTotal: Int,
val currentPage: Int, val currentPage: Int,
val totalPages: Int, val totalPages: Int,
@ -12,9 +15,14 @@ data class ReaderUiState(
val incognito: Boolean, val incognito: Boolean,
) { ) {
val chapterNumber: Int
get() = chapterIndex + 1
fun hasNextChapter(): Boolean = chapterNumber < chaptersTotal fun hasNextChapter(): Boolean = chapterNumber < chaptersTotal
fun hasPreviousChapter(): Boolean = chapterNumber > 1 fun hasPreviousChapter(): Boolean = chapterIndex > 0
fun isSliderAvailable(): Boolean = totalPages > 1 && currentPage < totalPages fun isSliderAvailable(): Boolean = totalPages > 1 && currentPage < totalPages
fun getChapterTitle(resources: Resources) = chapter.getLocalizedTitle(resources, chapterIndex)
} }

@ -49,45 +49,13 @@
android:elevation="@dimen/m3_card_elevated_elevation" android:elevation="@dimen/m3_card_elevated_elevation"
app:elevation="@dimen/m3_card_elevated_elevation" app:elevation="@dimen/m3_card_elevated_elevation"
app:popupTheme="@style/ThemeOverlay.Kotatsu" app:popupTheme="@style/ThemeOverlay.Kotatsu"
tools:menu="@menu/opt_reader_top"> tools:menu="@menu/opt_reader">
<LinearLayout <org.koitharu.kotatsu.reader.ui.ReaderActionsView
android:id="@+id/layout_slider" android:id="@+id/actionsView"
android:layout_width="wrap_content" android:layout_width="400dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical|end"
android:layout_marginEnd="2dp"
android:gravity="center_vertical|end">
<ImageButton
android:id="@+id/button_prev"
style="?actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/prev_chapter"
android:src="@drawable/ic_prev"
android:tooltipText="@string/prev_chapter" />
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="260dp"
android:layout_height="wrap_content"
android:stepSize="1.0"
android:valueFrom="0"
app:labelBehavior="floating"
tools:value="6"
tools:valueTo="20" />
<ImageButton
android:id="@+id/button_next"
style="?actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/next_chapter" android:layout_gravity="center_vertical|end" />
android:src="@drawable/ic_next"
android:tooltipText="@string/next_chapter" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar> </com.google.android.material.appbar.MaterialToolbar>

@ -49,68 +49,26 @@
android:elevation="@dimen/m3_card_elevated_elevation" android:elevation="@dimen/m3_card_elevated_elevation"
app:elevation="@dimen/m3_card_elevated_elevation" app:elevation="@dimen/m3_card_elevated_elevation"
app:popupTheme="@style/ThemeOverlay.Kotatsu" app:popupTheme="@style/ThemeOverlay.Kotatsu"
tools:menu="@menu/opt_reader_top" /> tools:menu="@menu/opt_reader" />
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<com.google.android.material.card.MaterialCardView <com.google.android.material.dockedtoolbar.DockedToolbarLayout
android:id="@+id/appbar_bottom" android:id="@+id/toolbar_docked"
style="?materialCardViewElevatedStyle"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"
android:layout_margin="8dp" android:fitsSystemWindows="false"
app:cardBackgroundColor="?colorSurfaceContainer" app:cardBackgroundColor="?colorSurfaceContainer"
app:layout_insetEdge="bottom"> app:layout_insetEdge="bottom">
<com.google.android.material.appbar.MaterialToolbar <org.koitharu.kotatsu.reader.ui.ReaderActionsView
android:id="@+id/toolbar_bottom" android:id="@+id/actionsView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:menu="@menu/opt_reader_bottom">
<LinearLayout
android:id="@+id/layout_slider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="center_vertical" android:minHeight="?actionBarSize" />
android:layout_marginEnd="2dp"
android:gravity="center_vertical|end">
<ImageButton
android:id="@+id/button_prev"
style="?actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/prev_chapter"
android:src="@drawable/ic_prev"
android:tooltipText="@string/prev_chapter" />
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:stepSize="1.0"
android:valueFrom="0"
app:labelBehavior="floating"
tools:value="6"
tools:valueTo="20" />
<ImageButton
android:id="@+id/button_next"
style="?actionButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/next_chapter"
android:src="@drawable/ic_next"
android:tooltipText="@string/next_chapter" />
</LinearLayout>
</com.google.android.material.appbar.MaterialToolbar>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.dockedtoolbar.DockedToolbarLayout>
<org.koitharu.kotatsu.reader.ui.ReaderToastView <org.koitharu.kotatsu.reader.ui.ReaderToastView
android:id="@+id/toastView" android:id="@+id/toastView"

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:layout_height="wrap_content"
tools:layout_width="match_parent"
tools:orientation="horizontal"
tools:parentTag="android.widget.LinearLayout">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_prev"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/prev_chapter"
app:icon="@drawable/ic_prev" />
</FrameLayout>
<com.google.android.material.slider.Slider
android:id="@+id/slider"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:stepSize="1.0"
android:valueFrom="0"
android:visibility="visible"
app:labelBehavior="floating"
tools:value="6"
tools:valueTo="20" />
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_next"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/next_chapter"
app:icon="@drawable/ic_next" />
</FrameLayout>
<FrameLayout
android:layout_width="0dp"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_save"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/save_page"
app:icon="@drawable/ic_save" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_screen_rotation"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/screen_orientation"
app:icon="@drawable/ic_screen_rotation" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_pages_thumbs"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/pages"
app:icon="@drawable/ic_grid" />
</FrameLayout>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_options"
style="@style/Widget.Kotatsu.IconButton.Action"
android:layout_width="48dp"
android:layout_height="48dp"
android:contentDescription="@string/options"
app:icon="@drawable/abc_ic_menu_overflow_material" />
</FrameLayout>
</merge>

@ -1,39 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:ignore="AlwaysShowAction">
<item
android:id="@+id/action_save_page"
android:icon="@drawable/ic_save"
android:title="@string/save_page"
android:visible="false"
app:showAsAction="always"
tools:visible="true" />
<item
android:id="@+id/action_screen_rotation"
android:icon="@drawable/ic_screen_rotation"
android:title="@string/screen_orientation"
android:visible="false"
app:showAsAction="always"
tools:visible="true" />
<item
android:id="@+id/action_pages_thumbs"
android:icon="@drawable/ic_grid"
android:title="@string/pages"
android:visible="false"
app:showAsAction="always"
tools:visible="true" />
<item
android:id="@+id/action_options"
android:icon="@drawable/abc_ic_menu_overflow_material"
android:title="@string/options"
app:showAsAction="always"
tools:visible="true" />
</menu>

@ -111,6 +111,10 @@
<item name="android:minHeight">42dp</item> <item name="android:minHeight">42dp</item>
</style> </style>
<style name="Widget.Kotatsu.IconButton.Action" parent="Widget.Material3.Button.IconButton">
<item name="iconTint">?colorControlNormal</item>
</style>
<style name="Widget.Kotatsu.ToggleButton" parent="Widget.Material3.Button.OutlinedButton"> <style name="Widget.Kotatsu.ToggleButton" parent="Widget.Material3.Button.OutlinedButton">
<item name="android:checkable">true</item> <item name="android:checkable">true</item>
<item name="android:textAlignment">textStart</item> <item name="android:textAlignment">textStart</item>

Loading…
Cancel
Save