diff --git a/app/src/main/java/io/legado/app/constant/PreferKey.kt b/app/src/main/java/io/legado/app/constant/PreferKey.kt index f23b90e75..53c5a5b5f 100644 --- a/app/src/main/java/io/legado/app/constant/PreferKey.kt +++ b/app/src/main/java/io/legado/app/constant/PreferKey.kt @@ -338,4 +338,16 @@ object PreferKey { const val homepageModuleHidden = "homepageModuleHidden" const val homepageLayoutMode = "homepageLayoutMode" const val navIconHome = "navIconHome" + + // 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" } diff --git a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt index 9e5020820..b06abe6bb 100644 --- a/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt @@ -70,7 +70,9 @@ 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.read.ReadBookActivity.Companion.RESULT_DELETED import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.toc.TocActivityResult @@ -81,6 +83,7 @@ import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.utils.GSON import io.legado.app.utils.NetworkUtils import io.legado.app.utils.StartActivityContract +import io.legado.app.utils.buildMainHandler import io.legado.app.utils.canScroll import io.legado.app.utils.fastBinarySearch import io.legado.app.utils.findCenterViewPosition @@ -138,6 +141,10 @@ class ReadMangaActivity : VMBaseActivity= 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(PreferKey.eyeProtectionEnabled) { + onRefresh() + scheduler.schedule() + } + observeEvent(PreferKey.colorTemperature) { + onRefresh() + } + observeEvent(PreferKey.eyeProtectionSchedule) { + onRefresh() + scheduler.schedule() + } + observeEvent(PreferKey.eyeProtectionStartTime) { + onRefresh() + scheduler.schedule() + } + observeEvent(PreferKey.eyeProtectionEndTime) { + onRefresh() + scheduler.schedule() + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/EyeProtectionOverlay.kt b/app/src/main/java/io/legado/app/ui/book/read/EyeProtectionOverlay.kt new file mode 100644 index 000000000..e58ed3ec5 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/book/read/EyeProtectionOverlay.kt @@ -0,0 +1,53 @@ +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) + } +} diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 1f0c263a9..7a2c8390c 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -278,6 +278,9 @@ class ReadBookActivity : BaseReadBookActivity(), private var pageChanged = false private val handler by lazy { buildMainHandler() } private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } } + private val eyeProtectionScheduler by lazy { + EyeProtectionRefreshScheduler(handler) { binding.eyeProtectionOverlay.refresh() } + } private val executor = ReadBook.executor private val upSeekBarThrottle = throttle(200) { runOnUiThread { @@ -302,6 +305,7 @@ class ReadBookActivity : BaseReadBookActivity(), ReadBook.register(this) binding.cursorLeft.setOnTouchListener(this) binding.cursorRight.setOnTouchListener(this) + binding.eyeProtectionOverlay.refresh() onBackPressedDispatcher.addCallback(this) { if (isShowingSearchResult) { @@ -400,6 +404,8 @@ class ReadBookActivity : BaseReadBookActivity(), registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter) binding.readView.upTime() screenOffTimerStart() + binding.eyeProtectionOverlay.refresh() + eyeProtectionScheduler.schedule() // 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作) networkChangedListener.register() networkChangedListener.onNetworkChanged = { @@ -413,6 +419,7 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onPause() { super.onPause() autoPageStop() + eyeProtectionScheduler.cancel() backupJob?.cancel() ReadBook.saveRead() ReadBook.stopAutoSaveSession() @@ -1985,6 +1992,10 @@ class ReadBookActivity : BaseReadBookActivity(), viewModel.refreshContentDur(it) } } + observeEyeProtectionEvents( + onRefresh = { binding.eyeProtectionOverlay.refresh() }, + scheduler = eyeProtectionScheduler + ) } private fun upScreenTimeOut() { diff --git a/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt b/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt index 6313b95c5..bada404f2 100644 --- a/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt +++ b/app/src/main/java/io/legado/app/ui/config/ConfigNavScreen.kt @@ -27,7 +27,8 @@ fun ConfigNavScreen( onNavigateToTheme: () -> Unit, onNavigateToBackup: () -> Unit, onNavigateToDownloadCache: () -> Unit, - onNavigateToTranslation: () -> Unit + onNavigateToTranslation: () -> Unit, + onNavigateToLab: () -> Unit ) { val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() @@ -80,6 +81,10 @@ fun ConfigNavScreen( title = stringResource(R.string.translation_config), onClick = onNavigateToTranslation ) + ClickableSettingItem( + title = stringResource(R.string.lab_setting), + onClick = onNavigateToLab + ) } } } diff --git a/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfig.kt b/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfig.kt new file mode 100644 index 000000000..98d11d9b9 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfig.kt @@ -0,0 +1,23 @@ +package io.legado.app.ui.config.labConfig + +import io.legado.app.constant.PreferKey +import io.legado.app.ui.config.prefDelegate + +object LabConfig { + + var labEnabled by prefDelegate( + PreferKey.labEnabled, + false + ) + + var eInkDisplay by prefDelegate( + PreferKey.labEInkDisplay, + false + ) + + var eyeProtection by prefDelegate( + PreferKey.labEyeProtection, + false + ) + +} diff --git a/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigScreen.kt new file mode 100644 index 000000000..aee349da3 --- /dev/null +++ b/app/src/main/java/io/legado/app/ui/config/labConfig/LabConfigScreen.kt @@ -0,0 +1,108 @@ +package io.legado.app.ui.config.labConfig + +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.nestedscroll.nestedScroll +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import io.legado.app.R +import io.legado.app.ui.theme.LegadoTheme +import io.legado.app.ui.theme.adaptiveContentPadding +import io.legado.app.ui.widget.components.AppScaffold +import io.legado.app.ui.widget.components.SplicedColumnGroup +import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem +import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar +import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults +import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun LabConfigScreen( + onBackClick: () -> Unit +) { + val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior() + + AppScaffold( + modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), + topBar = { + GlassMediumFlexibleTopAppBar( + title = stringResource(R.string.lab_setting), + scrollBehavior = scrollBehavior, + navigationIcon = { + TopBarNavigationButton(onClick = onBackClick) + } + ) + } + ) { paddingValues -> + LazyColumn( + modifier = Modifier.fillMaxSize(), + contentPadding = adaptiveContentPadding( + top = paddingValues.calculateTopPadding(), + bottom = 120.dp + ) + ) { + item { + SplicedColumnGroup { + SwitchSettingItem( + title = stringResource(R.string.lab_enabled_title), + description = stringResource(R.string.lab_enabled_summary), + checked = LabConfig.labEnabled, + onCheckedChange = { LabConfig.labEnabled = it } + ) + } + + AnimatedVisibility(visible = LabConfig.labEnabled) { + SplicedColumnGroup(title = stringResource(R.string.lab_display)) { + SwitchSettingItem( + title = stringResource(R.string.lab_eink_display_title), + description = stringResource(R.string.lab_eink_display_summary), + checked = LabConfig.eInkDisplay, + onCheckedChange = { + LabConfig.eInkDisplay = it + } + ) + + if (LabConfig.eInkDisplay) { + Text( + text = stringResource(R.string.lab_eink_display_hint), + style = LegadoTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp) + ) + } + + SwitchSettingItem( + title = stringResource(R.string.lab_eye_protection_title), + description = stringResource(R.string.lab_eye_protection_summary), + checked = LabConfig.eyeProtection, + onCheckedChange = { + LabConfig.eyeProtection = it + } + ) + + if (LabConfig.eyeProtection) { + Text( + text = stringResource(R.string.lab_eye_protection_hint), + style = LegadoTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp, vertical = 4.dp) + ) + } + } + } + } + } + } +} diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt index b9a400816..2db476737 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt @@ -182,6 +182,23 @@ object ThemeConfig { var navIconMy by prefDelegate(PreferKey.navIconMy, "") + // Eye Protection + var eyeProtectionEnabled by prefDelegate(PreferKey.eyeProtectionEnabled, false) { + postEvent(PreferKey.eyeProtectionEnabled, it) + } + var colorTemperature by prefDelegate(PreferKey.colorTemperature, 50) { + postEvent(PreferKey.colorTemperature, it) + } + var eyeProtectionSchedule by prefDelegate(PreferKey.eyeProtectionSchedule, false) { + postEvent(PreferKey.eyeProtectionSchedule, it) + } + var eyeProtectionStartTime by prefDelegate(PreferKey.eyeProtectionStartTime, "22:00") { + postEvent(PreferKey.eyeProtectionStartTime, it) + } + var eyeProtectionEndTime by prefDelegate(PreferKey.eyeProtectionEndTime, "07:00") { + postEvent(PreferKey.eyeProtectionEndTime, it) + } + fun hasImageBg(isDark: Boolean): Boolean = !(if (isDark) bgImageDark else bgImageLight).isNullOrBlank() diff --git a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt index 40f30317f..e94c92e31 100644 --- a/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt +++ b/app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt @@ -55,6 +55,7 @@ import androidx.compose.material3.ToggleButtonDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue @@ -84,6 +85,7 @@ import io.legado.app.help.LauncherIconHelp import io.legado.app.help.config.AppConfig import io.legado.app.help.config.OldThemeConfig import io.legado.app.help.loadFontFiles +import io.legado.app.ui.config.labConfig.LabConfig import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.ThemeEngine import io.legado.app.ui.theme.ThemeResolver @@ -296,9 +298,12 @@ fun ThemeConfigScreen( if (!isMiuixEngine) { Spacer(modifier = Modifier.height(16.dp)) + val visibleThemes = themes.filter { (_, value) -> + value != "4" || (LabConfig.labEnabled && LabConfig.eInkDisplay) + } ThemeColorSelector( context = context, - themes = themes, + themes = visibleThemes, selectedTheme = selectedTheme, isDark = isDarkTheme, isAmoled = ThemeConfig.isPureBlack, @@ -464,6 +469,119 @@ fun ThemeConfigScreen( ) } + SplicedColumnGroup(title = stringResource(R.string.eye_protection)) { + var eyeProtectionEnabled by remember { + mutableStateOf(ThemeConfig.eyeProtectionEnabled) + } + var colorTemperature by remember { + mutableIntStateOf(ThemeConfig.colorTemperature) + } + var eyeProtectionSchedule by remember { + mutableStateOf(ThemeConfig.eyeProtectionSchedule) + } + var eyeProtectionStartTime by remember { + mutableStateOf(ThemeConfig.eyeProtectionStartTime) + } + var eyeProtectionEndTime by remember { + mutableStateOf(ThemeConfig.eyeProtectionEndTime) + } + + SwitchSettingItem( + title = stringResource(R.string.eye_protection_enabled), + description = stringResource(R.string.eye_protection_enabled_summary), + checked = eyeProtectionEnabled, + onCheckedChange = { + eyeProtectionEnabled = it + ThemeConfig.eyeProtectionEnabled = it + } + ) + + AnimatedVisibility(visible = eyeProtectionEnabled) { + Column { + SliderSettingItem( + title = stringResource(R.string.color_temperature), + description = stringResource( + R.string.color_temperature_summary, + colorTemperature + ), + value = colorTemperature.toFloat(), + defaultValue = 50f, + valueRange = 0f..100f, + steps = 99, + onValueChange = { + colorTemperature = it.toInt() + ThemeConfig.colorTemperature = it.toInt() + } + ) + + SwitchSettingItem( + title = stringResource(R.string.eye_protection_schedule), + description = stringResource(R.string.eye_protection_schedule_summary), + checked = eyeProtectionSchedule, + onCheckedChange = { + eyeProtectionSchedule = it + ThemeConfig.eyeProtectionSchedule = it + } + ) + + AnimatedVisibility(visible = eyeProtectionSchedule) { + Column { + ClickableSettingItem( + title = stringResource(R.string.eye_protection_start_time), + option = eyeProtectionStartTime, + onClick = { + val parts = eyeProtectionStartTime.split(":") + val hour = parts.getOrNull(0)?.toIntOrNull() ?: 22 + val minute = parts.getOrNull(1)?.toIntOrNull() ?: 0 + android.app.TimePickerDialog( + context, + { _, h, m -> + val timeStr = String.format( + java.util.Locale.US, + "%02d:%02d", + h.coerceIn(0, 23), + m.coerceIn(0, 59) + ) + eyeProtectionStartTime = timeStr + ThemeConfig.eyeProtectionStartTime = timeStr + }, + hour.coerceIn(0, 23), + minute.coerceIn(0, 59), + true + ).show() + } + ) + ClickableSettingItem( + title = stringResource(R.string.eye_protection_end_time), + option = eyeProtectionEndTime, + onClick = { + val parts = eyeProtectionEndTime.split(":") + val hour = parts.getOrNull(0)?.toIntOrNull() ?: 7 + val minute = parts.getOrNull(1)?.toIntOrNull() ?: 0 + android.app.TimePickerDialog( + context, + { _, h, m -> + val timeStr = String.format( + java.util.Locale.US, + "%02d:%02d", + h.coerceIn(0, 23), + m.coerceIn(0, 59) + ) + eyeProtectionEndTime = timeStr + ThemeConfig.eyeProtectionEndTime = timeStr + }, + hour.coerceIn(0, 23), + minute.coerceIn(0, 59), + true + ).show() + } + ) + } + } + } + } + } + SplicedColumnGroup(title = stringResource(R.string.compose_related)) { if (!isMiuixEngine) { SwitchSettingItem( diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt index 15b818ddf..2c26e0cb0 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt @@ -42,6 +42,7 @@ import io.legado.app.ui.config.otherConfig.OtherConfigScreen import io.legado.app.ui.config.readConfig.ReadConfigScreen import io.legado.app.ui.config.themeConfig.ThemeConfigScreen import io.legado.app.ui.config.themeManage.ThemeManageScreen +import io.legado.app.ui.config.labConfig.LabConfigScreen import io.legado.app.ui.config.translation.TranslationConfigScreen import io.legado.app.ui.rss.article.MainRouteRssSort import io.legado.app.ui.rss.article.RssSortRouteScreen @@ -159,7 +160,8 @@ fun MainActivity.mainEntryProvider( onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) }, onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) }, onNavigateToDownloadCache = { backStack.add(MainRouteSettingsDownloadCache) }, - onNavigateToTranslation = { backStack.add(MainRouteSettingsTranslation) } + onNavigateToTranslation = { backStack.add(MainRouteSettingsTranslation) }, + onNavigateToLab = { backStack.add(MainRouteSettingsLabConfig) } ) } @@ -195,6 +197,10 @@ fun MainActivity.mainEntryProvider( TranslationConfigScreen(onBackClick = { onNavigateBack() }) } + entry { + LabConfigScreen(onBackClick = { onNavigateBack() }) + } + entry { CustomThemeScreen( onBackClick = { onNavigateBack() } diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt index 3ed426fc8..0f1d6209e 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavKey.kt @@ -33,6 +33,9 @@ data object MainRouteSettingsCustomTheme : MainRoute @Serializable data object MainRouteSettingsThemeManage : MainRoute +@Serializable +data object MainRouteSettingsLabConfig : MainRoute + @Serializable data object MainRouteSettingsDownloadCache : MainRoute @@ -98,6 +101,7 @@ object MainRouteConst { const val ROUTE_SETTINGS_THEME = "settings/theme" const val ROUTE_SETTINGS_BACKUP = "settings/backup" const val ROUTE_SETTINGS_CUSTOM_THEME = "settings/custom_theme" + const val ROUTE_SETTINGS_LAB_CONFIG = "settings/lab_config" const val ROUTE_SETTINGS_DOWNLOAD_CACHE = "settings/download_cache" const val ROUTE_SETTINGS_TRANSLATION = "settings/translation" const val ROUTE_IMPORT_LOCAL = "import/local" diff --git a/app/src/main/res/layout/activity_book_read.xml b/app/src/main/res/layout/activity_book_read.xml index 04b750a06..38f299f34 100644 --- a/app/src/main/res/layout/activity_book_read.xml +++ b/app/src/main/res/layout/activity_book_read.xml @@ -50,4 +50,12 @@ android:layout_height="0dp" android:layout_gravity="bottom" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_manga.xml b/app/src/main/res/layout/activity_manga.xml index fc6df5101..2071b378c 100644 --- a/app/src/main/res/layout/activity_manga.xml +++ b/app/src/main/res/layout/activity_manga.xml @@ -41,7 +41,6 @@ android:id="@+id/fl_loading" android:layout_width="match_parent" android:layout_height="match_parent"> - + + diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 42929d55e..2cb56a3c1 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -1786,4 +1786,27 @@ 返回原文 翻译中 %1$d%% 开始进行翻译,请耐心等待…… + + + 实验室 + 启用实验室 + 开启后显示实验性功能,可能不稳定 + 显示 + 墨水屏显示 + 在平板界面显示墨水屏选项 + 可在 主题 → 显示 中调整墨水屏设置 + 护眼模式 + 为漫画和小说阅读器叠加暖色滤镜,减少蓝光 + 可在 主题 → 外观 中调整护眼模式 + + + 护眼模式 + 启用护眼模式 + 使用暖色调降低蓝光 + 屏幕色温 + 当前色温:%1$d + 按时间段启用 + 仅在配置的时间段内生效 + 开始时间 + 结束时间 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9803ea86f..931c68c54 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1792,4 +1792,27 @@ · In effect · Blocked · Already joined + + + Lab + Enable Lab + Turn on to show experimental features. These may be unstable. + Display + E-Ink Display + Show E-Ink display option in the tablet interface + You can adjust E-Ink display settings in Theme → Display options + Eye Protection + Apply warm color tint to comic and novel readers to reduce eye strain + You can adjust eye protection settings in Theme → Appearance + + + Eye Protection + Enable Eye Protection + Reduce blue light via warm color tint + Color Temperature + Current temperature: %1$d + Scheduled + Only apply during the configured time range + Start Time + End Time diff --git a/modules/web/src/store/bookStore.ts b/modules/web/src/store/bookStore.ts index 0b7a1398d..b30d2cbc6 100644 --- a/modules/web/src/store/bookStore.ts +++ b/modules/web/src/store/bookStore.ts @@ -47,7 +47,7 @@ export const useBookStore = defineStore('book', { }, getters: { bookProgress: (state): BookProgress | undefined => { - if (state.catalog.length == 0) return + if (state.catalog.length === 0) return const { chapterIndex, chapterPos, name, author } = state.readingBook const title = state.catalog[chapterIndex]?.title if (!title) return @@ -63,13 +63,12 @@ export const useBookStore = defineStore('book', { theme: state => { return state.config.theme }, - isNight: state => state.config.theme == 6, + isNight: state => state.config.theme === 6, }, actions: { /** 从后端加载书架书籍,优先返回内存缓存 */ async loadBookShelf(): Promise { const fetchBookshellf_promise = API.getBookShelf().then(resp => { - console.log('API.getBookShelf数据返回') const { isSuccess, data, errorMsg } = resp.data if (isSuccess === true) { if ( @@ -91,16 +90,13 @@ export const useBookStore = defineStore('book', { } ElMessage.error(errorMsg ?? '后端返回格式错误!') } - console.log('书架数据已更新') return this.shelf }) if (this.shelf.length > 0) { // bookshelf data fetched before:do not await - console.log('返回缓存书架数据') return this.shelf } else { - console.log('从阅读后端获取书架数据...') return await fetchBookshellf_promise } }, @@ -126,7 +122,6 @@ export const useBookStore = defineStore('book', { ElMessage.info(`书籍${name}: 章节目录已更新`) } this.catalog = data - console.log(`书籍${name}: 章节目录已更新`) return this.catalog }) if ( @@ -134,10 +129,8 @@ export const useBookStore = defineStore('book', { this.catalog.length > 0 && this.catalog.length - 1 >= chapterIndex ) { - console.log(`返回书籍《${name}》 缓存的章节目录`) return this.catalog } else { - console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`) return await fetchChapterList_promise } }, @@ -155,14 +148,8 @@ export const useBookStore = defineStore('book', { if (webReadConfigLoadedDate === undefined) { const _config = await API.getReadConfig() webReadConfigLoadedDate = new Date() - console.log( - `${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`, - ) return this.setConfig(_config) } - console.log( - `${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`, - ) }, setConfig(config?: webReadConfig) { this.config = Object.assign({}, this.config, config)