Configurable reader tap actions

This commit is contained in:
Koitharu
2024-01-27 18:06:40 +02:00
parent 72187e7da0
commit 6f7f3dc5e2
17 changed files with 615 additions and 218 deletions

View File

@@ -145,6 +145,9 @@
<data android:host="sync-settings" /> <data android:host="sync-settings" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name="org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity"
android:label="@string/reader_actions" />
<activity <activity
android:name="org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity" android:name="org.koitharu.kotatsu.settings.storage.directories.MangaDirectoriesActivity"
android:label="@string/local_manga_directories" /> android:label="@string/local_manga_directories" />

View File

@@ -101,15 +101,12 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
} }
} }
val readerPageSwitch: Set<String> val isReaderVolumeButtonsEnabled: Boolean
get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS) get() = prefs.getBoolean(KEY_READER_VOLUME_BUTTONS, false)
val isReaderZoomButtonsEnabled: Boolean val isReaderZoomButtonsEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false) get() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false)
val isReaderTapsAdaptive: Boolean
get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
val isReaderOptimizationEnabled: Boolean val isReaderOptimizationEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_OPTIMIZE, false) get() = prefs.getBoolean(KEY_READER_OPTIMIZE, false)
@@ -453,7 +450,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
companion object { companion object {
const val PAGE_SWITCH_TAPS = "taps"
const val PAGE_SWITCH_VOLUME_KEYS = "volume" const val PAGE_SWITCH_VOLUME_KEYS = "volume"
const val TRACK_HISTORY = "history" const val TRACK_HISTORY = "history"
@@ -476,8 +472,8 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_GRID_SIZE = "grid_size" const val KEY_GRID_SIZE = "grid_size"
const val KEY_REMOTE_SOURCES = "remote_sources" const val KEY_REMOTE_SOURCES = "remote_sources"
const val KEY_LOCAL_STORAGE = "local_storage" const val KEY_LOCAL_STORAGE = "local_storage"
const val KEY_READER_SWITCHERS = "reader_switchers"
const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons" const val KEY_READER_ZOOM_BUTTONS = "reader_zoom_buttons"
const val KEY_READER_VOLUME_BUTTONS = "reader_volume_buttons"
const val KEY_TRACKER_ENABLED = "tracker_enabled" const val KEY_TRACKER_ENABLED = "tracker_enabled"
const val KEY_TRACKER_WIFI_ONLY = "tracker_wifi" const val KEY_TRACKER_WIFI_ONLY = "tracker_wifi"
const val KEY_TRACK_SOURCES = "track_sources" const val KEY_TRACK_SOURCES = "track_sources"
@@ -530,7 +526,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
const val KEY_READER_BACKGROUND = "reader_background" const val KEY_READER_BACKGROUND = "reader_background"
const val KEY_READER_SCREEN_ON = "reader_screen_on" const val KEY_READER_SCREEN_ON = "reader_screen_on"
const val KEY_SHORTCUTS = "dynamic_shortcuts" const val KEY_SHORTCUTS = "dynamic_shortcuts"
const val KEY_READER_TAPS_LTR = "reader_taps_ltr" const val KEY_READER_TAP_ACTIONS = "reader_tap_actions"
const val KEY_READER_OPTIMIZE = "reader_optimize" const val KEY_READER_OPTIMIZE = "reader_optimize"
const val KEY_LOCAL_LIST_ORDER = "local_order" const val KEY_LOCAL_LIST_ORDER = "local_order"
const val KEY_HISTORY_ORDER = "history_order" const val KEY_HISTORY_ORDER = "history_order"

View File

@@ -1,89 +0,0 @@
package org.koitharu.kotatsu.core.util
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import kotlin.math.roundToInt
class GridTouchHelper(
context: Context,
private val listener: OnGridTouchListener,
) : GestureDetector.SimpleOnGestureListener() {
private val detector = GestureDetector(context, this)
private val width = context.resources.displayMetrics.widthPixels
private val height = context.resources.displayMetrics.heightPixels
private var isDispatching = false
init {
detector.setIsLongpressEnabled(true)
detector.setOnDoubleTapListener(this)
}
fun dispatchTouchEvent(event: MotionEvent) {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
isDispatching = listener.onProcessTouch(event.rawX.toInt(), event.rawY.toInt())
}
detector.onTouchEvent(event)
}
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
if (!isDispatching) {
return true
}
val xIndex = (event.rawX * 2f / width).roundToInt()
val yIndex = (event.rawY * 2f / height).roundToInt()
listener.onGridTouch(
when (xIndex) {
0 -> AREA_LEFT
1 -> {
when (yIndex) {
0 -> AREA_TOP
1 -> AREA_CENTER
2 -> AREA_BOTTOM
else -> return false
}
}
2 -> AREA_RIGHT
else -> return false
},
)
return true
}
override fun onLongPress(event: MotionEvent) {
super.onLongPress(event)
val xIndex = (event.rawX * 2f / width).roundToInt()
val yIndex = (event.rawY * 2f / height).roundToInt()
listener.onGridLongTouch(
when(xIndex) {
1 -> {
when (yIndex) {
1 -> AREA_CENTER
else -> -1
}
}
else -> -1
}
)
}
companion object {
const val AREA_CENTER = 1
const val AREA_LEFT = 2
const val AREA_RIGHT = 3
const val AREA_TOP = 4
const val AREA_BOTTOM = 5
}
interface OnGridTouchListener {
fun onGridTouch(area: Int)
fun onGridLongTouch(area: Int)
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
}
}

View File

@@ -0,0 +1,53 @@
package org.koitharu.kotatsu.reader.data
import android.content.Context
import androidx.core.content.edit
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import org.koitharu.kotatsu.core.util.ext.getEnumValue
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.core.util.ext.putEnumValue
import org.koitharu.kotatsu.reader.domain.TapGridArea
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
import javax.inject.Inject
class TapGridSettings @Inject constructor(@ApplicationContext context: Context) {
private val prefs = context.getSharedPreferences("tap_grid", Context.MODE_PRIVATE)
fun getTapAction(area: TapGridArea, isLongTap: Boolean): TapAction? {
val key = getPrefKey(area, isLongTap)
return if (!isLongTap && key !in prefs) {
getDefaultTapAction(area)
} else {
prefs.getEnumValue(key, TapAction::class.java)
}
}
fun setTapAction(area: TapGridArea, isLongTap: Boolean, action: TapAction?) {
val key = getPrefKey(area, isLongTap)
prefs.edit { putEnumValue(key, action) }
}
fun observe() = prefs.observe().flowOn(Dispatchers.IO)
private fun getPrefKey(area: TapGridArea, isLongTap: Boolean): String = if (isLongTap) {
area.name + "_long"
} else {
area.name
}
private fun getDefaultTapAction(area: TapGridArea): TapAction = when (area) {
TapGridArea.TOP_LEFT,
TapGridArea.TOP_CENTER,
TapGridArea.CENTER_LEFT,
TapGridArea.BOTTOM_LEFT -> TapAction.PAGE_PREV
TapGridArea.CENTER -> TapAction.TOGGLE_UI
TapGridArea.TOP_RIGHT,
TapGridArea.CENTER_RIGHT,
TapGridArea.BOTTOM_CENTER,
TapGridArea.BOTTOM_RIGHT -> TapAction.PAGE_NEXT
}
}

View File

@@ -0,0 +1,14 @@
package org.koitharu.kotatsu.reader.domain
enum class TapGridArea {
TOP_LEFT,
TOP_CENTER,
TOP_RIGHT,
CENTER_LEFT,
CENTER,
CENTER_RIGHT,
BOTTOM_LEFT,
BOTTOM_CENTER,
BOTTOM_RIGHT;
}

View File

@@ -24,7 +24,6 @@ import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@@ -41,10 +40,8 @@ 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
import org.koitharu.kotatsu.core.ui.widgets.ZoomControl import org.koitharu.kotatsu.core.ui.widgets.ZoomControl
import org.koitharu.kotatsu.core.util.GridTouchHelper
import org.koitharu.kotatsu.core.util.IdlingDetector import org.koitharu.kotatsu.core.util.IdlingDetector
import org.koitharu.kotatsu.core.util.ShareHelper import org.koitharu.kotatsu.core.util.ShareHelper
import org.koitharu.kotatsu.core.util.ext.DIALOG_THEME_CENTERED
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.isRtl
@@ -57,9 +54,12 @@ import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.details.ui.DetailsActivity import org.koitharu.kotatsu.details.ui.DetailsActivity
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet import org.koitharu.kotatsu.reader.ui.config.ReaderConfigSheet
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.reader.ui.tapgrid.TapGridDispatcher
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@@ -68,7 +68,7 @@ import javax.inject.Inject
class ReaderActivity : class ReaderActivity :
BaseFullscreenActivity<ActivityReaderBinding>(), BaseFullscreenActivity<ActivityReaderBinding>(),
ChaptersSheet.OnChapterChangeListener, ChaptersSheet.OnChapterChangeListener,
GridTouchHelper.OnGridTouchListener, TapGridDispatcher.OnGridTouchListener,
OnPageSelectListener, OnPageSelectListener,
ReaderConfigSheet.Callback, ReaderConfigSheet.Callback,
ReaderControlDelegate.OnInteractionListener, ReaderControlDelegate.OnInteractionListener,
@@ -79,6 +79,9 @@ class ReaderActivity :
@Inject @Inject
lateinit var settings: AppSettings lateinit var settings: AppSettings
@Inject
lateinit var tapGridSettings: TapGridSettings
private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this) private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this)
private val viewModel: ReaderViewModel by viewModels() private val viewModel: ReaderViewModel by viewModels()
@@ -96,7 +99,7 @@ class ReaderActivity :
lateinit var scrollTimerFactory: ScrollTimer.Factory lateinit var scrollTimerFactory: ScrollTimer.Factory
private lateinit var scrollTimer: ScrollTimer private lateinit var scrollTimer: ScrollTimer
private lateinit var touchHelper: GridTouchHelper private lateinit var touchHelper: TapGridDispatcher
private lateinit var controlDelegate: ReaderControlDelegate private lateinit var controlDelegate: ReaderControlDelegate
private var gestureInsets: Insets = Insets.NONE private var gestureInsets: Insets = Insets.NONE
private lateinit var readerManager: ReaderManager private lateinit var readerManager: ReaderManager
@@ -107,9 +110,9 @@ class ReaderActivity :
setContentView(ActivityReaderBinding.inflate(layoutInflater)) setContentView(ActivityReaderBinding.inflate(layoutInflater))
readerManager = ReaderManager(supportFragmentManager, viewBinding.container) readerManager = ReaderManager(supportFragmentManager, viewBinding.container)
supportActionBar?.setDisplayHomeAsUpEnabled(true) supportActionBar?.setDisplayHomeAsUpEnabled(true)
touchHelper = GridTouchHelper(this, this) touchHelper = TapGridDispatcher(this, this)
scrollTimer = scrollTimerFactory.create(this, this) scrollTimer = scrollTimerFactory.create(this, this)
controlDelegate = ReaderControlDelegate(resources, settings, this, this) controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
viewBinding.slider.setLabelFormatter(PageLabelFormatter()) viewBinding.slider.setLabelFormatter(PageLabelFormatter())
viewBinding.zoomControl.listener = this viewBinding.zoomControl.listener = this
ReaderSliderListener(this, viewModel).attachToSlider(viewBinding.slider) ReaderSliderListener(this, viewModel).attachToSlider(viewBinding.slider)
@@ -202,12 +205,12 @@ class ReaderActivity :
viewBinding.toolbarBottom.invalidateMenu() viewBinding.toolbarBottom.invalidateMenu()
} }
override fun onGridTouch(area: Int) { override fun onGridTouch(area: TapGridArea): Boolean {
controlDelegate.onGridTouch(area, viewBinding.container) return controlDelegate.onGridTouch(area)
} }
override fun onGridLongTouch(area: Int) { override fun onGridLongTouch(area: TapGridArea) {
controlDelegate.onGridLongTouch(area, viewBinding.container) controlDelegate.onGridLongTouch(area)
} }
override fun onProcessTouch(rawX: Int, rawY: Int): Boolean { override fun onProcessTouch(rawX: Int, rawY: Int): Boolean {
@@ -233,7 +236,7 @@ class ReaderActivity :
} }
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
return controlDelegate.onKeyDown(keyCode, event) || super.onKeyDown(keyCode, event) return controlDelegate.onKeyDown(keyCode) || super.onKeyDown(keyCode, event)
} }
override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean { override fun onKeyUp(keyCode: Int, event: KeyEvent?): Boolean {
@@ -334,6 +337,16 @@ class ReaderActivity :
readerManager.currentReader?.switchPageBy(delta) readerManager.currentReader?.switchPageBy(delta)
} }
override fun switchChapterBy(delta: Int) {
viewModel.switchChapterBy(delta)
}
override fun openMenu() {
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
val currentMode = readerManager.currentMode ?: return
ReaderConfigSheet.show(supportFragmentManager, currentMode)
}
override fun scrollBy(delta: Int, smooth: Boolean): Boolean { override fun scrollBy(delta: Int, smooth: Boolean): Boolean {
return readerManager.currentReader?.scrollBy(delta, smooth) ?: false return readerManager.currentReader?.scrollBy(delta, smooth) ?: false
} }
@@ -342,13 +355,6 @@ class ReaderActivity :
setUiIsVisible(!viewBinding.appbarTop.isVisible) setUiIsVisible(!viewBinding.appbarTop.isVisible)
} }
override fun viewDialog() {
MaterialAlertDialogBuilder(this, DIALOG_THEME_CENTERED)
.setMessage("Called dialog on long press")
.setPositiveButton(R.string.got_it, null)
.show()
}
override fun isReaderResumed(): Boolean { override fun isReaderResumed(): Boolean {
val reader = readerManager.currentReader ?: return false val reader = readerManager.currentReader ?: return false
return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader

View File

@@ -1,92 +1,49 @@
package org.koitharu.kotatsu.reader.ui package org.koitharu.kotatsu.reader.ui
import android.content.SharedPreferences
import android.content.res.Resources import android.content.res.Resources
import android.view.KeyEvent import android.view.KeyEvent
import android.view.SoundEffectConstants
import android.view.View
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.prefs.AppSettings import org.koitharu.kotatsu.core.prefs.AppSettings
import org.koitharu.kotatsu.core.prefs.ReaderMode import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.util.GridTouchHelper import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
class ReaderControlDelegate( class ReaderControlDelegate(
resources: Resources, resources: Resources,
private val settings: AppSettings, private val settings: AppSettings,
private val tapGridSettings: TapGridSettings,
private val listener: OnInteractionListener, private val listener: OnInteractionListener,
owner: LifecycleOwner, ) {
) : DefaultLifecycleObserver, SharedPreferences.OnSharedPreferenceChangeListener {
private var isTapSwitchEnabled: Boolean = true
private var isVolumeKeysSwitchEnabled: Boolean = false
private var isReaderTapsAdaptive: Boolean = true
private var minScrollDelta = resources.getDimensionPixelSize(R.dimen.reader_scroll_delta_min) private var minScrollDelta = resources.getDimensionPixelSize(R.dimen.reader_scroll_delta_min)
init { fun onGridTouch(area: TapGridArea): Boolean {
owner.lifecycle.addObserver(this) val action = tapGridSettings.getTapAction(
settings.subscribe(this) area = area,
updateSettings() isLongTap = false,
) ?: return false
processAction(action)
return true
} }
override fun onDestroy(owner: LifecycleOwner) { fun onGridLongTouch(area: TapGridArea) {
settings.unsubscribe(this) val action = tapGridSettings.getTapAction(
owner.lifecycle.removeObserver(this) area = area,
super.onDestroy(owner) isLongTap = true,
) ?: return
processAction(action)
} }
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { fun onKeyDown(keyCode: Int): Boolean = when (keyCode) {
updateSettings() KeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) {
}
fun onGridTouch(area: Int, view: View) {
when (area) {
GridTouchHelper.AREA_CENTER -> {
listener.toggleUiVisibility()
view.playSoundEffect(SoundEffectConstants.CLICK)
}
GridTouchHelper.AREA_TOP -> if (isTapSwitchEnabled) {
listener.switchPageBy(-1)
view.playSoundEffect(SoundEffectConstants.NAVIGATION_UP)
}
GridTouchHelper.AREA_LEFT -> if (isTapSwitchEnabled) {
listener.switchPageBy(if (isReaderTapsReversed()) 1 else -1)
view.playSoundEffect(SoundEffectConstants.NAVIGATION_LEFT)
}
GridTouchHelper.AREA_BOTTOM -> if (isTapSwitchEnabled) {
listener.switchPageBy(1)
view.playSoundEffect(SoundEffectConstants.NAVIGATION_DOWN)
}
GridTouchHelper.AREA_RIGHT -> if (isTapSwitchEnabled) {
listener.switchPageBy(if (isReaderTapsReversed()) -1 else 1)
view.playSoundEffect(SoundEffectConstants.NAVIGATION_RIGHT)
}
}
}
fun onGridLongTouch(area: Int, view: View) {
when (area) {
GridTouchHelper.AREA_CENTER -> {
listener.viewDialog()
view.playSoundEffect(SoundEffectConstants.CLICK)
}
}
}
fun onKeyDown(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean = when (keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> if (isVolumeKeysSwitchEnabled) {
listener.switchPageBy(-1) listener.switchPageBy(-1)
true true
} else { } else {
false false
} }
KeyEvent.KEYCODE_VOLUME_DOWN -> if (isVolumeKeysSwitchEnabled) { KeyEvent.KEYCODE_VOLUME_DOWN -> if (settings.isReaderVolumeButtonsEnabled) {
listener.switchPageBy(1) listener.switchPageBy(1)
true true
} else { } else {
@@ -141,21 +98,23 @@ class ReaderControlDelegate(
} }
fun onKeyUp(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean { fun onKeyUp(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean {
return ( return (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
isVolumeKeysSwitchEnabled && && settings.isReaderVolumeButtonsEnabled
(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
)
} }
private fun updateSettings() { private fun processAction(action: TapAction) {
val switch = settings.readerPageSwitch when (action) {
isTapSwitchEnabled = AppSettings.PAGE_SWITCH_TAPS in switch TapAction.PAGE_NEXT -> listener.switchPageBy(1)
isVolumeKeysSwitchEnabled = AppSettings.PAGE_SWITCH_VOLUME_KEYS in switch TapAction.PAGE_PREV -> listener.switchPageBy(-1)
isReaderTapsAdaptive = settings.isReaderTapsAdaptive TapAction.CHAPTER_NEXT -> listener.switchChapterBy(1)
TapAction.CHAPTER_PREV -> listener.switchChapterBy(-1)
TapAction.TOGGLE_UI -> listener.toggleUiVisibility()
TapAction.SHOW_MENU -> listener.openMenu()
}
} }
private fun isReaderTapsReversed(): Boolean { private fun isReaderTapsReversed(): Boolean {
return isReaderTapsAdaptive && listener.readerMode == ReaderMode.REVERSED return listener.readerMode == ReaderMode.REVERSED
} }
interface OnInteractionListener { interface OnInteractionListener {
@@ -164,12 +123,14 @@ class ReaderControlDelegate(
fun switchPageBy(delta: Int) fun switchPageBy(delta: Int)
fun viewDialog() fun switchChapterBy(delta: Int)
fun scrollBy(delta: Int, smooth: Boolean): Boolean fun scrollBy(delta: Int, smooth: Boolean): Boolean
fun toggleUiVisibility() fun toggleUiVisibility()
fun openMenu()
fun isReaderResumed(): Boolean fun isReaderResumed(): Boolean
} }
} }

View File

@@ -262,6 +262,24 @@ class ReaderViewModel @Inject constructor(
} }
} }
fun switchChapterBy(delta: Int) {
val prevJob = loadingJob
loadingJob = launchLoadingJob(Dispatchers.Default) {
prevJob?.cancelAndJoin()
val currentChapterId = currentState.requireValue().chapterId
val allChapters = checkNotNull(manga).allChapters
var index = allChapters.indexOfFirst { x -> x.id == currentChapterId }
if (index < 0) {
return@launchLoadingJob
}
index += delta
val newChapterId = (allChapters.getOrNull(index) ?: return@launchLoadingJob).id
content.value = ReaderContent(emptyList(), null)
chaptersLoader.loadSingleChapter(newChapterId)
content.value = ReaderContent(chaptersLoader.snapshot(), ReaderState(newChapterId, 0, 0))
}
}
@MainThread @MainThread
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) { fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
val prevJob = stateChangeJob val prevJob = stateChangeJob

View File

@@ -0,0 +1,17 @@
package org.koitharu.kotatsu.reader.ui.tapgrid
import androidx.annotation.StringRes
import org.koitharu.kotatsu.R
enum class TapAction(
@StringRes val nameStringResId: Int,
val color: Int,
) {
PAGE_NEXT(R.string.next_page, 0x8BFF00),
PAGE_PREV(R.string.prev_page, 0xFF4700),
CHAPTER_NEXT(R.string.next_chapter, 0x327E49),
CHAPTER_PREV(R.string.prev_chapter, 0x7E1218),
TOGGLE_UI(R.string.toggle_ui, 0x3D69C5),
SHOW_MENU(R.string.show_menu, 0xAA1AC5),
}

View File

@@ -0,0 +1,82 @@
package org.koitharu.kotatsu.reader.ui.tapgrid
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import org.koitharu.kotatsu.reader.domain.TapGridArea
import kotlin.math.roundToInt
class TapGridDispatcher(
context: Context,
private val listener: OnGridTouchListener,
) : GestureDetector.SimpleOnGestureListener() {
private val detector = GestureDetector(context, this)
private val width = context.resources.displayMetrics.widthPixels
private val height = context.resources.displayMetrics.heightPixels
private var isDispatching = false
init {
detector.setIsLongpressEnabled(true)
detector.setOnDoubleTapListener(this)
}
fun dispatchTouchEvent(event: MotionEvent) {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
isDispatching = listener.onProcessTouch(event.rawX.toInt(), event.rawY.toInt())
}
detector.onTouchEvent(event)
}
override fun onSingleTapConfirmed(event: MotionEvent): Boolean {
if (!isDispatching) {
return true
}
return listener.onGridTouch(getArea(event.rawX, event.rawY))
}
override fun onLongPress(event: MotionEvent) {
if (isDispatching) {
listener.onGridLongTouch(getArea(event.rawX, event.rawY))
}
}
private fun getArea(x: Float, y: Float): TapGridArea {
val xIndex = (x * 2f / width).roundToInt()
val yIndex = (y * 2f / height).roundToInt()
val area = when (xIndex) {
0 -> when (yIndex) { // LEFT
0 -> TapGridArea.TOP_LEFT
1 -> TapGridArea.CENTER_LEFT
2 -> TapGridArea.BOTTOM_LEFT
else -> null
}
1 -> when (yIndex) { // CENTER
0 -> TapGridArea.TOP_CENTER
1 -> TapGridArea.CENTER
2 -> TapGridArea.BOTTOM_CENTER
else -> null
}
2 -> when (yIndex) { // RIGHT
0 -> TapGridArea.TOP_RIGHT
1 -> TapGridArea.CENTER_RIGHT
2 -> TapGridArea.BOTTOM_RIGHT
else -> null
}
else -> null
}
return checkNotNull(area) { "Invalid area ($xIndex, $yIndex)" }
}
interface OnGridTouchListener {
fun onGridTouch(area: TapGridArea): Boolean
fun onGridLongTouch(area: TapGridArea)
fun onProcessTouch(rawX: Int, rawY: Int): Boolean
}
}

View File

@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.settings package org.koitharu.kotatsu.settings
import android.content.Intent
import android.content.SharedPreferences import android.content.SharedPreferences
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import androidx.preference.ListPreference import androidx.preference.ListPreference
import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
@@ -16,7 +16,7 @@ import org.koitharu.kotatsu.core.prefs.ReaderMode
import org.koitharu.kotatsu.core.ui.BasePreferenceFragment import org.koitharu.kotatsu.core.ui.BasePreferenceFragment
import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat import org.koitharu.kotatsu.core.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.parsers.util.names import org.koitharu.kotatsu.parsers.util.names
import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider import org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity
@AndroidEntryPoint @AndroidEntryPoint
class ReaderSettingsFragment : class ReaderSettingsFragment :
@@ -26,7 +26,12 @@ class ReaderSettingsFragment :
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_reader) addPreferencesFromResource(R.xml.pref_reader)
findPreference<ListPreference>(AppSettings.KEY_READER_MODE)?.run { findPreference<ListPreference>(AppSettings.KEY_READER_MODE)?.run {
entryValues = ReaderMode.entries.names() entryValues = arrayOf(
ReaderMode.STANDARD.name,
ReaderMode.REVERSED.name,
ReaderMode.VERTICAL.name,
ReaderMode.WEBTOON.name,
)
setDefaultValueCompat(ReaderMode.STANDARD.name) setDefaultValueCompat(ReaderMode.STANDARD.name)
} }
findPreference<ListPreference>(AppSettings.KEY_READER_BACKGROUND)?.run { findPreference<ListPreference>(AppSettings.KEY_READER_BACKGROUND)?.run {
@@ -37,9 +42,6 @@ class ReaderSettingsFragment :
entryValues = ReaderAnimation.entries.names() entryValues = ReaderAnimation.entries.names()
setDefaultValueCompat(ReaderAnimation.DEFAULT.name) setDefaultValueCompat(ReaderAnimation.DEFAULT.name)
} }
findPreference<MultiSelectListPreference>(AppSettings.KEY_READER_SWITCHERS)?.run {
summaryProvider = MultiSummaryProvider(R.string.gestures_only)
}
findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.run { findPreference<ListPreference>(AppSettings.KEY_ZOOM_MODE)?.run {
entryValues = ZoomMode.entries.names() entryValues = ZoomMode.entries.names()
setDefaultValueCompat(ZoomMode.FIT_CENTER.name) setDefaultValueCompat(ZoomMode.FIT_CENTER.name)
@@ -57,6 +59,17 @@ class ReaderSettingsFragment :
super.onDestroyView() super.onDestroyView()
} }
override fun onPreferenceTreeClick(preference: Preference): Boolean {
return when (preference.key) {
AppSettings.KEY_READER_TAP_ACTIONS -> {
startActivity(Intent(preference.context, ReaderTapGridConfigActivity::class.java))
true
}
else -> super.onPreferenceTreeClick(preference)
}
}
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) { override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
when (key) { when (key) {
AppSettings.KEY_READER_MODE -> updateReaderModeDependency() AppSettings.KEY_READER_MODE -> updateReaderModeDependency()

View File

@@ -0,0 +1,129 @@
package org.koitharu.kotatsu.settings.reader
import android.content.DialogInterface
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.core.graphics.ColorUtils
import androidx.core.graphics.Insets
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.view.updatePadding
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import dagger.hilt.android.AndroidEntryPoint
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.core.ui.BaseActivity
import org.koitharu.kotatsu.core.util.ext.findKeyByValue
import org.koitharu.kotatsu.core.util.ext.getThemeDrawable
import org.koitharu.kotatsu.core.util.ext.observe
import org.koitharu.kotatsu.databinding.ActivityReaderTapActionsBinding
import org.koitharu.kotatsu.reader.data.TapGridSettings
import org.koitharu.kotatsu.reader.domain.TapGridArea
import org.koitharu.kotatsu.reader.ui.tapgrid.TapAction
import java.util.EnumMap
import javax.inject.Inject
import com.google.android.material.R as materialR
@AndroidEntryPoint
class ReaderTapGridConfigActivity : BaseActivity<ActivityReaderTapActionsBinding>(), View.OnClickListener,
View.OnLongClickListener {
@Inject
lateinit var tapGridSettings: TapGridSettings
private val controls = EnumMap<TapGridArea, TextView>(TapGridArea::class.java)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(ActivityReaderTapActionsBinding.inflate(layoutInflater))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
controls[TapGridArea.TOP_LEFT] = viewBinding.textViewTopLeft
controls[TapGridArea.TOP_CENTER] = viewBinding.textViewTopCenter
controls[TapGridArea.TOP_RIGHT] = viewBinding.textViewTopRight
controls[TapGridArea.CENTER_LEFT] = viewBinding.textViewCenterLeft
controls[TapGridArea.CENTER] = viewBinding.textViewCenter
controls[TapGridArea.CENTER_RIGHT] = viewBinding.textViewCenterRight
controls[TapGridArea.BOTTOM_LEFT] = viewBinding.textViewBottomLeft
controls[TapGridArea.BOTTOM_CENTER] = viewBinding.textViewBottomCenter
controls[TapGridArea.BOTTOM_RIGHT] = viewBinding.textViewBottomRight
controls.forEach { (_, view) ->
view.setOnClickListener(this)
view.setOnLongClickListener(this)
}
updateValues()
tapGridSettings.observe().observe(this) { updateValues() }
}
override fun onWindowInsetsChanged(insets: Insets) {
viewBinding.root.updatePadding(
left = insets.left,
top = insets.top,
right = insets.right,
bottom = insets.bottom,
)
}
override fun onClick(v: View) {
val area = controls.findKeyByValue(v) ?: return
showActionSelector(area, isLongTap = false)
}
override fun onLongClick(v: View?): Boolean {
val area = controls.findKeyByValue(v) ?: return false
showActionSelector(area, isLongTap = true)
return true
}
private fun updateValues() {
controls.forEach { (area, view) ->
view.text = buildSpannedString {
appendLine(getString(R.string.tap_action))
bold {
appendLine(getTapActionText(area, isLongTap = false))
}
appendLine()
appendLine(getString(R.string.long_tap_action))
bold {
appendLine(getTapActionText(area, isLongTap = true))
}
}
view.background = createBackground(tapGridSettings.getTapAction(area, false))
}
}
private fun getTapActionText(area: TapGridArea, isLongTap: Boolean): String {
return tapGridSettings.getTapAction(area, isLongTap)?.let {
getString(it.nameStringResId)
} ?: getString(R.string.none)
}
private fun showActionSelector(area: TapGridArea, isLongTap: Boolean) {
val selectedItem = tapGridSettings.getTapAction(area, isLongTap)?.ordinal ?: -1
val listener = DialogInterface.OnClickListener { dialog, which ->
tapGridSettings.setTapAction(area, isLongTap, TapAction.entries.getOrNull(which - 1))
dialog.dismiss()
}
val names = arrayOfNulls<String>(TapAction.entries.size + 1)
names[0] = getString(R.string.none)
TapAction.entries.forEachIndexed { index, action -> names[index + 1] = getString(action.nameStringResId) }
MaterialAlertDialogBuilder(this)
.setSingleChoiceItems(names, selectedItem + 1, listener)
.setTitle(if (isLongTap) R.string.long_tap_action else R.string.tap_action)
.setIcon(R.drawable.ic_tap)
.setNegativeButton(android.R.string.cancel, null)
.show()
}
private fun createBackground(action: TapAction?): Drawable? {
val ripple = getThemeDrawable(materialR.attr.selectableItemBackground)
return if (action == null) {
ripple
} else {
LayerDrawable(arrayOf(ripple, ColorDrawable(ColorUtils.setAlphaComponent(action.color, 60))))
}
}
}

View File

@@ -0,0 +1,191 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_top"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.33" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_bottom"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
app:layout_constraintGuide_percent="0.67" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_left"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.33" />
<androidx.constraintlayout.widget.Guideline
android:id="@+id/guideline_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.67" />
<TextView
android:id="@+id/textView_top_left"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/guideline_top"
app:layout_constraintEnd_toStartOf="@id/guideline_left"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView_top_center"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/guideline_top"
app:layout_constraintEnd_toStartOf="@id/guideline_right"
app:layout_constraintStart_toEndOf="@id/guideline_left"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView_top_right"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/guideline_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_right"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/textView_center_left"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/textView_bottom_left"
app:layout_constraintEnd_toStartOf="@id/guideline_left"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/textView_top_left" />
<TextView
android:id="@+id/textView_center"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/textView_bottom_center"
app:layout_constraintEnd_toStartOf="@id/guideline_right"
app:layout_constraintStart_toEndOf="@id/guideline_left"
app:layout_constraintTop_toBottomOf="@id/textView_top_center" />
<TextView
android:id="@+id/textView_center_right"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toTopOf="@id/textView_bottom_right"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_right"
app:layout_constraintTop_toBottomOf="@id/textView_top_right" />
<TextView
android:id="@+id/textView_bottom_left"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/guideline_left"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/guideline_bottom" />
<TextView
android:id="@+id/textView_bottom_center"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/guideline_right"
app:layout_constraintStart_toEndOf="@id/guideline_left"
app:layout_constraintTop_toBottomOf="@id/guideline_bottom" />
<TextView
android:id="@+id/textView_bottom_right"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?selectableItemBackground"
android:gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/guideline_right"
app:layout_constraintTop_toBottomOf="@id/guideline_bottom" />
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:background="?android:divider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/guideline_right"
app:layout_constraintStart_toStartOf="@id/guideline_right"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="1dp"
android:layout_height="0dp"
android:background="?android:divider"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="@id/guideline_left"
app:layout_constraintStart_toStartOf="@id/guideline_left"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?android:divider"
app:layout_constraintBottom_toBottomOf="@id/guideline_top"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline_top" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?android:divider"
app:layout_constraintBottom_toBottomOf="@id/guideline_bottom"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/guideline_bottom" />
<View
android:layout_width="0dp"
android:layout_height="1dp"
android:background="?android:divider"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>

View File

@@ -5,10 +5,6 @@
<item>@string/light</item> <item>@string/light</item>
<item>@string/dark</item> <item>@string/dark</item>
</string-array> </string-array>
<string-array name="reader_switchers" translatable="false">
<item>@string/taps_on_edges</item>
<item>@string/volume_buttons</item>
</string-array>
<string-array name="zoom_modes" translatable="false"> <string-array name="zoom_modes" translatable="false">
<item>@string/zoom_mode_fit_center</item> <item>@string/zoom_mode_fit_center</item>
<item>@string/zoom_mode_fit_height</item> <item>@string/zoom_mode_fit_height</item>
@@ -43,6 +39,7 @@
<string-array name="reader_modes" translatable="false"> <string-array name="reader_modes" translatable="false">
<item>@string/standard</item> <item>@string/standard</item>
<item>@string/right_to_left</item> <item>@string/right_to_left</item>
<item>@string/vertical</item>
<item>@string/webtoon</item> <item>@string/webtoon</item>
</string-array> </string-array>
<string-array name="scrobbling_statuses" translatable="false"> <string-array name="scrobbling_statuses" translatable="false">

View File

@@ -21,13 +21,6 @@
<item>1</item> <item>1</item>
<item>2</item> <item>2</item>
</string-array> </string-array>
<string-array name="values_reader_switchers" translatable="false">
<item>taps</item>
<item>volume</item>
</string-array>
<string-array name="values_reader_switchers_default" translatable="false">
<item>taps</item>
</string-array>
<string-array name="values_track_sources" translatable="false"> <string-array name="values_track_sources" translatable="false">
<item>favourites</item> <item>favourites</item>
<item>history</item> <item>history</item>

View File

@@ -568,4 +568,17 @@
<string name="vertical">Vertical</string> <string name="vertical">Vertical</string>
<string name="last_read">Last read</string> <string name="last_read">Last read</string>
<string name="two_pages">Two pages</string> <string name="two_pages">Two pages</string>
<string name="show_menu">Show menu</string>
<string name="toggle_ui">Show/hide UI</string>
<string name="prev_chapter">Previous chapter</string>
<string name="next_chapter">Next chapter</string>
<string name="prev_page">Previous page</string>
<string name="next_page">Next page</string>
<string name="reader_actions">Reader actions</string>
<string name="reader_actions_summary">Configure actions for tappable screen areas</string>
<string name="switch_pages_volume_buttons">Enable volume buttons</string>
<string name="switch_pages_volume_buttons_summary">Use volume buttons for switching pages</string>
<string name="tap_action">Tap action</string>
<string name="long_tap_action">Long tap action</string>
<string name="none">None</string>
</resources> </resources>

View File

@@ -34,19 +34,18 @@
android:summary="@string/reader_zoom_buttons_summary" android:summary="@string/reader_zoom_buttons_summary"
android:title="@string/reader_zoom_buttons" /> android:title="@string/reader_zoom_buttons" />
<MultiSelectListPreference <Preference
android:defaultValue="@array/values_reader_switchers_default" android:key="reader_tap_actions"
android:entries="@array/reader_switchers" android:persistent="false"
android:entryValues="@array/values_reader_switchers" android:summary="@string/reader_actions_summary"
android:key="reader_switchers" android:title="@string/reader_actions"
android:title="@string/switch_pages"
app:allowDividerAbove="true" /> app:allowDividerAbove="true" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"
android:key="reader_taps_ltr" android:key="reader_volume_buttons"
android:summary="@string/reader_control_ltr_summary" android:summary="@string/switch_pages_volume_buttons_summary"
android:title="@string/reader_control_ltr" /> android:title="@string/switch_pages_volume_buttons" />
<ListPreference <ListPreference
android:entries="@array/reader_animation" android:entries="@array/reader_animation"
@@ -58,7 +57,8 @@
android:defaultValue="false" android:defaultValue="false"
android:key="enhanced_colors" android:key="enhanced_colors"
android:summary="@string/enhanced_colors_summary" android:summary="@string/enhanced_colors_summary"
android:title="@string/enhanced_colors" /> android:title="@string/enhanced_colors"
app:allowDividerAbove="true" />
<SwitchPreferenceCompat <SwitchPreferenceCompat
android:defaultValue="false" android:defaultValue="false"