From 2f86f25d5b24c2054a604802dc65b8bc3a99c7c0 Mon Sep 17 00:00:00 2001 From: Maddie Witman Date: Wed, 26 Jun 2024 16:04:28 -0400 Subject: [PATCH] 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 --- .../settings/screen/SettingsReaderScreen.kt | 66 +++++++++++++++++-- .../presentation/reader/DisplayRefreshHost.kt | 60 ++++++++++++++--- .../reader/settings/GeneralSettingsPage.kt | 50 ++++++++++++++ .../ui/reader/setting/ReaderPreferences.kt | 14 ++++ .../moko-resources/base/plurals.xml | 5 ++ .../moko-resources/base/strings.xml | 7 ++ 6 files changed, 189 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt index be685a780..7b5aa15f3 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsReaderScreen.kt @@ -14,6 +14,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentMapOf import kotlinx.collections.immutable.toImmutableMap import tachiyomi.i18n.MR +import tachiyomi.presentation.core.i18n.pluralStringResource import tachiyomi.presentation.core.i18n.stringResource import tachiyomi.presentation.core.util.collectAsState import uy.kohesive.injekt.Injekt @@ -61,12 +62,8 @@ object SettingsReaderScreen : SearchableSettings { pref = readerPref.pageTransitions(), 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), + getEInkGroup(readerPreferences = readerPref), getReadingGroup(readerPreferences = readerPref), getPagedGroup(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 private fun getReadingGroup(readerPreferences: ReaderPreferences): Preference.PreferenceGroup { return Preference.PreferenceGroup( diff --git a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt index 82d3cad0f..ecf26119d 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/DisplayRefreshHost.kt @@ -7,19 +7,42 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences 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 class DisplayRefreshHost { internal var currentDisplayRefresh by mutableStateOf(false) + private val readerPreferences = Injekt.get() + + 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() { - currentDisplayRefresh = true + if (timesCalled % flashInterval == 0) { + currentDisplayRefresh = true + } + timesCalled += 1 + } + + fun setInterval(interval: Int) { + flashInterval = interval + timesCalled = 0 } } @@ -29,18 +52,39 @@ fun DisplayRefreshHost( modifier: Modifier = Modifier, ) { 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(null) } + LaunchedEffect(currentDisplayRefresh) { - if (currentDisplayRefresh) { - delay(1.5.seconds) - hostState.currentDisplayRefresh = false + if (!currentDisplayRefresh) { + 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 + } + + LaunchedEffect(flashInterval) { + hostState.setInterval(flashInterval) } Canvas( modifier = modifier.fillMaxSize(), ) { - if (currentDisplayRefresh) { - drawRect(Color.Black) - } + currentColor?.let { drawRect(it) } } } diff --git a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt index 2f2832feb..0bb2da6cc 100644 --- a/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt +++ b/app/src/main/java/eu/kanade/presentation/reader/settings/GeneralSettingsPage.kt @@ -5,10 +5,13 @@ import androidx.compose.material3.FilterChip import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences import eu.kanade.tachiyomi.ui.reader.setting.ReaderSettingsScreenModel import tachiyomi.i18n.MR import tachiyomi.presentation.core.components.CheckboxItem 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.util.collectAsState @@ -19,9 +22,27 @@ private val themes = listOf( 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 internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { 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) { themes.map { (labelRes, value) -> FilterChip( @@ -73,4 +94,33 @@ internal fun ColumnScope.GeneralPage(screenModel: ReaderSettingsScreenModel) { label = stringResource(MR.strings.pref_flash_page), 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)) }, + ) + } + } + } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt index 63162788c..57def3c12 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/setting/ReaderPreferences.kt @@ -17,6 +17,12 @@ class ReaderPreferences( 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 showPageNumber() = preferenceStore.getBoolean("pref_show_page_number_key", true) @@ -133,6 +139,12 @@ class ReaderPreferences( // endregion + enum class FlashColor { + BLACK, + WHITE, + WHITE_BLACK + } + enum class TappingInvertMode( val titleRes: StringResource, val shouldInvertHorizontal: Boolean = false, @@ -155,6 +167,8 @@ class ReaderPreferences( const val WEBTOON_PADDING_MIN = 0 const val WEBTOON_PADDING_MAX = 25 + const val MILLI_CONVERSION = 100 + val TapZones = listOf( MR.strings.label_default, MR.strings.l_nav, diff --git a/i18n/src/commonMain/moko-resources/base/plurals.xml b/i18n/src/commonMain/moko-resources/base/plurals.xml index d9c958afc..e436a8cb5 100644 --- a/i18n/src/commonMain/moko-resources/base/plurals.xml +++ b/i18n/src/commonMain/moko-resources/base/plurals.xml @@ -40,6 +40,11 @@ %d days + + 1 page + %1$s pages + + Missing %1$s chapter diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index afbdf686c..a3e7c3afd 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -366,6 +366,13 @@ Animate page transitions Flash on page change Reduces ghosting on e-ink displays + Flash duration + %1$s ms + Flash every + Flash with + Black + White + White and Black Double tap animation speed Show page number Show reading mode