Autoscroll in reader #204

This commit is contained in:
Koitharu
2022-08-02 12:34:50 +03:00
parent 57929f62ad
commit 6c43881cf4
7 changed files with 164 additions and 12 deletions

View File

@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.databinding.ActivityReaderBinding
import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.model.MangaChapter
import org.koitharu.kotatsu.parsers.model.MangaPage
import org.koitharu.kotatsu.reader.ui.config.PageSwitchTimer
import org.koitharu.kotatsu.reader.ui.config.ReaderConfigBottomSheet
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
@@ -65,6 +66,13 @@ class ReaderActivity :
)
}
override var pageSwitchDelay: Float
get() = pageSwitchTimer.delaySec
set(value) {
pageSwitchTimer.delaySec = value
}
private lateinit var pageSwitchTimer: PageSwitchTimer
private lateinit var touchHelper: GridTouchHelper
private lateinit var controlDelegate: ReaderControlDelegate
private var gestureInsets: Insets = Insets.NONE
@@ -77,6 +85,7 @@ class ReaderActivity :
readerManager = ReaderManager(supportFragmentManager, R.id.container)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
touchHelper = GridTouchHelper(this, this)
pageSwitchTimer = PageSwitchTimer(this, this)
controlDelegate = ReaderControlDelegate(lifecycleScope, settings, this)
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
binding.slider.setLabelFormatter(PageLabelFormatter())
@@ -100,6 +109,11 @@ class ReaderActivity :
}
}
override fun onUserInteraction() {
super.onUserInteraction()
pageSwitchTimer.onUserInteraction()
}
private fun onInitReader(mode: ReaderMode) {
if (readerManager.currentMode != mode) {
readerManager.replace(mode)

View File

@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.GridTouchHelper
class ReaderControlDelegate(
scope: LifecycleCoroutineScope,
settings: AppSettings,
private val listener: OnInteractionListener
private val listener: OnInteractionListener,
) {
private var isTapSwitchEnabled: Boolean = true
@@ -72,14 +72,16 @@ class ReaderControlDelegate(
KeyEvent.KEYCODE_PAGE_DOWN,
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
KeyEvent.KEYCODE_DPAD_DOWN,
KeyEvent.KEYCODE_DPAD_RIGHT -> {
KeyEvent.KEYCODE_DPAD_RIGHT,
-> {
listener.switchPageBy(1)
true
}
KeyEvent.KEYCODE_PAGE_UP,
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
KeyEvent.KEYCODE_DPAD_UP,
KeyEvent.KEYCODE_DPAD_LEFT -> {
KeyEvent.KEYCODE_DPAD_LEFT,
-> {
listener.switchPageBy(-1)
true
}
@@ -103,4 +105,4 @@ class ReaderControlDelegate(
fun toggleUiVisibility()
}
}
}

View File

@@ -0,0 +1,74 @@
package org.koitharu.kotatsu.reader.ui.config
import android.content.res.Resources
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.coroutineScope
import androidx.lifecycle.repeatOnLifecycle
import com.google.android.material.slider.LabelFormatter
import kotlin.math.roundToLong
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.ui.ReaderControlDelegate
class PageSwitchTimer(
private val listener: ReaderControlDelegate.OnInteractionListener,
private val lifecycleOwner: LifecycleOwner,
) {
var delaySec: Float = 0f
set(value) {
field = value
delayMs = mapDelay(value)
restartJob()
}
private var delayMs = 0L
fun onUserInteraction() {
restartJob()
}
private var job: Job? = null
private fun restartJob() {
job?.cancel()
if (delayMs == 0L) {
job = null
return
}
job = lifecycleOwner.lifecycle.coroutineScope.launch {
// FIXME: pause when bs is opened
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
while (isActive) {
delay(delayMs)
listener.switchPageBy(1)
}
}
}
}
class DelayLabelFormatter(resources: Resources) : LabelFormatter {
private val textOff = resources.getString(R.string.off_short)
private val textSec = resources.getString(R.string.seconds_pattern)
override fun getFormattedValue(value: Float): String {
val ms = mapDelay(value)
return if (ms == 0L) textOff else textSec.format((ms / 1000.0).format(1))
}
}
companion object {
private const val DELAY_MIN = 2000L
fun mapDelay(value: Float): Long {
val delay = (value * 1000L).roundToLong()
return if (delay < DELAY_MIN) 0L else delay
}
}
}

View File

@@ -11,6 +11,7 @@ import androidx.fragment.app.FragmentManager
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.flowWithLifecycle
import com.google.android.material.button.MaterialButtonToggleGroup
import com.google.android.material.slider.Slider
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import org.koitharu.kotatsu.R
@@ -28,7 +29,8 @@ class ReaderConfigBottomSheet :
BaseBottomSheet<SheetReaderConfigBinding>(),
ActivityResultCallback<Uri?>,
View.OnClickListener,
MaterialButtonToggleGroup.OnButtonCheckedListener {
MaterialButtonToggleGroup.OnButtonCheckedListener,
Slider.OnSliderTouchListener {
private val viewModel by activityViewModels<ReaderViewModel>()
private val savePageRequest = registerForActivityResult(PageSaveContract(), this)
@@ -57,6 +59,12 @@ class ReaderConfigBottomSheet :
binding.buttonSavePage.setOnClickListener(this)
binding.buttonScreenRotate.setOnClickListener(this)
binding.buttonSettings.setOnClickListener(this)
binding.sliderTimer.addOnSliderTouchListener(this)
binding.sliderTimer.setLabelFormatter(PageSwitchTimer.DelayLabelFormatter(view.resources))
findCallback()?.run {
binding.sliderTimer.value = pageSwitchDelay
}
}
override fun onClick(v: View) {
@@ -92,6 +100,12 @@ class ReaderConfigBottomSheet :
mode = newMode
}
override fun onStartTrackingTouch(slider: Slider) = Unit
override fun onStopTrackingTouch(slider: Slider) {
findCallback()?.pageSwitchDelay = slider.value
}
override fun onActivityResult(uri: Uri?) {
viewModel.onActivityResult(uri)
dismissAllowingStateLoss()
@@ -113,6 +127,8 @@ class ReaderConfigBottomSheet :
interface Callback {
var pageSwitchDelay: Float
fun onReaderModeChanged(mode: ReaderMode)
}

View 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="M12,20A7,7 0 0,1 5,13A7,7 0 0,1 12,6A7,7 0 0,1 19,13A7,7 0 0,1 12,20M19.03,7.39L20.45,5.97C20,5.46 19.55,5 19.04,4.56L17.62,6C16.07,4.74 14.12,4 12,4A9,9 0 0,0 3,13A9,9 0 0,0 12,22C17,22 21,17.97 21,13C21,10.88 20.26,8.93 19.03,7.39M11,14H13V8H11M15,1H9V3H15V1Z" />
</vector>

View File

@@ -27,24 +27,24 @@
android:id="@+id/button_save_page"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:drawableStart="@drawable/ic_save"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/save_page"
android:textAppearance="?attr/textAppearanceButton" />
android:textAppearance="?attr/textAppearanceButton"
app:drawableStartCompat="@drawable/ic_save" />
<org.koitharu.kotatsu.base.ui.widgets.ListItemTextView
android:id="@+id/button_screen_rotate"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:drawableStart="@drawable/ic_screen_rotation"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/rotate_screen"
android:textAppearance="?attr/textAppearanceButton"
android:visibility="gone"
app:drawableStartCompat="@drawable/ic_screen_rotation"
tools:visibility="visible" />
<TextView
@@ -62,7 +62,9 @@
android:layout_marginHorizontal="@dimen/margin_normal"
android:layout_marginTop="@dimen/margin_small"
android:baselineAligned="false"
android:orientation="horizontal">
android:orientation="horizontal"
app:selectionRequired="true"
app:singleSelection="true">
<com.google.android.material.button.MaterialButton
android:id="@+id/button_standard"
@@ -101,17 +103,46 @@
android:text="@string/reader_mode_hint"
android:textAppearance="?attr/textAppearanceBodySmall" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_normal"
android:baselineAligned="false"
android:gravity="center_vertical"
android:minHeight="?android:listPreferredItemHeightSmall"
android:orientation="horizontal"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:text="@string/automatic_scroll"
android:textAppearance="?attr/textAppearanceButton"
app:drawableStartCompat="@drawable/ic_timer" />
<com.google.android.material.slider.Slider
android:id="@+id/slider_timer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/margin_normal"
android:valueFrom="0"
android:valueTo="20"
app:labelBehavior="floating" />
</LinearLayout>
<org.koitharu.kotatsu.base.ui.widgets.ListItemTextView
android:id="@+id/button_settings"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:layout_marginTop="@dimen/margin_normal"
android:drawableStart="@drawable/ic_settings"
android:drawablePadding="?android:listPreferredItemPaddingStart"
android:paddingStart="?android:listPreferredItemPaddingStart"
android:paddingEnd="?android:listPreferredItemPaddingEnd"
android:text="@string/settings"
android:textAppearance="?attr/textAppearanceButton" />
android:textAppearance="?attr/textAppearanceButton"
app:drawableStartCompat="@drawable/ic_settings" />
</LinearLayout>

View File

@@ -361,4 +361,7 @@
<string name="incognito_mode">Incognito mode</string>
<string name="app_update_available_s">Application update available: %s</string>
<string name="no_chapters">No chapters</string>
<string name="automatic_scroll">Automatic scroll</string>
<string name="off_short">Off</string>
<string name="seconds_pattern">%ss</string>
</resources>