Преглед изворни кода

fix: 「护眼跟随深色模式」改为全局判定,不再回写总开关

此前 autoNight 是靠 ReadBookRouteScreen 的 LaunchedEffect 在进阅读页时
调 syncEnabledForNight,把 eyeProtectionEnabled 写成"当前是否深色"。两个后果:
- 只在阅读页触发,别的界面不跟随,语义不统一;
- 它改的是持久化的总开关,所以浅色下进一次阅读页,就把用户在外观/阅读菜单里
  打开的护眼给关掉了,返回书架再进来还是关的。

改为把 autoNight 并入 isActiveAt 的判定:开启跟随深色时由当前深浅色决定、
忽略总开关,否则看总开关;定时时段仍在其上叠加。判定在 BaseComposeActivity
统一求值,因此全局一致,且全程不落盘、不改用户设置。

随之删除 syncEnabledForNight、SyncEyeProtectionForTheme intent 及阅读页那条
LaunchedEffect。
5151561 пре 3 месеци
родитељ
комит
854f91ad7b

+ 2 - 0
app/src/main/java/io/legado/app/base/BaseComposeActivity.kt

@@ -84,6 +84,8 @@ abstract class BaseComposeActivity(
                     .collectAsStateWithLifecycle(initialThemeSettings)
                 val eyeProtectionActive = rememberEyeProtectionActive(
                     enabled = themeSettings.eyeProtectionEnabled,
+                    autoNight = themeSettings.eyeProtectionAutoNight,
+                    isDark = uiConfiguration.isDarkTheme,
                     schedule = themeSettings.eyeProtectionSchedule,
                     startTime = themeSettings.eyeProtectionStartTime,
                     endTime = themeSettings.eyeProtectionEndTime,

+ 19 - 10
app/src/main/java/io/legado/app/ui/book/read/EyeProtection.kt

@@ -49,24 +49,23 @@ object EyeProtection {
         )
     }
 
-    fun syncEnabledForNight(isNight: Boolean, autoNight: Boolean): Boolean? =
-        if (autoNight) isNight else null
-
     /**
-     * 计算护眼在给定时刻是否应生效。
-     * - 总开关关闭 → 永远不生效;
-     * - 未启用定时 → 只要总开关开就生效;
-     * - 启用定时但时间无法解析 → 退化为仅按总开关生效;
+     * 计算护眼在给定时刻是否应生效。全 App 共用这一处判定,不落盘、不回写总开关。
+     * - 开启「跟随深色模式」→ 由当前深浅色决定,忽略总开关;否则看总开关;
+     * - 未启用定时 → 上一条为真即生效;
+     * - 启用定时但时间无法解析 → 退化为不看时段;
      * - 启用定时 → 仅在 [startTime, endTime) 时段内生效(支持跨零点,如 22:00–07:00)。
      */
     fun isActiveAt(
         enabled: Boolean,
+        autoNight: Boolean,
+        isDark: Boolean,
         schedule: Boolean,
         startTime: String,
         endTime: String,
         now: LocalTime,
     ): Boolean {
-        if (!enabled) return false
+        if (!(if (autoNight) isDark else enabled)) return false
         if (!schedule) return true
         val start = parseTime(startTime) ?: return true
         val end = parseTime(endTime) ?: return true
@@ -116,11 +115,13 @@ fun Modifier.eyeProtectionColorFilter(
 @Composable
 fun rememberEyeProtectionActive(
     enabled: Boolean,
+    autoNight: Boolean,
+    isDark: Boolean,
     schedule: Boolean,
     startTime: String,
     endTime: String,
 ): Boolean {
-    if (!enabled) return false
+    if (!(if (autoNight) isDark else enabled)) return false
     if (!schedule) return true
     var now by remember { mutableStateOf(LocalTime.now()) }
     LaunchedEffect(startTime, endTime) {
@@ -129,5 +130,13 @@ fun rememberEyeProtectionActive(
             delay(60_000L)
         }
     }
-    return EyeProtection.isActiveAt(enabled, schedule, startTime, endTime, now)
+    return EyeProtection.isActiveAt(
+        enabled = enabled,
+        autoNight = autoNight,
+        isDark = isDark,
+        schedule = schedule,
+        startTime = startTime,
+        endTime = endTime,
+        now = now,
+    )
 }

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

@@ -680,7 +680,6 @@ sealed interface ReadBookIntent {
     data class EyeProtectionScheduleChanged(val value: Boolean) : ReadBookIntent
     data class EyeProtectionStartTimeChanged(val value: String) : ReadBookIntent
     data class EyeProtectionEndTimeChanged(val value: String) : ReadBookIntent
-    data class SyncEyeProtectionForTheme(val isNight: Boolean) : ReadBookIntent
 
     // Default font picker (needs Activity for AlertDialog)
     // Text action menu (moved from Activity)

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

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

+ 0 - 8
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -1599,14 +1599,6 @@ class ReadBookViewModel(
             is ReadBookIntent.EyeProtectionEndTimeChanged -> updateEyeProtection {
                 it.copy(eyeProtectionEndTime = intent.value)
             }
-            is ReadBookIntent.SyncEyeProtectionForTheme -> {
-                EyeProtection.syncEnabledForNight(
-                    isNight = intent.isNight,
-                    autoNight = _uiState.value.eyeProtection.autoNight,
-                )?.let { enabled ->
-                    updateEyeProtection { it.copy(eyeProtectionEnabled = enabled) }
-                }
-            }
         }
     }
 

+ 21 - 10
app/src/test/java/io/legado/app/ui/book/read/EyeProtectionTest.kt

@@ -1,9 +1,7 @@
 package io.legado.app.ui.book.read
 
 import org.junit.Assert.assertArrayEquals
-import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
-import org.junit.Assert.assertNull
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import java.time.LocalTime
@@ -38,21 +36,34 @@ class EyeProtectionTest {
         )
     }
 
-    @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))
-    }
-
     private fun activeAt(
         hour: Int,
         minute: Int = 0,
         enabled: Boolean = true,
+        autoNight: Boolean = false,
+        isDark: Boolean = false,
         schedule: Boolean = true,
         start: String = "22:00",
         end: String = "07:00",
-    ) = EyeProtection.isActiveAt(enabled, schedule, start, end, LocalTime.of(hour, minute))
+    ) = EyeProtection.isActiveAt(
+        enabled, autoNight, isDark, schedule, start, end, LocalTime.of(hour, minute)
+    )
+
+    @Test
+    fun `跟随深色模式时由深浅色决定并忽略总开关`() {
+        // 浅色下即使总开关是开的也不生效
+        assertFalse(activeAt(3, enabled = true, autoNight = true, isDark = false, schedule = false))
+        // 深色下即使总开关是关的也生效
+        assertTrue(activeAt(3, enabled = false, autoNight = true, isDark = true, schedule = false))
+        // 未开跟随深色时,深浅色不参与判定
+        assertTrue(activeAt(3, enabled = true, autoNight = false, isDark = false, schedule = false))
+    }
+
+    @Test
+    fun `跟随深色模式仍受定时时段约束`() {
+        assertTrue(activeAt(23, autoNight = true, isDark = true))
+        assertFalse(activeAt(12, autoNight = true, isDark = true))
+    }
 
     @Test
     fun `总开关关闭时永不生效`() {