Autoscroll in reader #204
This commit is contained in:
@@ -35,6 +35,7 @@ import org.koitharu.kotatsu.databinding.ActivityReaderBinding
|
|||||||
import org.koitharu.kotatsu.parsers.model.Manga
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
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.config.ReaderConfigBottomSheet
|
||||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
|
||||||
import org.koitharu.kotatsu.reader.ui.thumbnails.OnPageSelectListener
|
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 touchHelper: GridTouchHelper
|
||||||
private lateinit var controlDelegate: ReaderControlDelegate
|
private lateinit var controlDelegate: ReaderControlDelegate
|
||||||
private var gestureInsets: Insets = Insets.NONE
|
private var gestureInsets: Insets = Insets.NONE
|
||||||
@@ -77,6 +85,7 @@ class ReaderActivity :
|
|||||||
readerManager = ReaderManager(supportFragmentManager, R.id.container)
|
readerManager = ReaderManager(supportFragmentManager, R.id.container)
|
||||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||||
touchHelper = GridTouchHelper(this, this)
|
touchHelper = GridTouchHelper(this, this)
|
||||||
|
pageSwitchTimer = PageSwitchTimer(this, this)
|
||||||
controlDelegate = ReaderControlDelegate(lifecycleScope, settings, this)
|
controlDelegate = ReaderControlDelegate(lifecycleScope, settings, this)
|
||||||
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
|
binding.toolbarBottom.setOnMenuItemClickListener(::onOptionsItemSelected)
|
||||||
binding.slider.setLabelFormatter(PageLabelFormatter())
|
binding.slider.setLabelFormatter(PageLabelFormatter())
|
||||||
@@ -100,6 +109,11 @@ class ReaderActivity :
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onUserInteraction() {
|
||||||
|
super.onUserInteraction()
|
||||||
|
pageSwitchTimer.onUserInteraction()
|
||||||
|
}
|
||||||
|
|
||||||
private fun onInitReader(mode: ReaderMode) {
|
private fun onInitReader(mode: ReaderMode) {
|
||||||
if (readerManager.currentMode != mode) {
|
if (readerManager.currentMode != mode) {
|
||||||
readerManager.replace(mode)
|
readerManager.replace(mode)
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import org.koitharu.kotatsu.utils.GridTouchHelper
|
|||||||
class ReaderControlDelegate(
|
class ReaderControlDelegate(
|
||||||
scope: LifecycleCoroutineScope,
|
scope: LifecycleCoroutineScope,
|
||||||
settings: AppSettings,
|
settings: AppSettings,
|
||||||
private val listener: OnInteractionListener
|
private val listener: OnInteractionListener,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private var isTapSwitchEnabled: Boolean = true
|
private var isTapSwitchEnabled: Boolean = true
|
||||||
@@ -72,14 +72,16 @@ class ReaderControlDelegate(
|
|||||||
KeyEvent.KEYCODE_PAGE_DOWN,
|
KeyEvent.KEYCODE_PAGE_DOWN,
|
||||||
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
|
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
|
||||||
KeyEvent.KEYCODE_DPAD_DOWN,
|
KeyEvent.KEYCODE_DPAD_DOWN,
|
||||||
KeyEvent.KEYCODE_DPAD_RIGHT -> {
|
KeyEvent.KEYCODE_DPAD_RIGHT,
|
||||||
|
-> {
|
||||||
listener.switchPageBy(1)
|
listener.switchPageBy(1)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
KeyEvent.KEYCODE_PAGE_UP,
|
KeyEvent.KEYCODE_PAGE_UP,
|
||||||
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
|
KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
|
||||||
KeyEvent.KEYCODE_DPAD_UP,
|
KeyEvent.KEYCODE_DPAD_UP,
|
||||||
KeyEvent.KEYCODE_DPAD_LEFT -> {
|
KeyEvent.KEYCODE_DPAD_LEFT,
|
||||||
|
-> {
|
||||||
listener.switchPageBy(-1)
|
listener.switchPageBy(-1)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import androidx.fragment.app.FragmentManager
|
|||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.flowWithLifecycle
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
import com.google.android.material.button.MaterialButtonToggleGroup
|
import com.google.android.material.button.MaterialButtonToggleGroup
|
||||||
|
import com.google.android.material.slider.Slider
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import org.koitharu.kotatsu.R
|
import org.koitharu.kotatsu.R
|
||||||
@@ -28,7 +29,8 @@ class ReaderConfigBottomSheet :
|
|||||||
BaseBottomSheet<SheetReaderConfigBinding>(),
|
BaseBottomSheet<SheetReaderConfigBinding>(),
|
||||||
ActivityResultCallback<Uri?>,
|
ActivityResultCallback<Uri?>,
|
||||||
View.OnClickListener,
|
View.OnClickListener,
|
||||||
MaterialButtonToggleGroup.OnButtonCheckedListener {
|
MaterialButtonToggleGroup.OnButtonCheckedListener,
|
||||||
|
Slider.OnSliderTouchListener {
|
||||||
|
|
||||||
private val viewModel by activityViewModels<ReaderViewModel>()
|
private val viewModel by activityViewModels<ReaderViewModel>()
|
||||||
private val savePageRequest = registerForActivityResult(PageSaveContract(), this)
|
private val savePageRequest = registerForActivityResult(PageSaveContract(), this)
|
||||||
@@ -57,6 +59,12 @@ class ReaderConfigBottomSheet :
|
|||||||
binding.buttonSavePage.setOnClickListener(this)
|
binding.buttonSavePage.setOnClickListener(this)
|
||||||
binding.buttonScreenRotate.setOnClickListener(this)
|
binding.buttonScreenRotate.setOnClickListener(this)
|
||||||
binding.buttonSettings.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) {
|
override fun onClick(v: View) {
|
||||||
@@ -92,6 +100,12 @@ class ReaderConfigBottomSheet :
|
|||||||
mode = newMode
|
mode = newMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onStartTrackingTouch(slider: Slider) = Unit
|
||||||
|
|
||||||
|
override fun onStopTrackingTouch(slider: Slider) {
|
||||||
|
findCallback()?.pageSwitchDelay = slider.value
|
||||||
|
}
|
||||||
|
|
||||||
override fun onActivityResult(uri: Uri?) {
|
override fun onActivityResult(uri: Uri?) {
|
||||||
viewModel.onActivityResult(uri)
|
viewModel.onActivityResult(uri)
|
||||||
dismissAllowingStateLoss()
|
dismissAllowingStateLoss()
|
||||||
@@ -113,6 +127,8 @@ class ReaderConfigBottomSheet :
|
|||||||
|
|
||||||
interface Callback {
|
interface Callback {
|
||||||
|
|
||||||
|
var pageSwitchDelay: Float
|
||||||
|
|
||||||
fun onReaderModeChanged(mode: ReaderMode)
|
fun onReaderModeChanged(mode: ReaderMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
12
app/src/main/res/drawable/ic_timer.xml
Normal file
12
app/src/main/res/drawable/ic_timer.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="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>
|
||||||
@@ -27,24 +27,24 @@
|
|||||||
android:id="@+id/button_save_page"
|
android:id="@+id/button_save_page"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
android:drawableStart="@drawable/ic_save"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
android:text="@string/save_page"
|
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
|
<org.koitharu.kotatsu.base.ui.widgets.ListItemTextView
|
||||||
android:id="@+id/button_screen_rotate"
|
android:id="@+id/button_screen_rotate"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
android:drawableStart="@drawable/ic_screen_rotation"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
android:text="@string/rotate_screen"
|
android:text="@string/rotate_screen"
|
||||||
android:textAppearance="?attr/textAppearanceButton"
|
android:textAppearance="?attr/textAppearanceButton"
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
app:drawableStartCompat="@drawable/ic_screen_rotation"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
@@ -62,7 +62,9 @@
|
|||||||
android:layout_marginHorizontal="@dimen/margin_normal"
|
android:layout_marginHorizontal="@dimen/margin_normal"
|
||||||
android:layout_marginTop="@dimen/margin_small"
|
android:layout_marginTop="@dimen/margin_small"
|
||||||
android:baselineAligned="false"
|
android:baselineAligned="false"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal"
|
||||||
|
app:selectionRequired="true"
|
||||||
|
app:singleSelection="true">
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/button_standard"
|
android:id="@+id/button_standard"
|
||||||
@@ -101,17 +103,46 @@
|
|||||||
android:text="@string/reader_mode_hint"
|
android:text="@string/reader_mode_hint"
|
||||||
android:textAppearance="?attr/textAppearanceBodySmall" />
|
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
|
<org.koitharu.kotatsu.base.ui.widgets.ListItemTextView
|
||||||
android:id="@+id/button_settings"
|
android:id="@+id/button_settings"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="?android:listPreferredItemHeightSmall"
|
android:layout_height="?android:listPreferredItemHeightSmall"
|
||||||
android:layout_marginTop="@dimen/margin_normal"
|
|
||||||
android:drawableStart="@drawable/ic_settings"
|
|
||||||
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
android:drawablePadding="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingStart="?android:listPreferredItemPaddingStart"
|
android:paddingStart="?android:listPreferredItemPaddingStart"
|
||||||
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
android:paddingEnd="?android:listPreferredItemPaddingEnd"
|
||||||
android:text="@string/settings"
|
android:text="@string/settings"
|
||||||
android:textAppearance="?attr/textAppearanceButton" />
|
android:textAppearance="?attr/textAppearanceButton"
|
||||||
|
app:drawableStartCompat="@drawable/ic_settings" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|||||||
@@ -361,4 +361,7 @@
|
|||||||
<string name="incognito_mode">Incognito mode</string>
|
<string name="incognito_mode">Incognito mode</string>
|
||||||
<string name="app_update_available_s">Application update available: %s</string>
|
<string name="app_update_available_s">Application update available: %s</string>
|
||||||
<string name="no_chapters">No chapters</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>
|
</resources>
|
||||||
|
|||||||
Reference in New Issue
Block a user