Added screen stabilization

This commit is contained in:
bring 2023-08-02 21:56:22 +03:00
parent 81cd765543
commit dc63129b50
6 changed files with 173 additions and 0 deletions

View file

@ -78,6 +78,11 @@ object SettingsReaderScreen : SearchableSettings {
private fun getDisplayGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val fullscreenPref = readerPreferences.fullscreen()
val fullscreen by fullscreenPref.collectAsState()
val stabilizationNumberFormat = remember { NumberFormat.getPercentInstance() }
val stabilizationStrengthPref = readerPreferences.stabilization()
val stabilizationStrength by stabilizationStrengthPref.collectAsState()
return Preference.PreferenceGroup(
title = stringResource(R.string.pref_category_display),
preferenceItems = listOf(
@ -101,6 +106,17 @@ object SettingsReaderScreen : SearchableSettings {
pref = fullscreenPref,
title = stringResource(R.string.pref_fullscreen),
),
Preference.PreferenceItem.SliderPreference(
value = stabilizationStrength,
title = stringResource(R.string.pref_stabilization_strength),
subtitle = stabilizationNumberFormat.format(stabilizationStrength / 100f),
min = ReaderPreferences.STABILIZATION_STRENGTH_MIN,
max = ReaderPreferences.STABILIZATION_STRENGTH_MAX,
onValueChanged = {
stabilizationStrengthPref.set(it)
true
},
),
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.cutoutShort(),
title = stringResource(R.string.pref_cutout_short),

View file

@ -10,6 +10,10 @@ import android.graphics.Color
import android.graphics.ColorMatrix
import android.graphics.ColorMatrixColorFilter
import android.graphics.Paint
import android.hardware.Sensor
import android.hardware.SensorEvent
import android.hardware.SensorEventListener
import android.hardware.SensorManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -73,6 +77,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
import eu.kanade.tachiyomi.ui.reader.viewer.ReaderProgressIndicator
import eu.kanade.tachiyomi.ui.reader.viewer.pager.R2LPagerViewer
import eu.kanade.tachiyomi.ui.webview.WebViewActivity
import eu.kanade.tachiyomi.util.stabilization.StabilizationProvider
import eu.kanade.tachiyomi.util.system.applySystemAnimatorScale
import eu.kanade.tachiyomi.util.system.hasDisplayCutout
import eu.kanade.tachiyomi.util.system.isNightMode
@ -138,6 +143,11 @@ class ReaderActivity : BaseActivity() {
private var loadingIndicator: ReaderProgressIndicator? = null
private lateinit var sensorManager: SensorManager
private lateinit var accelerometerEventListener: SensorEventListener
private lateinit var accelerometer: Sensor
private lateinit var stabilizationProvider: StabilizationProvider
var isScrollingThroughPages = false
private set
@ -175,6 +185,7 @@ class ReaderActivity : BaseActivity() {
config = ReaderConfig()
initializeMenu()
initializeStabilization()
// Finish when incognito mode is disabled
preferences.incognitoMode().changes()
@ -239,6 +250,7 @@ class ReaderActivity : BaseActivity() {
override fun onPause() {
viewModel.flushReadTimer()
super.onPause()
pauseStabilization()
}
/**
@ -249,6 +261,7 @@ class ReaderActivity : BaseActivity() {
super.onResume()
viewModel.restartReadTimer()
setMenuVisibility(viewModel.state.value.menuVisible, animate = false)
startStabilization()
}
/**
@ -478,6 +491,42 @@ class ReaderActivity : BaseActivity() {
setMenuVisibility(viewModel.state.value.menuVisible)
}
/**
* Initializes screen shake stabilization.
*/
private fun initializeStabilization() {
stabilizationProvider = StabilizationProvider(binding.readerContainer,
readerPreferences.stabilization())
sensorManager = getSystemService(SENSOR_SERVICE) as SensorManager
accelerometerEventListener = object : SensorEventListener {
override fun onSensorChanged(event: SensorEvent?) {
if (event != null) {
stabilizationProvider.stabilize(event)
}
}
override fun onAccuracyChanged(p0: Sensor?, p1: Int) { }
}
accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION)!!
}
/**
* Starts screen shake stabilization.
*/
private fun startStabilization() {
sensorManager.registerListener(accelerometerEventListener,
accelerometer, SensorManager.SENSOR_DELAY_FASTEST)
stabilizationProvider.resetStabilization()
}
/**
* Pauses screen shake stabilization.
*/
private fun pauseStabilization() {
sensorManager.unregisterListener(accelerometerEventListener)
}
private fun initBottomShortcuts() {
// Reading mode
with(binding.actionReadingMode) {

View file

@ -25,6 +25,8 @@ class ReaderPreferences(
fun fullscreen() = preferenceStore.getBoolean("fullscreen", true)
fun stabilization() = preferenceStore.getInt("stabilization", 40)
fun cutoutShort() = preferenceStore.getBoolean("cutout_short", true)
fun keepScreenOn() = preferenceStore.getBoolean("pref_keep_screen_on_key", true)
@ -146,6 +148,9 @@ class ReaderPreferences(
const val WEBTOON_PADDING_MIN = 0
const val WEBTOON_PADDING_MAX = 25
const val STABILIZATION_STRENGTH_MIN = 0
const val STABILIZATION_STRENGTH_MAX = 100
val TapZones = listOf(
R.string.label_default,
R.string.l_nav,

View file

@ -0,0 +1,101 @@
package eu.kanade.tachiyomi.util.stabilization
import android.hardware.SensorEvent
import android.view.View
import tachiyomi.core.preference.Preference
const val NANOSECONDS_TO_SECONDS = 1e-9f
const val BASE_VELOCITY_FRICTION = .2f
const val VELOCITY_SHIFT = .1f
const val BASE_POSITION_FRICTION = BASE_VELOCITY_FRICTION / 2
const val POSITION_SHIFT = VELOCITY_SHIFT / 2
const val MAX_ACCELERATION = 5.0f
const val ALPHA = 0.85f
/**
* This class represents stabilization provider.
* @param targetView view to be stabilized.
* @param strengthPreference strength of stabilization.
* Note that strength will be changed according to settings even after object creation.
*/
class StabilizationProvider(
private val targetView: View,
private val strengthPreference: Preference<Int>,
) {
private val position: MutableList<Float> = MutableList(2) { 0f }
private val velocity: MutableList<Float> = MutableList(2) { 0f }
private val acceleration: MutableList<Float> = MutableList(2) { 0f }
private var timestamp: Long = 0L
/**
* Stabilizes view according to provided event.
* @param sensorEvent acceleration sensor event.
*/
fun stabilize(sensorEvent: SensorEvent) {
val strength = strengthPreference.get()
if (strength != 0) {
if (timestamp != 0L) {
val deltaTime = (sensorEvent.timestamp - timestamp) * NANOSECONDS_TO_SECONDS
val velocityFriction = BASE_VELOCITY_FRICTION * ((100 - strength) / 100f) + VELOCITY_SHIFT
val positionFriction = BASE_POSITION_FRICTION * ((100 - strength) / 100f) + POSITION_SHIFT
for (i in 0..1) {
acceleration[i] = alphaFilter(
rangeValue(sensorEvent.values[i], -MAX_ACCELERATION, MAX_ACCELERATION),
acceleration[i],
)
velocity[i] += acceleration[i] * deltaTime - velocityFriction * velocity[i]
velocity[i] = fixNanOrInfinite(velocity[i])
position[i] += velocity[i] * deltaTime * 10000 - positionFriction * position[i]
}
targetView.translationX = -position[0]
targetView.translationY = position[1]
}
} else {
targetView.translationX = 0f
targetView.translationY = 0f
}
timestamp = sensorEvent.timestamp
}
/**
* Fully resets stabilization state.
*/
fun resetStabilization() {
position[0] = 0f
position[1] = 0f
velocity[0] = 0f
velocity[1] = 0f
acceleration[0] = 0f
acceleration[1] = 0f
timestamp = 0
targetView.translationX = 0f
targetView.translationY = 0f
}
private fun alphaFilter(current: Float, previous: Float): Float {
return previous + ALPHA * (current - previous)
}
private fun rangeValue(value: Float, min: Float, max: Float): Float {
return value.coerceAtLeast(min).coerceAtMost(max)
}
private fun fixNanOrInfinite(value: Float): Float {
return if (value.isFinite()) {
value
} else {
0f
}
}
}

View file

@ -131,6 +131,7 @@
<string name="pref_enable_acra">Отправлять отчёты об ошибках</string>
<string name="pref_page_transitions">Анимированные переходы страниц</string>
<string name="pref_fullscreen">Полноэкранный режим</string>
<string name="pref_stabilization_strength">Сила стабилизации</string>
<string name="pref_image_scale_type">Масштабирование</string>
<string name="pref_keep_screen_on">Не выключать экран</string>
<string name="pref_library_columns">Размер сетки</string>

View file

@ -319,6 +319,7 @@
<!-- Reader section -->
<string name="pref_fullscreen">Fullscreen</string>
<string name="pref_stabilization_strength">Stabilization strength</string>
<string name="pref_show_navigation_mode">Show tap zones overlay</string>
<string name="pref_show_navigation_mode_summary">Briefly show when reader is opened</string>
<string name="pref_dual_page_split">Split wide pages</string>