Procházet zdrojové kódy

fix: 完善阅读护眼模式交互

HapeLee před 3 měsíci
rodič
revize
7ceeabfb15

+ 9 - 2
app/src/main/java/io/legado/app/ui/book/read/EyeProtection.kt

@@ -4,7 +4,6 @@ import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.ColorFilter
-import androidx.compose.ui.graphics.CompositingStrategy
 import androidx.compose.ui.graphics.graphicsLayer
 import androidx.compose.ui.graphics.ColorMatrix as ComposeColorMatrix
 
@@ -29,6 +28,8 @@ object EyeProtection {
     }
 
     fun matrixValuesForIntensity(intensity: Int): FloatArray {
+        if (intensity <= MIN_INTENSITY) return IDENTITY_MATRIX.copyOf()
+
         val cct = temperatureForIntensity(intensity).toFloat()
         val cctSquared = cct * cct
         val red = cctSquared * coefficients[0] + cct * coefficients[1] + coefficients[2]
@@ -44,6 +45,13 @@ object EyeProtection {
 
     fun syncEnabledForNight(isNight: Boolean, autoNight: Boolean): Boolean? =
         if (autoNight) isNight else null
+
+    private val IDENTITY_MATRIX = floatArrayOf(
+        1f, 0f, 0f, 0f, 0f,
+        0f, 1f, 0f, 0f, 0f,
+        0f, 0f, 1f, 0f, 0f,
+        0f, 0f, 0f, 1f, 0f,
+    )
 }
 
 @Composable
@@ -63,7 +71,6 @@ fun Modifier.eyeProtectionColorFilter(
     if (cachedColorFilter == null) return this
 
     return graphicsLayer {
-        compositingStrategy = CompositingStrategy.Offscreen
         colorFilter = cachedColorFilter
     }
 }

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt

@@ -664,6 +664,7 @@ sealed interface ReadBookIntent {
     data class EyeProtectionEnabledChanged(val value: Boolean) : ReadBookIntent
     data class EyeProtectionIntensityChanged(val value: Int) : ReadBookIntent
     data class EyeProtectionAutoNightChanged(val value: Boolean) : ReadBookIntent
+    data class SyncEyeProtectionForTheme(val isNight: Boolean) : ReadBookIntent
 
     // Default font picker (needs Activity for AlertDialog)
     // Text action menu (moved from Activity)

+ 4 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt

@@ -155,6 +155,10 @@ fun ReadBookRouteScreen(
         controller.onMenuVisibilityChanged(state.menuVisible)
     }
 
+    LaunchedEffect(isDarkTheme, readPreferences.eyeProtectionAutoNight) {
+        viewModel.onIntent(ReadBookIntent.SyncEyeProtectionForTheme(isDarkTheme))
+    }
+
     LaunchedEffect(viewModel, controller, lifecycleOwner) {
         lifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
             viewModel.readAloudProgress.collect { chapterStart ->

+ 10 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -1485,6 +1485,15 @@ class ReadBookViewModel(
                     }
                 }
             }
+            is ReadBookIntent.SyncEyeProtectionForTheme -> {
+                EyeProtection.syncEnabledForNight(
+                    isNight = intent.isNight,
+                    autoNight = _readPreferences.value.eyeProtectionAutoNight,
+                )?.takeIf { it != _readPreferences.value.eyeProtectionEnabled }
+                    ?.let { enabled ->
+                        updateEyeProtection(ReadSettingsUpdate.EyeProtectionEnabled(enabled))
+                    }
+            }
             // Text action menu
             is ReadBookIntent.TextActionAloud -> {
                 when (readAloudSettingsRepository.currentSettings.contentSelectSpeakMode) {
@@ -5919,7 +5928,7 @@ class ReadBookViewModel(
         viewModelScope.launch {
             appShellSettingsGateway.update(AppShellSettingsUpdate.ThemeMode(nextMode))
             EyeProtection.syncEnabledForNight(
-                isNight = nextMode == "1",
+                isNight = nextMode == "2",
                 autoNight = _readPreferences.value.eyeProtectionAutoNight,
             )?.let { enabled ->
                 readSettingsRepository.update(ReadSettingsUpdate.EyeProtectionEnabled(enabled))

+ 18 - 2
app/src/main/java/io/legado/app/ui/book/read/sheet/GlobalThemePage.kt

@@ -27,6 +27,7 @@ import androidx.compose.material.icons.filled.GridView
 import androidx.compose.material.icons.filled.LightMode
 import androidx.compose.material.icons.filled.SpaceBar
 import androidx.compose.material.icons.filled.TextFields
+import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.material3.Icon
 import androidx.compose.material3.PlainTooltip
 import androidx.compose.material3.TooltipAnchorPosition
@@ -55,6 +56,7 @@ import io.legado.app.help.config.ReadStyleResolver
 import io.legado.app.model.ReadBook
 import io.legado.app.ui.book.read.ConfigUpdate
 import io.legado.app.ui.book.read.ReadBookIntent
+import io.legado.app.ui.book.read.ReadBookSheet
 import io.legado.app.ui.book.read.ReadBookStyleConfig
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.fadingEdge
@@ -72,6 +74,7 @@ import io.legado.app.ui.widget.components.text.AppText
 @Composable
 fun GlobalThemePage(
     onToggleDayNight: () -> Unit,
+    eyeProtectionEnabled: Boolean,
     onOpenBgTextConfig: (Int) -> Unit,
     onOpenTextTitle: () -> Unit,
     onOpenPaddingConfig: () -> Unit,
@@ -86,6 +89,7 @@ fun GlobalThemePage(
     val pageAnim = styleConfig.pageAnim
     val styleSelect = styleConfig.styleSelect
     val shareLayout = styleConfig.shareLayout
+    val isNightTheme = LegadoTheme.isDark
 
     val configList = styleConfig.styleItems
 
@@ -153,7 +157,17 @@ fun GlobalThemePage(
                         color = LegadoTheme.colorScheme.onSurfaceVariant,
                     )
                 }
-                val isNightTheme = LegadoTheme.isDark
+                SmallTonalButton(
+                    onClick = { onIntent(ReadBookIntent.ToggleEyeProtection) },
+                    onLongClick = {
+                        onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.EyeProtection))
+                    },
+                    selected = eyeProtectionEnabled,
+                    icon = Icons.Default.Visibility,
+                    contentColor = LegadoTheme.colorScheme.onSurfaceVariant,
+                    containerColor = LegadoTheme.colorScheme.surfaceContainerHigh,
+                    contentDescription = stringResource(R.string.eye_protection),
+                )
                 SmallTonalButton(
                     onClick = onToggleDayNight,
                     icon = if (isNightTheme) Icons.Default.DarkMode else Icons.Default.LightMode,
@@ -252,6 +266,7 @@ fun GlobalThemePage(
                         StyleCard(
                             config = config,
                             isSelected = styleSelect == index,
+                            isNightTheme = isNightTheme,
                             onClick = {
                                 onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.StyleSelect(index)))
                                 onStyleSelect(index)
@@ -370,10 +385,11 @@ fun GlobalThemePage(
 fun StyleCard(
     config: ReadStyleItem,
     isSelected: Boolean,
+    isNightTheme: Boolean,
     onClick: () -> Unit,
     onLongClick: () -> Unit,
 ) {
-    val mode = ReadStyleResolver.currentMode()
+    val mode = ReadStyleResolver.currentMode(isNightTheme)
     val bgType = when (mode) {
         ReadStyleResolver.ReadStyleMode.Day -> config.bgType
         ReadStyleResolver.ReadStyleMode.Night -> config.bgTypeNight

+ 1 - 0
app/src/main/java/io/legado/app/ui/book/read/sheet/ReadStyleSheet.kt

@@ -94,6 +94,7 @@ fun ReadStyleContent(
                 when (page) {
                     0 -> GlobalThemePage(
                         onToggleDayNight = onToggleDayNight,
+                        eyeProtectionEnabled = preferences.eyeProtectionEnabled,
                         onOpenBgTextConfig = onOpenBgTextConfig,
                         onOpenTextTitle = onOpenTextTitle,
                         onOpenPaddingConfig = onOpenPaddingConfig,

+ 6 - 7
app/src/test/java/io/legado/app/data/repository/LabSettingsMappingTest.kt

@@ -19,7 +19,6 @@ class LabSettingsMappingTest {
         val samples = listOf(
             LabSettings(enabled = true),
             LabSettings(eInkDisplay = true),
-            LabSettings(eyeProtection = true),
         )
 
         samples.forEach { expected ->
@@ -32,11 +31,11 @@ class LabSettingsMappingTest {
         val current = LabSettings(enabled = true)
 
         val diff = current.diffPrefMap(
-            transform = { it.copy(eyeProtection = true) },
+            transform = { it.copy(eInkDisplay = true) },
             toPrefMap = LabSettings::toPrefMap,
         )
 
-        assertEquals(mapOf(PreferKey.labEyeProtection to true), diff)
+        assertEquals(mapOf(PreferKey.labEInkDisplay to true), diff)
         assertTrue(
             current.diffPrefMap(
                 transform = { it.copy() },
@@ -52,8 +51,8 @@ class LabSettingsMappingTest {
             transform = { it.copy(enabled = true) },
             toPrefMap = LabSettings::toPrefMap,
         )
-        val eyeProtectionDiff = current.diffPrefMap(
-            transform = { it.copy(eyeProtection = true) },
+        val eInkDisplayDiff = current.diffPrefMap(
+            transform = { it.copy(eInkDisplay = true) },
             toPrefMap = LabSettings::toPrefMap,
         )
         val core = PendingOverlayCore(
@@ -63,7 +62,7 @@ class LabSettingsMappingTest {
             persistAll = { error("不会执行落盘") },
         )
         val start = CountDownLatch(1)
-        val writers = listOf(enabledDiff, eyeProtectionDiff).map { diff ->
+        val writers = listOf(enabledDiff, eInkDisplayDiff).map { diff ->
             thread(start = true) {
                 start.await()
                 core.putAll(diff)
@@ -74,7 +73,7 @@ class LabSettingsMappingTest {
         writers.forEach(Thread::join)
 
         assertEquals(
-            current.copy(enabled = true, eyeProtection = true),
+            current.copy(enabled = true, eInkDisplay = true),
             core.preferencesFlow.value.toLabSettings(),
         )
     }

+ 44 - 0
app/src/test/java/io/legado/app/ui/book/read/EyeProtectionTest.kt

@@ -0,0 +1,44 @@
+package io.legado.app.ui.book.read
+
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Test
+
+class EyeProtectionTest {
+
+    @Test
+    fun `零强度不改变颜色`() {
+        assertArrayEquals(
+            floatArrayOf(
+                1f, 0f, 0f, 0f, 0f,
+                0f, 1f, 0f, 0f, 0f,
+                0f, 0f, 1f, 0f, 0f,
+                0f, 0f, 0f, 1f, 0f,
+            ),
+            EyeProtection.matrixValuesForIntensity(0),
+            0f,
+        )
+    }
+
+    @Test
+    fun `强度限制在有效范围`() {
+        assertArrayEquals(
+            EyeProtection.matrixValuesForIntensity(0),
+            EyeProtection.matrixValuesForIntensity(-1),
+            0f,
+        )
+        assertArrayEquals(
+            EyeProtection.matrixValuesForIntensity(100),
+            EyeProtection.matrixValuesForIntensity(101),
+            0f,
+        )
+    }
+
+    @Test
+    fun `自动模式跟随当前主题`() {
+        assertEquals(true, EyeProtection.syncEnabledForNight(isNight = true, autoNight = true))
+        assertEquals(false, EyeProtection.syncEnabledForNight(isNight = false, autoNight = true))
+        assertNull(EyeProtection.syncEnabledForNight(isNight = true, autoNight = false))
+    }
+}