@@ -338,4 +338,16 @@ object PreferKey {
|
|||||||
const val homepageModuleHidden = "homepageModuleHidden"
|
const val homepageModuleHidden = "homepageModuleHidden"
|
||||||
const val homepageLayoutMode = "homepageLayoutMode"
|
const val homepageLayoutMode = "homepageLayoutMode"
|
||||||
const val navIconHome = "navIconHome"
|
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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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.MangaLayoutManager
|
||||||
import io.legado.app.ui.book.manga.recyclerview.ScrollTimer
|
import io.legado.app.ui.book.manga.recyclerview.ScrollTimer
|
||||||
import io.legado.app.ui.book.manga.recyclerview.WebtoonFrame
|
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.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.read.ReadBookActivity.Companion.RESULT_DELETED
|
||||||
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
|
||||||
import io.legado.app.ui.book.toc.TocActivityResult
|
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.GSON
|
||||||
import io.legado.app.utils.NetworkUtils
|
import io.legado.app.utils.NetworkUtils
|
||||||
import io.legado.app.utils.StartActivityContract
|
import io.legado.app.utils.StartActivityContract
|
||||||
|
import io.legado.app.utils.buildMainHandler
|
||||||
import io.legado.app.utils.canScroll
|
import io.legado.app.utils.canScroll
|
||||||
import io.legado.app.utils.fastBinarySearch
|
import io.legado.app.utils.fastBinarySearch
|
||||||
import io.legado.app.utils.findCenterViewPosition
|
import io.legado.app.utils.findCenterViewPosition
|
||||||
@@ -138,6 +141,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
|
|
||||||
private var justInitData: Boolean = false
|
private var justInitData: Boolean = false
|
||||||
private var syncDialog: AlertDialog? = null
|
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 {
|
private val mScrollTimer by lazy {
|
||||||
ScrollTimer(this, binding.recyclerView, lifecycleScope).apply {
|
ScrollTimer(this, binding.recyclerView, lifecycleScope).apply {
|
||||||
setSpeed(AppConfig.mangaAutoPageSpeed)
|
setSpeed(AppConfig.mangaAutoPageSpeed)
|
||||||
@@ -213,6 +220,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
ReadManga.loadOrUpContent()
|
ReadManga.loadOrUpContent()
|
||||||
}
|
}
|
||||||
binding.flLoading.isVisible = !AppConfig.isEInkMode
|
binding.flLoading.isVisible = !AppConfig.isEInkMode
|
||||||
|
binding.eyeProtectionOverlay.refresh()
|
||||||
mAdapter.addFooterView {
|
mAdapter.addFooterView {
|
||||||
ViewLoadMoreBinding.bind(loadMoreView)
|
ViewLoadMoreBinding.bind(loadMoreView)
|
||||||
}
|
}
|
||||||
@@ -242,6 +250,10 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
val item = mAdapter.getItem(binding.recyclerView.findCenterViewPosition())
|
val item = mAdapter.getItem(binding.recyclerView.findCenterViewPosition())
|
||||||
upInfoBar(item)
|
upInfoBar(item)
|
||||||
}
|
}
|
||||||
|
observeEyeProtectionEvents(
|
||||||
|
onRefresh = { binding.eyeProtectionOverlay.refresh() },
|
||||||
|
scheduler = eyeProtectionScheduler
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initRecyclerView() {
|
private fun initRecyclerView() {
|
||||||
@@ -457,10 +469,13 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewMode
|
|||||||
}
|
}
|
||||||
ReadManga.initReadTime()
|
ReadManga.initReadTime()
|
||||||
ReadManga.startAutoSaveSession()
|
ReadManga.startAutoSaveSession()
|
||||||
|
binding.eyeProtectionOverlay.refresh()
|
||||||
|
eyeProtectionScheduler.schedule()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
|
eyeProtectionScheduler.cancel()
|
||||||
if (ReadManga.inBookshelf) {
|
if (ReadManga.inBookshelf) {
|
||||||
ReadManga.saveRead()
|
ReadManga.saveRead()
|
||||||
if (!BuildConfig.DEBUG) {
|
if (!BuildConfig.DEBUG) {
|
||||||
|
|||||||
@@ -122,13 +122,16 @@ class MangaAdapter(private val context: Context) :
|
|||||||
) {
|
) {
|
||||||
"ARGB values must be between 0-255"
|
"ARGB values must be between 0-255"
|
||||||
}
|
}
|
||||||
val matrix = floatArrayOf(
|
val baseMatrix = floatArrayOf(
|
||||||
(255 - mConfig.r) / 255f, 0f, 0f, 0f, 0f,
|
(255 - mConfig.r) / 255f, 0f, 0f, 0f, 0f,
|
||||||
0f, (255 - mConfig.g) / 255f, 0f, 0f, 0f,
|
0f, (255 - mConfig.g) / 255f, 0f, 0f, 0f,
|
||||||
0f, 0f, (255 - mConfig.b) / 255f, 0f, 0f,
|
0f, 0f, (255 - mConfig.b) / 255f, 0f, 0f,
|
||||||
0f, 0f, 0f, (255 - mConfig.a) / 255f, 0f
|
0f, 0f, 0f, (255 - mConfig.a) / 255f, 0f
|
||||||
)
|
)
|
||||||
binding.image.colorFilter = ColorMatrixColorFilter(ColorMatrix(matrix))
|
val epMatrix = io.legado.app.ui.book.read.EyeProtectionHelper.buildColorMatrix()
|
||||||
|
val combined = android.graphics.ColorMatrix(baseMatrix)
|
||||||
|
combined.postConcat(epMatrix)
|
||||||
|
binding.image.colorFilter = ColorMatrixColorFilter(combined)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setBackground() {
|
fun setBackground() {
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
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,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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -278,6 +278,9 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
private var pageChanged = false
|
private var pageChanged = false
|
||||||
private val handler by lazy { buildMainHandler() }
|
private val handler by lazy { buildMainHandler() }
|
||||||
private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }
|
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 executor = ReadBook.executor
|
||||||
private val upSeekBarThrottle = throttle(200) {
|
private val upSeekBarThrottle = throttle(200) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
@@ -302,6 +305,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
ReadBook.register(this)
|
ReadBook.register(this)
|
||||||
binding.cursorLeft.setOnTouchListener(this)
|
binding.cursorLeft.setOnTouchListener(this)
|
||||||
binding.cursorRight.setOnTouchListener(this)
|
binding.cursorRight.setOnTouchListener(this)
|
||||||
|
binding.eyeProtectionOverlay.refresh()
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(this) {
|
onBackPressedDispatcher.addCallback(this) {
|
||||||
if (isShowingSearchResult) {
|
if (isShowingSearchResult) {
|
||||||
@@ -400,6 +404,8 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)
|
registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)
|
||||||
binding.readView.upTime()
|
binding.readView.upTime()
|
||||||
screenOffTimerStart()
|
screenOffTimerStart()
|
||||||
|
binding.eyeProtectionOverlay.refresh()
|
||||||
|
eyeProtectionScheduler.schedule()
|
||||||
// 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作)
|
// 网络监听,当从无网切换到网络环境时同步进度(注意注册的同时就会收到监听,因此界面激活时无需重复执行同步操作)
|
||||||
networkChangedListener.register()
|
networkChangedListener.register()
|
||||||
networkChangedListener.onNetworkChanged = {
|
networkChangedListener.onNetworkChanged = {
|
||||||
@@ -413,6 +419,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
autoPageStop()
|
autoPageStop()
|
||||||
|
eyeProtectionScheduler.cancel()
|
||||||
backupJob?.cancel()
|
backupJob?.cancel()
|
||||||
ReadBook.saveRead()
|
ReadBook.saveRead()
|
||||||
ReadBook.stopAutoSaveSession()
|
ReadBook.stopAutoSaveSession()
|
||||||
@@ -1985,6 +1992,10 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
viewModel.refreshContentDur(it)
|
viewModel.refreshContentDur(it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
observeEyeProtectionEvents(
|
||||||
|
onRefresh = { binding.eyeProtectionOverlay.refresh() },
|
||||||
|
scheduler = eyeProtectionScheduler
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun upScreenTimeOut() {
|
private fun upScreenTimeOut() {
|
||||||
|
|||||||
@@ -27,7 +27,8 @@ fun ConfigNavScreen(
|
|||||||
onNavigateToTheme: () -> Unit,
|
onNavigateToTheme: () -> Unit,
|
||||||
onNavigateToBackup: () -> Unit,
|
onNavigateToBackup: () -> Unit,
|
||||||
onNavigateToDownloadCache: () -> Unit,
|
onNavigateToDownloadCache: () -> Unit,
|
||||||
onNavigateToTranslation: () -> Unit
|
onNavigateToTranslation: () -> Unit,
|
||||||
|
onNavigateToLab: () -> Unit
|
||||||
) {
|
) {
|
||||||
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||||
|
|
||||||
@@ -80,6 +81,10 @@ fun ConfigNavScreen(
|
|||||||
title = stringResource(R.string.translation_config),
|
title = stringResource(R.string.translation_config),
|
||||||
onClick = onNavigateToTranslation
|
onClick = onNavigateToTranslation
|
||||||
)
|
)
|
||||||
|
ClickableSettingItem(
|
||||||
|
title = stringResource(R.string.lab_setting),
|
||||||
|
onClick = onNavigateToLab
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -182,6 +182,23 @@ object ThemeConfig {
|
|||||||
|
|
||||||
var navIconMy by prefDelegate(PreferKey.navIconMy, "")
|
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 =
|
fun hasImageBg(isDark: Boolean): Boolean =
|
||||||
!(if (isDark) bgImageDark else bgImageLight).isNullOrBlank()
|
!(if (isDark) bgImageDark else bgImageLight).isNullOrBlank()
|
||||||
|
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ import androidx.compose.material3.ToggleButtonDefaults
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
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.AppConfig
|
||||||
import io.legado.app.help.config.OldThemeConfig
|
import io.legado.app.help.config.OldThemeConfig
|
||||||
import io.legado.app.help.loadFontFiles
|
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.LegadoTheme
|
||||||
import io.legado.app.ui.theme.ThemeEngine
|
import io.legado.app.ui.theme.ThemeEngine
|
||||||
import io.legado.app.ui.theme.ThemeResolver
|
import io.legado.app.ui.theme.ThemeResolver
|
||||||
@@ -296,9 +298,12 @@ fun ThemeConfigScreen(
|
|||||||
if (!isMiuixEngine) {
|
if (!isMiuixEngine) {
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|
||||||
|
val visibleThemes = themes.filter { (_, value) ->
|
||||||
|
value != "4" || (LabConfig.labEnabled && LabConfig.eInkDisplay)
|
||||||
|
}
|
||||||
ThemeColorSelector(
|
ThemeColorSelector(
|
||||||
context = context,
|
context = context,
|
||||||
themes = themes,
|
themes = visibleThemes,
|
||||||
selectedTheme = selectedTheme,
|
selectedTheme = selectedTheme,
|
||||||
isDark = isDarkTheme,
|
isDark = isDarkTheme,
|
||||||
isAmoled = ThemeConfig.isPureBlack,
|
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)) {
|
SplicedColumnGroup(title = stringResource(R.string.compose_related)) {
|
||||||
if (!isMiuixEngine) {
|
if (!isMiuixEngine) {
|
||||||
SwitchSettingItem(
|
SwitchSettingItem(
|
||||||
|
|||||||
@@ -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.readConfig.ReadConfigScreen
|
||||||
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
|
import io.legado.app.ui.config.themeConfig.ThemeConfigScreen
|
||||||
import io.legado.app.ui.config.themeManage.ThemeManageScreen
|
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.config.translation.TranslationConfigScreen
|
||||||
import io.legado.app.ui.rss.article.MainRouteRssSort
|
import io.legado.app.ui.rss.article.MainRouteRssSort
|
||||||
import io.legado.app.ui.rss.article.RssSortRouteScreen
|
import io.legado.app.ui.rss.article.RssSortRouteScreen
|
||||||
@@ -159,7 +160,8 @@ fun MainActivity.mainEntryProvider(
|
|||||||
onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) },
|
onNavigateToTheme = { backStack.add(MainRouteSettingsTheme) },
|
||||||
onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) },
|
onNavigateToBackup = { backStack.add(MainRouteSettingsBackup) },
|
||||||
onNavigateToDownloadCache = { backStack.add(MainRouteSettingsDownloadCache) },
|
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() })
|
TranslationConfigScreen(onBackClick = { onNavigateBack() })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
entry<MainRouteSettingsLabConfig> {
|
||||||
|
LabConfigScreen(onBackClick = { onNavigateBack() })
|
||||||
|
}
|
||||||
|
|
||||||
entry<MainRouteSettingsCustomTheme> {
|
entry<MainRouteSettingsCustomTheme> {
|
||||||
CustomThemeScreen(
|
CustomThemeScreen(
|
||||||
onBackClick = { onNavigateBack() }
|
onBackClick = { onNavigateBack() }
|
||||||
|
|||||||
@@ -33,6 +33,9 @@ data object MainRouteSettingsCustomTheme : MainRoute
|
|||||||
@Serializable
|
@Serializable
|
||||||
data object MainRouteSettingsThemeManage : MainRoute
|
data object MainRouteSettingsThemeManage : MainRoute
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object MainRouteSettingsLabConfig : MainRoute
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object MainRouteSettingsDownloadCache : MainRoute
|
data object MainRouteSettingsDownloadCache : MainRoute
|
||||||
|
|
||||||
@@ -98,6 +101,7 @@ object MainRouteConst {
|
|||||||
const val ROUTE_SETTINGS_THEME = "settings/theme"
|
const val ROUTE_SETTINGS_THEME = "settings/theme"
|
||||||
const val ROUTE_SETTINGS_BACKUP = "settings/backup"
|
const val ROUTE_SETTINGS_BACKUP = "settings/backup"
|
||||||
const val ROUTE_SETTINGS_CUSTOM_THEME = "settings/custom_theme"
|
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_DOWNLOAD_CACHE = "settings/download_cache"
|
||||||
const val ROUTE_SETTINGS_TRANSLATION = "settings/translation"
|
const val ROUTE_SETTINGS_TRANSLATION = "settings/translation"
|
||||||
const val ROUTE_IMPORT_LOCAL = "import/local"
|
const val ROUTE_IMPORT_LOCAL = "import/local"
|
||||||
|
|||||||
@@ -50,4 +50,12 @@
|
|||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_gravity="bottom" />
|
android:layout_gravity="bottom" />
|
||||||
|
|
||||||
|
<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>
|
</FrameLayout>
|
||||||
@@ -41,7 +41,6 @@
|
|||||||
android:id="@+id/fl_loading"
|
android:id="@+id/fl_loading"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/ll_loading"
|
android:id="@+id/ll_loading"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@@ -94,4 +93,12 @@
|
|||||||
|
|
||||||
</FrameLayout>
|
</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>
|
</FrameLayout>
|
||||||
|
|||||||
@@ -1786,4 +1786,27 @@
|
|||||||
<string name="return_to_original">返回原文</string>
|
<string name="return_to_original">返回原文</string>
|
||||||
<string name="translation_progress">翻译中 %1$d%%</string>
|
<string name="translation_progress">翻译中 %1$d%%</string>
|
||||||
<string name="translation_started">开始进行翻译,请耐心等待……</string>
|
<string name="translation_started">开始进行翻译,请耐心等待……</string>
|
||||||
|
|
||||||
|
<!-- 实验室 -->
|
||||||
|
<string name="lab_setting">实验室</string>
|
||||||
|
<string name="lab_enabled_title">启用实验室</string>
|
||||||
|
<string name="lab_enabled_summary">开启后显示实验性功能,可能不稳定</string>
|
||||||
|
<string name="lab_display">显示</string>
|
||||||
|
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -1792,4 +1792,27 @@
|
|||||||
<string name="homepage_status_in_effect"> · In effect</string>
|
<string name="homepage_status_in_effect"> · In effect</string>
|
||||||
<string name="homepage_status_blocked"> · Blocked</string>
|
<string name="homepage_status_blocked"> · Blocked</string>
|
||||||
<string name="homepage_status_joined"> · Already joined</string>
|
<string name="homepage_status_joined"> · Already joined</string>
|
||||||
|
|
||||||
|
<!-- Lab -->
|
||||||
|
<string name="lab_setting">Lab</string>
|
||||||
|
<string name="lab_enabled_title">Enable Lab</string>
|
||||||
|
<string name="lab_enabled_summary">Turn on to show experimental features. These may be unstable.</string>
|
||||||
|
<string name="lab_display">Display</string>
|
||||||
|
<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>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ export const useBookStore = defineStore('book', {
|
|||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
bookProgress: (state): BookProgress | undefined => {
|
bookProgress: (state): BookProgress | undefined => {
|
||||||
if (state.catalog.length == 0) return
|
if (state.catalog.length === 0) return
|
||||||
const { chapterIndex, chapterPos, name, author } = state.readingBook
|
const { chapterIndex, chapterPos, name, author } = state.readingBook
|
||||||
const title = state.catalog[chapterIndex]?.title
|
const title = state.catalog[chapterIndex]?.title
|
||||||
if (!title) return
|
if (!title) return
|
||||||
@@ -63,13 +63,12 @@ export const useBookStore = defineStore('book', {
|
|||||||
theme: state => {
|
theme: state => {
|
||||||
return state.config.theme
|
return state.config.theme
|
||||||
},
|
},
|
||||||
isNight: state => state.config.theme == 6,
|
isNight: state => state.config.theme === 6,
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
/** 从后端加载书架书籍,优先返回内存缓存 */
|
/** 从后端加载书架书籍,优先返回内存缓存 */
|
||||||
async loadBookShelf(): Promise<Book[]> {
|
async loadBookShelf(): Promise<Book[]> {
|
||||||
const fetchBookshellf_promise = API.getBookShelf().then(resp => {
|
const fetchBookshellf_promise = API.getBookShelf().then(resp => {
|
||||||
console.log('API.getBookShelf数据返回')
|
|
||||||
const { isSuccess, data, errorMsg } = resp.data
|
const { isSuccess, data, errorMsg } = resp.data
|
||||||
if (isSuccess === true) {
|
if (isSuccess === true) {
|
||||||
if (
|
if (
|
||||||
@@ -91,16 +90,13 @@ export const useBookStore = defineStore('book', {
|
|||||||
}
|
}
|
||||||
ElMessage.error(errorMsg ?? '后端返回格式错误!')
|
ElMessage.error(errorMsg ?? '后端返回格式错误!')
|
||||||
}
|
}
|
||||||
console.log('书架数据已更新')
|
|
||||||
return this.shelf
|
return this.shelf
|
||||||
})
|
})
|
||||||
|
|
||||||
if (this.shelf.length > 0) {
|
if (this.shelf.length > 0) {
|
||||||
// bookshelf data fetched before:do not await
|
// bookshelf data fetched before:do not await
|
||||||
console.log('返回缓存书架数据')
|
|
||||||
return this.shelf
|
return this.shelf
|
||||||
} else {
|
} else {
|
||||||
console.log('从阅读后端获取书架数据...')
|
|
||||||
return await fetchBookshellf_promise
|
return await fetchBookshellf_promise
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -126,7 +122,6 @@ export const useBookStore = defineStore('book', {
|
|||||||
ElMessage.info(`书籍${name}: 章节目录已更新`)
|
ElMessage.info(`书籍${name}: 章节目录已更新`)
|
||||||
}
|
}
|
||||||
this.catalog = data
|
this.catalog = data
|
||||||
console.log(`书籍${name}: 章节目录已更新`)
|
|
||||||
return this.catalog
|
return this.catalog
|
||||||
})
|
})
|
||||||
if (
|
if (
|
||||||
@@ -134,10 +129,8 @@ export const useBookStore = defineStore('book', {
|
|||||||
this.catalog.length > 0 &&
|
this.catalog.length > 0 &&
|
||||||
this.catalog.length - 1 >= chapterIndex
|
this.catalog.length - 1 >= chapterIndex
|
||||||
) {
|
) {
|
||||||
console.log(`返回书籍《${name}》 缓存的章节目录`)
|
|
||||||
return this.catalog
|
return this.catalog
|
||||||
} else {
|
} else {
|
||||||
console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`)
|
|
||||||
return await fetchChapterList_promise
|
return await fetchChapterList_promise
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -155,14 +148,8 @@ export const useBookStore = defineStore('book', {
|
|||||||
if (webReadConfigLoadedDate === undefined) {
|
if (webReadConfigLoadedDate === undefined) {
|
||||||
const _config = await API.getReadConfig()
|
const _config = await API.getReadConfig()
|
||||||
webReadConfigLoadedDate = new Date()
|
webReadConfigLoadedDate = new Date()
|
||||||
console.log(
|
|
||||||
`${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`,
|
|
||||||
)
|
|
||||||
return this.setConfig(_config)
|
return this.setConfig(_config)
|
||||||
}
|
}
|
||||||
console.log(
|
|
||||||
`${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`,
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
setConfig(config?: webReadConfig) {
|
setConfig(config?: webReadConfig) {
|
||||||
this.config = Object.assign({}, this.config, config)
|
this.config = Object.assign({}, this.config, config)
|
||||||
|
|||||||
Reference in New Issue
Block a user