Improve reader scroll timer
This commit is contained in:
@@ -4,7 +4,7 @@ import java.util.EnumSet
|
|||||||
|
|
||||||
enum class ReaderControl {
|
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 {
|
companion object {
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import androidx.appcompat.widget.ActionMenuView
|
|||||||
import androidx.appcompat.widget.Toolbar
|
import androidx.appcompat.widget.Toolbar
|
||||||
import androidx.core.view.children
|
import androidx.core.view.children
|
||||||
import androidx.core.view.descendants
|
import androidx.core.view.descendants
|
||||||
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
import androidx.fragment.app.FragmentManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
@@ -34,6 +35,9 @@ fun View.hasGlobalPoint(x: Int, y: Int): Boolean {
|
|||||||
return rect.contains(x, y)
|
return rect.contains(x, y)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val ViewGroup.hasVisibleChildren: Boolean
|
||||||
|
get() = children.any { it.isVisible }
|
||||||
|
|
||||||
fun View.measureHeight(): Int {
|
fun View.measureHeight(): Int {
|
||||||
val vh = height
|
val vh = height
|
||||||
return if (vh == 0) {
|
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.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.ReaderControl
|
import org.koitharu.kotatsu.core.prefs.ReaderControl
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.hasVisibleChildren
|
||||||
import org.koitharu.kotatsu.core.util.ext.isRtl
|
import org.koitharu.kotatsu.core.util.ext.isRtl
|
||||||
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
import org.koitharu.kotatsu.core.util.ext.setValueRounded
|
||||||
import org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding
|
import org.koitharu.kotatsu.databinding.LayoutReaderActionsBinding
|
||||||
@@ -83,6 +84,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
|||||||
binding.buttonOptions.initAction()
|
binding.buttonOptions.initAction()
|
||||||
binding.buttonScreenRotation.initAction()
|
binding.buttonScreenRotation.initAction()
|
||||||
binding.buttonPagesThumbs.initAction()
|
binding.buttonPagesThumbs.initAction()
|
||||||
|
binding.buttonTimer.initAction()
|
||||||
binding.slider.setLabelFormatter(PageLabelFormatter())
|
binding.slider.setLabelFormatter(PageLabelFormatter())
|
||||||
binding.slider.addOnChangeListener(this)
|
binding.slider.addOnChangeListener(this)
|
||||||
binding.slider.addOnSliderTouchListener(this)
|
binding.slider.addOnSliderTouchListener(this)
|
||||||
@@ -110,6 +112,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
|||||||
R.id.button_prev -> listener?.switchChapterBy(-1)
|
R.id.button_prev -> listener?.switchChapterBy(-1)
|
||||||
R.id.button_next -> listener?.switchChapterBy(1)
|
R.id.button_next -> listener?.switchChapterBy(1)
|
||||||
R.id.button_save -> listener?.onSavePageClick()
|
R.id.button_save -> listener?.onSavePageClick()
|
||||||
|
R.id.button_timer -> listener?.onScrollTimerClick()
|
||||||
R.id.button_pages_thumbs -> AppRouter.from(this)?.showChapterPagesSheet()
|
R.id.button_pages_thumbs -> AppRouter.from(this)?.showChapterPagesSheet()
|
||||||
R.id.button_screen_rotation -> listener?.toggleScreenOrientation()
|
R.id.button_screen_rotation -> listener?.toggleScreenOrientation()
|
||||||
R.id.button_options -> listener?.openMenu()
|
R.id.button_options -> listener?.openMenu()
|
||||||
@@ -158,6 +161,12 @@ class ReaderActionsView @JvmOverloads constructor(
|
|||||||
binding.slider.isRtl = reversed != isRtl
|
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() {
|
private fun updateControlsVisibility() {
|
||||||
val controls = settings.readerControls
|
val controls = settings.readerControls
|
||||||
binding.buttonPrev.isVisible = ReaderControl.PREV_CHAPTER in controls
|
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.buttonPagesThumbs.isVisible = ReaderControl.PAGES_SHEET in controls
|
||||||
binding.buttonScreenRotation.isVisible = ReaderControl.SCREEN_ROTATION in controls
|
binding.buttonScreenRotation.isVisible = ReaderControl.SCREEN_ROTATION in controls
|
||||||
binding.buttonSave.isVisible = ReaderControl.SAVE_PAGE in controls
|
binding.buttonSave.isVisible = ReaderControl.SAVE_PAGE in controls
|
||||||
|
binding.buttonTimer.isVisible = ReaderControl.TIMER in controls
|
||||||
binding.slider.isVisible = ReaderControl.SLIDER in controls
|
binding.slider.isVisible = ReaderControl.SLIDER in controls
|
||||||
adjustLayoutParams()
|
adjustLayoutParams()
|
||||||
}
|
}
|
||||||
@@ -185,6 +195,7 @@ class ReaderActionsView @JvmOverloads constructor(
|
|||||||
repeat(childCount) { i ->
|
repeat(childCount) { i ->
|
||||||
val child = getChildAt(i)
|
val child = getChildAt(i)
|
||||||
if (child is FrameLayout) {
|
if (child is FrameLayout) {
|
||||||
|
child.isVisible = child.hasVisibleChildren
|
||||||
child.updateLayoutParams<LayoutParams> {
|
child.updateLayoutParams<LayoutParams> {
|
||||||
width = if (isSliderVisible) LayoutParams.WRAP_CONTENT else 0
|
width = if (isSliderVisible) LayoutParams.WRAP_CONTENT else 0
|
||||||
weight = if (isSliderVisible) 0f else 1f
|
weight = if (isSliderVisible) 0f else 1f
|
||||||
|
|||||||
@@ -87,12 +87,6 @@ class ReaderActivity :
|
|||||||
override val readerMode: ReaderMode?
|
override val readerMode: ReaderMode?
|
||||||
get() = readerManager.currentMode
|
get() = readerManager.currentMode
|
||||||
|
|
||||||
override var isAutoScrollEnabled: Boolean
|
|
||||||
get() = scrollTimer.isEnabled
|
|
||||||
set(value) {
|
|
||||||
scrollTimer.isEnabled = value
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var scrollTimer: ScrollTimer
|
private lateinit var scrollTimer: ScrollTimer
|
||||||
private lateinit var pageSaveHelper: PageSaveHelper
|
private lateinit var pageSaveHelper: PageSaveHelper
|
||||||
private lateinit var touchHelper: TapGridDispatcher
|
private lateinit var touchHelper: TapGridDispatcher
|
||||||
@@ -107,13 +101,15 @@ class ReaderActivity :
|
|||||||
readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings)
|
readerManager = ReaderManager(supportFragmentManager, viewBinding.container, settings)
|
||||||
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)
|
setDisplayHomeAsUp(isEnabled = true, showUpAsClose = false)
|
||||||
touchHelper = TapGridDispatcher(this, this)
|
touchHelper = TapGridDispatcher(this, this)
|
||||||
scrollTimer = scrollTimerFactory.create(this, this)
|
scrollTimer = scrollTimerFactory.create(resources, this, this)
|
||||||
pageSaveHelper = pageSaveHelperFactory.create(this)
|
pageSaveHelper = pageSaveHelperFactory.create(this)
|
||||||
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
|
controlDelegate = ReaderControlDelegate(resources, settings, tapGridSettings, this)
|
||||||
viewBinding.zoomControl.listener = this
|
viewBinding.zoomControl.listener = this
|
||||||
viewBinding.actionsView.listener = this
|
viewBinding.actionsView.listener = this
|
||||||
idlingDetector.bindToLifecycle(this)
|
idlingDetector.bindToLifecycle(this)
|
||||||
screenOrientationHelper.applySettings()
|
screenOrientationHelper.applySettings()
|
||||||
|
scrollTimer.isActive.observe(this) { viewBinding.actionsView.setTimerActive(it) }
|
||||||
|
viewBinding.timerControl.attach(scrollTimer, this)
|
||||||
|
|
||||||
viewModel.onError.observeEvent(
|
viewModel.onError.observeEvent(
|
||||||
this,
|
this,
|
||||||
@@ -162,7 +158,9 @@ class ReaderActivity :
|
|||||||
|
|
||||||
override fun onUserInteraction() {
|
override fun onUserInteraction() {
|
||||||
super.onUserInteraction()
|
super.onUserInteraction()
|
||||||
scrollTimer.onUserInteraction()
|
if (!viewBinding.timerControl.isVisible) {
|
||||||
|
scrollTimer.onUserInteraction()
|
||||||
|
}
|
||||||
idlingDetector.onUserInteraction()
|
idlingDetector.onUserInteraction()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -196,6 +194,7 @@ class ReaderActivity :
|
|||||||
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
|
lifecycle.postDelayed(TimeUnit.SECONDS.toMillis(1), hideUiRunnable)
|
||||||
}
|
}
|
||||||
viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED)
|
viewBinding.actionsView.setSliderReversed(mode == ReaderMode.REVERSED)
|
||||||
|
viewBinding.timerControl.onReaderModeChanged(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLoadingStateChanged(isLoading: Boolean) {
|
private fun onLoadingStateChanged(isLoading: Boolean) {
|
||||||
@@ -237,7 +236,9 @@ class ReaderActivity :
|
|||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||||
touchHelper.dispatchTouchEvent(ev)
|
touchHelper.dispatchTouchEvent(ev)
|
||||||
scrollTimer.onTouchEvent(ev)
|
if (!viewBinding.timerControl.hasGlobalPoint(ev.rawX.toInt(), ev.rawY.toInt())) {
|
||||||
|
scrollTimer.onTouchEvent(ev)
|
||||||
|
}
|
||||||
return super.dispatchTouchEvent(ev)
|
return super.dispatchTouchEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -272,6 +273,7 @@ class ReaderActivity :
|
|||||||
override fun onReaderModeChanged(mode: ReaderMode) {
|
override fun onReaderModeChanged(mode: ReaderMode) {
|
||||||
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
|
viewModel.saveCurrentState(readerManager.currentReader?.getCurrentState())
|
||||||
viewModel.switchMode(mode)
|
viewModel.switchMode(mode)
|
||||||
|
viewBinding.timerControl.onReaderModeChanged(mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDoubleModeChanged(isEnabled: Boolean) {
|
override fun onDoubleModeChanged(isEnabled: Boolean) {
|
||||||
@@ -361,6 +363,10 @@ class ReaderActivity :
|
|||||||
viewModel.saveCurrentPage(pageSaveHelper)
|
viewModel.saveCurrentPage(pageSaveHelper)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onScrollTimerClick() {
|
||||||
|
viewBinding.timerControl.showOrHide()
|
||||||
|
}
|
||||||
|
|
||||||
override fun toggleScreenOrientation() {
|
override fun toggleScreenOrientation() {
|
||||||
if (screenOrientationHelper.toggleScreenOrientation()) {
|
if (screenOrientationHelper.toggleScreenOrientation()) {
|
||||||
Snackbar.make(
|
Snackbar.make(
|
||||||
|
|||||||
@@ -142,6 +142,8 @@ class ReaderControlDelegate(
|
|||||||
|
|
||||||
fun onSavePageClick()
|
fun onSavePageClick()
|
||||||
|
|
||||||
|
fun onScrollTimerClick()
|
||||||
|
|
||||||
fun toggleScreenOrientation()
|
fun toggleScreenOrientation()
|
||||||
|
|
||||||
fun isReaderResumed(): Boolean
|
fun isReaderResumed(): Boolean
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package org.koitharu.kotatsu.reader.ui
|
package org.koitharu.kotatsu.reader.ui
|
||||||
|
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.os.SystemClock
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import androidx.lifecycle.LifecycleOwner
|
import androidx.lifecycle.LifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
@@ -10,6 +12,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
@@ -19,6 +22,7 @@ import kotlinx.coroutines.launch
|
|||||||
import kotlinx.coroutines.yield
|
import kotlinx.coroutines.yield
|
||||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||||
|
import org.koitharu.kotatsu.core.util.ext.resolveDp
|
||||||
import kotlin.math.roundToLong
|
import kotlin.math.roundToLong
|
||||||
|
|
||||||
private const val MAX_DELAY = 8L
|
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
|
private const val SPEED_FACTOR_DELTA = 0.02f
|
||||||
|
|
||||||
class ScrollTimer @AssistedInject constructor(
|
class ScrollTimer @AssistedInject constructor(
|
||||||
|
@Assisted resources: Resources,
|
||||||
@Assisted private val listener: ReaderControlDelegate.OnInteractionListener,
|
@Assisted private val listener: ReaderControlDelegate.OnInteractionListener,
|
||||||
@Assisted lifecycleOwner: LifecycleOwner,
|
@Assisted lifecycleOwner: LifecycleOwner,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
@@ -35,17 +40,15 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
private val coroutineScope = lifecycleOwner.lifecycleScope
|
private val coroutineScope = lifecycleOwner.lifecycleScope
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
private var delayMs: Long = 10L
|
private var delayMs: Long = 10L
|
||||||
private var pageSwitchDelay: Long = 100L
|
var pageSwitchDelay: Long = 100L
|
||||||
|
private set
|
||||||
private var resumeAt = 0L
|
private var resumeAt = 0L
|
||||||
private var isTouchDown = MutableStateFlow(false)
|
private var isTouchDown = MutableStateFlow(false)
|
||||||
|
private val isRunning = MutableStateFlow(false)
|
||||||
|
private val scrollDelta = resources.resolveDp(2)
|
||||||
|
|
||||||
var isEnabled: Boolean = false
|
val isActive: StateFlow<Boolean>
|
||||||
set(value) {
|
get() = isRunning
|
||||||
if (field != value) {
|
|
||||||
field = value
|
|
||||||
restartJob()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
settings.observeAsFlow(AppSettings.KEY_READER_AUTOSCROLL_SPEED) {
|
settings.observeAsFlow(AppSettings.KEY_READER_AUTOSCROLL_SPEED) {
|
||||||
@@ -56,8 +59,15 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
}.launchIn(coroutineScope)
|
}.launchIn(coroutineScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setActive(value: Boolean) {
|
||||||
|
if (isRunning.value != value) {
|
||||||
|
isRunning.value = value
|
||||||
|
restartJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun onUserInteraction() {
|
fun onUserInteraction() {
|
||||||
resumeAt = System.currentTimeMillis() + INTERACTION_SKIP_MS
|
resumeAt = SystemClock.elapsedRealtime() + INTERACTION_SKIP_MS
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onTouchEvent(event: MotionEvent) {
|
fun onTouchEvent(event: MotionEvent) {
|
||||||
@@ -90,7 +100,7 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
private fun restartJob() {
|
private fun restartJob() {
|
||||||
job?.cancel()
|
job?.cancel()
|
||||||
resumeAt = 0L
|
resumeAt = 0L
|
||||||
if (!isEnabled || delayMs == 0L) {
|
if (!isRunning.value || delayMs == 0L) {
|
||||||
job = null
|
job = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -114,7 +124,7 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
if (!listener.isReaderResumed()) {
|
if (!listener.isReaderResumed()) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if (!listener.scrollBy(1, false)) {
|
if (!listener.scrollBy(scrollDelta, false)) {
|
||||||
accumulator += delayMs
|
accumulator += delayMs
|
||||||
}
|
}
|
||||||
if (accumulator >= pageSwitchDelay) {
|
if (accumulator >= pageSwitchDelay) {
|
||||||
@@ -126,12 +136,12 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isPaused(): Boolean {
|
private fun isPaused(): Boolean {
|
||||||
return isTouchDown.value || resumeAt > System.currentTimeMillis()
|
return isTouchDown.value || resumeAt > SystemClock.elapsedRealtime()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun delayUntilResumed() {
|
private suspend fun delayUntilResumed() {
|
||||||
while (isPaused()) {
|
while (isPaused()) {
|
||||||
val delayTime = resumeAt - System.currentTimeMillis()
|
val delayTime = resumeAt - SystemClock.elapsedRealtime()
|
||||||
if (delayTime > 0) {
|
if (delayTime > 0) {
|
||||||
delay(delayTime)
|
delay(delayTime)
|
||||||
} else {
|
} else {
|
||||||
@@ -145,6 +155,7 @@ class ScrollTimer @AssistedInject constructor(
|
|||||||
interface Factory {
|
interface Factory {
|
||||||
|
|
||||||
fun create(
|
fun create(
|
||||||
|
resources: Resources,
|
||||||
lifecycleOwner: LifecycleOwner,
|
lifecycleOwner: LifecycleOwner,
|
||||||
listener: ReaderControlDelegate.OnInteractionListener,
|
listener: ReaderControlDelegate.OnInteractionListener,
|
||||||
): ScrollTimer
|
): 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.isVisible
|
||||||
import androidx.core.view.updatePadding
|
import androidx.core.view.updatePadding
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||||
import com.google.android.material.slider.Slider
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.plus
|
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
import org.koitharu.kotatsu.core.nav.AppRouter
|
import org.koitharu.kotatsu.core.nav.AppRouter
|
||||||
import org.koitharu.kotatsu.core.nav.router
|
import org.koitharu.kotatsu.core.nav.router
|
||||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
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.prefs.observeAsStateFlow
|
|
||||||
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
import org.koitharu.kotatsu.core.ui.sheet.BaseAdaptiveSheet
|
||||||
import org.koitharu.kotatsu.core.util.ext.consume
|
import org.koitharu.kotatsu.core.util.ext.consume
|
||||||
import org.koitharu.kotatsu.core.util.ext.findParentCallback
|
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.core.util.ext.viewLifecycleScope
|
||||||
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
import org.koitharu.kotatsu.databinding.SheetReaderConfigBinding
|
||||||
import org.koitharu.kotatsu.reader.domain.PageLoader
|
import org.koitharu.kotatsu.reader.domain.PageLoader
|
||||||
@@ -42,7 +36,6 @@ class ReaderConfigSheet :
|
|||||||
BaseAdaptiveSheet<SheetReaderConfigBinding>(),
|
BaseAdaptiveSheet<SheetReaderConfigBinding>(),
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
MaterialButtonToggleGroup.OnButtonCheckedListener,
|
MaterialButtonToggleGroup.OnButtonCheckedListener,
|
||||||
Slider.OnChangeListener,
|
|
||||||
CompoundButton.OnCheckedChangeListener {
|
CompoundButton.OnCheckedChangeListener {
|
||||||
|
|
||||||
private val viewModel by activityViewModels<ReaderViewModel>()
|
private val viewModel by activityViewModels<ReaderViewModel>()
|
||||||
@@ -99,8 +92,7 @@ class ReaderConfigSheet :
|
|||||||
binding.buttonSettings.setOnClickListener(this)
|
binding.buttonSettings.setOnClickListener(this)
|
||||||
binding.buttonImageServer.setOnClickListener(this)
|
binding.buttonImageServer.setOnClickListener(this)
|
||||||
binding.buttonColorFilter.setOnClickListener(this)
|
binding.buttonColorFilter.setOnClickListener(this)
|
||||||
binding.sliderTimer.addOnChangeListener(this)
|
binding.buttonScrollTimer.setOnClickListener(this)
|
||||||
binding.switchScrollTimer.setOnCheckedChangeListener(this)
|
|
||||||
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
binding.switchDoubleReader.setOnCheckedChangeListener(this)
|
||||||
|
|
||||||
viewLifecycleScope.launch {
|
viewLifecycleScope.launch {
|
||||||
@@ -110,20 +102,6 @@ class ReaderConfigSheet :
|
|||||||
}
|
}
|
||||||
binding.buttonImageServer.isVisible = isAvailable
|
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 {
|
override fun onApplyWindowInsets(v: View, insets: WindowInsetsCompat): WindowInsetsCompat {
|
||||||
@@ -141,6 +119,11 @@ class ReaderConfigSheet :
|
|||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
R.id.button_scroll_timer -> {
|
||||||
|
findParentCallback(Callback::class.java)?.onScrollTimerClick() ?: return
|
||||||
|
dismissAllowingStateLoss()
|
||||||
|
}
|
||||||
|
|
||||||
R.id.button_save_page -> {
|
R.id.button_save_page -> {
|
||||||
findParentCallback(Callback::class.java)?.onSavePageClick() ?: return
|
findParentCallback(Callback::class.java)?.onSavePageClick() ?: return
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
@@ -168,12 +151,6 @@ class ReaderConfigSheet :
|
|||||||
|
|
||||||
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) {
|
||||||
when (buttonView.id) {
|
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 -> {
|
R.id.switch_screen_lock_rotation -> {
|
||||||
orientationHelper.isLocked = isChecked
|
orientationHelper.isLocked = isChecked
|
||||||
}
|
}
|
||||||
@@ -208,13 +185,6 @@ class ReaderConfigSheet :
|
|||||||
mode = newMode
|
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() {
|
private fun observeScreenOrientation() {
|
||||||
orientationHelper.observeAutoOrientation()
|
orientationHelper.observeAutoOrientation()
|
||||||
.onEach {
|
.onEach {
|
||||||
@@ -243,12 +213,12 @@ class ReaderConfigSheet :
|
|||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|
||||||
var isAutoScrollEnabled: Boolean
|
|
||||||
|
|
||||||
fun onReaderModeChanged(mode: ReaderMode)
|
fun onReaderModeChanged(mode: ReaderMode)
|
||||||
|
|
||||||
fun onDoubleModeChanged(isEnabled: Boolean)
|
fun onDoubleModeChanged(isEnabled: Boolean)
|
||||||
|
|
||||||
fun onSavePageClick()
|
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"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
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
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appbar_top"
|
android:id="@+id/appbar_top"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -34,6 +34,17 @@
|
|||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
tools:visibility="visible" />
|
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
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appbar_top"
|
android:id="@+id/appbar_top"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
@@ -59,6 +59,19 @@
|
|||||||
app:icon="@drawable/ic_save" />
|
app:icon="@drawable/ic_save" />
|
||||||
</FrameLayout>
|
</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
|
<FrameLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|||||||
@@ -146,61 +146,17 @@
|
|||||||
android:textColor="?colorOnSurfaceVariant"
|
android:textColor="?colorOnSurfaceVariant"
|
||||||
app:drawableStartCompat="@drawable/ic_split_horizontal" />
|
app:drawableStartCompat="@drawable/ic_split_horizontal" />
|
||||||
|
|
||||||
<com.google.android.material.materialswitch.MaterialSwitch
|
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||||
android:id="@+id/switch_scroll_timer"
|
android:id="@+id/button_scroll_timer"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
android:ellipsize="end"
|
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
android:singleLine="true"
|
|
||||||
android:text="@string/automatic_scroll"
|
android:text="@string/automatic_scroll"
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
android:textAppearance="?attr/textAppearanceButton"
|
||||||
android:textColor="?colorOnSurfaceVariant"
|
|
||||||
app:drawableStartCompat="@drawable/ic_timer" />
|
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
|
<org.koitharu.kotatsu.core.ui.widgets.ListItemTextView
|
||||||
android:id="@+id/button_color_filter"
|
android:id="@+id/button_color_filter"
|
||||||
android:layout_width="match_parent"
|
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/chapters_and_pages</item>
|
||||||
<item>@string/screen_orientation</item>
|
<item>@string/screen_orientation</item>
|
||||||
<item>@string/save_page</item>
|
<item>@string/save_page</item>
|
||||||
|
<item>@string/automatic_scroll</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
<string-array name="list_badges" translatable="false">
|
<string-array name="list_badges" translatable="false">
|
||||||
<item>@string/favourites</item>
|
<item>@string/favourites</item>
|
||||||
|
|||||||
@@ -831,4 +831,5 @@
|
|||||||
<string name="pick_manga_page">Pick manga page</string>
|
<string name="pick_manga_page">Pick manga page</string>
|
||||||
<string name="pick_custom_file">Pick custom file</string>
|
<string name="pick_custom_file">Pick custom file</string>
|
||||||
<string name="change_cover">Change cover</string>
|
<string name="change_cover">Change cover</string>
|
||||||
|
<string name="page_switch_timer">The page will switch every ~%d seconds</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user