mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-11-04 16:18:55 +01:00 
			
		
		
		
	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:
		@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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<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() {
 | 
			
		||||
        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<Color?>(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) }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)) },
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,11 @@
 | 
			
		||||
        <item quantity="other">%d days</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
 | 
			
		||||
    <plurals name="pref_pages">
 | 
			
		||||
        <item quantity="one">1 page</item>
 | 
			
		||||
        <item quantity="other">%1$s pages</item>
 | 
			
		||||
    </plurals>
 | 
			
		||||
 | 
			
		||||
    <!-- Manga info -->
 | 
			
		||||
    <plurals name="missing_chapters">
 | 
			
		||||
        <item quantity="one">Missing %1$s chapter</item>
 | 
			
		||||
 
 | 
			
		||||
@@ -366,6 +366,13 @@
 | 
			
		||||
    <string name="pref_page_transitions">Animate page transitions</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_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_show_page_number">Show page number</string>
 | 
			
		||||
    <string name="pref_show_reading_mode">Show reading mode</string>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user