Browse Source

refactor:护眼模式重构
- 移除旧的View实现,改为Compose适用的方案
- 外观里护眼模式的设置入口移到阅读设置
- 移除设置-实验室-护眼模式
- 阅读菜单添加可配置的护眼模式开关
- 添加了护眼模式跟随深色模式开启的开关

# Conflicts:
# app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

fansangg 3 months ago
parent
commit
ac4c8eb5df
39 changed files with 321 additions and 486 deletions
  1. 1 4
      app/src/main/java/io/legado/app/constant/PreferKey.kt
  2. 0 11
      app/src/main/java/io/legado/app/data/repository/FeatureSettingsRepositories.kt
  3. 18 0
      app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt
  4. 3 0
      app/src/main/java/io/legado/app/domain/gateway/ReadSettingsGateway.kt
  5. 0 4
      app/src/main/java/io/legado/app/domain/gateway/ThemeSettingsGateway.kt
  6. 0 1
      app/src/main/java/io/legado/app/domain/model/settings/LabSettings.kt
  7. 3 0
      app/src/main/java/io/legado/app/domain/model/settings/ReadSettings.kt
  8. 0 5
      app/src/main/java/io/legado/app/domain/model/settings/ThemeSettings.kt
  9. 4 1
      app/src/main/java/io/legado/app/help/storage/BackupConfig.kt
  10. 0 13
      app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt
  11. 3 5
      app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaAdapter.kt
  12. 69 0
      app/src/main/java/io/legado/app/ui/book/read/EyeProtection.kt
  13. 0 167
      app/src/main/java/io/legado/app/ui/book/read/EyeProtectionHelper.kt
  14. 0 53
      app/src/main/java/io/legado/app/ui/book/read/EyeProtectionOverlay.kt
  15. 6 0
      app/src/main/java/io/legado/app/ui/book/read/ReadBookContract.kt
  16. 29 2
      app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt
  17. 8 1
      app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt
  18. 11 0
      app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt
  19. 38 1
      app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt
  20. 47 0
      app/src/main/java/io/legado/app/ui/book/read/sheet/EyeProtectionConfigSheet.kt
  21. 9 0
      app/src/main/java/io/legado/app/ui/book/read/sheet/MoreConfigSheet.kt
  22. 2 0
      app/src/main/java/io/legado/app/ui/book/read/sheet/SystemMenuPage.kt
  23. 0 12
      app/src/main/java/io/legado/app/ui/config/labConfig/LabConfig.kt
  24. 0 1
      app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigContract.kt
  25. 0 9
      app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigScreen.kt
  26. 0 1
      app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigViewModel.kt
  27. 8 0
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt
  28. 22 0
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt
  29. 20 1
      app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt
  30. 0 5
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt
  31. 0 14
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigContract.kt
  32. 1 27
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigRouteScreen.kt
  33. 0 88
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt
  34. 3 15
      app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt
  35. 0 9
      app/src/main/res/layout/activity_manga.xml
  36. 4 9
      app/src/main/res/values-zh-rCN/strings.xml
  37. 4 9
      app/src/main/res/values-zh-rHK/strings.xml
  38. 4 9
      app/src/main/res/values-zh-rTW/strings.xml
  39. 4 9
      app/src/main/res/values/strings.xml

+ 1 - 4
app/src/main/java/io/legado/app/constant/PreferKey.kt

@@ -406,14 +406,11 @@ object PreferKey {
     // Lab
     const val labEnabled = "labEnabled"
     const val labEInkDisplay = "labEInkDisplay"
-    const val labEyeProtection = "labEyeProtection"
 
     // Eye Protection
     const val eyeProtectionEnabled = "eyeProtectionEnabled"
     const val colorTemperature = "colorTemperature"
-    const val eyeProtectionSchedule = "eyeProtectionSchedule"
-    const val eyeProtectionStartTime = "eyeProtectionStartTime"
-    const val eyeProtectionEndTime = "eyeProtectionEndTime"
+    const val eyeProtectionAutoNight = "eyeProtectionAutoNight"
 }
 
 object ReadMenuBlurMode {

+ 0 - 11
app/src/main/java/io/legado/app/data/repository/FeatureSettingsRepositories.kt

@@ -146,8 +146,6 @@ class ThemeSettingsRepository : ThemeSettingsGateway {
 
                 ThemeBooleanSetting.DisableSplicedColumnGroupCornerRadius ->
                     PreferKey.disableSplicedColumnGroupCornerRadius to update.value
-                ThemeBooleanSetting.EyeProtectionEnabled -> PreferKey.eyeProtectionEnabled to update.value
-                ThemeBooleanSetting.EyeProtectionSchedule -> PreferKey.eyeProtectionSchedule to update.value
                 ThemeBooleanSetting.ShowRefactorTip ->
                     io.legado.app.data.local.preferences.LocalPreferencesKeys.SHOW_THEME_REFACTOR_TIP.name to update.value
                 ThemeBooleanSetting.EnableCustomTagColors -> PreferKey.enableCustomTagColors to update.value
@@ -182,8 +180,6 @@ class ThemeSettingsRepository : ThemeSettingsGateway {
                     PreferKey.bookInfoDefaultCoverBackground to update.value
                 ThemeStringSetting.BackgroundImageLight -> PreferKey.bgImage to update.value
                 ThemeStringSetting.BackgroundImageDark -> PreferKey.bgImageN to update.value
-                ThemeStringSetting.EyeProtectionStartTime -> PreferKey.eyeProtectionStartTime to update.value
-                ThemeStringSetting.EyeProtectionEndTime -> PreferKey.eyeProtectionEndTime to update.value
                 ThemeStringSetting.CustomTagColorsJson -> PreferKey.customTagColors to update.value
             }
             }
@@ -355,13 +351,11 @@ internal fun CoverSettings.toPrefMap(): Map<String, Any?> = mapOf(
 internal fun Preferences.toLabSettings(): LabSettings = LabSettings(
     enabled = compatDsBoolean(PreferKey.labEnabled) ?: false,
     eInkDisplay = compatDsBoolean(PreferKey.labEInkDisplay) ?: false,
-    eyeProtection = compatDsBoolean(PreferKey.labEyeProtection) ?: false,
 )
 
 internal fun LabSettings.toPrefMap(): Map<String, Any?> = mapOf(
     PreferKey.labEnabled to enabled,
     PreferKey.labEInkDisplay to eInkDisplay,
-    PreferKey.labEyeProtection to eyeProtection,
 )
 
 internal fun Preferences.toTranslationSettings(): TranslationSettings {
@@ -488,11 +482,6 @@ internal fun Preferences.toThemeSettings(): ThemeSettings = ThemeSettings(
     itemDividerWidth = compatDsFloat(PreferKey.itemDividerWidth) ?: 1f,
     itemDividerLength = compatDsFloat(PreferKey.itemDividerLength) ?: 80f,
     itemDividerColor = compatDsInt(PreferKey.itemDividerColor) ?: 0,
-    eyeProtectionEnabled = compatDsBoolean(PreferKey.eyeProtectionEnabled) ?: false,
-    colorTemperature = compatDsInt(PreferKey.colorTemperature) ?: 50,
-    eyeProtectionSchedule = compatDsBoolean(PreferKey.eyeProtectionSchedule) ?: false,
-    eyeProtectionStartTime = compatDsString(PreferKey.eyeProtectionStartTime) ?: "22:00",
-    eyeProtectionEndTime = compatDsString(PreferKey.eyeProtectionEndTime) ?: "07:00",
     showRefactorTip = compatDsBoolean(
         io.legado.app.data.local.preferences.LocalPreferencesKeys.SHOW_THEME_REFACTOR_TIP.name
     ) ?: true,

+ 18 - 0
app/src/main/java/io/legado/app/data/repository/ReadSettingsRepository.kt

@@ -49,6 +49,9 @@ class ReadSettingsRepository(
             is ReadSettingsUpdate.TextBottomJustify -> setTextBottomJustify(update.value)
             is ReadSettingsUpdate.AdaptSpecialStyle -> setAdaptSpecialStyle(update.value)
             is ReadSettingsUpdate.UseZhLayout -> setUseZhLayout(update.value)
+            is ReadSettingsUpdate.EyeProtectionEnabled -> setEyeProtectionEnabled(update.value)
+            is ReadSettingsUpdate.EyeProtectionIntensity -> setEyeProtectionIntensity(update.value)
+            is ReadSettingsUpdate.EyeProtectionAutoNight -> setEyeProtectionAutoNight(update.value)
             is ReadSettingsUpdate.ShowBrightnessView -> setShowBrightnessView(update.value)
             is ReadSettingsUpdate.BrightnessVwPos -> setBrightnessVwPos(update.value)
             is ReadSettingsUpdate.Brightness -> setReadBrightness(update.value)
@@ -101,6 +104,15 @@ class ReadSettingsRepository(
     suspend fun setTitleBarMode(value: String) =
         settingsRepository.putString(PreferKey.titleBarMode, value)
 
+    suspend fun setEyeProtectionEnabled(value: Boolean) =
+        settingsRepository.putBoolean(PreferKey.eyeProtectionEnabled, value)
+
+    suspend fun setEyeProtectionIntensity(value: Int) =
+        settingsRepository.putInt(PreferKey.colorTemperature, value.coerceIn(0, 100))
+
+    suspend fun setEyeProtectionAutoNight(value: Boolean) =
+        settingsRepository.putBoolean(PreferKey.eyeProtectionAutoNight, value)
+
     suspend fun setMenuAlpha(value: Int) =
         settingsRepository.putInt(PreferKey.menuAlpha, value)
 
@@ -383,6 +395,9 @@ class ReadSettingsRepository(
             textBottomJustify = compatDsValue(Keys.TextBottomJustify, true),
             adaptSpecialStyle = compatDsValue(Keys.AdaptSpecialStyle, true),
             useZhLayout = compatDsValue(Keys.UseZhLayout, false),
+            eyeProtectionEnabled = compatDsValue(Keys.EyeProtectionEnabled, false),
+            eyeProtectionIntensity = compatDsValue(Keys.EyeProtectionIntensity, 50).coerceIn(0, 100),
+            eyeProtectionAutoNight = compatDsValue(Keys.EyeProtectionAutoNight, false),
             showBrightnessView = compatDsValue(Keys.ShowBrightnessView, "0"),
             brightnessVwPos = compatDsValue(Keys.BrightnessVwPos, "1"),
             readBrightness = compatDsValue(Keys.ReadBrightness, 100),
@@ -491,6 +506,9 @@ class ReadSettingsRepository(
         val TextBottomJustify = booleanPreferencesKey(PreferKey.textBottomJustify)
         val AdaptSpecialStyle = booleanPreferencesKey(PreferKey.adaptSpecialStyle)
         val UseZhLayout = booleanPreferencesKey(PreferKey.useZhLayout)
+        val EyeProtectionEnabled = booleanPreferencesKey(PreferKey.eyeProtectionEnabled)
+        val EyeProtectionIntensity = intPreferencesKey(PreferKey.colorTemperature)
+        val EyeProtectionAutoNight = booleanPreferencesKey(PreferKey.eyeProtectionAutoNight)
         val ShowBrightnessView = stringPreferencesKey(PreferKey.showBrightnessView)
         val BrightnessVwPos = stringPreferencesKey(PreferKey.brightnessVwPos)
         val ReadBrightness = intPreferencesKey(PreferKey.brightness)

+ 3 - 0
app/src/main/java/io/legado/app/domain/gateway/ReadSettingsGateway.kt

@@ -24,6 +24,9 @@ sealed interface ReadSettingsUpdate {
     data class TextBottomJustify(val value: Boolean) : ReadSettingsUpdate
     data class AdaptSpecialStyle(val value: Boolean) : ReadSettingsUpdate
     data class UseZhLayout(val value: Boolean) : ReadSettingsUpdate
+    data class EyeProtectionEnabled(val value: Boolean) : ReadSettingsUpdate
+    data class EyeProtectionIntensity(val value: Int) : ReadSettingsUpdate
+    data class EyeProtectionAutoNight(val value: Boolean) : ReadSettingsUpdate
     data class ShowBrightnessView(val value: String) : ReadSettingsUpdate
     data class BrightnessVwPos(val value: String) : ReadSettingsUpdate
     data class Brightness(val value: Int) : ReadSettingsUpdate

+ 0 - 4
app/src/main/java/io/legado/app/domain/gateway/ThemeSettingsGateway.kt

@@ -49,8 +49,6 @@ enum class ThemeBooleanSetting {
     OverrideBaseCardCornerRadius,
     OverrideBaseCardBorder,
     DisableSplicedColumnGroupCornerRadius,
-    EyeProtectionEnabled,
-    EyeProtectionSchedule,
     ShowRefactorTip,
     EnableCustomTagColors,
 }
@@ -84,8 +82,6 @@ enum class ThemeStringSetting {
     BookInfoDefaultCoverBackground,
     BackgroundImageLight,
     BackgroundImageDark,
-    EyeProtectionStartTime,
-    EyeProtectionEndTime,
     CustomTagColorsJson,
 }
 

+ 0 - 1
app/src/main/java/io/legado/app/domain/model/settings/LabSettings.kt

@@ -3,5 +3,4 @@ package io.legado.app.domain.model.settings
 data class LabSettings(
     val enabled: Boolean = false,
     val eInkDisplay: Boolean = false,
-    val eyeProtection: Boolean = false,
 )

+ 3 - 0
app/src/main/java/io/legado/app/domain/model/settings/ReadSettings.kt

@@ -17,6 +17,9 @@ data class ReadSettings(
     val textBottomJustify: Boolean = true,
     val adaptSpecialStyle: Boolean = true,
     val useZhLayout: Boolean = false,
+    val eyeProtectionEnabled: Boolean = false,
+    val eyeProtectionIntensity: Int = 50,
+    val eyeProtectionAutoNight: Boolean = false,
     val showBrightnessView: String = "0",
     val brightnessVwPos: String = "1",
     val readBrightness: Int = 100,

+ 0 - 5
app/src/main/java/io/legado/app/domain/model/settings/ThemeSettings.kt

@@ -54,11 +54,6 @@ data class ThemeSettings(
     val itemDividerWidth: Float = 1f,
     val itemDividerLength: Float = 80f,
     val itemDividerColor: Int = 0,
-    val eyeProtectionEnabled: Boolean = false,
-    val colorTemperature: Int = 50,
-    val eyeProtectionSchedule: Boolean = false,
-    val eyeProtectionStartTime: String = "22:00",
-    val eyeProtectionEndTime: String = "07:00",
     val showRefactorTip: Boolean = true,
     val enableCustomTagColors: Boolean = false,
     val customTagColorsJson: String? = null,

+ 4 - 1
app/src/main/java/io/legado/app/help/storage/BackupConfig.kt

@@ -244,7 +244,10 @@ object BackupConfig {
         PreferKey.showTitleBarIcons,
         PreferKey.showMenuIcon,
         PreferKey.titleBarMode,
-        PreferKey.shouldShowExpandButton
+        PreferKey.shouldShowExpandButton,
+        PreferKey.eyeProtectionEnabled,
+        PreferKey.colorTemperature,
+        PreferKey.eyeProtectionAutoNight,
     )
 
     private val themePrefKeys = arrayOf(

+ 0 - 13
app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt

@@ -70,9 +70,7 @@ import io.legado.app.ui.book.manga.recyclerview.MangaAdapter
 import io.legado.app.ui.book.manga.recyclerview.MangaLayoutManager
 import io.legado.app.ui.book.manga.recyclerview.ScrollTimer
 import io.legado.app.ui.book.manga.recyclerview.WebtoonFrame
-import io.legado.app.ui.book.read.EyeProtectionRefreshScheduler
 import io.legado.app.ui.book.read.MangaMenu
-import io.legado.app.ui.book.read.observeEyeProtectionEvents
 import io.legado.app.ui.book.source.edit.BookSourceEditActivity
 import io.legado.app.ui.book.toc.TocActivityResult
 import io.legado.app.ui.browser.WebViewActivity
@@ -145,9 +143,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
     private var isRestoredFromSavedState = false
     private var syncDialog: AlertDialog? = null
     private val handler by lazy { buildMainHandler() }
-    private val eyeProtectionScheduler by lazy {
-        EyeProtectionRefreshScheduler(handler) { binding.eyeProtectionOverlay.refresh() }
-    }
     private val mScrollTimer by lazy {
         ScrollTimer(this, binding.recyclerView, lifecycleScope).apply {
             setSpeed(viewModel.mangaSettings.value.autoPageSpeed)
@@ -224,7 +219,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
             ReadManga.loadOrUpContent()
         }
         binding.flLoading.isVisible = !ReadConfig.isEInkMode
-        binding.eyeProtectionOverlay.refresh()
         mAdapter.addFooterView {
             ViewLoadMoreBinding.bind(loadMoreView)
         }
@@ -300,10 +294,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
             val item = mAdapter.getItem(binding.recyclerView.findCenterViewPosition())
             upInfoBar(item)
         }
-        observeEyeProtectionEvents(
-            onRefresh = { binding.eyeProtectionOverlay.refresh() },
-            scheduler = eyeProtectionScheduler
-        )
     }
 
     private fun initRecyclerView() {
@@ -528,13 +518,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
         }
         ReadManga.initReadTime()
         ReadManga.startAutoSaveSession()
-        binding.eyeProtectionOverlay.refresh()
-        eyeProtectionScheduler.schedule()
     }
 
     override fun onPause() {
         super.onPause()
-        eyeProtectionScheduler.cancel()
         if (ReadManga.inBookshelf) {
             ReadManga.saveRead()
             if (!BuildConfig.DEBUG) {

+ 3 - 5
app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaAdapter.kt

@@ -1,7 +1,6 @@
 package io.legado.app.ui.book.manga.recyclerview
 
 import android.content.Context
-import android.graphics.ColorMatrix
 import android.graphics.ColorMatrixColorFilter
 import android.util.SparseArray
 import android.view.LayoutInflater
@@ -31,6 +30,7 @@ import io.legado.app.ui.book.manga.entities.EpaperTransformation
 import io.legado.app.ui.book.manga.entities.GrayscaleTransformation
 import io.legado.app.ui.book.manga.entities.MangaPage
 import io.legado.app.ui.book.manga.entities.ReaderLoading
+import io.legado.app.ui.config.readMangaConfig.ReadMangaConfig
 import io.legado.app.utils.dpToPx
 
 
@@ -131,10 +131,8 @@ class MangaAdapter(
                 0f, 0f, (255 - mConfig.b) / 255f, 0f, 0f,
                 0f, 0f, 0f, (255 - mConfig.a) / 255f, 0f
             )
-            val epMatrix = io.legado.app.ui.book.read.EyeProtectionHelper.buildColorMatrix()
-            val combined = android.graphics.ColorMatrix(baseMatrix)
-            combined.postConcat(epMatrix)
-            binding.image.colorFilter = ColorMatrixColorFilter(combined)
+
+            binding.image.colorFilter = ColorMatrixColorFilter(baseMatrix)
         }
 
         fun setBackground() {

+ 69 - 0
app/src/main/java/io/legado/app/ui/book/read/EyeProtection.kt

@@ -0,0 +1,69 @@
+package io.legado.app.ui.book.read
+
+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
+
+
+object EyeProtection {
+    const val MIN_INTENSITY = 0
+    const val MAX_INTENSITY = 100
+
+    private const val MIN_FILTER_TEMPERATURE = 2596
+    private const val MAX_FILTER_TEMPERATURE = 5500
+
+    private val coefficients = floatArrayOf(
+        0f, 0f, 1f,
+        -9.623533E-9f, 1.5304548E-4f, 0.39078277f,
+        -1.8935904E-8f, 3.024122E-4f, -0.1986509f,
+    )
+
+    fun temperatureForIntensity(intensity: Int): Int {
+        val normalized = intensity.coerceIn(MIN_INTENSITY, MAX_INTENSITY)
+        return (MAX_FILTER_TEMPERATURE - (MAX_FILTER_TEMPERATURE - MIN_FILTER_TEMPERATURE) * normalized / MAX_INTENSITY.toFloat())
+            .toInt()
+    }
+
+    fun matrixValuesForIntensity(intensity: Int): FloatArray {
+        val cct = temperatureForIntensity(intensity).toFloat()
+        val cctSquared = cct * cct
+        val red = cctSquared * coefficients[0] + cct * coefficients[1] + coefficients[2]
+        val green = cctSquared * coefficients[3] + cct * coefficients[4] + coefficients[5]
+        val blue = cctSquared * coefficients[6] + cct * coefficients[7] + coefficients[8]
+        return floatArrayOf(
+            red, 0f, 0f, 0f, 0f,
+            0f, green, 0f, 0f, 0f,
+            0f, 0f, blue, 0f, 0f,
+            0f, 0f, 0f, 1f, 0f,
+        )
+    }
+
+    fun syncEnabledForNight(isNight: Boolean, autoNight: Boolean): Boolean? =
+        if (autoNight) isNight else null
+}
+
+@Composable
+fun Modifier.eyeProtectionColorFilter(
+    enabled: Boolean,
+    intensity: Int,
+): Modifier {
+    val cachedColorFilter = remember(enabled, intensity) {
+        if (enabled) {
+            ColorFilter.colorMatrix(
+                ComposeColorMatrix(EyeProtection.matrixValuesForIntensity(intensity))
+            )
+        } else {
+            null
+        }
+    }
+    if (cachedColorFilter == null) return this
+
+    return graphicsLayer {
+        compositingStrategy = CompositingStrategy.Offscreen
+        colorFilter = cachedColorFilter
+    }
+}

+ 0 - 167
app/src/main/java/io/legado/app/ui/book/read/EyeProtectionHelper.kt

@@ -1,167 +0,0 @@
-package io.legado.app.ui.book.read
-
-import android.graphics.Color
-import android.graphics.ColorMatrix
-import android.graphics.Paint
-import android.os.Handler
-import androidx.appcompat.app.AppCompatActivity
-import io.legado.app.constant.PreferKey
-import io.legado.app.ui.config.labConfig.LabConfig
-import io.legado.app.ui.config.themeConfig.ThemeConfig
-import io.legado.app.utils.observeEvent
-import java.util.Calendar
-
-/**
- * 护眼色温工具类
- * 提供色温(暖色)转 ColorMatrix 以及覆盖层颜色计算
- *
- * 激活条件:
- * Lab 总开关开启 && 实验室护眼模式开启 && 主题护眼模式开启 &&
- * (未开启定时 OR 当前时间在定时区间内)
- */
-object EyeProtectionHelper {
-
-    /**
-     * 判断当前时间是否在护眼定时区间内
-     */
-    fun isInSchedule(): Boolean {
-        if (!ThemeConfig.eyeProtectionSchedule) return true
-        val now = Calendar.getInstance()
-        val curMinute = now.get(Calendar.HOUR_OF_DAY) * 60 + now.get(Calendar.MINUTE)
-        val start = parseTime(ThemeConfig.eyeProtectionStartTime)
-        val end = parseTime(ThemeConfig.eyeProtectionEndTime)
-        if (start == null || end == null) return true
-        return if (start == end) {
-            true
-        } else if (start < end) {
-            curMinute in start..end
-        } else {
-            curMinute >= start || curMinute <= end
-        }
-    }
-
-    private fun parseTime(value: String?): Int? {
-        if (value.isNullOrBlank()) return null
-        return try {
-            val parts = value.split(":")
-            if (parts.size < 2) return null
-            val h = parts[0].trim().toInt().coerceIn(0, 23)
-            val m = parts[1].trim().toInt().coerceIn(0, 59)
-            h * 60 + m
-        } catch (e: Exception) {
-            null
-        }
-    }
-
-    /**
-     * 是否应该激活护眼色温
-     * 仅当总开关打开,并且满足定时条件时返回 true
-     */
-    fun isActive(): Boolean {
-        if (!LabConfig.labEnabled) return false
-        if (!LabConfig.eyeProtection) return false
-        if (!ThemeConfig.eyeProtectionEnabled) return false
-        return isInSchedule()
-    }
-
-    /**
-     * 根据当前色温设置获取暖色覆盖颜色
-     * temperature: 0-100,0 不变,100 最暖
-     */
-    fun overlayColor(): Int {
-        if (!isActive()) return Color.TRANSPARENT
-        val t = ThemeConfig.colorTemperature.coerceIn(0, 100)
-        if (t == 0) return Color.TRANSPARENT
-        val alpha = (t / 100f * 90f).toInt().coerceIn(0, 255)
-        return Color.argb(alpha, 255, 180, 90)
-    }
-
-    /**
-     * 创建色温 ColorMatrix(暖色调,降低蓝光通道)
-     */
-    fun buildColorMatrix(): ColorMatrix {
-        val t = if (isActive()) ThemeConfig.colorTemperature.coerceIn(0, 100) else 0
-        val ratio = t / 100f
-        val r = 1f
-        val g = 1f - 0.45f * ratio
-        val b = 1f - 0.85f * ratio
-        val brightness = 1f - 0.05f * ratio
-        val matrix = ColorMatrix(
-            floatArrayOf(
-                r * brightness, 0f, 0f, 0f, 8f * ratio,
-                0f, g * brightness, 0f, 0f, 4f * ratio,
-                0f, 0f, b * brightness, 0f, 0f,
-                0f, 0f, 0f, 1f, 0f
-            )
-        )
-        return matrix
-    }
-
-    /**
-     * 为 Paint 应用护眼色温 ColorFilter
-     */
-    fun applyColorFilter(paint: Paint) {
-        if (isActive()) {
-            paint.colorFilter = android.graphics.ColorMatrixColorFilter(buildColorMatrix())
-        } else {
-            paint.colorFilter = null
-        }
-    }
-}
-
-/**
- * 护眼定时刷新调度器
- * 在定时区间边界(每分钟)自动刷新护眼状态
- */
-class EyeProtectionRefreshScheduler(
-    private val handler: Handler,
-    private val onRefresh: () -> Unit
-) {
-    private val refreshRunnable = Runnable {
-        onRefresh()
-        schedule()
-    }
-
-    fun schedule() {
-        cancel()
-        if (!ThemeConfig.eyeProtectionSchedule) return
-        val now = Calendar.getInstance()
-        val delayMs = (60 - now.get(Calendar.SECOND)) * 1000L -
-            now.get(Calendar.MILLISECOND)
-        val safeDelay = delayMs.coerceAtLeast(1000L)
-        handler.postDelayed(refreshRunnable, safeDelay)
-    }
-
-    fun cancel() {
-        handler.removeCallbacks(refreshRunnable)
-    }
-}
-
-/**
- * 在 Activity 中注册护眼模式相关的 EventBus 监听
- * 设置变化时自动刷新覆盖层,定时/时间变化时重新调度
- */
-fun AppCompatActivity.observeEyeProtectionEvents(
-    onRefresh: () -> Unit,
-    scheduler: EyeProtectionRefreshScheduler
-) {
-    observeEvent<Boolean>(PreferKey.eyeProtectionEnabled) {
-        onRefresh()
-        scheduler.schedule()
-    }
-    observeEvent<Int>(PreferKey.colorTemperature) {
-        onRefresh()
-    }
-    observeEvent<Boolean>(PreferKey.eyeProtectionSchedule) {
-        onRefresh()
-        scheduler.schedule()
-    }
-    observeEvent<String>(PreferKey.eyeProtectionStartTime) {
-        onRefresh()
-        scheduler.schedule()
-    }
-    observeEvent<String>(PreferKey.eyeProtectionEndTime) {
-        onRefresh()
-        scheduler.schedule()
-    }
-}

+ 0 - 53
app/src/main/java/io/legado/app/ui/book/read/EyeProtectionOverlay.kt

@@ -1,53 +0,0 @@
-package io.legado.app.ui.book.read
-
-import android.content.Context
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Paint
-import android.util.AttributeSet
-import android.view.View
-
-/**
- * 护眼色温覆盖层
- * 在父 View 之上叠加一层半透明暖色,模拟护眼色温
- */
-class EyeProtectionOverlay @JvmOverloads constructor(
-    context: Context,
-    attrs: AttributeSet? = null,
-    defStyleAttr: Int = 0
-) : View(context, attrs, defStyleAttr) {
-
-    private val paint = Paint().apply {
-        style = Paint.Style.FILL
-    }
-
-    init {
-        isClickable = false
-        isFocusable = false
-        setWillNotDraw(false)
-    }
-
-    /**
-     * 刷新当前护眼状态
-     */
-    fun refresh() {
-        val color = EyeProtectionHelper.overlayColor()
-        if (color == Color.TRANSPARENT) {
-            if (visibility != GONE) {
-                visibility = GONE
-            }
-        } else {
-            if (visibility != VISIBLE) {
-                visibility = VISIBLE
-            }
-            paint.color = color
-            invalidate()
-        }
-    }
-
-    override fun onDraw(canvas: Canvas) {
-        super.onDraw(canvas)
-        if (paint.color == Color.TRANSPARENT) return
-        canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
-    }
-}

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

@@ -426,6 +426,7 @@ internal val ReadBookButtonIds = listOf(
     "auto_page",
     "catalog",
     "read_aloud",
+    "eye_protection",
     "setting",
     "addBookmark",
     "theme",
@@ -659,6 +660,10 @@ sealed interface ReadBookIntent {
 
     // Day/night toggle
     data object ToggleDayNight : ReadBookIntent
+    data object ToggleEyeProtection : ReadBookIntent
+    data class EyeProtectionEnabledChanged(val value: Boolean) : ReadBookIntent
+    data class EyeProtectionIntensityChanged(val value: Int) : ReadBookIntent
+    data class EyeProtectionAutoNightChanged(val value: Boolean) : ReadBookIntent
 
     // Default font picker (needs Activity for AlertDialog)
     // Text action menu (moved from Activity)
@@ -947,6 +952,7 @@ sealed interface ReadBookSheet {
     data object Charset : ReadBookSheet
     data object SimulatedReading : ReadBookSheet
     data object ToolButtonConfig : ReadBookSheet
+    data object EyeProtection : ReadBookSheet
     data object TitleBarIconConfig : ReadBookSheet
     data object EffectiveReplaces : ReadBookSheet
     data object ContentProcesses : ReadBookSheet

+ 29 - 2
app/src/main/java/io/legado/app/ui/book/read/ReadBookMenuBar.kt

@@ -261,6 +261,7 @@ fun ReadBookMenuBar(
                 if (state.menuConfig.showTitleBarIcons && state.menuConfig.titleBarIconPosition <= 1) {
                     FloatingIconRow(
                         state = state,
+                        preferences = preferences,
                         colors = menuColors,
                         alignment = if (state.menuConfig.titleBarIconPosition == 0) {
                             Alignment.Start
@@ -375,6 +376,7 @@ fun ReadBookMenuBar(
                 ) {
                     FloatingIconRow(
                         state = state,
+                        preferences = preferences,
                         colors = menuColors,
                         alignment = if (state.menuConfig.titleBarIconPosition == 2) {
                             Alignment.Start
@@ -670,6 +672,7 @@ private fun ReadBookMenuSurface(
                     ReadBookMenuRoute.Main -> {
                     MenuBottomBar(
                         state = state,
+                        eyeProtectionEnabled = preferences.eyeProtectionEnabled,
                         colors = colors,
                         onIntent = onIntent,
                         context = context,
@@ -1930,6 +1933,7 @@ private fun CharsetActionButton(
 @Composable
 private fun FloatingIconRow(
     state: ReadBookUiState,
+    preferences: ReadPreferences,
     colors: ReadMenuColors,
     alignment: Alignment.Horizontal = Alignment.CenterHorizontally,
     onIntent: (ReadBookIntent) -> Unit,
@@ -1942,8 +1946,14 @@ private fun FloatingIconRow(
         state.isAutoPage,
         state.translationMode,
         state.useReplaceRule,
+        preferences.eyeProtectionEnabled,
     ) {
-        loadFloatingIcons(context, state, onIntent)
+        loadFloatingIcons(
+            context = context,
+            state = state,
+            preferences = preferences,
+            onIntent = onIntent,
+        )
     }
 
     if (titleBarIcons.isEmpty()) return
@@ -2403,6 +2413,7 @@ private fun SearchMenuActionButton(
 @Composable
 private fun MenuBottomBar(
     state: ReadBookUiState,
+    eyeProtectionEnabled: Boolean,
     colors: ReadMenuColors,
     onIntent: (ReadBookIntent) -> Unit,
     context: Context,
@@ -2563,8 +2574,14 @@ private fun MenuBottomBar(
             state.isAutoPage,
             state.translationMode,
             state.useReplaceRule,
+            eyeProtectionEnabled,
         ) {
-            loadToolButtons(context, state, onIntent)
+            loadToolButtons(
+                context = context,
+                state = state,
+                eyeProtectionEnabled = eyeProtectionEnabled,
+                onIntent = onIntent,
+            )
         }
         val itemsPerRow = state.menuConfig.readMenuIconItemsPerRow
         val rowCount = state.menuConfig.readMenuIconRowCount
@@ -3047,6 +3064,7 @@ private data class ToolButtonDef(
 private fun loadToolButtons(
     context: Context,
     state: ReadBookUiState,
+    eyeProtectionEnabled: Boolean,
     onIntent: (ReadBookIntent) -> Unit,
 ): List<ToolButtonDef> {
     val customIcons = state.menuConfig.readMenuCustomIcons
@@ -3087,6 +3105,12 @@ private fun loadToolButtons(
         infoMap.getValue("theme").toButton {
             onIntent(ReadBookIntent.ToggleDayNight)
         },
+        infoMap.getValue("eye_protection").toButton(
+            isActive = eyeProtectionEnabled,
+            onLongClick = { onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.EyeProtection)) },
+        ) {
+            onIntent(ReadBookIntent.ToggleEyeProtection)
+        },
         infoMap.getValue("prev_chapter").toButton {
             onIntent(ReadBookIntent.PrevChapter)
         },
@@ -3319,6 +3343,7 @@ private data class TitleBarIconDef(
 private fun loadFloatingIcons(
     context: Context,
     state: ReadBookUiState,
+    preferences: ReadPreferences,
     onIntent: (ReadBookIntent) -> Unit,
 ): List<TitleBarIconDef> {
     val infoMap = readMenuButtonInfos(context).associateBy { it.id }
@@ -3337,6 +3362,7 @@ private fun loadFloatingIcons(
         "setting" to { onIntent(ReadBookIntent.OpenReadMenuRoute(ReadBookMenuRoute.ReadStyle)) },
         "addBookmark" to { onIntent(ReadBookIntent.AddBookmark) },
         "theme" to { onIntent(ReadBookIntent.ToggleDayNight) },
+        "eye_protection" to { onIntent(ReadBookIntent.ToggleEyeProtection) },
         "prev_chapter" to { onIntent(ReadBookIntent.PrevChapter) },
         "next_chapter" to { onIntent(ReadBookIntent.NextChapter) },
         "replace" to { onIntent(ReadBookIntent.MenuEnableReplace) },
@@ -3359,6 +3385,7 @@ private fun loadFloatingIcons(
         if (state.isAutoPage) add("auto_page")
         if (state.translationMode) add("translate")
         if (state.useReplaceRule) add("replace")
+        if (preferences.eyeProtectionEnabled) add("eye_protection")
     }
 
     return state.menuConfig.titleBarButtons

+ 8 - 1
app/src/main/java/io/legado/app/ui/book/read/ReadBookRouteScreen.kt

@@ -518,7 +518,14 @@ fun ReadBookRouteScreen(
 
     var showSelectMenuConfigSheet by rememberSaveable { mutableStateOf(false) }
 
-    Box(Modifier.fillMaxSize()) {
+    Box(
+        Modifier
+            .fillMaxSize()
+            .eyeProtectionColorFilter(
+                enabled = readPreferences.eyeProtectionEnabled,
+                intensity = readPreferences.eyeProtectionIntensity,
+            )
+    ) {
         key(controller) {
             ReadBookViewLayer(
                 modifier = Modifier

+ 11 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookScreen.kt

@@ -27,6 +27,7 @@ import io.legado.app.ui.book.read.sheet.ContentEditSheet
 import io.legado.app.ui.book.read.sheet.ContentProcessesSheet
 import io.legado.app.ui.book.read.sheet.DownloadSheet
 import io.legado.app.ui.book.read.sheet.EffectiveReplacesSheet
+import io.legado.app.ui.book.read.sheet.EyeProtectionConfigSheet
 import io.legado.app.ui.book.read.sheet.HighlightRuleConfigSheet
 import io.legado.app.ui.book.read.sheet.HttpTtsEditSheet
 import io.legado.app.ui.book.read.sheet.MoreConfigSheet
@@ -240,6 +241,16 @@ fun ReadBookScreen(
         onDismissRequest = dismissSheet,
         onIntent = onIntent,
     )
+    EyeProtectionConfigSheet(
+        show = state.activeSheet is ReadBookSheet.EyeProtection,
+        enabled = preferences.eyeProtectionEnabled,
+        intensity = preferences.eyeProtectionIntensity,
+        autoNight = preferences.eyeProtectionAutoNight,
+        onDismissRequest = dismissSheet,
+        onEnabledChange = { onIntent(ReadBookIntent.EyeProtectionEnabledChanged(it)) },
+        onIntensityChange = { onIntent(ReadBookIntent.EyeProtectionIntensityChanged(it)) },
+        onAutoNightChange = { onIntent(ReadBookIntent.EyeProtectionAutoNightChanged(it)) },
+    )
     TitleBarIconSheet(
         show = state.activeSheet is ReadBookSheet.TitleBarIconConfig,
         items = state.menuConfig.titleBarButtons,

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

@@ -1457,6 +1457,34 @@ class ReadBookViewModel(
             }
 
             is ReadBookIntent.ToggleDayNight -> toggleDayNight()
+            is ReadBookIntent.ToggleEyeProtection -> {
+                viewModelScope.launch {
+                    readSettingsRepository.update(
+                        ReadSettingsUpdate.EyeProtectionEnabled(
+                            !_readPreferences.value.eyeProtectionEnabled
+                        )
+                    )
+                }
+            }
+            is ReadBookIntent.EyeProtectionEnabledChanged ->
+                updateEyeProtection(ReadSettingsUpdate.EyeProtectionEnabled(intent.value))
+            is ReadBookIntent.EyeProtectionIntensityChanged ->
+                updateEyeProtection(ReadSettingsUpdate.EyeProtectionIntensity(intent.value))
+            is ReadBookIntent.EyeProtectionAutoNightChanged -> {
+                viewModelScope.launch {
+                    readSettingsRepository.update(ReadSettingsUpdate.EyeProtectionAutoNight(intent.value))
+                    if (intent.value) {
+                        EyeProtection.syncEnabledForNight(
+                            isNight = isNightTheme(),
+                            autoNight = true,
+                        )?.let { enabled ->
+                            readSettingsRepository.update(
+                                ReadSettingsUpdate.EyeProtectionEnabled(enabled)
+                            )
+                        }
+                    }
+                }
+            }
             // Text action menu
             is ReadBookIntent.TextActionAloud -> {
                 when (readAloudSettingsRepository.currentSettings.contentSelectSpeakMode) {
@@ -5890,6 +5918,12 @@ class ReadBookViewModel(
         val nextMode = if (isNightTheme()) "1" else "2"
         viewModelScope.launch {
             appShellSettingsGateway.update(AppShellSettingsUpdate.ThemeMode(nextMode))
+            EyeProtection.syncEnabledForNight(
+                isNight = nextMode == "1",
+                autoNight = _readPreferences.value.eyeProtectionAutoNight,
+            )?.let { enabled ->
+                readSettingsRepository.update(ReadSettingsUpdate.EyeProtectionEnabled(enabled))
+            }
         }
         _uiState.update {
             val newActiveReminder = if (it.activeReminder?.type is ReminderType.DayNightReminder) {
@@ -5915,6 +5949,10 @@ class ReadBookViewModel(
         ))
     }
 
+    private fun updateEyeProtection(update: ReadSettingsUpdate) {
+        viewModelScope.launch { readSettingsRepository.update(update) }
+    }
+
     private fun applyReadStyleBackgroundImage(uri: Uri) {
         viewModelScope.launch(IO) {
             runCatching {
@@ -6470,7 +6508,6 @@ private val DEFAULT_ENABLED_BUTTON_IDS = setOf(
     "setting",
 )
 private val DEFAULT_AI_TOOL_BUTTON_IDS = setOf("ai_summary", "ai_rewrite")
-
 private const val DARK_LUX_THRESHOLD = 8f
 private const val BRIGHT_LUX_THRESHOLD = 100f
 private const val LIGHT_LUMINANCE_THRESHOLD = 0.35

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

@@ -0,0 +1,47 @@
+package io.legado.app.ui.book.read.sheet
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.res.stringResource
+import io.legado.app.R
+import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
+import io.legado.app.ui.widget.components.settingItem.SliderSettingItem
+import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
+
+@Composable
+fun EyeProtectionConfigSheet(
+    show: Boolean,
+    enabled: Boolean,
+    intensity: Int,
+    autoNight: Boolean,
+    onDismissRequest: () -> Unit,
+    onEnabledChange: (Boolean) -> Unit,
+    onIntensityChange: (Int) -> Unit,
+    onAutoNightChange: (Boolean) -> Unit,
+) {
+    AppModalBottomSheet(
+        show = show,
+        onDismissRequest = onDismissRequest,
+        title = stringResource(R.string.eye_protection),
+    ) {
+        SwitchSettingItem(
+            title = stringResource(R.string.eye_protection_enabled),
+            description = stringResource(R.string.eye_protection_enabled_summary),
+            checked = enabled,
+            onCheckedChange = onEnabledChange,
+        )
+        SliderSettingItem(
+            title = stringResource(R.string.eye_protection_intensity),
+            description = stringResource(R.string.eye_protection_intensity_summary, intensity),
+            value = intensity.toFloat(),
+            defaultValue = 50f,
+            valueRange = 0f..100f,
+            onValueChange = { onIntensityChange(it.toInt()) },
+        )
+        SwitchSettingItem(
+            title = stringResource(R.string.eye_protection_auto_night),
+            description = stringResource(R.string.eye_protection_auto_night_summary),
+            checked = autoNight,
+            onCheckedChange = onAutoNightChange,
+        )
+    }
+}

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

@@ -17,6 +17,7 @@ import io.legado.app.data.repository.ReadPreferences
 import io.legado.app.data.repository.ReadSettingsRepository
 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.widget.components.SectionTitle
 import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
 import io.legado.app.ui.widget.components.settingItem.TinyClickableSettingItem
@@ -86,6 +87,9 @@ fun MoreConfigSheet(
                 onUseUnderlineChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.UseUnderlineGlobal(it)))
                 },
+                onOpenEyeProtectionConfig = {
+                    onIntent(ReadBookIntent.ShowSheet(ReadBookSheet.EyeProtection))
+                }
             )
 
             // Page control
@@ -174,6 +178,7 @@ private fun ScreenSettings(
     onAdaptSpecialStyleChange: (Boolean) -> Unit,
     onUseZhLayoutChange: (Boolean) -> Unit,
     onUseUnderlineChange: (Boolean) -> Unit,
+    onOpenEyeProtectionConfig:() -> Unit,
 ) {
     val screenDirectionEntries = stringArrayResource(R.array.screen_direction_title)
     val screenDirectionValues = stringArrayResource(R.array.screen_direction_value)
@@ -239,6 +244,10 @@ private fun ScreenSettings(
         checked = preferences.useUnderline,
         onCheckedChange = onUseUnderlineChange,
     )
+    TinyClickableSettingItem(
+        title = stringResource(R.string.eye_protection),
+        onClick = onOpenEyeProtectionConfig,
+    )
 }
 
 @Composable

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

@@ -31,6 +31,7 @@ import androidx.compose.material.icons.filled.Settings
 import androidx.compose.material.icons.filled.SkipNext
 import androidx.compose.material.icons.filled.SkipPrevious
 import androidx.compose.material.icons.filled.Translate
+import androidx.compose.material.icons.filled.Visibility
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
@@ -888,6 +889,7 @@ internal fun readMenuButtonInfos(context: Context): List<ReadMenuButtonInfo> = l
     ReadMenuButtonInfo("setting", Icons.Default.Settings, context.getString(R.string.setting)),
     ReadMenuButtonInfo("addBookmark", Icons.Default.Bookmark, context.getString(R.string.bookmark)),
     ReadMenuButtonInfo("theme", Icons.Default.Brightness6, context.getString(R.string.day_night_switch)),
+    ReadMenuButtonInfo("eye_protection", Icons.Default.Visibility, context.getString(R.string.eye_protection)),
     ReadMenuButtonInfo("prev_chapter", Icons.Default.SkipPrevious, context.getString(R.string.previous_chapter)),
     ReadMenuButtonInfo("next_chapter", Icons.Default.SkipNext, context.getString(R.string.next_chapter)),
     ReadMenuButtonInfo("replace", Icons.Default.FindReplace, context.getString(R.string.replace_purify)),

+ 0 - 12
app/src/main/java/io/legado/app/ui/config/labConfig/LabConfig.kt

@@ -1,12 +0,0 @@
-package io.legado.app.ui.config.labConfig
-
-import io.legado.app.domain.gateway.LabSettingsGateway
-import org.koin.core.context.GlobalContext
-
-@Deprecated("使用 LabSettingsGateway.currentSettings")
-object LabConfig {
-    private val settings get() = GlobalContext.get().get<LabSettingsGateway>().currentSettings
-    val labEnabled get() = settings.enabled
-    val eInkDisplay get() = settings.eInkDisplay
-    val eyeProtection get() = settings.eyeProtection
-}

+ 0 - 1
app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigContract.kt

@@ -11,7 +11,6 @@ data class LabConfigUiState(
 sealed interface LabConfigIntent {
     data class SetEnabled(val value: Boolean) : LabConfigIntent
     data class SetEInkDisplay(val value: Boolean) : LabConfigIntent
-    data class SetEyeProtection(val value: Boolean) : LabConfigIntent
 }
 
 sealed interface LabConfigEffect

+ 0 - 9
app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigScreen.kt

@@ -83,15 +83,6 @@ fun LabConfigScreen(
                         if (settings.eInkDisplay) {
                             HintText(R.string.lab_eink_display_hint)
                         }
-                        SwitchSettingItem(
-                            title = stringResource(R.string.lab_eye_protection_title),
-                            description = stringResource(R.string.lab_eye_protection_summary),
-                            checked = settings.eyeProtection,
-                            onCheckedChange = { onIntent(LabConfigIntent.SetEyeProtection(it)) },
-                        )
-                        if (settings.eyeProtection) {
-                            HintText(R.string.lab_eye_protection_hint)
-                        }
                     }
                 }
             }

+ 0 - 1
app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigViewModel.kt

@@ -35,7 +35,6 @@ class LabConfigViewModel(
                 when (intent) {
                     is LabConfigIntent.SetEnabled -> settings.copy(enabled = intent.value)
                     is LabConfigIntent.SetEInkDisplay -> settings.copy(eInkDisplay = intent.value)
-                    is LabConfigIntent.SetEyeProtection -> settings.copy(eyeProtection = intent.value)
                 }
             }
         }

+ 8 - 0
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigContract.kt

@@ -17,6 +17,9 @@ data class ReadConfigUiState(
     val textBottomJustify: Boolean = true,
     val adaptSpecialStyle: Boolean = true,
     val useZhLayout: Boolean = false,
+    val eyeProtectionEnabled: Boolean = false,
+    val eyeProtectionIntensity: Int = 50,
+    val eyeProtectionAutoNight: Boolean = false,
     val showBrightnessView: String = "0",
     val brightnessVwPos: String = "1",
     val brightnessAuto: Boolean = true,
@@ -51,6 +54,7 @@ data class ReadConfigUiState(
 sealed interface ReadConfigSheet {
     data object PageKeys : ReadConfigSheet
     data object ClickActions : ReadConfigSheet
+    data object EyeProtection : ReadConfigSheet
 }
 
 sealed interface ReadConfigIntent {
@@ -70,6 +74,10 @@ sealed interface ReadConfigIntent {
     data class TextBottomJustifyChanged(val value: Boolean) : ReadConfigIntent
     data class AdaptSpecialStyleChanged(val value: Boolean) : ReadConfigIntent
     data class UseZhLayoutChanged(val value: Boolean) : ReadConfigIntent
+    data object OpenEyeProtection : ReadConfigIntent
+    data class EyeProtectionEnabledChanged(val value: Boolean) : ReadConfigIntent
+    data class EyeProtectionIntensityChanged(val value: Int) : ReadConfigIntent
+    data class EyeProtectionAutoNightChanged(val value: Boolean) : ReadConfigIntent
     data class ShowBrightnessViewChanged(val value: String) : ReadConfigIntent
     data class BrightnessVwPosChanged(val value: String) : ReadConfigIntent
     data class UseUnderlineChanged(val value: Boolean) : ReadConfigIntent

+ 22 - 0
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigScreen.kt

@@ -11,6 +11,7 @@ import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import io.legado.app.R
 import io.legado.app.ui.book.read.sheet.ClickActionConfigSheet
+import io.legado.app.ui.book.read.sheet.EyeProtectionConfigSheet
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.SplicedColumnGroup
@@ -168,6 +169,16 @@ fun ReadConfigScreen(
                     }
                 )
 
+                ClickableSettingItem(
+                    title = stringResource(R.string.eye_protection),
+                    option = if (settings.eyeProtectionEnabled) {
+                        stringResource(R.string.enabled)
+                    } else {
+                        stringResource(R.string.disabled)
+                    },
+                    onClick = { onIntent(ReadConfigIntent.OpenEyeProtection) },
+                )
+
                     DropdownListSettingItem(
                     title = stringResource(R.string.show_brightness_view),
                         selectedValue = settings.showBrightnessView,
@@ -400,4 +411,15 @@ fun ReadConfigScreen(
             onDismissRequest = { onIntent(ReadConfigIntent.DismissSheet) },
         )
     }
+
+    EyeProtectionConfigSheet(
+        show = state.activeSheet == ReadConfigSheet.EyeProtection,
+        enabled = settings.eyeProtectionEnabled,
+        intensity = settings.eyeProtectionIntensity,
+        autoNight = settings.eyeProtectionAutoNight,
+        onDismissRequest = { onIntent(ReadConfigIntent.DismissSheet) },
+        onEnabledChange = { onIntent(ReadConfigIntent.EyeProtectionEnabledChanged(it)) },
+        onIntensityChange = { onIntent(ReadConfigIntent.EyeProtectionIntensityChanged(it)) },
+        onAutoNightChange = { onIntent(ReadConfigIntent.EyeProtectionAutoNightChanged(it)) },
+    )
 }

+ 20 - 1
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt

@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.domain.gateway.ReadSettingsGateway
 import io.legado.app.domain.gateway.ReadSettingsUpdate
 import io.legado.app.domain.model.settings.ReadSettings
+import io.legado.app.ui.book.read.EyeProtection
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
@@ -37,6 +38,7 @@ class ReadConfigViewModel(
         when (intent) {
             ReadConfigIntent.OpenPageKeys -> setSheet(ReadConfigSheet.PageKeys)
             ReadConfigIntent.OpenClickActions -> setSheet(ReadConfigSheet.ClickActions)
+            ReadConfigIntent.OpenEyeProtection -> setSheet(ReadConfigSheet.EyeProtection)
             ReadConfigIntent.DismissSheet -> setSheet(null)
             else -> updateSetting(intent.toSettingsUpdate())
         }
@@ -48,7 +50,17 @@ class ReadConfigViewModel(
 
     private fun updateSetting(update: ReadSettingsUpdate) {
         viewModelScope.launch {
-            runCatching { settingsGateway.update(update) }
+            runCatching {
+                settingsGateway.update(update)
+                if (update is ReadSettingsUpdate.EyeProtectionAutoNight && update.value) {
+                    EyeProtection.syncEnabledForNight(
+                        isNight = ReadConfig.isNightTheme,
+                        autoNight = true,
+                    )?.let { enabled ->
+                        settingsGateway.update(ReadSettingsUpdate.EyeProtectionEnabled(enabled))
+                    }
+                }
+            }
                 .onSuccess {
                     applyReadSetting(update)
                     if (update is ReadSettingsUpdate.PageKeys) setSheet(null)
@@ -78,6 +90,10 @@ private fun ReadConfigIntent.toSettingsUpdate(): ReadSettingsUpdate = when (this
     is ReadConfigIntent.TextBottomJustifyChanged -> ReadSettingsUpdate.TextBottomJustify(value)
     is ReadConfigIntent.AdaptSpecialStyleChanged -> ReadSettingsUpdate.AdaptSpecialStyle(value)
     is ReadConfigIntent.UseZhLayoutChanged -> ReadSettingsUpdate.UseZhLayout(value)
+    ReadConfigIntent.OpenEyeProtection -> error("Sheet intents do not update settings")
+    is ReadConfigIntent.EyeProtectionEnabledChanged -> ReadSettingsUpdate.EyeProtectionEnabled(value)
+    is ReadConfigIntent.EyeProtectionIntensityChanged -> ReadSettingsUpdate.EyeProtectionIntensity(value)
+    is ReadConfigIntent.EyeProtectionAutoNightChanged -> ReadSettingsUpdate.EyeProtectionAutoNight(value)
     is ReadConfigIntent.ShowBrightnessViewChanged -> ReadSettingsUpdate.ShowBrightnessView(value)
     is ReadConfigIntent.BrightnessVwPosChanged -> ReadSettingsUpdate.BrightnessVwPos(value)
     is ReadConfigIntent.UseUnderlineChanged -> ReadSettingsUpdate.UseUnderline(value)
@@ -121,6 +137,9 @@ private fun ReadSettings.toUiState(activeSheet: ReadConfigSheet?): ReadConfigUiS
         textBottomJustify = textBottomJustify,
         adaptSpecialStyle = adaptSpecialStyle,
         useZhLayout = useZhLayout,
+        eyeProtectionEnabled = eyeProtectionEnabled,
+        eyeProtectionIntensity = eyeProtectionIntensity,
+        eyeProtectionAutoNight = eyeProtectionAutoNight,
         showBrightnessView = showBrightnessView,
         brightnessVwPos = brightnessVwPos,
         brightnessAuto = brightnessAuto,

+ 0 - 5
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt

@@ -49,11 +49,6 @@ object ThemeConfig {
     val defaultHomePage get() = shell.defaultHomePage
     val autoRefreshBook get() = other.autoRefresh
     val autoCheckNewBackup get() = backup.autoCheckNewBackup
-    val eyeProtectionEnabled get() = theme.eyeProtectionEnabled
-    val colorTemperature get() = theme.colorTemperature
-    val eyeProtectionSchedule get() = theme.eyeProtectionSchedule
-    val eyeProtectionStartTime get() = theme.eyeProtectionStartTime
-    val eyeProtectionEndTime get() = theme.eyeProtectionEndTime
 
     fun initNightMode() {
         val mode = when (themeMode) {

+ 0 - 14
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigContract.kt

@@ -26,11 +26,6 @@ sealed interface ThemeConfigSheet {
     data object Font : ThemeConfigSheet
 }
 
-enum class ThemeTimeField {
-    EyeProtectionStart,
-    EyeProtectionEnd,
-}
-
 sealed interface ThemeConfigIntent {
     data class UpdateAppShell(val update: AppShellSettingsUpdate) : ThemeConfigIntent
     data class UpdateTheme(val update: ThemeSettingsUpdate) : ThemeConfigIntent
@@ -56,11 +51,6 @@ sealed interface ThemeConfigIntent {
     data object ClearAppFont : ThemeConfigIntent
     data class SetFontFolder(val path: String) : ThemeConfigIntent
     data object RequestFontFolder : ThemeConfigIntent
-    data class RequestTimePicker(
-        val field: ThemeTimeField,
-        val currentValue: String,
-    ) : ThemeConfigIntent
-    data class SetTime(val field: ThemeTimeField, val value: String) : ThemeConfigIntent
     data object DismissRefactorTip : ThemeConfigIntent
 }
 
@@ -71,9 +61,5 @@ sealed interface ThemeConfigEffect {
     data object OpenFontFolder : ThemeConfigEffect
     data class OpenNavigationIcon(val destination: String) : ThemeConfigEffect
     data class OpenBackgroundImage(val dark: Boolean) : ThemeConfigEffect
-    data class OpenTimePicker(
-        val field: ThemeTimeField,
-        val currentValue: String,
-    ) : ThemeConfigEffect
     data class ShowToast(val stringRes: Int) : ThemeConfigEffect
 }

+ 1 - 27
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigRouteScreen.kt

@@ -17,10 +17,9 @@ import io.legado.app.help.config.ThemeConfigStore
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.takePersistablePermissionSafely
 import io.legado.app.utils.toastOnUi
-import java.io.File
-import java.util.Locale
 import kotlinx.coroutines.flow.collectLatest
 import org.koin.androidx.compose.koinViewModel
+import java.io.File
 
 @Composable
 fun ThemeConfigRouteScreen(
@@ -89,31 +88,6 @@ fun ThemeConfigRouteScreen(
                     pendingBackgroundDark = effect.dark
                     backgroundImageLauncher.launch("image/*")
                 }
-                is ThemeConfigEffect.OpenTimePicker -> {
-                    val parts = effect.currentValue.split(":")
-                    val defaultHour = if (effect.field == ThemeTimeField.EyeProtectionStart) 22 else 7
-                    val hour = parts.getOrNull(0)?.toIntOrNull() ?: defaultHour
-                    val minute = parts.getOrNull(1)?.toIntOrNull() ?: 0
-                    android.app.TimePickerDialog(
-                        context,
-                        { _, selectedHour, selectedMinute ->
-                            viewModel.onIntent(
-                                ThemeConfigIntent.SetTime(
-                                    effect.field,
-                                    String.format(
-                                        Locale.US,
-                                        "%02d:%02d",
-                                        selectedHour.coerceIn(0, 23),
-                                        selectedMinute.coerceIn(0, 59),
-                                    ),
-                                )
-                            )
-                        },
-                        hour.coerceIn(0, 23),
-                        minute.coerceIn(0, 59),
-                        true,
-                    ).show()
-                }
                 is ThemeConfigEffect.ShowToast -> context.toastOnUi(effect.stringRes)
             }
         }

+ 0 - 88
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -522,94 +522,6 @@ fun ThemeConfigScreen(
                     )
                 }
 
-                SplicedColumnGroup(title = stringResource(R.string.eye_protection)) {
-                    SwitchSettingItem(
-                        title = stringResource(R.string.eye_protection_enabled),
-                        description = stringResource(R.string.eye_protection_enabled_summary),
-                        checked = theme.eyeProtectionEnabled,
-                        onCheckedChange = {
-                            onIntent(
-                                ThemeConfigIntent.UpdateTheme(
-                                    ThemeSettingsUpdate.BooleanValue(
-                                        ThemeBooleanSetting.EyeProtectionEnabled,
-                                        it,
-                                    )
-                                )
-                            )
-                        }
-                    )
-
-                    AnimatedVisibility(visible = theme.eyeProtectionEnabled) {
-                        Column {
-                            SliderSettingItem(
-                                title = stringResource(R.string.color_temperature),
-                                description = stringResource(
-                                    R.string.color_temperature_summary,
-                                    theme.colorTemperature
-                                ),
-                                value = theme.colorTemperature.toFloat(),
-                                defaultValue = 50f,
-                                valueRange = 0f..100f,
-                                onValueChange = {
-                                    onIntent(
-                                        ThemeConfigIntent.UpdateTheme(
-                                            ThemeSettingsUpdate.IntValue(
-                                                ThemeIntSetting.ColorTemperature,
-                                                it.toInt(),
-                                            )
-                                        )
-                                    )
-                                }
-                            )
-
-                            SwitchSettingItem(
-                                title = stringResource(R.string.eye_protection_schedule),
-                                description = stringResource(R.string.eye_protection_schedule_summary),
-                                checked = theme.eyeProtectionSchedule,
-                                onCheckedChange = {
-                                    onIntent(
-                                        ThemeConfigIntent.UpdateTheme(
-                                            ThemeSettingsUpdate.BooleanValue(
-                                                ThemeBooleanSetting.EyeProtectionSchedule,
-                                                it,
-                                            )
-                                        )
-                                    )
-                                }
-                            )
-
-                            AnimatedVisibility(visible = theme.eyeProtectionSchedule) {
-                                Column {
-                                    ClickableSettingItem(
-                                        title = stringResource(R.string.eye_protection_start_time),
-                                        option = theme.eyeProtectionStartTime,
-                                        onClick = {
-                                            onIntent(
-                                                ThemeConfigIntent.RequestTimePicker(
-                                                    ThemeTimeField.EyeProtectionStart,
-                                                    theme.eyeProtectionStartTime,
-                                                )
-                                            )
-                                        }
-                                    )
-                                    ClickableSettingItem(
-                                        title = stringResource(R.string.eye_protection_end_time),
-                                        option = theme.eyeProtectionEndTime,
-                                        onClick = {
-                                            onIntent(
-                                                ThemeConfigIntent.RequestTimePicker(
-                                                    ThemeTimeField.EyeProtectionEnd,
-                                                    theme.eyeProtectionEndTime,
-                                                )
-                                            )
-                                        }
-                                    )
-                                }
-                            }
-                        }
-                    }
-                }
-
                 SplicedColumnGroup(title = stringResource(R.string.compose_related)) {
                     if (!isMiuixEngine) {
                         SwitchSettingItem(

+ 3 - 15
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigViewModel.kt

@@ -9,6 +9,7 @@ import io.legado.app.domain.gateway.AppShellBooleanSetting
 import io.legado.app.domain.gateway.AppShellSettingsGateway
 import io.legado.app.domain.gateway.AppShellSettingsUpdate
 import io.legado.app.domain.gateway.AppShellStringSetting
+import io.legado.app.domain.gateway.LabSettingsGateway
 import io.legado.app.domain.gateway.ReadSettingsGateway
 import io.legado.app.domain.gateway.ReadSettingsUpdate
 import io.legado.app.domain.gateway.ThemeBooleanSetting
@@ -16,7 +17,6 @@ import io.legado.app.domain.gateway.ThemeIntSetting
 import io.legado.app.domain.gateway.ThemeSettingsGateway
 import io.legado.app.domain.gateway.ThemeSettingsUpdate
 import io.legado.app.domain.gateway.ThemeStringSetting
-import io.legado.app.domain.gateway.LabSettingsGateway
 import io.legado.app.ui.main.MainDestination
 import io.legado.app.utils.FileDoc
 import io.legado.app.utils.FileUtils
@@ -24,8 +24,6 @@ import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.externalFiles
 import io.legado.app.utils.inputStream
 import io.legado.app.utils.openInputStream
-import java.io.File
-import java.io.FileOutputStream
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -34,6 +32,8 @@ import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 import splitties.init.appCtx
+import java.io.File
+import java.io.FileOutputStream
 
 class ThemeConfigViewModel(
     private val appShellSettingsGateway: AppShellSettingsGateway,
@@ -116,10 +116,6 @@ class ThemeConfigViewModel(
                 readSettingsGateway.update(ReadSettingsUpdate.FontFolder(intent.path))
             }
             ThemeConfigIntent.RequestFontFolder -> _effects.tryEmit(ThemeConfigEffect.OpenFontFolder)
-            is ThemeConfigIntent.RequestTimePicker -> _effects.tryEmit(
-                ThemeConfigEffect.OpenTimePicker(intent.field, intent.currentValue)
-            )
-            is ThemeConfigIntent.SetTime -> setTime(intent.field, intent.value)
             ThemeConfigIntent.DismissRefactorTip -> updateTheme(
                 ThemeSettingsUpdate.BooleanValue(ThemeBooleanSetting.ShowRefactorTip, false)
             )
@@ -257,14 +253,6 @@ class ThemeConfigViewModel(
         updateAppShell(AppShellSettingsUpdate.StringValue(setting, intent.path))
     }
 
-    private fun setTime(field: ThemeTimeField, value: String) {
-        val setting = when (field) {
-            ThemeTimeField.EyeProtectionStart -> ThemeStringSetting.EyeProtectionStartTime
-            ThemeTimeField.EyeProtectionEnd -> ThemeStringSetting.EyeProtectionEndTime
-        }
-        updateTheme(ThemeSettingsUpdate.StringValue(setting, value))
-    }
-
     private fun setBackground(uriString: String, dark: Boolean) {
         viewModelScope.launch(Dispatchers.IO) {
             runCatching {

+ 0 - 9
app/src/main/res/layout/activity_manga.xml

@@ -92,13 +92,4 @@
         </LinearLayout>
 
     </FrameLayout>
-
-    <io.legado.app.ui.book.read.EyeProtectionOverlay
-        android:id="@+id/eye_protection_overlay"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:clickable="false"
-        android:focusable="false"
-        android:visibility="gone" />
-
 </FrameLayout>

+ 4 - 9
app/src/main/res/values-zh-rCN/strings.xml

@@ -2207,20 +2207,15 @@
     <string name="lab_eink_display_title">墨水屏显示</string>
     <string name="lab_eink_display_summary">在平板界面显示墨水屏选项</string>
     <string name="lab_eink_display_hint">可在 主题 → 显示 中调整墨水屏设置</string>
-    <string name="lab_eye_protection_title">护眼模式</string>
-    <string name="lab_eye_protection_summary">为漫画和小说阅读器叠加暖色滤镜,减少蓝光</string>
-    <string name="lab_eye_protection_hint">可在 主题 → 外观 中调整护眼模式</string>
 
     <!-- 护眼模式 -->
     <string name="eye_protection">护眼模式</string>
     <string name="eye_protection_enabled">启用护眼模式</string>
     <string name="eye_protection_enabled_summary">使用暖色调降低蓝光</string>
-    <string name="color_temperature">屏幕色温</string>
-    <string name="color_temperature_summary">当前色温:%1$d</string>
-    <string name="eye_protection_schedule">按时间段启用</string>
-    <string name="eye_protection_schedule_summary">仅在配置的时间段内生效</string>
-    <string name="eye_protection_start_time">开始时间</string>
-    <string name="eye_protection_end_time">结束时间</string>
+    <string name="eye_protection_intensity">护眼强度</string>
+    <string name="eye_protection_intensity_summary">当前强度:%1$d</string>
+    <string name="eye_protection_auto_night">深色模式时自动开启</string>
+    <string name="eye_protection_auto_night_summary">跟随阅读界面的深色/浅色模式自动切换</string>
 
     <!-- 标签规则 -->
     <string name="highlight_tag_config">标签规则</string>

+ 4 - 9
app/src/main/res/values-zh-rHK/strings.xml

@@ -1624,8 +1624,6 @@
     <string name="clipboard_empty_or_invalid">剪貼簿為空或格式無效</string>
     <string name="coll_volume">收起全部卷</string>
     <string name="collapse">收起</string>
-    <string name="color_temperature">色溫</string>
-    <string name="color_temperature_summary">目前色溫:%1$d</string>
     <string name="color_value">顏色值</string>
     <string name="compact_details">緊湊詳情</string>
     <string name="compact_grid">緊湊網格</string>
@@ -1730,10 +1728,10 @@
     <string name="eye_protection">護眼模式</string>
     <string name="eye_protection_enabled">啟用護眼模式</string>
     <string name="eye_protection_enabled_summary">透過暖色調減少藍光</string>
-    <string name="eye_protection_end_time">結束時間</string>
-    <string name="eye_protection_schedule">定時啟用</string>
-    <string name="eye_protection_schedule_summary">僅在設定的時間範圍內啟用</string>
-    <string name="eye_protection_start_time">開始時間</string>
+    <string name="eye_protection_intensity">護眼強度</string>
+    <string name="eye_protection_intensity_summary">目前強度:%1$d</string>
+    <string name="eye_protection_auto_night">深色閱讀模式時自動開啟</string>
+    <string name="eye_protection_auto_night_summary">跟隨閱讀介面的深色/淺色模式自動切換</string>
     <string name="filter_hide_in_shelf">隱藏書架中同源書籍</string>
     <string name="filter_hide_same_name_author">隱藏書架中不同源書籍</string>
     <string name="filter_show_all">顯示全部</string>
@@ -1828,9 +1826,6 @@
     <string name="lab_eink_display_title">E-Ink 顯示</string>
     <string name="lab_enabled_summary">開啟以顯示實驗性功能。這些功能可能不穩定。</string>
     <string name="lab_enabled_title">啟用實驗室</string>
-    <string name="lab_eye_protection_hint">可在主題 → 外觀中調整護眼設定</string>
-    <string name="lab_eye_protection_summary">為漫畫和小說閱讀器應用暖色調以減少眼睛疲勞</string>
-    <string name="lab_eye_protection_title">護眼模式</string>
     <string name="lab_setting">實驗室</string>
     <string name="less_count">較少(</string>
     <string name="light_mode">淺色模式</string>

+ 4 - 9
app/src/main/res/values-zh-rTW/strings.xml

@@ -2157,18 +2157,13 @@
     <string name="lab_eink_display_title">電子墨水屏顯示</string>
     <string name="lab_eink_display_summary">在平板介面中顯示電子墨水屏選項</string>
     <string name="lab_eink_display_hint">您可以在主題 → 顯示選項中調整電子墨水屏顯示設定</string>
-    <string name="lab_eye_protection_title">護眼模式</string>
-    <string name="lab_eye_protection_summary">為漫畫和小說閱讀器套用暖色色調,減少眼睛疲勞</string>
-    <string name="lab_eye_protection_hint">您可以在主題 → 外觀中調整護眼設定</string>
     <string name="eye_protection">護眼模式</string>
     <string name="eye_protection_enabled">啟用護眼模式</string>
     <string name="eye_protection_enabled_summary">透過暖色色調減少藍光</string>
-    <string name="color_temperature">色溫</string>
-    <string name="color_temperature_summary">目前色溫:%1$d</string>
-    <string name="eye_protection_schedule">定時</string>
-    <string name="eye_protection_schedule_summary">僅在設定的時間範圍內生效</string>
-    <string name="eye_protection_start_time">開始時間</string>
-    <string name="eye_protection_end_time">結束時間</string>
+    <string name="eye_protection_intensity">護眼強度</string>
+    <string name="eye_protection_intensity_summary">目前強度:%1$d</string>
+    <string name="eye_protection_auto_night">深色閱讀模式時自動開啟</string>
+    <string name="eye_protection_auto_night_summary">跟隨閱讀介面的深色/淺色模式自動切換</string>
     <string name="highlight_tag_config">標籤規則</string>
     <string name="import_highlight_tag_rule">匯入標籤規則</string>
     <string name="highlight_tag_title">標題</string>

+ 4 - 9
app/src/main/res/values/strings.xml

@@ -2228,20 +2228,15 @@
     <string name="lab_eink_display_title">E-Ink Display</string>
     <string name="lab_eink_display_summary">Show E-Ink display option in the tablet interface</string>
     <string name="lab_eink_display_hint">You can adjust E-Ink display settings in Theme → Display options</string>
-    <string name="lab_eye_protection_title">Eye Protection</string>
-    <string name="lab_eye_protection_summary">Apply warm color tint to comic and novel readers to reduce eye strain</string>
-    <string name="lab_eye_protection_hint">You can adjust eye protection settings in Theme → Appearance</string>
 
     <!-- Eye Protection -->
     <string name="eye_protection">Eye Protection</string>
     <string name="eye_protection_enabled">Enable Eye Protection</string>
     <string name="eye_protection_enabled_summary">Reduce blue light via warm color tint</string>
-    <string name="color_temperature">Color Temperature</string>
-    <string name="color_temperature_summary">Current temperature: %1$d</string>
-    <string name="eye_protection_schedule">Scheduled</string>
-    <string name="eye_protection_schedule_summary">Only apply during the configured time range</string>
-    <string name="eye_protection_start_time">Start Time</string>
-    <string name="eye_protection_end_time">End Time</string>
+    <string name="eye_protection_intensity">Eye protection intensity</string>
+    <string name="eye_protection_intensity_summary">Current intensity: %1$d</string>
+    <string name="eye_protection_auto_night">Enable with dark reading mode</string>
+    <string name="eye_protection_auto_night_summary">Automatically follow the reader day/night mode</string>
 
     <!-- Tag Rules -->
     <string name="highlight_tag_config">Tag Rules</string>