diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 84121cf11..c22072304 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -145,6 +145,9 @@
+
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
index d87ffe78d..ac50c83e7 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/core/prefs/AppSettings.kt
@@ -101,15 +101,12 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
}
}
- val readerPageSwitch: Set
- get() = prefs.getStringSet(KEY_READER_SWITCHERS, null) ?: setOf(PAGE_SWITCH_TAPS)
+ val isReaderVolumeButtonsEnabled: Boolean
+ get() = prefs.getBoolean(KEY_READER_VOLUME_BUTTONS, false)
val isReaderZoomButtonsEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_ZOOM_BUTTONS, false)
- val isReaderTapsAdaptive: Boolean
- get() = !prefs.getBoolean(KEY_READER_TAPS_LTR, false)
-
val isReaderOptimizationEnabled: Boolean
get() = prefs.getBoolean(KEY_READER_OPTIMIZE, false)
@@ -453,7 +450,6 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
companion object {
- const val PAGE_SWITCH_TAPS = "taps"
const val PAGE_SWITCH_VOLUME_KEYS = "volume"
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_REMOTE_SOURCES = "remote_sources"
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_VOLUME_BUTTONS = "reader_volume_buttons"
const val KEY_TRACKER_ENABLED = "tracker_enabled"
const val KEY_TRACKER_WIFI_ONLY = "tracker_wifi"
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_SCREEN_ON = "reader_screen_on"
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_LOCAL_LIST_ORDER = "local_order"
const val KEY_HISTORY_ORDER = "history_order"
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt b/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt
deleted file mode 100644
index a5b49a0c0..000000000
--- a/app/src/main/kotlin/org/koitharu/kotatsu/core/util/GridTouchHelper.kt
+++ /dev/null
@@ -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
- }
-}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/TapGridSettings.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/TapGridSettings.kt
new file mode 100644
index 000000000..d26b9fb7b
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/data/TapGridSettings.kt
@@ -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
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/TapGridArea.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/TapGridArea.kt
new file mode 100644
index 000000000..4d51f36e0
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/domain/TapGridArea.kt
@@ -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;
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
index c80aacf81..7c444d428 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderActivity.kt
@@ -24,7 +24,6 @@ import androidx.core.view.updateLayoutParams
import androidx.core.view.updatePadding
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
-import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
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.util.MenuInvalidator
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.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.isAnimationsEnabled
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.parsers.model.Manga
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.pager.ReaderPage
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 java.util.concurrent.TimeUnit
import javax.inject.Inject
@@ -68,7 +68,7 @@ import javax.inject.Inject
class ReaderActivity :
BaseFullscreenActivity(),
ChaptersSheet.OnChapterChangeListener,
- GridTouchHelper.OnGridTouchListener,
+ TapGridDispatcher.OnGridTouchListener,
OnPageSelectListener,
ReaderConfigSheet.Callback,
ReaderControlDelegate.OnInteractionListener,
@@ -79,6 +79,9 @@ class ReaderActivity :
@Inject
lateinit var settings: AppSettings
+ @Inject
+ lateinit var tapGridSettings: TapGridSettings
+
private val idlingDetector = IdlingDetector(TimeUnit.SECONDS.toMillis(10), this)
private val viewModel: ReaderViewModel by viewModels()
@@ -96,7 +99,7 @@ class ReaderActivity :
lateinit var scrollTimerFactory: ScrollTimer.Factory
private lateinit var scrollTimer: ScrollTimer
- private lateinit var touchHelper: GridTouchHelper
+ private lateinit var touchHelper: TapGridDispatcher
private lateinit var controlDelegate: ReaderControlDelegate
private var gestureInsets: Insets = Insets.NONE
private lateinit var readerManager: ReaderManager
@@ -107,9 +110,9 @@ class ReaderActivity :
setContentView(ActivityReaderBinding.inflate(layoutInflater))
readerManager = ReaderManager(supportFragmentManager, viewBinding.container)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
- touchHelper = GridTouchHelper(this, this)
+ touchHelper = TapGridDispatcher(this, this)
scrollTimer = scrollTimerFactory.create(this, this)
- controlDelegate = ReaderControlDelegate(resources, settings, this, this)
+ controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
viewBinding.slider.setLabelFormatter(PageLabelFormatter())
viewBinding.zoomControl.listener = this
ReaderSliderListener(this, viewModel).attachToSlider(viewBinding.slider)
@@ -202,12 +205,12 @@ class ReaderActivity :
viewBinding.toolbarBottom.invalidateMenu()
}
- override fun onGridTouch(area: Int) {
- controlDelegate.onGridTouch(area, viewBinding.container)
+ override fun onGridTouch(area: TapGridArea): Boolean {
+ return controlDelegate.onGridTouch(area)
}
- override fun onGridLongTouch(area: Int) {
- controlDelegate.onGridLongTouch(area, viewBinding.container)
+ override fun onGridLongTouch(area: TapGridArea) {
+ controlDelegate.onGridLongTouch(area)
}
override fun onProcessTouch(rawX: Int, rawY: Int): Boolean {
@@ -233,7 +236,7 @@ class ReaderActivity :
}
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 {
@@ -334,6 +337,16 @@ class ReaderActivity :
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 {
return readerManager.currentReader?.scrollBy(delta, smooth) ?: false
}
@@ -342,13 +355,6 @@ class ReaderActivity :
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 {
val reader = readerManager.currentReader ?: return false
return reader.isResumed && supportFragmentManager.fragments.lastOrNull() === reader
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderControlDelegate.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderControlDelegate.kt
index d8fc27512..f2663d10f 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderControlDelegate.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderControlDelegate.kt
@@ -1,92 +1,49 @@
package org.koitharu.kotatsu.reader.ui
-import android.content.SharedPreferences
import android.content.res.Resources
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.core.prefs.AppSettings
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(
resources: Resources,
private val settings: AppSettings,
+ private val tapGridSettings: TapGridSettings,
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)
- init {
- owner.lifecycle.addObserver(this)
- settings.subscribe(this)
- updateSettings()
+ fun onGridTouch(area: TapGridArea): Boolean {
+ val action = tapGridSettings.getTapAction(
+ area = area,
+ isLongTap = false,
+ ) ?: return false
+ processAction(action)
+ return true
}
- override fun onDestroy(owner: LifecycleOwner) {
- settings.unsubscribe(this)
- owner.lifecycle.removeObserver(this)
- super.onDestroy(owner)
+ fun onGridLongTouch(area: TapGridArea) {
+ val action = tapGridSettings.getTapAction(
+ area = area,
+ isLongTap = true,
+ ) ?: return
+ processAction(action)
}
- override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {
- updateSettings()
- }
-
- 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) {
+ fun onKeyDown(keyCode: Int): Boolean = when (keyCode) {
+ KeyEvent.KEYCODE_VOLUME_UP -> if (settings.isReaderVolumeButtonsEnabled) {
listener.switchPageBy(-1)
true
} else {
false
}
- KeyEvent.KEYCODE_VOLUME_DOWN -> if (isVolumeKeysSwitchEnabled) {
+ KeyEvent.KEYCODE_VOLUME_DOWN -> if (settings.isReaderVolumeButtonsEnabled) {
listener.switchPageBy(1)
true
} else {
@@ -141,21 +98,23 @@ class ReaderControlDelegate(
}
fun onKeyUp(keyCode: Int, @Suppress("UNUSED_PARAMETER") event: KeyEvent?): Boolean {
- return (
- isVolumeKeysSwitchEnabled &&
- (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
- )
+ return (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN || keyCode == KeyEvent.KEYCODE_VOLUME_UP)
+ && settings.isReaderVolumeButtonsEnabled
}
- private fun updateSettings() {
- val switch = settings.readerPageSwitch
- isTapSwitchEnabled = AppSettings.PAGE_SWITCH_TAPS in switch
- isVolumeKeysSwitchEnabled = AppSettings.PAGE_SWITCH_VOLUME_KEYS in switch
- isReaderTapsAdaptive = settings.isReaderTapsAdaptive
+ private fun processAction(action: TapAction) {
+ when (action) {
+ TapAction.PAGE_NEXT -> listener.switchPageBy(1)
+ TapAction.PAGE_PREV -> listener.switchPageBy(-1)
+ 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 {
- return isReaderTapsAdaptive && listener.readerMode == ReaderMode.REVERSED
+ return listener.readerMode == ReaderMode.REVERSED
}
interface OnInteractionListener {
@@ -164,12 +123,14 @@ class ReaderControlDelegate(
fun switchPageBy(delta: Int)
- fun viewDialog()
+ fun switchChapterBy(delta: Int)
fun scrollBy(delta: Int, smooth: Boolean): Boolean
fun toggleUiVisibility()
+ fun openMenu()
+
fun isReaderResumed(): Boolean
}
}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
index 8f1cf6a64..05399af96 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/ReaderViewModel.kt
@@ -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
fun onCurrentPageChanged(lowerPos: Int, upperPos: Int) {
val prevJob = stateChangeJob
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapAction.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapAction.kt
new file mode 100644
index 000000000..6bcc9f7b7
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapAction.kt
@@ -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),
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapGridDispatcher.kt b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapGridDispatcher.kt
new file mode 100644
index 000000000..60108c372
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/reader/ui/tapgrid/TapGridDispatcher.kt
@@ -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
+ }
+}
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt
index a605cb80f..411840007 100644
--- a/app/src/main/kotlin/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/ReaderSettingsFragment.kt
@@ -1,10 +1,10 @@
package org.koitharu.kotatsu.settings
+import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.view.View
import androidx.preference.ListPreference
-import androidx.preference.MultiSelectListPreference
import androidx.preference.Preference
import dagger.hilt.android.AndroidEntryPoint
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.util.ext.setDefaultValueCompat
import org.koitharu.kotatsu.parsers.util.names
-import org.koitharu.kotatsu.settings.utils.MultiSummaryProvider
+import org.koitharu.kotatsu.settings.reader.ReaderTapGridConfigActivity
@AndroidEntryPoint
class ReaderSettingsFragment :
@@ -26,7 +26,12 @@ class ReaderSettingsFragment :
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.pref_reader)
findPreference(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)
}
findPreference(AppSettings.KEY_READER_BACKGROUND)?.run {
@@ -37,9 +42,6 @@ class ReaderSettingsFragment :
entryValues = ReaderAnimation.entries.names()
setDefaultValueCompat(ReaderAnimation.DEFAULT.name)
}
- findPreference(AppSettings.KEY_READER_SWITCHERS)?.run {
- summaryProvider = MultiSummaryProvider(R.string.gestures_only)
- }
findPreference(AppSettings.KEY_ZOOM_MODE)?.run {
entryValues = ZoomMode.entries.names()
setDefaultValueCompat(ZoomMode.FIT_CENTER.name)
@@ -57,6 +59,17 @@ class ReaderSettingsFragment :
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?) {
when (key) {
AppSettings.KEY_READER_MODE -> updateReaderModeDependency()
diff --git a/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt b/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt
new file mode 100644
index 000000000..b1c4d0848
--- /dev/null
+++ b/app/src/main/kotlin/org/koitharu/kotatsu/settings/reader/ReaderTapGridConfigActivity.kt
@@ -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(), View.OnClickListener,
+ View.OnLongClickListener {
+
+ @Inject
+ lateinit var tapGridSettings: TapGridSettings
+
+ private val controls = EnumMap(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(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))))
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_reader_tap_actions.xml b/app/src/main/res/layout/activity_reader_tap_actions.xml
new file mode 100644
index 000000000..a559104f0
--- /dev/null
+++ b/app/src/main/res/layout/activity_reader_tap_actions.xml
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index d98a64d48..0abbd7895 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -5,10 +5,6 @@
- @string/light
- @string/dark
-
- - @string/taps_on_edges
- - @string/volume_buttons
-
- @string/zoom_mode_fit_center
- @string/zoom_mode_fit_height
@@ -43,6 +39,7 @@
- @string/standard
- @string/right_to_left
+ - @string/vertical
- @string/webtoon
diff --git a/app/src/main/res/values/constants.xml b/app/src/main/res/values/constants.xml
index 93ba5aeb4..ef192ce88 100644
--- a/app/src/main/res/values/constants.xml
+++ b/app/src/main/res/values/constants.xml
@@ -21,13 +21,6 @@
- 1
- 2
-
- - taps
- - volume
-
-
- - taps
-
- favourites
- history
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 094a3eb67..a921a8a8c 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -568,4 +568,17 @@
Vertical
Last read
Two pages
+ Show menu
+ Show/hide UI
+ Previous chapter
+ Next chapter
+ Previous page
+ Next page
+ Reader actions
+ Configure actions for tappable screen areas
+ Enable volume buttons
+ Use volume buttons for switching pages
+ Tap action
+ Long tap action
+ None
diff --git a/app/src/main/res/xml/pref_reader.xml b/app/src/main/res/xml/pref_reader.xml
index 06b4a4931..066d87d41 100644
--- a/app/src/main/res/xml/pref_reader.xml
+++ b/app/src/main/res/xml/pref_reader.xml
@@ -34,19 +34,18 @@
android:summary="@string/reader_zoom_buttons_summary"
android:title="@string/reader_zoom_buttons" />
-
+ android:key="reader_volume_buttons"
+ android:summary="@string/switch_pages_volume_buttons_summary"
+ android:title="@string/switch_pages_volume_buttons" />
+ android:title="@string/enhanced_colors"
+ app:allowDividerAbove="true" />