Improve reader scroll timer
parent
1a8045b89f
commit
c871255eb7
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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>
|
||||
@ -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>
|
||||
Loading…
Reference in New Issue