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

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

* refactor: 重构SwitchDayNightReminder为通用ActionReminder
- ReadConfigScreen 补上 主题模式切换提醒 开关
This commit is contained in:
fansan
2026-06-30 01:50:52 +08:00
committed by GitHub
parent a767d70d9f
commit af5094801c
17 changed files with 342 additions and 0 deletions
@@ -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"
@@ -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)
@@ -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)
}
}
}
}
}
}
}
}
@@ -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>()
}
@@ -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) {
@@ -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) },
)
}
@@ -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,
@@ -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,
@@ -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
@@ -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
}
@@ -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,
@@ -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,
@@ -25,6 +25,11 @@ object ReadDataConfig {
true
)
var autoSuggestDayNight by prefDelegate(
PreferKey.autoSuggestDayNight,
false
)
var defaultSourceChangeAll by prefDelegate(
PreferKey.defaultSourceChangeAll,
true
@@ -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>
@@ -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>
@@ -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
View File
@@ -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>