Просмотр исходного кода

feat:添加浅色、深色切换提醒功能 (#1584)

* feat:添加浅色、深色切换提醒功能

* refactor: 重构SwitchDayNightReminder为通用ActionReminder
- ReadConfigScreen 补上 主题模式切换提醒 开关
fansan 4 месяцев назад
Родитель
Сommit
af5094801c

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

@@ -103,6 +103,7 @@ object PreferKey {
     const val adaptSpecialStyle = "adaptSpecialStyle"
     const val autoClearExpired = "autoClearExpired"
     const val autoChangeSource = "autoChangeSource"
+    const val autoSuggestDayNight = "autoSuggestDayNight"
     const val importKeepName = "importKeepName"
     const val importKeepGroup = "importKeepGroup"
     const val screenOrientation = "screenOrientation"

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

@@ -45,6 +45,7 @@ data class ReadPreferences(
     val sliderVibrator: Boolean = false,
     val selectVibrator: Boolean = false,
     val autoChangeSource: Boolean = true,
+    val autoSuggestDayNight: Boolean = false,
     val selectText: Boolean = true,
     val noAnimScrollPage: Boolean = false,
     val clickImgWay: String = "2",
@@ -214,6 +215,9 @@ class ReadSettingsRepository(
     suspend fun setAutoChangeSource(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.autoChangeSource, value)
 
+    suspend fun setAutoSuggestDayNight(value: Boolean) =
+        settingsRepository.putBoolean(PreferKey.autoSuggestDayNight, value)
+
     suspend fun setSelectText(value: Boolean) =
         settingsRepository.putBoolean(PreferKey.selectText, value)
 
@@ -422,6 +426,7 @@ class ReadSettingsRepository(
             sliderVibrator = this[Keys.SliderVibrator] ?: false,
             selectVibrator = this[Keys.SelectVibrator] ?: false,
             autoChangeSource = this[Keys.AutoChangeSource] ?: true,
+            autoSuggestDayNight = this[Keys.AutoSuggestDayNight] ?: false,
             selectText = this[Keys.SelectText] ?: true,
             noAnimScrollPage = this[Keys.NoAnimScrollPage] ?: false,
             clickImgWay = this[Keys.ClickImgWay] ?: "2",
@@ -522,6 +527,7 @@ class ReadSettingsRepository(
         val SliderVibrator = booleanPreferencesKey(PreferKey.sliderVibrator)
         val SelectVibrator = booleanPreferencesKey(PreferKey.selectVibrator)
         val AutoChangeSource = booleanPreferencesKey(PreferKey.autoChangeSource)
+        val AutoSuggestDayNight = booleanPreferencesKey(PreferKey.autoSuggestDayNight)
         val SelectText = booleanPreferencesKey(PreferKey.selectText)
         val NoAnimScrollPage = booleanPreferencesKey(PreferKey.noAnimScrollPage)
         val ClickImgWay = stringPreferencesKey(PreferKey.clickImgWay)

+ 107 - 0
app/src/main/java/io/legado/app/ui/book/read/ActionReminder.kt

@@ -0,0 +1,107 @@
+package io.legado.app.ui.book.read
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutVertically
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.card.NormalCard
+import kotlinx.coroutines.delay
+import kotlin.time.Duration.Companion.seconds
+
+@Composable
+fun ActionReminder(
+    modifier: Modifier = Modifier,
+    reminder: ReminderUiState?,
+    onAction: (ReminderUiState) -> Unit,
+    onDismiss: () -> Unit,
+) {
+    val show = reminder != null
+    var currentReminder by remember { mutableStateOf(reminder) }
+    if (reminder != null) {
+        currentReminder = reminder
+    }
+
+    LaunchedEffect(show, reminder?.id) {
+        if (show) {
+            delay(4.seconds)
+            onDismiss()
+        }
+    }
+
+    Box(
+        modifier = modifier
+            .fillMaxSize()
+            .navigationBarsPadding(),
+        contentAlignment = Alignment.BottomCenter,
+    ) {
+        AnimatedVisibility(
+            visible = show,
+            enter = slideInVertically(initialOffsetY = { it }) + fadeIn(),
+            exit = slideOutVertically(targetOffsetY = { it }) + fadeOut(),
+        ) {
+            currentReminder?.let { target ->
+                NormalCard(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(horizontal = 16.dp, vertical = 8.dp),
+                ) {
+                    Row(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .padding(start = 16.dp, top = 8.dp, end = 12.dp, bottom = 8.dp),
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.SpaceBetween,
+                    ) {
+                        Text(
+                            text = target.message,
+                            fontSize = 13.sp,
+                            style = LegadoTheme.typography.bodyMedium,
+                            color = LegadoTheme.colorScheme.onSurface,
+                            maxLines = 2,
+                            overflow = TextOverflow.Ellipsis,
+                            modifier = Modifier
+                                .weight(1f)
+                                .padding(end = 12.dp),
+                        )
+                        if (target.actionText != null) {
+                            Button(
+                                onClick = {
+                                    onDismiss()
+                                    onAction(target)
+                                },
+                                modifier = Modifier.height(32.dp),
+                                contentPadding = PaddingValues(horizontal = 14.dp, vertical = 0.dp),
+                            ) {
+                                Text(target.actionText, fontSize = 12.sp)
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}

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

@@ -21,6 +21,15 @@ import kotlinx.collections.immutable.ImmutableList
 import kotlinx.collections.immutable.ImmutableMap
 import kotlinx.collections.immutable.persistentListOf
 import kotlinx.collections.immutable.persistentMapOf
+import java.util.UUID
+
+@Stable
+data class ReminderUiState(
+    val id: String = UUID.randomUUID().toString(),
+    val message: String,
+    val actionText: String? = null,
+    val actionIntent: ReadBookIntent? = null,
+)
 
 @Stable
 data class ReadBookMenuState(
@@ -94,6 +103,7 @@ data class ReadBookUiState(
     val isLocalBook: Boolean = true,
     val msg: String? = null,
     val isInitFinish: Boolean = false,
+    val activeReminder: ReminderUiState? = null,
     // Search
     val searchMenuVisible: Boolean = false,
     val isShowingSearchResult: Boolean = false,
@@ -242,6 +252,8 @@ sealed interface ReadBookIntent {
     // Initialization
     data class InitData(val intent: android.content.Intent) : ReadBookIntent
     data class InitReadBookConfig(val intent: android.content.Intent) : ReadBookIntent
+    data class CheckSwitchDayNight(val lux: Float) : ReadBookIntent
+    data object DismissReminder : ReadBookIntent
 
     // Navigation
     data object NextPage : ReadBookIntent
@@ -1175,6 +1187,9 @@ sealed interface ConfigUpdate {
     data class AutoChangeSource(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }
+    data class AutoSuggestDayNight(val value: Boolean) : ConfigUpdate {
+        override val actions = emptySet<ConfigUpdateAction>()
+    }
     data class SelectText(val value: Boolean) : ConfigUpdate {
         override val actions = emptySet<ConfigUpdateAction>()
     }

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

@@ -17,10 +17,18 @@ import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import android.content.Context
+import android.hardware.Sensor
+import android.hardware.SensorEvent
+import android.hardware.SensorEventListener
+import android.hardware.SensorManager
+import androidx.compose.runtime.DisposableEffect
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.viewinterop.AndroidView
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import io.legado.app.ui.config.readConfig.ReadConfig
 import androidx.lifecycle.compose.LocalLifecycleOwner
 import androidx.lifecycle.repeatOnLifecycle
 import com.kyant.backdrop.backdrops.layerBackdrop
@@ -233,6 +241,44 @@ fun ReadBookRouteScreen(
         viewModel.onIntent(ReadBookIntent.BookInfoResult(result.resultCode == android.app.Activity.RESULT_OK))
     }
 
+    DisposableEffect(lifecycleOwner) {
+        val sensorManager = context.getSystemService(Context.SENSOR_SERVICE) as? SensorManager
+        val lightSensor = sensorManager?.getDefaultSensor(Sensor.TYPE_LIGHT)
+        var listener: SensorEventListener? = null
+
+        val observer = LifecycleEventObserver { _, event ->
+            if (event == Lifecycle.Event.ON_RESUME) {
+                if (!ReadConfig.autoSuggestDayNight) return@LifecycleEventObserver
+                if (lightSensor != null) {
+                    listener = object : SensorEventListener {
+                        override fun onSensorChanged(sensorEvent: SensorEvent?) {
+                            sensorEvent?.values?.firstOrNull()?.let { lux ->
+                                //Log.d("fansangg","lux = $lux")
+                                viewModel.onIntent(ReadBookIntent.CheckSwitchDayNight(lux))
+                            }
+                            listener?.let { sensorManager.unregisterListener(it) }
+                            listener = null
+                        }
+
+                        override fun onAccuracyChanged(sensor: Sensor?, accuracy: Int) {}
+                    }
+                    sensorManager.registerListener(listener, lightSensor, SensorManager.SENSOR_DELAY_NORMAL)
+                }
+            } else if (event == Lifecycle.Event.ON_PAUSE) {
+                listener?.let {
+                    sensorManager?.unregisterListener(it)
+                    listener = null
+                }
+            }
+        }
+
+        lifecycleOwner.lifecycle.addObserver(observer)
+        onDispose {
+            lifecycleOwner.lifecycle.removeObserver(observer)
+            listener?.let { sensorManager?.unregisterListener(it) }
+        }
+    }
+
     // ── Effect collection: route handles launcher effects, rest goes to bridge ──
 
     LaunchedEffect(viewModel) {

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

@@ -503,4 +503,10 @@ fun ReadBookScreen(
         // Sheets using AppModalBottomSheet are composed unconditionally above
         else -> {}
     }
+
+    ActionReminder(
+        reminder = state.activeReminder,
+        onAction = { reminder -> reminder.actionIntent?.let { onIntent(it) } },
+        onDismiss = { onIntent(ReadBookIntent.DismissReminder) },
+    )
 }

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

@@ -8,6 +8,8 @@ import android.provider.OpenableColumns
 import android.speech.tts.TextToSpeech
 import android.util.Base64
 import androidx.lifecycle.viewModelScope
+import androidx.core.graphics.toColorInt
+import androidx.core.graphics.ColorUtils as AndroidColorUtils
 import io.legado.app.BuildConfig
 import io.legado.app.R
 import io.legado.app.constant.AppConst
@@ -76,6 +78,7 @@ import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.widget.components.importComponents.BaseImportUiState
 import io.legado.app.ui.widget.components.importComponents.ImportItemWrapper
 import io.legado.app.ui.widget.components.importComponents.ImportStatus
+import io.legado.app.utils.ColorUtils
 import io.legado.app.utils.GSON
 import io.legado.app.utils.ImageSaveUtils
 import io.legado.app.utils.NetworkUtils
@@ -120,6 +123,7 @@ import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.io.FileNotFoundException
 import kotlin.coroutines.coroutineContext
+import kotlin.time.Duration.Companion.milliseconds
 
 /**
  * 阅读界面 ViewModel — MVI/UDF 架构
@@ -196,6 +200,8 @@ class ReadBookViewModel(
                 justInitData = true
             }
             is ReadBookIntent.InitReadBookConfig -> initReadBookConfig(intent.intent)
+            is ReadBookIntent.CheckSwitchDayNight -> checkSwitchDayNight(intent.lux)
+            is ReadBookIntent.DismissReminder -> dismissReminder()
             is ReadBookIntent.NextPage -> ReadBook.moveToNextPage()
             is ReadBookIntent.PrevPage -> ReadBook.moveToPrevPage()
             is ReadBookIntent.NextChapter -> ReadBook.moveToNextChapter(upContent = true)
@@ -3430,6 +3436,12 @@ class ReadBookViewModel(
                     readSettingsRepository.setAutoChangeSource(update.value)
                 }
             }
+            is ConfigUpdate.AutoSuggestDayNight -> {
+                ReadConfig.autoSuggestDayNight = update.value
+                viewModelScope.launch {
+                    readSettingsRepository.setAutoSuggestDayNight(update.value)
+                }
+            }
             is ConfigUpdate.SelectText -> {
                 ReadConfig.selectText = update.value
                 viewModelScope.launch {
@@ -4007,6 +4019,86 @@ class ReadBookViewModel(
             context.toastOnUi("添加书籍失败")
         }
     }
+
+    private var lastSwitchDayNightReminderTime: Long = 0L
+    private val reminderQueue = ArrayDeque<ReminderUiState>()
+
+    private fun showReminder(reminder: ReminderUiState) {
+        if (_uiState.value.activeReminder == null && reminderQueue.isEmpty()) {
+            _uiState.update { it.copy(activeReminder = reminder) }
+        } else {
+            reminderQueue.addLast(reminder)
+        }
+    }
+
+    private fun isReadBgLight(colorInt: Int): Boolean {
+        // io.legado.app.utils.ColorUtils.isColorLight 判断条件是 >= 0.5
+        // 实际很多肉眼觉得亮的颜色会被判断为false,例如 0xFFC5B098
+        return AndroidColorUtils.calculateLuminance(colorInt) >= LIGHT_LUMINANCE_THRESHOLD
+    }
+
+    private fun checkSwitchDayNight(lux: Float) {
+        if (!ReadConfig.autoSuggestDayNight) return
+        if (System.currentTimeMillis() - lastSwitchDayNightReminderTime < REMINDER_COOLDOWN_MS) return
+
+        val isNight = ReadConfig.isNightTheme
+        val styleConfig = _uiState.value.styleConfig
+        if (!isNight && lux <= DARK_LUX_THRESHOLD) {
+            val bgType = styleConfig.bgType
+            val isLightBg = if (bgType == 0) {
+                val colorInt = runCatching { styleConfig.bgStr.toColorInt() }.getOrDefault(0xFFEEEEEE.toInt())
+                isReadBgLight(colorInt)
+            } else {
+                val meanColor = ReadBookConfig.bgMeanColor
+                //Log.d("fansangg","meanColor = ${ColorUtils.intToString(meanColor)},isLight = ${isReadBgLight(meanColor)}")
+                if (meanColor != 0) isReadBgLight(meanColor) else true
+            }
+            if (isLightBg) {
+                lastSwitchDayNightReminderTime = System.currentTimeMillis()
+                showReminder(
+                    ReminderUiState(
+                        message = context.getString(R.string.switch_to_dark_mode_tip),
+                        actionText = context.getString(R.string.switch_action),
+                        actionIntent = ReadBookIntent.ToggleDayNight,
+                    )
+                )
+            }
+        } else if (isNight && lux >= BRIGHT_LUX_THRESHOLD) {
+            val bgTypeNight = styleConfig.bgTypeNight
+            val isDarkBg = if (bgTypeNight == 0) {
+                val colorInt = runCatching { styleConfig.bgStrNight.toColorInt() }.getOrDefault(0xFF000000.toInt())
+                !isReadBgLight(colorInt)
+            } else {
+                val meanColor = ReadBookConfig.bgMeanColor
+                //Log.d("fansangg","meanColor = ${ColorUtils.intToString(meanColor)},isLight = ${isReadBgLight(meanColor)}")
+                if (meanColor != 0) !isReadBgLight(meanColor) else true
+            }
+            if (isDarkBg) {
+                lastSwitchDayNightReminderTime = System.currentTimeMillis()
+                showReminder(
+                    ReminderUiState(
+                        message = context.getString(R.string.switch_to_light_mode_tip),
+                        actionText = context.getString(R.string.switch_action),
+                        actionIntent = ReadBookIntent.ToggleDayNight,
+                    )
+                )
+            }
+        }
+    }
+
+    private fun dismissReminder() {
+        _uiState.update { it.copy(activeReminder = null) }
+        if (reminderQueue.isNotEmpty()) {
+            viewModelScope.launch {
+                //延迟一下,让上一个提醒的动画结束
+                delay(500.milliseconds)
+                if (_uiState.value.activeReminder == null && reminderQueue.isNotEmpty()) {
+                    val next = reminderQueue.removeFirst()
+                    _uiState.update { it.copy(activeReminder = next) }
+                }
+            }
+        }
+    }
 }
 
 private const val TITLE_BAR_ICON_PREFS = "title_bar_icons"
@@ -4015,6 +4107,11 @@ private const val TOOL_BUTTON_PREFS = "tool_button_config"
 private const val TOOL_BUTTON_KEY = "tool_buttons"
 private const val DEFAULT_ENABLED_BUTTON_COUNT = 5
 
+private const val DARK_LUX_THRESHOLD = 10f
+private const val BRIGHT_LUX_THRESHOLD = 100f
+private const val LIGHT_LUMINANCE_THRESHOLD = 0.35
+private const val REMINDER_COOLDOWN_MS = 10 * 60 * 1000L
+
 private data class SearchTextPoint(
     val pageIndex: Int,
     val lineIndex: Int,

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

@@ -125,6 +125,9 @@ fun MoreConfigSheet(
                 onAutoChangeSourceChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoChangeSource(it)))
                 },
+                onAutoSuggestDayNightChange = {
+                    onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.AutoSuggestDayNight(it)))
+                },
                 onSelectTextChange = {
                     onIntent(ReadBookIntent.UpdateConfig(ConfigUpdate.SelectText(it)))
                 },
@@ -298,6 +301,7 @@ private fun OtherSettings(
     onSliderVibratorChange: (Boolean) -> Unit,
     onSelectVibratorChange: (Boolean) -> Unit,
     onAutoChangeSourceChange: (Boolean) -> Unit,
+    onAutoSuggestDayNightChange: (Boolean) -> Unit,
     onSelectTextChange: (Boolean) -> Unit,
     onNoAnimScrollPageChange: (Boolean) -> Unit,
     onOptimizeRenderChange: (Boolean) -> Unit,
@@ -329,6 +333,12 @@ private fun OtherSettings(
         checked = preferences.autoChangeSource,
         onCheckedChange = onAutoChangeSourceChange,
     )
+    TinySwitchSettingItem(
+        title = stringResource(R.string.auto_switch_theme_reminder_title),
+        description = stringResource(R.string.auto_switch_theme_reminder_desc),
+        checked = preferences.autoSuggestDayNight,
+        onCheckedChange = onAutoSuggestDayNightChange,
+    )
     TinySwitchSettingItem(
         title = stringResource(R.string.selectText),
         checked = preferences.selectText,

+ 4 - 0
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfig.kt

@@ -214,6 +214,9 @@ object ReadConfig {
     var autoChangeSource
         get() = ReadDataConfig.autoChangeSource
         set(value) { ReadDataConfig.autoChangeSource = value }
+    var autoSuggestDayNight
+        get() = ReadDataConfig.autoSuggestDayNight
+        set(value) { ReadDataConfig.autoSuggestDayNight = value }
     var defaultSourceChangeAll
         get() = ReadDataConfig.defaultSourceChangeAll
         set(value) { ReadDataConfig.defaultSourceChangeAll = value }
@@ -299,6 +302,7 @@ object ReadConfig {
             keepLight = preferences.keepLight
             screenOrientation = preferences.screenOrientation
             autoChangeSource = preferences.autoChangeSource
+            autoSuggestDayNight = preferences.autoSuggestDayNight
             defaultSourceChangeAll = preferences.defaultSourceChangeAll
             tocUiUseReplace = preferences.tocUiUseReplace
             tocCountWords = preferences.tocCountWords

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

@@ -29,6 +29,7 @@ data class ReadConfigUiState(
     val sliderVibrator: Boolean = false,
     val selectVibrator: Boolean = false,
     val autoChangeSource: Boolean = true,
+    val autoSuggestDayNight: Boolean = false,
     val selectText: Boolean = true,
     val noAnimScrollPage: Boolean = false,
     val clickImgWay: String = "2",
@@ -82,5 +83,6 @@ sealed interface ReadConfigIntent {
     data class TextSelectMenuFilterChanged(val value: String) : ReadConfigIntent
     data class ShowReadTitleAdditionChanged(val value: Boolean) : ReadConfigIntent
     data class ShowMenuIconChanged(val value: Boolean) : ReadConfigIntent
+    data class AutoSuggestDayNightChanged(val value: Boolean) : ReadConfigIntent
     data class PageKeysChanged(val prevKeys: String, val nextKeys: String) : ReadConfigIntent
 }

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

@@ -307,6 +307,15 @@ fun ReadConfigScreen(
                     }
                 )
 
+                SwitchSettingItem(
+                    title = stringResource(R.string.auto_switch_theme_reminder_title),
+                    description = stringResource(R.string.auto_switch_theme_reminder_desc),
+                    checked = state.autoSuggestDayNight,
+                    onCheckedChange = {
+                        viewModel.onIntent(ReadConfigIntent.AutoSuggestDayNightChanged(it))
+                    }
+                )
+
                 SwitchSettingItem(
                     title = stringResource(R.string.selectText),
                     checked = state.selectText,

+ 6 - 0
app/src/main/java/io/legado/app/ui/config/readConfig/ReadConfigViewModel.kt

@@ -166,6 +166,11 @@ class ReadConfigViewModel(
                     readSettingsRepository.setAutoChangeSource(intent.value)
                 }
 
+                is ReadConfigIntent.AutoSuggestDayNightChanged -> {
+                    ReadConfig.autoSuggestDayNight = intent.value
+                    readSettingsRepository.setAutoSuggestDayNight(intent.value)
+                }
+
                 is ReadConfigIntent.SelectTextChanged -> {
                     ReadConfig.selectText = intent.value
                     readSettingsRepository.setSelectText(intent.value)
@@ -267,6 +272,7 @@ class ReadConfigViewModel(
             sliderVibrator = sliderVibrator,
             selectVibrator = selectVibrator,
             autoChangeSource = autoChangeSource,
+            autoSuggestDayNight = autoSuggestDayNight,
             selectText = selectText,
             noAnimScrollPage = noAnimScrollPage,
             clickImgWay = clickImgWay,

+ 5 - 0
app/src/main/java/io/legado/app/ui/config/readConfig/ReadDataConfig.kt

@@ -25,6 +25,11 @@ object ReadDataConfig {
         true
     )
 
+    var autoSuggestDayNight by prefDelegate(
+        PreferKey.autoSuggestDayNight,
+        false
+    )
+
     var defaultSourceChangeAll by prefDelegate(
         PreferKey.defaultSourceChangeAll,
         true

+ 7 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -2213,4 +2213,11 @@
     <string name="is_event_listener">EventListener</string>
     <string name="rule_call_back">callBackJs</string>
     <!-- Missing zh-rTW/HK translations (683 keys) -->
+
+    <!-- 日夜间/深浅色主题切换提醒 -->
+    <string name="auto_switch_theme_reminder_title">主题模式切换提醒</string>
+    <string name="auto_switch_theme_reminder_desc">根据环境光提醒切换浅色/深色模式</string>
+    <string name="switch_to_dark_mode_tip">当前环境光较暗,是否切换至深色模式?</string>
+    <string name="switch_to_light_mode_tip">当前环境光较亮,是否切换至浅色模式?</string>
+    <string name="switch_action">切换</string>
 </resources>

+ 7 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -2205,4 +2205,11 @@
     <string name="weekday_tue">週二</string>
     <string name="weekday_wed">週三</string>
     <string name="welcome_title">歡迎!</string>
+
+    <!-- 日夜間/深淺色主題切換提醒 -->
+    <string name="auto_switch_theme_reminder_title">主題模式切換提醒</string>
+    <string name="auto_switch_theme_reminder_desc">根據環境光提醒切換淺色/深色模式</string>
+    <string name="switch_to_dark_mode_tip">當前環境光較暗,是否切換至深色模式?</string>
+    <string name="switch_to_light_mode_tip">當前環境光較亮,是否切換至淺色模式?</string>
+    <string name="switch_action">切換</string>
 </resources>

+ 7 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -2210,4 +2210,11 @@
     <string name="export_complete">匯出完成</string>
     <string name="download_no_storage_permission">下載失敗:無儲存權限</string>
     <string name="already_in_download_list">已在下載列表中</string>
+
+    <!-- 日夜間/深淺色主題切換提醒 -->
+    <string name="auto_switch_theme_reminder_title">主題模式切換提醒</string>
+    <string name="auto_switch_theme_reminder_desc">根據環境光提醒切換淺色/深色模式</string>
+    <string name="switch_to_dark_mode_tip">目前環境光較暗,是否切換至深色模式?</string>
+    <string name="switch_to_light_mode_tip">目前環境光較亮,是否切換至淺色模式?</string>
+    <string name="switch_action">切換</string>
 </resources>

+ 7 - 0
app/src/main/res/values/strings.xml

@@ -2218,4 +2218,11 @@
     <string name="export_complete">Export complete</string>
     <string name="download_no_storage_permission">Download error: no storage permission</string>
     <string name="already_in_download_list">Already in download list</string>
+
+    <!-- Day/Night Theme Switch Reminder -->
+    <string name="auto_switch_theme_reminder_title">Theme Mode Switch Reminder</string>
+    <string name="auto_switch_theme_reminder_desc">Remind to switch light/dark mode based on ambient light</string>
+    <string name="switch_to_dark_mode_tip">Ambient light is low. Switch to dark mode?</string>
+    <string name="switch_to_light_mode_tip">Ambient light is bright. Switch to light mode?</string>
+    <string name="switch_action">Switch</string>
 </resources>