Added configuration options to e-ink page flashes (#625)

* Recommit for e-ink pref changes

* Fixed state holder for flash interval

* Detekt

* Refactor suggested by Antsy

* inverted currentDisplayRefresh check for early exit
This commit is contained in:
Maddie Witman 2024-06-26 16:04:28 -04:00 committed by GitHub
parent 239c38982c
commit 2f86f25d5b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 189 additions and 13 deletions

View File

@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableMap import kotlinx.collections.immutable.toImmutableMap
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
@ -61,12 +62,8 @@ object SettingsReaderScreen : SearchableSettings {
pref = readerPref.pageTransitions(), pref = readerPref.pageTransitions(),
title = stringResource(MR.strings.pref_page_transitions), title = stringResource(MR.strings.pref_page_transitions),
), ),
Preference.PreferenceItem.SwitchPreference(
pref = readerPref.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
getDisplayGroup(readerPreferences = readerPref), getDisplayGroup(readerPreferences = readerPref),
getEInkGroup(readerPreferences = readerPref),
getReadingGroup(readerPreferences = readerPref), getReadingGroup(readerPreferences = readerPref),
getPagedGroup(readerPreferences = readerPref), getPagedGroup(readerPreferences = readerPref),
getWebtoonGroup(readerPreferences = readerPref), getWebtoonGroup(readerPreferences = readerPref),
@ -122,6 +119,65 @@ object SettingsReaderScreen : SearchableSettings {
) )
} }
@Composable
private fun getEInkGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
val flashPageState by readerPreferences.flashOnPageChange().collectAsState()
val flashMillisPref = readerPreferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = readerPreferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = readerPreferences.flashColor()
return Preference.PreferenceGroup(
title = "E-Ink",
preferenceItems = persistentListOf(
Preference.PreferenceItem.SwitchPreference(
pref = readerPreferences.flashOnPageChange(),
title = stringResource(MR.strings.pref_flash_page),
subtitle = stringResource(MR.strings.pref_flash_page_summ),
),
Preference.PreferenceItem.SliderPreference(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
min = 1,
max = 15,
title = stringResource(MR.strings.pref_flash_duration),
subtitle = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onValueChanged = {
flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.SliderPreference(
value = flashInterval,
min = 1,
max = 10,
title = stringResource(MR.strings.pref_flash_page_interval),
subtitle = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onValueChanged = {
flashIntervalPref.set(it)
true
},
enabled = flashPageState,
),
Preference.PreferenceItem.ListPreference(
pref = flashColorPref,
title = stringResource(MR.strings.pref_flash_with),
entries = persistentMapOf(
ReaderPreferences.FlashColor.BLACK to stringResource(MR.strings.pref_flash_style_black),
ReaderPreferences.FlashColor.WHITE to stringResource(MR.strings.pref_flash_style_white),
ReaderPreferences.FlashColor.WHITE_BLACK
to stringResource(MR.strings.pref_flash_style_white_black),
),
enabled = flashPageState,
),
),
)
}
@Composable @Composable
private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup {
return Preference.PreferenceGroup( return Preference.PreferenceGroup(

View File

@ -7,20 +7,43 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlin.time.Duration.Companion.seconds import tachiyomi.presentation.core.util.collectAsState
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import kotlin.time.Duration.Companion.milliseconds
@Stable @Stable
class DisplayRefreshHost { class DisplayRefreshHost {
internal var currentDisplayRefresh by mutableStateOf(false) internal var currentDisplayRefresh by mutableStateOf(false)
private val readerPreferences = Injekt.get<ReaderPreferences>()
internal val flashMillis = readerPreferences.flashDurationMillis()
internal val flashMode = readerPreferences.flashColor()
internal val flashIntervalPref = readerPreferences.flashPageInterval()
// Internal State for Flash
private var flashInterval = flashIntervalPref.get()
private var timesCalled = 0
fun flash() { fun flash() {
if (timesCalled % flashInterval == 0) {
currentDisplayRefresh = true currentDisplayRefresh = true
} }
timesCalled += 1
}
fun setInterval(interval: Int) {
flashInterval = interval
timesCalled = 0
}
} }
@Composable @Composable
@ -29,18 +52,39 @@ fun DisplayRefreshHost(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val currentDisplayRefresh = hostState.currentDisplayRefresh val currentDisplayRefresh = hostState.currentDisplayRefresh
val refreshDuration by hostState.flashMillis.collectAsState()
val flashMode by hostState.flashMode.collectAsState()
val flashInterval by hostState.flashIntervalPref.collectAsState()
var currentColor by remember { mutableStateOf<Color?>(null) }
LaunchedEffect(currentDisplayRefresh) { LaunchedEffect(currentDisplayRefresh) {
if (currentDisplayRefresh) { if (!currentDisplayRefresh) {
delay(1.5.seconds) currentColor = null
return@LaunchedEffect
}
val refreshDurationHalf = refreshDuration.milliseconds / 2
currentColor = if (flashMode == ReaderPreferences.FlashColor.BLACK) {
Color.Black
} else {
Color.White
}
delay(refreshDurationHalf)
if (flashMode == ReaderPreferences.FlashColor.WHITE_BLACK) {
currentColor = Color.Black
}
delay(refreshDurationHalf)
hostState.currentDisplayRefresh = false hostState.currentDisplayRefresh = false
} }
LaunchedEffect(flashInterval) {
hostState.setInterval(flashInterval)
} }
Canvas( Canvas(
modifier = modifier.fillMaxSize(), modifier = modifier.fillMaxSize(),
) { ) {
if (currentDisplayRefresh) { currentColor?.let { drawRect(it) }
drawRect(Color.Black)
}
} }
} }

View File

@ -5,10 +5,13 @@ import androidx.compose.material3.FilterChip
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel
import tachiyomi.i18n.MR import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.CheckboxItem import tachiyomi.presentation.core.components.CheckboxItem
import tachiyomi.presentation.core.components.SettingsChipRow import tachiyomi.presentation.core.components.SettingsChipRow
import tachiyomi.presentation.core.components.SliderItem
import tachiyomi.presentation.core.i18n.pluralStringResource
import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.i18n.stringResource
import tachiyomi.presentation.core.util.collectAsState import tachiyomi.presentation.core.util.collectAsState
@ -19,9 +22,27 @@ private val themes = listOf(
MR.strings.automatic_background to 3, MR.strings.automatic_background to 3,
) )
private val flashColors = listOf(
MR.strings.pref_flash_style_black to ReaderPreferences.FlashColor.BLACK,
MR.strings.pref_flash_style_white to ReaderPreferences.FlashColor.WHITE,
MR.strings.pref_flash_style_white_black to ReaderPreferences.FlashColor.WHITE_BLACK,
)
@Composable @Composable
internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
val readerTheme by screenModel.preferences.readerTheme().collectAsState() val readerTheme by screenModel.preferences.readerTheme().collectAsState()
val flashPageState by screenModel.preferences.flashOnPageChange().collectAsState()
val flashMillisPref = screenModel.preferences.flashDurationMillis()
val flashMillis by flashMillisPref.collectAsState()
val flashIntervalPref = screenModel.preferences.flashPageInterval()
val flashInterval by flashIntervalPref.collectAsState()
val flashColorPref = screenModel.preferences.flashColor()
val flashColor by flashColorPref.collectAsState()
SettingsChipRow(MR.strings.pref_reader_theme) { SettingsChipRow(MR.strings.pref_reader_theme) {
themes.map { (labelRes, value) -> themes.map { (labelRes, value) ->
FilterChip( FilterChip(
@ -73,4 +94,33 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) {
label = stringResource(MR.strings.pref_flash_page), label = stringResource(MR.strings.pref_flash_page),
pref = screenModel.preferences.flashOnPageChange(), pref = screenModel.preferences.flashOnPageChange(),
) )
if (flashPageState) {
SliderItem(
value = flashMillis / ReaderPreferences.MILLI_CONVERSION,
label = stringResource(MR.strings.pref_flash_duration),
valueText = stringResource(MR.strings.pref_flash_duration_summary, flashMillis),
onChange = { flashMillisPref.set(it * ReaderPreferences.MILLI_CONVERSION) },
min = 1,
max = 15,
)
SliderItem(
value = flashInterval,
label = stringResource(MR.strings.pref_flash_page_interval),
valueText = pluralStringResource(MR.plurals.pref_pages, flashInterval, flashInterval),
onChange = {
flashIntervalPref.set(it)
},
min = 1,
max = 10,
)
SettingsChipRow(MR.strings.pref_flash_with) {
flashColors.map { (labelRes, value) ->
FilterChip(
selected = flashColor == value,
onClick = { flashColorPref.set(value) },
label = { Text(stringResource(labelRes)) },
)
}
}
}
} }

View File

@ -17,6 +17,12 @@ class ReaderPreferences(
fun flashOnPageChange() = preferenceStore.getBoolean("pref_reader_flash", false) fun flashOnPageChange() = preferenceStore.getBoolean("pref_reader_flash", false)
fun flashDurationMillis() = preferenceStore.getInt("pref_reader_flash_duration", MILLI_CONVERSION)
fun flashPageInterval() = preferenceStore.getInt("pref_reader_flash_interval", 1)
fun flashColor() = preferenceStore.getEnum("pref_reader_flash_mode", FlashColor.BLACK)
fun doubleTapAnimSpeed() = preferenceStore.getInt("pref_double_tap_anim_speed", 500) fun doubleTapAnimSpeed() = preferenceStore.getInt("pref_double_tap_anim_speed", 500)
fun showPageNumber() = preferenceStore.getBoolean("pref_show_page_number_key", true) fun showPageNumber() = preferenceStore.getBoolean("pref_show_page_number_key", true)
@ -133,6 +139,12 @@ class ReaderPreferences(
// endregion // endregion
enum class FlashColor {
BLACK,
WHITE,
WHITE_BLACK
}
enum class TappingInvertMode( enum class TappingInvertMode(
val titleRes: StringResource, val titleRes: StringResource,
val shouldInvertHorizontal: Boolean = false, val shouldInvertHorizontal: Boolean = false,
@ -155,6 +167,8 @@ class ReaderPreferences(
const val WEBTOON_PADDING_MIN = 0 const val WEBTOON_PADDING_MIN = 0
const val WEBTOON_PADDING_MAX = 25 const val WEBTOON_PADDING_MAX = 25
const val MILLI_CONVERSION = 100
val TapZones = listOf( val TapZones = listOf(
MR.strings.label_default, MR.strings.label_default,
MR.strings.l_nav, MR.strings.l_nav,

View File

@ -40,6 +40,11 @@
<item quantity="other">%d days</item> <item quantity="other">%d days</item>
</plurals> </plurals>
<plurals name="pref_pages">
<item quantity="one">1 page</item>
<item quantity="other">%1$s pages</item>
</plurals>
<!-- Manga info --> <!-- Manga info -->
<plurals name="missing_chapters"> <plurals name="missing_chapters">
<item quantity="one">Missing %1$s chapter</item> <item quantity="one">Missing %1$s chapter</item>

View File

@ -366,6 +366,13 @@
<string name="pref_page_transitions">Animate page transitions</string> <string name="pref_page_transitions">Animate page transitions</string>
<string name="pref_flash_page">Flash on page change</string> <string name="pref_flash_page">Flash on page change</string>
<string name="pref_flash_page_summ">Reduces ghosting on e-ink displays</string> <string name="pref_flash_page_summ">Reduces ghosting on e-ink displays</string>
<string name="pref_flash_duration">Flash duration</string>
<string name="pref_flash_duration_summary">%1$s ms</string>
<string name="pref_flash_page_interval">Flash every</string>
<string name="pref_flash_with">Flash with</string>
<string name="pref_flash_style_black">Black</string>
<string name="pref_flash_style_white">White</string>
<string name="pref_flash_style_white_black">White and Black</string>
<string name="pref_double_tap_anim_speed">Double tap animation speed</string> <string name="pref_double_tap_anim_speed">Double tap animation speed</string>
<string name="pref_show_page_number">Show page number</string> <string name="pref_show_page_number">Show page number</string>
<string name="pref_show_reading_mode">Show reading mode</string> <string name="pref_show_reading_mode">Show reading mode</string>