Configurable reader tap actions
This commit is contained in:
@@ -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" />
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()
|
||||||
|
|||||||
@@ -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))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
191
app/src/main/res/layout/activity_reader_tap_actions.xml
Normal file
191
app/src/main/res/layout/activity_reader_tap_actions.xml
Normal 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>
|
||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user