fix: 优化书籍详情封面主题与背景

This commit is contained in:
HapeLee
2026-07-02 22:59:39 +08:00
parent cda772f835
commit 6f87071a85
13 changed files with 210 additions and 22 deletions
@@ -204,6 +204,8 @@ object PreferKey {
const val bottomBarBlurAlpha = "bottomBarBlurAlpha"
const val bottomBarLensRadius = "bottomBarLensRadius"
const val useFlexibleTopAppBar = "useFlexibleTopAppBar"
const val bookInfoFollowCoverColor = "bookInfoFollowCoverColor"
const val bookInfoBackgroundBlur = "bookInfoBackgroundBlur"
const val cBackground = "colorBackground"
const val cBBackground = "colorBottomBackground"
const val enableDeepPersonalization = "enableDeepPersonalization"
@@ -157,6 +157,8 @@ object ThemeImportExport {
themeBackgroundColorNight = ThemeConfig.themeBackgroundColorNight,
labelContainerColorNight = ThemeConfig.labelContainerColorNight,
bookInfoInputColor = ThemeConfig.bookInfoInputColor,
bookInfoFollowCoverColor = ThemeConfig.bookInfoFollowCoverColor,
bookInfoBackgroundBlur = ThemeConfig.bookInfoBackgroundBlur,
// 容器设置
containerOpacity = ThemeConfig.containerOpacity,
@@ -334,6 +336,8 @@ object ThemeImportExport {
ThemeConfig.themeBackgroundColorNight = data.themeBackgroundColorNight
ThemeConfig.labelContainerColorNight = data.labelContainerColorNight
ThemeConfig.bookInfoInputColor = data.bookInfoInputColor
ThemeConfig.bookInfoFollowCoverColor = data.bookInfoFollowCoverColor
ThemeConfig.bookInfoBackgroundBlur = data.bookInfoBackgroundBlur
// 容器设置
ThemeConfig.containerOpacity = data.containerOpacity
@@ -697,6 +701,8 @@ data class ThemeExportData(
val themeBackgroundColorNight: Int = 0,
val labelContainerColorNight: Int = 0,
val bookInfoInputColor: Int = 0,
val bookInfoFollowCoverColor: Boolean = true,
val bookInfoBackgroundBlur: String = ThemeConfig.BOOK_INFO_BACKGROUND_BLUR_ON,
// 容器设置
val containerOpacity: Int = 100,
@@ -110,6 +110,8 @@ object BackupConfig {
PreferKey.bottomBarBlurAlpha,
PreferKey.bottomBarLensRadius,
PreferKey.useFlexibleTopAppBar,
PreferKey.bookInfoFollowCoverColor,
PreferKey.bookInfoBackgroundBlur,
PreferKey.cBackground,
PreferKey.cBBackground,
PreferKey.cNBackground,
@@ -2,8 +2,11 @@ package io.legado.app.ui.book.info
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.Crossfade
import androidx.compose.animation.EnterExitState
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@@ -68,14 +71,16 @@ import coil.size.Size
import io.legado.app.R
import io.legado.app.constant.BookType
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.config.themeConfig.ThemeConfig
import io.legado.app.ui.main.homepage.modules.BannerModule
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.LocalHazeState
import io.legado.app.ui.theme.ProvideThemeOverride
import io.legado.app.ui.theme.LocalLegadoThemeColors
import io.legado.app.ui.theme.ProvideColorSchemeOverride
import io.legado.app.ui.theme.ThemeOverrideState
import io.legado.app.ui.theme.ThemeResolver
import io.legado.app.ui.theme.animateColorSchemeAsState
import io.legado.app.ui.theme.fadingEdge
import io.legado.app.ui.theme.rememberImageSeedColor
import io.legado.app.ui.theme.rememberThemeOverride
@@ -92,6 +97,7 @@ import io.legado.app.ui.widget.components.changeSource.ChangeSourceSheet
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.cover.BookCoverImage
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.usesDefaultBookCover
import io.legado.app.ui.widget.components.log.AppLogSheet
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -121,7 +127,30 @@ fun BookInfoScreen(
animatedVisibilityScope: AnimatedVisibilityScope? = null,
sharedCoverKey: String? = null,
) {
val bookColorTheme = rememberBookInfoColorTheme(state.book)
val waitForSharedTransition = sharedCoverKey != null && animatedVisibilityScope != null
val transition = animatedVisibilityScope?.transition
val transitionSettled = transition?.let {
it.currentState == EnterExitState.Visible &&
it.targetState == EnterExitState.Visible &&
!it.isRunning
} == true
val sharedTransitionFinished = !waitForSharedTransition || transitionSettled
var canApplyCoverTheme by remember(
sharedCoverKey,
state.book?.bookUrl,
waitForSharedTransition,
) {
mutableStateOf(!waitForSharedTransition)
}
LaunchedEffect(sharedTransitionFinished) {
if (sharedTransitionFinished) {
canApplyCoverTheme = true
}
}
val bookColorTheme = rememberBookInfoColorTheme(
book = state.book,
enabled = canApplyCoverTheme,
)
BookInfoColorTheme(theme = bookColorTheme) {
BookInfoScreenContent(
@@ -344,7 +373,26 @@ private fun BookInfoColorTheme(
theme: ThemeOverrideState?,
content: @Composable () -> Unit,
) {
ProvideThemeOverride(theme = theme, content = content)
val baseTheme = LocalLegadoThemeColors.current
val animationSpec = tween<Color>(
durationMillis = 400,
easing = FastOutSlowInEasing,
)
val targetColorScheme = theme?.colorScheme ?: baseTheme.colorScheme
val targetSeedColor = theme?.seedColor ?: baseTheme.seedColor
val animatedColorScheme = targetColorScheme.animateColorSchemeAsState(animationSpec)
val animatedSeedColor by animateColorAsState(
targetValue = targetSeedColor,
animationSpec = animationSpec,
label = "book_info_theme_seed",
)
ProvideColorSchemeOverride(
colorScheme = animatedColorScheme,
seedColor = animatedSeedColor,
overrideIsDark = theme?.isDark ?: baseTheme.isDark,
content = content,
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterial3ExpressiveApi::class)
@@ -418,9 +466,17 @@ private fun BookInfoTransparentTopAppBar(
}
@Composable
private fun rememberBookInfoColorTheme(book: BookInfoBookUi?): ThemeOverrideState? {
val useDefaultCover = AppConfig.useDefaultCover || book?.coverPath == "use_default_cover"
if (useDefaultCover) return null
private fun rememberBookInfoColorTheme(
book: BookInfoBookUi?,
enabled: Boolean,
): ThemeOverrideState? {
if (
!enabled ||
!ThemeConfig.bookInfoFollowCoverColor ||
usesDefaultBookCover(book?.coverPath)
) {
return null
}
val imageLoader = koinInject<ImageLoader>()
val coverPath = book?.coverPath
@@ -481,6 +537,19 @@ private fun BookInfoTopBarActions(
private fun BookInfoBackdrop(
book: BookInfoBookUi,
) {
val initiallyUsesDefaultCover = usesDefaultBookCover(book.coverPath)
var usesDefaultCover by remember(
book.coverPath,
initiallyUsesDefaultCover,
) {
mutableStateOf(initiallyUsesDefaultCover)
}
val blurBackground = when (ThemeConfig.bookInfoBackgroundBlur) {
ThemeConfig.BOOK_INFO_BACKGROUND_BLUR_OFF -> false
ThemeConfig.BOOK_INFO_BACKGROUND_BLUR_OFF_FOR_DEFAULT -> !usesDefaultCover
else -> true
}
val backdropState = remember(
book.name,
book.author,
@@ -514,9 +583,16 @@ private fun BookInfoBackdrop(
modifier = Modifier
.fillMaxWidth()
.height(480.dp)
.blur(24.dp),
.then(
if (blurBackground) {
Modifier.blur(24.dp)
} else {
Modifier
}
),
contentScale = ContentScale.Crop,
showLoadingPlaceholder = false,
onDefaultCoverVisibilityChange = { usesDefaultCover = it },
requestBuilder = {
size(Size(384, 384))
}
@@ -35,6 +35,10 @@ object ThemeConfig {
private const val CUSTOM_APP_THEME = "12"
const val BOOK_INFO_BACKGROUND_BLUR_OFF = "off"
const val BOOK_INFO_BACKGROUND_BLUR_ON = "on"
const val BOOK_INFO_BACKGROUND_BLUR_OFF_FOR_DEFAULT = "off_for_default"
var containerOpacity by prefDelegate(PreferKey.containerOpacity, 100)
var topBarOpacity by prefDelegate(PreferKey.topBarOpacity, 100)
@@ -57,6 +61,13 @@ object ThemeConfig {
var useFlexibleTopAppBar by prefDelegate(PreferKey.useFlexibleTopAppBar, true)
var bookInfoFollowCoverColor by prefDelegate(PreferKey.bookInfoFollowCoverColor, true)
var bookInfoBackgroundBlur by prefDelegate(
PreferKey.bookInfoBackgroundBlur,
BOOK_INFO_BACKGROUND_BLUR_ON
)
var paletteStyle by prefDelegate(PreferKey.paletteStyle, "tonalSpot")
//m3 or miuix
@@ -444,6 +444,22 @@ fun ThemeConfigScreen(
)
}
SplicedColumnGroup(title = stringResource(R.string.book_info_page)) {
SwitchSettingItem(
title = stringResource(R.string.book_info_follow_cover_color),
description = stringResource(R.string.book_info_follow_cover_color_summary),
checked = ThemeConfig.bookInfoFollowCoverColor,
onCheckedChange = { ThemeConfig.bookInfoFollowCoverColor = it }
)
DropdownListSettingItem(
title = stringResource(R.string.book_info_background_blur),
selectedValue = ThemeConfig.bookInfoBackgroundBlur,
displayEntries = stringArrayResource(R.array.book_info_background_blur_entries),
entryValues = stringArrayResource(R.array.book_info_background_blur_values),
onValueChange = { ThemeConfig.bookInfoBackgroundBlur = it }
)
}
SplicedColumnGroup(title = stringResource(R.string.eye_protection)) {
var eyeProtectionEnabled by remember {
mutableStateOf(ThemeConfig.eyeProtectionEnabled)
@@ -50,8 +50,13 @@ import org.koin.compose.koinInject
import io.legado.app.model.BookCover as BookCoverModel
private const val SharedCoverRadiusCacheMaxSize = 256
private const val DefaultCoverPath = "use_default_cover"
private val sharedCoverRadiusCache = mutableStateMapOf<String, Dp>()
internal fun usesDefaultBookCover(path: String?): Boolean {
return CoverConfig.useDefaultCover || path.isNullOrBlank() || path == DefaultCoverPath
}
@Composable
fun BookCoverImage(
name: String?,
@@ -66,13 +71,16 @@ fun BookCoverImage(
onLoadFinish: (() -> Unit)? = null,
onSuccess: (() -> Unit)? = null,
onError: (() -> Unit)? = null,
onDefaultCoverVisibilityChange: ((Boolean) -> Unit)? = null,
sharedCoverKey: String? = null,
requestBuilder: ImageRequest.Builder.() -> Unit = {},
) {
val context = LocalContext.current
val isNight = LegadoTheme.isDark
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
val useDefault = (!ignoreUseDefaultCover && CoverConfig.useDefaultCover) ||
path.isNullOrBlank() ||
path == DefaultCoverPath
val finalPath = if (useDefault) null else path
val defaultCoverPaths =
if (isNight) CoverConfig.defaultCoverDark else CoverConfig.defaultCover
@@ -86,13 +94,27 @@ fun BookCoverImage(
val hasCustomDefault = !randomPath.isNullOrBlank()
var isOnlineCoverLoaded by remember(finalPath) { mutableStateOf(false) }
var onlineCoverLoadFailed by remember(finalPath) { mutableStateOf(false) }
LaunchedEffect(finalPath) {
if (finalPath == null) {
isOnlineCoverLoaded = false
onlineCoverLoadFailed = false
}
}
val showDefaultIcon = !hasCustomDefault &&
(
finalPath == null ||
onlineCoverLoadFailed ||
(showLoadingPlaceholder && !isOnlineCoverLoaded)
)
val isDefaultCoverVisible =
(hasCustomDefault && !isOnlineCoverLoaded) || showDefaultIcon
LaunchedEffect(isDefaultCoverVisible) {
onDefaultCoverVisibilityChange?.invoke(isDefaultCoverVisible)
}
Box(modifier = modifier) {
if (hasCustomDefault && !isOnlineCoverLoaded) {
AsyncImage(
@@ -111,6 +133,17 @@ fun BookCoverImage(
)
}
if (showDefaultIcon) {
Icon(
Icons.Default.Book,
contentDescription = null,
tint = LegadoTheme.colorScheme.secondary,
modifier = Modifier
.fillMaxSize(0.35f)
.align(Alignment.Center)
)
}
if (finalPath != null) {
AsyncImage(
model = buildCoverImageRequest(
@@ -128,11 +161,13 @@ fun BookCoverImage(
modifier = Modifier.fillMaxSize(),
onSuccess = {
isOnlineCoverLoaded = true
onlineCoverLoadFailed = false
onSuccess?.invoke()
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onlineCoverLoadFailed = true
onError?.invoke()
onLoadFinish?.invoke()
}
@@ -164,7 +199,9 @@ fun CoilBookCover(
) {
val isNight = LegadoTheme.isDark
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
val useDefault = (!ignoreUseDefaultCover && CoverConfig.useDefaultCover) ||
path.isNullOrBlank() ||
path == DefaultCoverPath
val finalPath = if (useDefault) null else path
val defaultCoverPaths =
if (isNight) CoverConfig.defaultCoverDark else CoverConfig.defaultCover
@@ -178,10 +215,12 @@ fun CoilBookCover(
val hasCustomDefault = !randomPath.isNullOrBlank()
var isOnlineCoverLoaded by remember(finalPath) { mutableStateOf(false) }
var onlineCoverLoadFailed by remember(finalPath) { mutableStateOf(false) }
LaunchedEffect(finalPath) {
if (finalPath == null) {
isOnlineCoverLoaded = false
onlineCoverLoadFailed = false
}
}
@@ -229,26 +268,22 @@ fun CoilBookCover(
showLoadingPlaceholder = showLoadingPlaceholder,
onSuccess = {
isOnlineCoverLoaded = true
onlineCoverLoadFailed = false
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onlineCoverLoadFailed = true
onLoadFinish?.invoke()
},
sharedCoverKey = sharedCoverKey
)
if (showLoadingPlaceholder && !isOnlineCoverLoaded) {
if (!hasCustomDefault) {
Icon(
Icons.Default.Book,
contentDescription = null,
tint = LegadoTheme.colorScheme.secondary,
modifier = Modifier
.fillMaxSize(0.35f)
.align(Alignment.Center)
)
}
if (
finalPath == null ||
onlineCoverLoadFailed ||
(showLoadingPlaceholder && !isOnlineCoverLoaded)
) {
CoverTextOverlay(
name = name,
author = author,
@@ -1473,6 +1473,13 @@
<string name="theme_manage_page_turn_animation">翻页动画</string>
<string name="theme_manage_label_visibility">标签显示</string>
<string name="theme_manage_section_blur">模糊效果</string>
<string name="book_info_page">书籍详情页</string>
<string name="book_info_follow_cover_color">界面颜色跟随封面取色</string>
<string name="book_info_follow_cover_color_summary">从书籍封面提取详情页配色</string>
<string name="book_info_background_blur">背景模糊</string>
<string name="book_info_background_blur_off">关闭</string>
<string name="book_info_background_blur_on">开启</string>
<string name="book_info_background_blur_off_for_default">显示默认封面时关闭</string>
<string name="theme_manage_top_bar_blur_radius">顶栏模糊半径</string>
<string name="theme_manage_bottom_bar_blur_radius">底栏模糊半径</string>
<string name="theme_manage_top_bar_blur_opacity">顶栏模糊不透明度</string>
@@ -2132,6 +2132,13 @@
<string name="theme_manage_secondary_text_color">副文字色</string>
<string name="theme_manage_section_basic">基本設定</string>
<string name="theme_manage_section_blur">模糊效果</string>
<string name="book_info_page">書籍詳情頁</string>
<string name="book_info_follow_cover_color">介面顏色跟隨封面取色</string>
<string name="book_info_follow_cover_color_summary">從書籍封面擷取詳情頁配色</string>
<string name="book_info_background_blur">背景模糊</string>
<string name="book_info_background_blur_off">關閉</string>
<string name="book_info_background_blur_on">開啟</string>
<string name="book_info_background_blur_off_for_default">顯示預設封面時關閉</string>
<string name="theme_manage_section_colors">顏色</string>
<string name="theme_manage_section_container">容器</string>
<string name="theme_manage_section_layout">介面佈局</string>
@@ -1620,6 +1620,13 @@
<string name="theme_manage_page_turn_animation">翻頁動畫</string>
<string name="theme_manage_label_visibility">標籤可見性</string>
<string name="theme_manage_section_blur">模糊效果</string>
<string name="book_info_page">書籍詳情頁</string>
<string name="book_info_follow_cover_color">介面顏色跟隨封面取色</string>
<string name="book_info_follow_cover_color_summary">從書籍封面擷取詳情頁配色</string>
<string name="book_info_background_blur">背景模糊</string>
<string name="book_info_background_blur_off">關閉</string>
<string name="book_info_background_blur_on">開啟</string>
<string name="book_info_background_blur_off_for_default">顯示預設封面時關閉</string>
<string name="theme_manage_top_bar_blur_radius">頂部工具列模糊半徑</string>
<string name="theme_manage_bottom_bar_blur_radius">底部工具列模糊半徑</string>
<string name="theme_manage_top_bar_blur_opacity">頂部工具列模糊不透明度</string>
+7 -1
View File
@@ -132,6 +132,12 @@
<item>unlabeled</item>
</string-array>
<string-array name="book_info_background_blur_values">
<item>off</item>
<item>on</item>
<item>off_for_default</item>
</string-array>
<string-array name="read_slider_mode_value">
<item>0</item>
<item>1</item>
@@ -156,4 +162,4 @@
<item>webdav</item>
</string-array>
</resources>
</resources>
+6
View File
@@ -329,6 +329,12 @@
<item>Unlabeled</item>
</string-array>
<string-array name="book_info_background_blur_entries">
<item>@string/book_info_background_blur_off</item>
<item>@string/book_info_background_blur_on</item>
<item>@string/book_info_background_blur_off_for_default</item>
</string-array>
<string-array name="customContrast">
<item>Default</item>
<item>High</item>
+7
View File
@@ -1508,6 +1508,13 @@
<string name="theme_manage_page_turn_animation">Page Turn Animation</string>
<string name="theme_manage_label_visibility">Label Visibility</string>
<string name="theme_manage_section_blur">Blur Effects</string>
<string name="book_info_page">Book details</string>
<string name="book_info_follow_cover_color">Use colors from book cover</string>
<string name="book_info_follow_cover_color_summary">Generate the book details color scheme from the cover</string>
<string name="book_info_background_blur">Blurred cover background</string>
<string name="book_info_background_blur_off">Off</string>
<string name="book_info_background_blur_on">On</string>
<string name="book_info_background_blur_off_for_default">Off when showing the default cover</string>
<string name="theme_manage_top_bar_blur_radius">Top Bar Blur Radius</string>
<string name="theme_manage_bottom_bar_blur_radius">Bottom Bar Blur Radius</string>
<string name="theme_manage_top_bar_blur_opacity">Top Bar Blur Opacity</string>