Improve reader scroll timer
This commit is contained in:
@@ -4,7 +4,7 @@ import java.util.EnumSet
|
||||
|
||||
enum class ReaderControl {
|
||||
|
||||
PREV_CHAPTER, NEXT_CHAPTER, SLIDER, PAGES_SHEET, SCREEN_ROTATION, SAVE_PAGE;
|
||||
PREV_CHAPTER, NEXT_CHAPTER, SLIDER, PAGES_SHEET, SCREEN_ROTATION, SAVE_PAGE, TIMER;
|
||||
|
||||
companion object {
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import androidx.appcompat.widget.ActionMenuView
|
||||
import androidx.appcompat.widget.Toolbar
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.descendants
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
@@ -34,6 +35,9 @@ fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
|
||||
return rect.contains(x, y)
|
||||
}
|
||||
|
||||
val ViewGroup.hasVisibleChildren: Boolean
|
||||
get() = children.any { it.isVisible }
|
||||
|
||||
fun View.measureHeight(): Int {
|
||||
val vh = height
|
||||
return if (vh == 0) {
|
||||
|
||||
@@ -22,6 +22,7 @@ import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderControl
|
||||
import org.koitharu.kotatsu.core.util.ext.hasVisibleChildren
|
||||
import org.koitharu.kotatsu.core.util.ext.isRtl
|
||||
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
||||
import org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding
|
||||
@@ -83,6 +84,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
binding.buttonOptions.initAction()
|
||||
binding.buttonScreenRotation.initAction()
|
||||
binding.buttonPagesThumbs.initAction()
|
||||
binding.buttonTimer.initAction()
|
||||
binding.slider.setLabelFormatter(PageLabelFormatter())
|
||||
binding.slider.addOnChangeListener(this)
|
||||
binding.slider.addOnSliderTouchListener(this)
|
||||
@@ -110,6 +112,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
R.id.button_prev -> listener?.switchChapterBy(-1)
|
||||
R.id.button_next -> listener?.switchChapterBy(1)
|
||||
R.id.button_save -> listener?.onSavePageClick()
|
||||
R.id.button_timer -> listener?.onScrollTimerClick()
|
||||
R.id.button_pages_thumbs -> AppRouter.from(this)?.showChapterPagesSheet()
|
||||
R.id.button_screen_rotation -> listener?.toggleScreenOrientation()
|
||||
R.id.button_options -> listener?.openMenu()
|
||||
@@ -158,6 +161,12 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
binding.slider.isRtl = reversed != isRtl
|
||||
}
|
||||
|
||||
fun setTimerActive(isActive: Boolean) {
|
||||
binding.buttonTimer.setIconResource(
|
||||
if (isActive) R.drawable.ic_timer_run else R.drawable.ic_timer,
|
||||
)
|
||||
}
|
||||
|
||||
private fun updateControlsVisibility() {
|
||||
val controls = settings.readerControls
|
||||
binding.buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
|
||||
@@ -165,6 +174,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
binding.buttonPagesThumbs.isVisible = ReaderControl.PAGES_SHEET in controls
|
||||
binding.buttonScreenRotation.isVisible = ReaderControl.SCREEN_ROTATION in controls
|
||||
binding.buttonSave.isVisible = ReaderControl.SAVE_PAGE in controls
|
||||
binding.buttonTimer.isVisible = ReaderControl.TIMER in controls
|
||||
binding.slider.isVisible = ReaderControl.SLIDER in controls
|
||||
adjustLayoutParams()
|
||||
}
|
||||
@@ -185,6 +195,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
||||
repeat(childCount) { i ->
|
||||
val child = getChildAt(i)
|
||||
if (child is FrameLayout) {
|
||||
child.isVisible = child.hasVisibleChildren
|
||||
child.updateLayoutParams<LayoutParams> {
|
||||
width = if (isSliderVisible) LayoutParams.WRAP_CONTENT else 0
|
||||
weight = if (isSliderVisible) 0f else 1f
|
||||
|
||||
@@ -87,12 +87,6 @@ class ReaderActivity :
|
||||
override val readerMode: ReaderMode?
|
||||
get() = readerManager.currentMode
|
||||
|
||||
override var isAutoScrollEnabled: Boolean
|
||||
get() = scrollTimer.isEnabled
|
||||
set(value) {
|
||||
scrollTimer.isEnabled = value
|
||||
}
|
||||
|
||||
private lateinit var scrollTimer: ScrollTimer
|
||||
private lateinit var pageSaveHelper: PageSaveHelper
|
||||
private lateinit var touchHelper: TapGridDispatcher
|
||||
@@ -107,13 +101,15 @@ class ReaderActivity :
|
||||
readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings)
|
||||
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)
|
||||
touchHelper = TapGridDispatcher(this, this)
|
||||
scrollTimer = scrollTimerFactory.create(this, this)
|
||||
scrollTimer = scrollTimerFactory.create(resources, this, this)
|
||||
pageSaveHelper = pageSaveHelperFactory.create(this)
|
||||
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
|
||||
viewBinding.zoomControl.listener = this
|
||||
viewBinding.actionsView.listener = this
|
||||
idlingDetector.bindToLifecycle(this)
|
||||
screenOrientationHelper.applySettings()
|
||||
scrollTimer.isActive.observe(this) { viewBinding.actionsView.setTimerActive(it) }
|
||||
viewBinding.timerControl.attach(scrollTimer, this)
|
||||
|
||||
viewModel.onError.observeEvent(
|
||||
this,
|
||||
@@ -162,7 +158,9 @@ class ReaderActivity :
|
||||
|
||||
override fun onUserInteraction() {
|
||||
super.onUserInteraction()
|
||||
scrollTimer.onUserInteraction()
|
||||
if (!viewBinding.timerControl.isVisible) {
|
||||
scrollTimer.onUserInteraction()
|
||||
}
|
||||
idlingDetector.onUserInteraction()
|
||||
}
|
||||
|
||||
@@ -196,6 +194,7 @@ class ReaderActivity :
|
||||
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
|
||||
}
|
||||
viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED)
|
||||
viewBinding.timerControl.onReaderModeChanged(mode)
|
||||
}
|
||||
|
||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||
@@ -237,7 +236,9 @@ class ReaderActivity :
|
||||
|
||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||
touchHelper.dispatchTouchEvent(ev)
|
||||
scrollTimer.onTouchEvent(ev)
|
||||
if (!viewBinding.timerControl.hasGlobalPoint(ev.rawX.toInt(), ev.rawY.toInt())) {
|
||||
scrollTimer.onTouchEvent(ev)
|
||||
}
|
||||
return super.dispatchTouchEvent(ev)
|
||||
}
|
||||
|
||||
@@ -272,6 +273,7 @@ class ReaderActivity :
|
||||
override fun onReaderModeChanged(mode: ReaderMode) {
|
||||
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
|
||||
viewModel.switchMode(mode)
|
||||
viewBinding.timerControl.onReaderModeChanged(mode)
|
||||
}
|
||||
|
||||
override fun onDoubleModeChanged(isEnabled: Boolean) {
|
||||
@@ -361,6 +363,10 @@ class ReaderActivity :
|
||||
viewModel.saveCurrentPage(pageSaveHelper)
|
||||
}
|
||||
|
||||
override fun onScrollTimerClick() {
|
||||
viewBinding.timerControl.showOrHide()
|
||||
}
|
||||
|
||||
override fun toggleScreenOrientation() {
|
||||
if (screenOrientationHelper.toggleScreenOrientation()) {
|
||||
Snackbar.make(
|
||||
|
||||
@@ -142,6 +142,8 @@ class ReaderControlDelegate(
|
||||
|
||||
fun onSavePageClick()
|
||||
|
||||
fun onScrollTimerClick()
|
||||
|
||||
fun toggleScreenOrientation()
|
||||
|
||||
fun isReaderResumed(): Boolean
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import android.content.res.Resources
|
||||
import android.os.SystemClock
|
||||
import android.view.MotionEvent
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
@@ -10,6 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
@@ -19,6 +22,7 @@ import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.yield
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
private const val MAX_DELAY = 8L
|
||||
@@ -27,6 +31,7 @@ private const val INTERACTION_SKIP_MS = 2_000L
|
||||
private const val SPEED_FACTOR_DELTA = 0.02f
|
||||
|
||||
class ScrollTimer @AssistedInject constructor(
|
||||
@Assisted resources: Resources,
|
||||
@Assisted private val listener: ReaderControlDelegate.OnInteractionListener,
|
||||
@Assisted lifecycleOwner: LifecycleOwner,
|
||||
settings: AppSettings,
|
||||
@@ -35,17 +40,15 @@ class ScrollTimer @AssistedInject constructor(
|
||||
private val coroutineScope = lifecycleOwner.lifecycleScope
|
||||
private var job: Job? = null
|
||||
private var delayMs: Long = 10L
|
||||
private var pageSwitchDelay: Long = 100L
|
||||
var pageSwitchDelay: Long = 100L
|
||||
private set
|
||||
private var resumeAt = 0L
|
||||
private var isTouchDown = MutableStateFlow(false)
|
||||
private val isRunning = MutableStateFlow(false)
|
||||
private val scrollDelta = resources.resolveDp(2)
|
||||
|
||||
var isEnabled: Boolean = false
|
||||
set(value) {
|
||||
if (field != value) {
|
||||
field = value
|
||||
restartJob()
|
||||
}
|
||||
}
|
||||
val isActive: StateFlow<Boolean>
|
||||
get() = isRunning
|
||||
|
||||
init {
|
||||
settings.observeAsFlow(AppSettings.KEY_READER_AUTOSCROLL_SPEED) {
|
||||
@@ -56,8 +59,15 @@ class ScrollTimer @AssistedInject constructor(
|
||||
}.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
fun setActive(value: Boolean) {
|
||||
if (isRunning.value != value) {
|
||||
isRunning.value = value
|
||||
restartJob()
|
||||
}
|
||||
}
|
||||
|
||||
fun onUserInteraction() {
|
||||
resumeAt = System.currentTimeMillis() + INTERACTION_SKIP_MS
|
||||
resumeAt = SystemClock.elapsedRealtime() + INTERACTION_SKIP_MS
|
||||
}
|
||||
|
||||
fun onTouchEvent(event: MotionEvent) {
|
||||
@@ -90,7 +100,7 @@ class ScrollTimer @AssistedInject constructor(
|
||||
private fun restartJob() {
|
||||
job?.cancel()
|
||||
resumeAt = 0L
|
||||
if (!isEnabled || delayMs == 0L) {
|
||||
if (!isRunning.value || delayMs == 0L) {
|
||||
job = null
|
||||
return
|
||||
}
|
||||
@@ -114,7 +124,7 @@ class ScrollTimer @AssistedInject constructor(
|
||||
if (!listener.isReaderResumed()) {
|
||||
continue
|
||||
}
|
||||
if (!listener.scrollBy(1, false)) {
|
||||
if (!listener.scrollBy(scrollDelta, false)) {
|
||||
accumulator += delayMs
|
||||
}
|
||||
if (accumulator >= pageSwitchDelay) {
|
||||
@@ -126,12 +136,12 @@ class ScrollTimer @AssistedInject constructor(
|
||||
}
|
||||
|
||||
private fun isPaused(): Boolean {
|
||||
return isTouchDown.value || resumeAt > System.currentTimeMillis()
|
||||
return isTouchDown.value || resumeAt > SystemClock.elapsedRealtime()
|
||||
}
|
||||
|
||||
private suspend fun delayUntilResumed() {
|
||||
while (isPaused()) {
|
||||
val delayTime = resumeAt - System.currentTimeMillis()
|
||||
val delayTime = resumeAt - SystemClock.elapsedRealtime()
|
||||
if (delayTime > 0) {
|
||||
delay(delayTime)
|
||||
} else {
|
||||
@@ -145,6 +155,7 @@ class ScrollTimer @AssistedInject constructor(
|
||||
interface Factory {
|
||||
|
||||
fun create(
|
||||
resources: Resources,
|
||||
lifecycleOwner: LifecycleOwner,
|
||||
listener: ReaderControlDelegate.OnInteractionListener,
|
||||
): ScrollTimer
|
||||
|
||||
@@ -0,0 +1,142 @@
|
||||
package org.koitharu.kotatsu.reader.ui
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.CompoundButton
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.transition.Slide
|
||||
import androidx.transition.TransitionManager
|
||||
import com.google.android.material.slider.LabelFormatter
|
||||
import com.google.android.material.slider.Slider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.util.ext.isAnimationsEnabled
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.parentView
|
||||
import org.koitharu.kotatsu.databinding.ViewScrollTimerBinding
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
class ScrollTimerControlView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : ConstraintLayout(context, attrs), CompoundButton.OnCheckedChangeListener, Slider.OnChangeListener,
|
||||
View.OnClickListener, LabelFormatter {
|
||||
|
||||
@Inject
|
||||
lateinit var settings: AppSettings
|
||||
|
||||
private val binding = ViewScrollTimerBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
private var scrollTimer: ScrollTimer? = null
|
||||
private var labelPattern = context.getString(R.string.speed_value)
|
||||
private var readerMode: ReaderMode = ReaderMode.STANDARD
|
||||
|
||||
init {
|
||||
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
||||
binding.sliderTimer.addOnChangeListener(this)
|
||||
binding.sliderTimer.setLabelFormatter(this)
|
||||
binding.buttonClose.setOnClickListener(this)
|
||||
setPadding(0, 0, 0, context.resources.getDimensionPixelOffset(R.dimen.margin_normal))
|
||||
}
|
||||
|
||||
fun attach(timer: ScrollTimer, lifecycleOwner: LifecycleOwner) {
|
||||
scrollTimer = timer
|
||||
timer.isActive.observe(lifecycleOwner) {
|
||||
binding.switchScrollTimer.setOnCheckedChangeListener(null)
|
||||
binding.switchScrollTimer.isChecked = it
|
||||
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
||||
}
|
||||
settings.observeAsStateFlow(
|
||||
scope = lifecycleOwner.lifecycleScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_READER_AUTOSCROLL_SPEED,
|
||||
valueProducer = { readerAutoscrollSpeed },
|
||||
).observe(lifecycleOwner) {
|
||||
binding.sliderTimer.value = it.coerceIn(
|
||||
binding.sliderTimer.valueFrom,
|
||||
binding.sliderTimer.valueTo,
|
||||
)
|
||||
}
|
||||
updateDescription()
|
||||
}
|
||||
|
||||
fun onReaderModeChanged(mode: ReaderMode) {
|
||||
readerMode = mode
|
||||
updateDescription()
|
||||
}
|
||||
|
||||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
R.id.button_close -> hide()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFormattedValue(value: Float): String {
|
||||
// val minValue = binding.sliderTimer.valueFrom
|
||||
// val maxValue = binding.sliderTimer.valueTo
|
||||
return labelPattern.format(value * 10f)
|
||||
}
|
||||
|
||||
override fun onValueChange(
|
||||
slider: Slider,
|
||||
value: Float,
|
||||
fromUser: Boolean
|
||||
) {
|
||||
if (fromUser) {
|
||||
settings.readerAutoscrollSpeed = value
|
||||
}
|
||||
updateDescription()
|
||||
}
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) {
|
||||
scrollTimer?.setActive(isChecked)
|
||||
}
|
||||
|
||||
fun show() {
|
||||
setupVisibilityTransition()
|
||||
isVisible = true
|
||||
}
|
||||
|
||||
fun hide() {
|
||||
setupVisibilityTransition()
|
||||
isVisible = false
|
||||
}
|
||||
|
||||
fun showOrHide() {
|
||||
setupVisibilityTransition()
|
||||
isVisible = !isVisible
|
||||
}
|
||||
|
||||
private fun setupVisibilityTransition() {
|
||||
if (context.isAnimationsEnabled) {
|
||||
val sceneRoot = parentView ?: return
|
||||
val transition = Slide()
|
||||
transition.addTarget(this)
|
||||
TransitionManager.beginDelayedTransition(sceneRoot, transition)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDescription() {
|
||||
val timePerPage = scrollTimer?.pageSwitchDelay ?: 0L
|
||||
if (timePerPage <= 0L || readerMode == ReaderMode.WEBTOON) {
|
||||
binding.textViewDescription.isVisible = false
|
||||
} else {
|
||||
binding.textViewDescription.text = context.getString(
|
||||
R.string.page_switch_timer,
|
||||
TimeUnit.MILLISECONDS.toSeconds((scrollTimer ?: return).pageSwitchDelay),
|
||||
)
|
||||
binding.textViewDescription.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,26 +10,20 @@ import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.view.updatePadding
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||
import com.google.android.material.slider.Slider
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||
import org.koitharu.kotatsu.core.nav.router
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.ReaderMode
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsStateFlow
|
||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||
import org.koitharu.kotatsu.core.util.ext.consume
|
||||
import org.koitharu.kotatsu.core.util.ext.findParentCallback
|
||||
import org.koitharu.kotatsu.core.util.ext.observe
|
||||
import org.koitharu.kotatsu.core.util.ext.viewLifecycleScope
|
||||
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||
@@ -42,7 +36,6 @@ class ReaderConfigSheet :
|
||||
BaseAdaptiveSheet<SheetReaderConfigBinding>(),
|
||||
View.OnClickListener,
|
||||
MaterialButtonToggleGroup.OnButtonCheckedListener,
|
||||
Slider.OnChangeListener,
|
||||
CompoundButton.OnCheckedChangeListener {
|
||||
|
||||
private val viewModel by activityViewModels<ReaderViewModel>()
|
||||
@@ -99,8 +92,7 @@ class ReaderConfigSheet :
|
||||
binding.buttonSettings.setOnClickListener(this)
|
||||
binding.buttonImageServer.setOnClickListener(this)
|
||||
binding.buttonColorFilter.setOnClickListener(this)
|
||||
binding.sliderTimer.addOnChangeListener(this)
|
||||
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
||||
binding.buttonScrollTimer.setOnClickListener(this)
|
||||
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
||||
|
||||
viewLifecycleScope.launch {
|
||||
@@ -110,20 +102,6 @@ class ReaderConfigSheet :
|
||||
}
|
||||
binding.buttonImageServer.isVisible = isAvailable
|
||||
}
|
||||
|
||||
settings.observeAsStateFlow(
|
||||
scope = lifecycleScope + Dispatchers.Default,
|
||||
key = AppSettings.KEY_READER_AUTOSCROLL_SPEED,
|
||||
valueProducer = { readerAutoscrollSpeed },
|
||||
).observe(viewLifecycleOwner) {
|
||||
binding.sliderTimer.value = it.coerceIn(
|
||||
binding.sliderTimer.valueFrom,
|
||||
binding.sliderTimer.valueTo,
|
||||
)
|
||||
}
|
||||
findParentCallback(Callback::class.java)?.run {
|
||||
binding.switchScrollTimer.isChecked = isAutoScrollEnabled
|
||||
}
|
||||
}
|
||||
|
||||
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
||||
@@ -141,6 +119,11 @@ class ReaderConfigSheet :
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
R.id.button_scroll_timer -> {
|
||||
findParentCallback(Callback::class.java)?.onScrollTimerClick() ?: return
|
||||
dismissAllowingStateLoss()
|
||||
}
|
||||
|
||||
R.id.button_save_page -> {
|
||||
findParentCallback(Callback::class.java)?.onSavePageClick() ?: return
|
||||
dismissAllowingStateLoss()
|
||||
@@ -168,12 +151,6 @@ class ReaderConfigSheet :
|
||||
|
||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||
when (buttonView.id) {
|
||||
R.id.switch_scroll_timer -> {
|
||||
findParentCallback(Callback::class.java)?.isAutoScrollEnabled = isChecked
|
||||
requireViewBinding().layoutTimer.isVisible = isChecked
|
||||
requireViewBinding().sliderTimer.isVisible = isChecked
|
||||
}
|
||||
|
||||
R.id.switch_screen_lock_rotation -> {
|
||||
orientationHelper.isLocked = isChecked
|
||||
}
|
||||
@@ -208,13 +185,6 @@ class ReaderConfigSheet :
|
||||
mode = newMode
|
||||
}
|
||||
|
||||
override fun onValueChange(slider: Slider, value: Float, fromUser: Boolean) {
|
||||
if (fromUser) {
|
||||
settings.readerAutoscrollSpeed = value
|
||||
}
|
||||
(viewBinding ?: return).labelTimerValue.text = getString(R.string.speed_value, value * 10f)
|
||||
}
|
||||
|
||||
private fun observeScreenOrientation() {
|
||||
orientationHelper.observeAutoOrientation()
|
||||
.onEach {
|
||||
@@ -243,12 +213,12 @@ class ReaderConfigSheet :
|
||||
|
||||
interface Callback {
|
||||
|
||||
var isAutoScrollEnabled: Boolean
|
||||
|
||||
fun onReaderModeChanged(mode: ReaderMode)
|
||||
|
||||
fun onDoubleModeChanged(isEnabled: Boolean)
|
||||
|
||||
fun onSavePageClick()
|
||||
|
||||
fun onScrollTimerClick()
|
||||
}
|
||||
}
|
||||
|
||||
12
app/src/main/res/drawable/ic_timer_run.xml
Normal file
12
app/src/main/res/drawable/ic_timer_run.xml
Normal file
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="#000"
|
||||
android:pathData="M15 3H9V1H15V3M11 14H13V8H11V14M19 13C19.7 13 20.36 13.13 21 13.35C21 13.23 21 13.12 21 13C21 10.88 20.26 8.93 19.03 7.39L20.45 5.97C20 5.46 19.55 5 19.04 4.56L17.62 6C16.07 4.74 14.12 4 12 4C7.03 4 3 8.03 3 13S7.03 22 12 22C12.59 22 13.16 21.94 13.71 21.83C13.4 21.25 13.18 20.6 13.08 19.91C12.72 19.96 12.37 20 12 20C8.13 20 5 16.87 5 13S8.13 6 12 6 19 9.13 19 13M17 16V22L22 19L17 16Z" />
|
||||
</vector>
|
||||
@@ -34,6 +34,17 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.reader.ui.ScrollTimerControlView
|
||||
android:id="@+id/timerControl"
|
||||
android:layout_width="320dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|end"
|
||||
android:layout_margin="@dimen/screen_padding"
|
||||
android:background="@drawable/bg_card"
|
||||
android:elevation="@dimen/m3_card_elevated_elevation"
|
||||
android:visibility="gone"
|
||||
app:layout_dodgeInsetEdges="top" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_top"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -34,6 +34,17 @@
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.reader.ui.ScrollTimerControlView
|
||||
android:id="@+id/timerControl"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layout_margin="@dimen/screen_padding"
|
||||
android:background="@drawable/bg_card"
|
||||
android:elevation="@dimen/m3_card_elevated_elevation"
|
||||
android:visibility="gone"
|
||||
app:layout_dodgeInsetEdges="bottom" />
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appbar_top"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
@@ -59,6 +59,19 @@
|
||||
app:icon="@drawable/ic_save" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/button_timer"
|
||||
style="@style/Widget.Kotatsu.IconButton.Action"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:contentDescription="@string/save_page"
|
||||
app:icon="@drawable/ic_timer" />
|
||||
</FrameLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
@@ -146,61 +146,17 @@
|
||||
android:textColor="?colorOnSurfaceVariant"
|
||||
app:drawableStartCompat="@drawable/ic_split_horizontal" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_scroll_timer"
|
||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||
android:id="@+id/button_scroll_timer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||
android:ellipsize="end"
|
||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||
android:singleLine="true"
|
||||
android:text="@string/automatic_scroll"
|
||||
android:textAppearance="?attr/textAppearanceButton"
|
||||
android:textColor="?colorOnSurfaceVariant"
|
||||
app:drawableStartCompat="@drawable/ic_timer" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_timer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
||||
android:layout_marginTop="@dimen/margin_normal"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/speed"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_timer_value"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_small"
|
||||
android:textAppearance="?attr/textAppearanceBodySmall"
|
||||
tools:text="x0.5" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CubicSlider
|
||||
android:id="@+id/slider_timer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
||||
android:contentDescription="@string/automatic_scroll"
|
||||
android:labelFor="@id/switch_scroll_timer"
|
||||
android:valueFrom="0.2"
|
||||
android:valueTo="0.9"
|
||||
android:visibility="gone"
|
||||
app:labelBehavior="gone"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||
android:id="@+id/button_color_filter"
|
||||
android:layout_width="match_parent"
|
||||
|
||||
87
app/src/main/res/layout/view_scroll_timer.xml
Normal file
87
app/src/main/res/layout/view_scroll_timer.xml
Normal file
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<merge
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:background="@drawable/bg_card"
|
||||
tools:layout_height="wrap_content"
|
||||
tools:layout_margin="@dimen/screen_padding"
|
||||
tools:layout_width="match_parent"
|
||||
tools:paddingBottom="@dimen/margin_normal"
|
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?minTouchTargetSize"
|
||||
android:paddingHorizontal="@dimen/margin_normal"
|
||||
android:text="@string/automatic_scroll"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
app:layout_constraintEnd_toStartOf="@id/button_close"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_close"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/close"
|
||||
android:minWidth="?minTouchTargetSize"
|
||||
android:minHeight="?minTouchTargetSize"
|
||||
android:src="?actionModeCloseDrawable"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switch_scroll_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:text="@string/enable"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/textView_title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label_timer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="@dimen/margin_normal"
|
||||
android:text="@string/speed"
|
||||
android:textAppearance="?attr/textAppearanceTitleSmall"
|
||||
app:layout_constraintBottom_toBottomOf="@id/slider_timer"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@id/slider_timer" />
|
||||
|
||||
<org.koitharu.kotatsu.core.ui.widgets.CubicSlider
|
||||
android:id="@+id/slider_timer"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginEnd="@dimen/margin_normal"
|
||||
android:contentDescription="@string/speed"
|
||||
android:labelFor="@id/switch_scroll_timer"
|
||||
android:valueFrom="0.001"
|
||||
android:valueTo="0.9"
|
||||
app:labelBehavior="floating"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/label_timer"
|
||||
app:layout_constraintTop_toBottomOf="@id/switch_scroll_timer" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
||||
android:layout_marginTop="@dimen/margin_small"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/slider_timer"
|
||||
tools:text="@string/page_switch_timer" />
|
||||
</merge>
|
||||
@@ -131,6 +131,7 @@
|
||||
<item>@string/chapters_and_pages</item>
|
||||
<item>@string/screen_orientation</item>
|
||||
<item>@string/save_page</item>
|
||||
<item>@string/automatic_scroll</item>
|
||||
</string-array>
|
||||
<string-array name="list_badges" translatable="false">
|
||||
<item>@string/favourites</item>
|
||||
|
||||
@@ -831,4 +831,5 @@
|
||||
<string name="pick_manga_page">Pick manga page</string>
|
||||
<string name="pick_custom_file">Pick custom file</string>
|
||||
<string name="change_cover">Change cover</string>
|
||||
<string name="page_switch_timer">The page will switch every ~%d seconds</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user