Procházet zdrojové kódy

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

HapeLee před 4 měsíci
rodič
revize
6f87071a85

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

@@ -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"

+ 6 - 0
app/src/main/java/io/legado/app/help/config/ThemeImportExport.kt

@@ -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,

+ 2 - 0
app/src/main/java/io/legado/app/help/storage/BackupConfig.kt

@@ -110,6 +110,8 @@ object BackupConfig {
         PreferKey.bottomBarBlurAlpha,
         PreferKey.bottomBarLensRadius,
         PreferKey.useFlexibleTopAppBar,
+        PreferKey.bookInfoFollowCoverColor,
+        PreferKey.bookInfoBackgroundBlur,
         PreferKey.cBackground,
         PreferKey.cBBackground,
         PreferKey.cNBackground,

+ 84 - 8
app/src/main/java/io/legado/app/ui/book/info/BookInfoScreen.kt

@@ -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))
                 }

+ 11 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfig.kt

@@ -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

+ 16 - 0
app/src/main/java/io/legado/app/ui/config/themeConfig/ThemeConfigScreen.kt

@@ -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)

+ 48 - 13
app/src/main/java/io/legado/app/ui/widget/components/image/cover/CoilBookCover.kt

@@ -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,

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

@@ -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>

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

@@ -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>

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

@@ -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
app/src/main/res/values/array_values.xml

@@ -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 - 0
app/src/main/res/values/arrays.xml

@@ -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 - 0
app/src/main/res/values/strings.xml

@@ -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>