Explorar o código

feat: 书架书籍封面增加简介放在下面的神秘功能

HapeLee hai 3 meses
pai
achega
c5f7fb88f7

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

@@ -57,6 +57,7 @@ object PreferKey {
     const val bookshelfLayoutModePortrait = "bookshelfLayoutPortrait"
     const val bookshelfLayoutModeLandscape = "bookshelf_layout_landscape"
     const val bookshelfLayoutCompact = "bookshelfLayoutCompact"
+    const val bookshelfListIntroBelowContent = "bookshelfListIntroBelowContent"
     const val bookshelfShowDivider = "bookshelfShowDivider"
     const val bookshelfGridLayout = "bookshelfGridLayout"
     const val bookshelfSort = "bookshelfSort"

+ 3 - 0
app/src/main/java/io/legado/app/data/repository/BookshelfSettingsRepository.kt

@@ -63,6 +63,8 @@ internal fun Preferences.toBookshelfSettings() = BookshelfSettings(
     bookshelfFolderLayoutListLandscape = compatDsInt(PreferKey.bookshelfFolderLayoutListLandscape) ?: 1,
     bookshelfGridLayout = compatDsInt(PreferKey.bookshelfGridLayout) ?: 0,
     bookshelfLayoutCompact = compatDsBoolean(PreferKey.bookshelfLayoutCompact) ?: false,
+    bookshelfListIntroBelowContent = compatDsBoolean(PreferKey.bookshelfListIntroBelowContent)
+        ?: false,
     bookshelfShowDivider = compatDsBoolean(PreferKey.bookshelfShowDivider) ?: true,
     bookshelfTitleSmallFont = compatDsBoolean(PreferKey.bookshelfTitleSmallFont) ?: false,
     bookshelfTitleCenter = compatDsBoolean(PreferKey.bookshelfTitleCenter) ?: true,
@@ -112,6 +114,7 @@ internal fun BookshelfSettings.toPrefMap(): Map<String, Any?> = mapOf(
     PreferKey.bookshelfFolderLayoutListLandscape to bookshelfFolderLayoutListLandscape,
     PreferKey.bookshelfGridLayout to bookshelfGridLayout,
     PreferKey.bookshelfLayoutCompact to bookshelfLayoutCompact,
+    PreferKey.bookshelfListIntroBelowContent to bookshelfListIntroBelowContent,
     PreferKey.bookshelfShowDivider to bookshelfShowDivider,
     PreferKey.bookshelfTitleSmallFont to bookshelfTitleSmallFont,
     PreferKey.bookshelfTitleCenter to bookshelfTitleCenter,

+ 1 - 0
app/src/main/java/io/legado/app/domain/model/settings/BookshelfSettings.kt

@@ -35,6 +35,7 @@ data class BookshelfSettings(
     val bookshelfFolderLayoutListLandscape: Int = 1,
     val bookshelfGridLayout: Int = 0,
     val bookshelfLayoutCompact: Boolean = false,
+    val bookshelfListIntroBelowContent: Boolean = false,
     val bookshelfShowDivider: Boolean = true,
     val bookshelfTitleSmallFont: Boolean = false,
     val bookshelfTitleCenter: Boolean = true,

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

@@ -306,6 +306,7 @@ object BackupConfig {
         PreferKey.bookshelfLayoutModePortrait,
         PreferKey.bookshelfLayoutModeLandscape,
         PreferKey.bookshelfLayoutCompact,
+        PreferKey.bookshelfListIntroBelowContent,
         PreferKey.bookshelfShowDivider,
         PreferKey.bookshelfGridLayout,
         PreferKey.bookshelfSort,

+ 1 - 0
app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt

@@ -37,6 +37,7 @@ object BookshelfConfig {
     val bookshelfFolderLayoutListLandscape get() = settings.bookshelfFolderLayoutListLandscape
     val bookshelfGridLayout get() = settings.bookshelfGridLayout
     val bookshelfLayoutCompact get() = settings.bookshelfLayoutCompact
+    val bookshelfListIntroBelowContent get() = settings.bookshelfListIntroBelowContent
     val bookshelfShowDivider get() = settings.bookshelfShowDivider
     val bookshelfTitleSmallFont get() = settings.bookshelfTitleSmallFont
     val bookshelfTitleCenter get() = settings.bookshelfTitleCenter

+ 264 - 270
app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt

@@ -55,6 +55,7 @@ import io.legado.app.constant.BookType
 import io.legado.app.domain.model.settings.BookshelfSettings
 import io.legado.app.ui.config.themeConfig.TagColorPair
 import io.legado.app.ui.theme.LegadoTheme
+import io.legado.app.ui.widget.components.card.GlassCard
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.icon.AppIcon
@@ -65,17 +66,83 @@ import io.legado.app.utils.HtmlFormatter
 import io.legado.app.utils.toTimeAgo
 import kotlinx.collections.immutable.ImmutableList
 
-/**
- * 通用的书架条目布局组件
- * 支持 列表/网格 模式及 标准/紧凑/仅封面 样式
- */
 @OptIn(ExperimentalFoundationApi::class)
 @Composable
-fun BookshelfItem(
+fun BookshelfGridItem(
+    cover: @Composable (Modifier) -> Unit,
+    title: String,
+    gridStyle: Int,
+    modifier: Modifier = Modifier,
+    isSelected: Boolean = false,
+    titleSmallFont: Boolean = false,
+    titleCenter: Boolean = true,
+    titleMaxLines: Int = 2,
+    coverShadow: Boolean = false,
+    accessibilityLabel: String? = null,
+    coverWidth: Int = 84,
+    onClick: () -> Unit,
+    onLongClick: (() -> Unit)?
+) {
+    Box(
+        modifier = modifier
+            .fillMaxWidth()
+            .clip(RoundedCornerShape(4.dp))
+            .then(if (isSelected) Modifier.background(LegadoTheme.colorScheme.secondaryContainer) else Modifier)
+            .combinedClickable(role = Role.Button, onClick = onClick, onLongClick = onLongClick)
+            .bookshelfItemSemantics(accessibilityLabel ?: title, isSelected)
+    ) {
+        Column(
+            horizontalAlignment = Alignment.CenterHorizontally,
+            modifier = Modifier
+                .align(Alignment.Center)
+                .width(coverWidth.dp)
+        ) {
+            BookshelfItemCover(coverShadow = coverShadow, cover = cover) {
+                if (gridStyle == 1) {
+                    AppText(
+                        text = title,
+                        style = (if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium).copy(
+                            color = Color.White,
+                            shadow = Shadow(color = Color.Black.copy(alpha = 0.5f), blurRadius = 4f)
+                        ),
+                        textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
+                        maxLines = 2,
+                        overflow = TextOverflow.Ellipsis,
+                        modifier = Modifier
+                            .align(Alignment.BottomStart)
+                            .fillMaxWidth()
+                            .background(
+                                Brush.verticalGradient(
+                                    listOf(
+                                        Color.Transparent,
+                                        Color.Black.copy(alpha = 0.7f)
+                                    )
+                                )
+                            )
+                            .padding(all = 4.dp)
+                    )
+                }
+            }
+            if (gridStyle == 0) {
+                AppText(
+                    text = title,
+                    style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
+                    maxLines = titleMaxLines,
+                    overflow = TextOverflow.Ellipsis,
+                    textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(start = 4.dp, end = 4.dp, bottom = 4.dp)
+                )
+            }
+        }
+    }
+}
+
+@Composable
+fun BookshelfListItem(
     settings: BookshelfSettings,
-    isGrid: Boolean,
-    gridStyle: Int, // 0: Standard, 1: Compact, 2: Cover Only
-    isCompact: Boolean, // For List Mode
+    isCompact: Boolean,
     cover: @Composable (Modifier) -> Unit,
     title: String,
     modifier: Modifier = Modifier,
@@ -83,237 +150,125 @@ fun BookshelfItem(
     titleEnd: @Composable (() -> Unit)? = null,
     subTitle: String? = null,
     desc: String? = null,
+    descAnnotated: AnnotatedString? = null,
     descMaxLines: Int = 1,
     extra: @Composable (RowScope.() -> Unit)? = null,
     columnContent: @Composable (ColumnScope.() -> Unit)? = null,
     bottomContent: @Composable (() -> Unit)? = null,
-    titleSmallFont: Boolean = false,
-    titleCenter: Boolean = true,
     titleMaxLines: Int = 2,
     coverShadow: Boolean = false,
     titleColor: Color? = null,
-    descAnnotated: AnnotatedString? = null,
     accessibilityLabel: String? = null,
     coverWidth: Int = 84,
     onClick: () -> Unit,
     onLongClick: (() -> Unit)?
 ) {
-    val isDark = LegadoTheme.isDark
-    val bookshelfCardColor =
-        if (isDark) settings.bookshelfCardColorDark else settings.bookshelfCardColor
-    val containerColor = if (!isGrid && bookshelfCardColor != 0) {
-        Color(bookshelfCardColor)
-    } else {
-        LegadoTheme.colorScheme.cardContainer
-    }
-    val itemAccessibilityLabel = accessibilityLabel ?: title
-    val accessibilityModifier = Modifier.semantics(mergeDescendants = true) {
-        contentDescription = itemAccessibilityLabel
-        role = Role.Button
-        if (isSelected) {
-            selected = true
-        }
-    }
-
-    if (isGrid) {
-        Box(
+    val cardColor =
+        if (LegadoTheme.isDark) settings.bookshelfCardColorDark else settings.bookshelfCardColor
+    Column {
+        NormalCard(
             modifier = modifier
                 .fillMaxWidth()
-                .clip(RoundedCornerShape(4.dp))
-                .then(
-                    if (isSelected) {
-                        Modifier.background(LegadoTheme.colorScheme.secondaryContainer)
-                    } else {
-                        Modifier
-                    }
-                )
-                .combinedClickable(
-                    role = Role.Button,
-                    onClick = onClick,
-                    onLongClick = onLongClick
-                )
-                .then(accessibilityModifier)
+                .padding(vertical = 4.dp)
+                .bookshelfItemSemantics(accessibilityLabel ?: title, isSelected),
+            cornerRadius = 8.dp,
+            containerColor = if (isSelected) LegadoTheme.colorScheme.secondaryContainer else if (cardColor != 0) Color(
+                cardColor
+            ) else LegadoTheme.colorScheme.cardContainer,
+            onClick = onClick,
+            onLongClick = onLongClick
         ) {
-            Column(
-                horizontalAlignment = Alignment.CenterHorizontally,
-                modifier = Modifier
-                    .align(Alignment.Center)
-                    .width(coverWidth.dp)
-            ) {
-
-                Box(
+            Row(Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
+                Box(Modifier
+                    .align(Alignment.CenterVertically)
+                    .width(coverWidth.dp)) {
+                    BookshelfItemCover(coverShadow = coverShadow, cover = cover)
+                }
+                Column(
                     modifier = Modifier
-                        .padding(4.dp)
-                        .fillMaxWidth()
-                        .aspectRatio(5f / 7f)
-                        .then(
-                            if (coverShadow) Modifier.shadow(
-                                4.dp,
-                                RoundedCornerShape(4.dp)
-                            ) else Modifier
-                        )
-                        .clip(RoundedCornerShape(4.dp))
+                        .weight(1f)
+                        .padding(top = 4.dp, bottom = 4.dp, end = 8.dp, start = 4.dp),
+                    verticalArrangement = Arrangement.Center
                 ) {
-                    cover(Modifier.fillMaxSize())
-                    if (gridStyle == 1) {
+                    Row(verticalAlignment = Alignment.Top) {
                         AppText(
                             text = title,
-                            style = (if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium).copy(
-                                color = Color.White,
-                                shadow = Shadow(
-                                    color = Color.Black.copy(alpha = 0.5f),
-                                    blurRadius = 4f
-                                )
-                            ),
-                            textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
-                            maxLines = 2,
+                            style = if (titleColor != null) LegadoTheme.typography.titleMediumEmphasized.copy(
+                                color = titleColor
+                            ) else LegadoTheme.typography.titleMediumEmphasized,
+                            maxLines = titleMaxLines,
                             overflow = TextOverflow.Ellipsis,
-                            modifier = Modifier
-                                .align(Alignment.BottomStart)
-                                .fillMaxWidth()
-                                .background(
-                                    Brush.verticalGradient(
-                                        colors = listOf(
-                                            Color.Transparent,
-                                            Color.Black.copy(alpha = 0.7f)
-                                        )
-                                    )
-                                )
-                                .padding(all = 4.dp)
+                            modifier = Modifier.weight(1f)
                         )
+                        titleEnd?.let { Box(Modifier.padding(top = 4.dp, start = 4.dp)) { it() } }
                     }
-                }
-
-                if (gridStyle == 0) {
-                    AppText(
-                        text = title,
-                        style = if (titleSmallFont) LegadoTheme.typography.labelSmall else LegadoTheme.typography.labelMedium,
-                        maxLines = titleMaxLines,
-                        overflow = TextOverflow.Ellipsis,
-                        textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(start = 4.dp, end = 4.dp, bottom = 4.dp)
-                    )
-                }
-            }
-        }
-    } else {
-        Column {
-            NormalCard(
-                modifier = modifier
-                    .fillMaxWidth()
-                    .padding(vertical = 4.dp)
-                    .then(accessibilityModifier),
-                cornerRadius = 8.dp,
-                containerColor = if (isSelected) {
-                    LegadoTheme.colorScheme.secondaryContainer
-                } else {
-                    containerColor
-                },
-                onClick = onClick,
-                onLongClick = onLongClick
-            ) {
-                Row(
-                    modifier = Modifier
-                        .fillMaxWidth(),
-                    verticalAlignment = Alignment.CenterVertically
-                ) {
-                    Box(
-                        modifier = Modifier
-                            .align(Alignment.CenterVertically)
-                            .width(coverWidth.dp)
-                    ) {
-                        Box(
-                            modifier = Modifier
-                                .padding(4.dp)
-                                .fillMaxWidth()
-                                .aspectRatio(5f / 7f)
-                                .then(
-                                    if (coverShadow) Modifier.shadow(
-                                        4.dp,
-                                        RoundedCornerShape(4.dp)
-                                    ) else Modifier
-                                )
-                                .clip(RoundedCornerShape(4.dp))
-                        ) {
-                            cover(Modifier.fillMaxSize())
-                        }
-                    }
-                    Column(
-                        modifier = Modifier
-                            .weight(1f)
-                            .padding(top = 4.dp, bottom = 4.dp, end = 8.dp, start = 4.dp),
-                        verticalArrangement = Arrangement.Center
-                    ) {
-                    Row(
-                        verticalAlignment = Alignment.Top
-                    ) {
+                    subTitle?.let {
                         AppText(
-                            text = title,
-                            style = if (titleColor != null) {
-                                LegadoTheme.typography.titleMediumEmphasized.copy(color = titleColor)
-                            } else {
-                                LegadoTheme.typography.titleMediumEmphasized
-                            },
-                            maxLines = settings.bookshelfTitleMaxLines,
+                            text = it,
+                            style = LegadoTheme.typography.bodySmall,
+                            maxLines = 1,
                             overflow = TextOverflow.Ellipsis,
-                            modifier = Modifier.weight(1f)
                         )
-                        titleEnd?.let {
-                            Box(modifier = Modifier.padding(top = 4.dp, start = 4.dp)) {
-                                it.invoke()
-                            }
-                        }
                     }
-                        subTitle?.let {
+                    if (!isCompact) {
+                        descAnnotated?.let {
                             AppText(
                                 text = it,
-                                style = LegadoTheme.typography.bodySmall,
-                                maxLines = 1,
+                                style = LegadoTheme.typography.labelSmallEmphasized,
+                                maxLines = descMaxLines,
                                 overflow = TextOverflow.Ellipsis,
                             )
-                        }
-                        if (!isCompact) {
-                            if (descAnnotated != null) {
-                                AppText(
-                                    text = descAnnotated,
-                                    style = LegadoTheme.typography.labelSmallEmphasized,
-                                    maxLines = descMaxLines,
-                                    overflow = TextOverflow.Ellipsis
-                                )
-                            } else {
-                                desc?.let {
-                                    AppText(
-                                        text = it,
-                                        style = LegadoTheme.typography.labelSmallEmphasized,
-                                        maxLines = descMaxLines,
-                                        overflow = TextOverflow.Ellipsis
-                                    )
-                                }
-                            }
-                        }
-                        extra?.let {
-                            Row(
-                                verticalAlignment = Alignment.CenterVertically,
-                                content = it
+                        } ?: desc?.let {
+                            AppText(
+                                text = it,
+                                style = LegadoTheme.typography.labelSmallEmphasized,
+                                maxLines = descMaxLines,
+                                overflow = TextOverflow.Ellipsis,
                             )
                         }
-                        columnContent?.invoke(this)
                     }
+                    extra?.let { Row(verticalAlignment = Alignment.CenterVertically, content = it) }
+                    columnContent?.invoke(this)
                 }
-                bottomContent?.invoke()
             }
-            if (settings.bookshelfShowDivider)
-                HorizontalDivider(
-                    modifier = Modifier.padding(horizontal = 16.dp),
-                    thickness = 0.5.dp,
-                    color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)
-                )
+            bottomContent?.invoke()
         }
+        if (settings.bookshelfShowDivider) {
+            HorizontalDivider(
+                Modifier.padding(horizontal = 16.dp),
+                thickness = 0.5.dp,
+                color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)
+            )
+        }
+    }
+}
+
+@Composable
+private fun BookshelfItemCover(
+    coverShadow: Boolean,
+    cover: @Composable (Modifier) -> Unit,
+    overlay: @Composable androidx.compose.foundation.layout.BoxScope.() -> Unit = {},
+) {
+    Box(
+        modifier = Modifier
+            .padding(4.dp)
+            .fillMaxWidth()
+            .aspectRatio(5f / 7f)
+            .then(if (coverShadow) Modifier.shadow(4.dp, RoundedCornerShape(4.dp)) else Modifier)
+            .clip(RoundedCornerShape(4.dp))
+    ) {
+        cover(Modifier.fillMaxSize())
+        overlay()
     }
 }
 
+private fun Modifier.bookshelfItemSemantics(label: String, isSelected: Boolean): Modifier =
+    semantics(mergeDescendants = true) {
+        contentDescription = label
+        role = Role.Button
+        if (isSelected) selected = true
+    }
+
 @Composable
 fun BookGroupCover(
     settings: BookshelfSettings,
@@ -446,11 +401,8 @@ fun BookGroupItemGrid(
     onClick: () -> Unit,
     onLongClick: (() -> Unit)?
 ) {
-    BookshelfItem(
-        settings = settings,
-        isGrid = true,
+    BookshelfGridItem(
         gridStyle = gridStyle,
-        isCompact = false,
         cover = {
             BookGroupCover(
                 settings = settings,
@@ -482,8 +434,6 @@ fun BookGroupItemList(
     modifier: Modifier = Modifier,
     countText: String? = null,
     isCompact: Boolean = false,
-    titleSmallFont: Boolean = false,
-    titleCenter: Boolean = true,
     titleMaxLines: Int = 2,
     coverShadow: Boolean = false,
     onLongClick: (() -> Unit)? = null,
@@ -513,10 +463,8 @@ fun BookGroupItemList(
     } else {
         null
     }
-    BookshelfItem(
+    BookshelfListItem(
         settings = settings,
-        isGrid = false,
-        gridStyle = 0,
         isCompact = settings.bookshelfGroupListStyle == 1 || isCompact,
         cover = {
             BookGroupCover(
@@ -534,8 +482,6 @@ fun BookGroupItemList(
             countText,
             firstBookName?.let { "${stringResource(R.string.recently_read)}$it" },
         ),
-        titleSmallFont = titleSmallFont,
-        titleCenter = titleCenter,
         titleMaxLines = titleMaxLines,
         coverShadow = coverShadow,
         coverWidth = settings.bookshelfListCoverWidth,
@@ -684,6 +630,12 @@ fun BookItem(
     onLongClick: (() -> Unit)?
 ) {
     val book = bookUi.book
+    val intro = remember(book.intro) {
+        HtmlFormatter.formatDisplayText(book.intro).takeIf { it.isNotBlank() }
+    }
+    val showListDetails = layoutMode == 0 && !isCompact && settings.showBookIntro
+    val showIntro = showListDetails && settings.bookshelfShowIntro && intro != null
+    val showIntroBelowContent = showIntro && settings.bookshelfListIntroBelowContent
     val unreadCount = book.getUnreadChapterNum()
     val unreadText = if (settings.showUnread && unreadCount > 0) unreadCount.toString() else null
     val showUpdateBadge = settings.showUnread && settings.showUnreadNew && book.isNew
@@ -708,14 +660,64 @@ fun BookItem(
         null
     }
 
-    BookshelfItem(
+    val cover: @Composable (Modifier) -> Unit = { coverModifier ->
+        BookshelfCover(
+            name = book.name,
+            author = book.author,
+            path = book.getDisplayCover(),
+            isUpdating = isUpdating,
+            modifier = coverModifier,
+            coverModifier = Modifier
+                .fillMaxWidth()
+                .aspectRatio(5f / 7f),
+            sourceOrigin = book.origin,
+            badgeText = if (layoutMode != 0) unreadText else null,
+            showBadgeDot = showUpdateBadge,
+            leftBottomText = matchedSourceLabel ?: bookTypeLabel,
+            showLoadingPlaceholder = true,
+            sharedTransitionScope = sharedTransitionScope,
+            animatedVisibilityScope = animatedVisibilityScope,
+            sharedCoverKey = sharedCoverKey,
+        )
+    }
+    val accessibilityLabel = bookAccessibilityLabel(
+        name = book.name,
+        author = book.author,
+        unreadText?.let { "$it ${stringResource(R.string.is_unread)}" },
+        if (isUpdating) stringResource(R.string.loading) else null,
+        book.durChapterTitle,
+        book.latestChapterTitle,
+        matchedSourceLabel,
+        bookTypeLabel,
+    )
+
+    if (layoutMode != 0) {
+        BookshelfGridItem(
+            cover = cover,
+            title = book.name,
+            gridStyle = gridStyle,
+            modifier = modifier,
+            isSelected = isSelected,
+            titleSmallFont = titleSmallFont,
+            titleCenter = titleCenter,
+            titleMaxLines = titleMaxLines,
+            coverShadow = coverShadow,
+            accessibilityLabel = accessibilityLabel,
+            coverWidth = settings.bookshelfGridCoverWidth,
+            onClick = onClick,
+            onLongClick = onLongClick,
+        )
+        return
+    }
+
+    BookshelfListItem(
         settings = settings,
-        isGrid = layoutMode != 0,
-        gridStyle = gridStyle,
         isCompact = isCompact,
-        isSelected = isSelected,
+        cover = cover,
+        title = book.name,
         modifier = modifier,
-        titleEnd = if (layoutMode == 0 && unreadText != null) {
+        isSelected = isSelected,
+        titleEnd = if (unreadText != null) {
             {
                 TextCard(
                     text = unreadText,
@@ -727,39 +729,15 @@ fun BookItem(
                 )
             }
         } else null,
-        cover = { modifier ->
-            BookshelfCover(
-                name = book.name,
-                author = book.author,
-                path = book.getDisplayCover(),
-                isUpdating = isUpdating,
-                modifier = modifier,
-                coverModifier = Modifier
-                    .fillMaxWidth()
-                    .aspectRatio(5f / 7f),
-                sourceOrigin = book.origin,
-                badgeText = if (layoutMode != 0) unreadText else null,
-                showBadgeDot = showUpdateBadge,
-                leftBottomText = matchedSourceLabel ?: bookTypeLabel,
-                showLoadingPlaceholder = true,
-                sharedTransitionScope = sharedTransitionScope,
-                animatedVisibilityScope = animatedVisibilityScope,
-                sharedCoverKey = sharedCoverKey,
-            )
-        },
-        title = book.name,
-        subTitle = if (layoutMode == 0 && isCompact) {
+        subTitle = if (isCompact) {
             stringResource(R.string.author_read, book.author, unreadCount)
         } else {
             book.author
         },
         desc = book.durChapterTitle ?: "",
-        columnContent = if (layoutMode == 0 && !isCompact && settings.showBookIntro) {
+        columnContent = if (showListDetails) {
             {
                 val kindList = bookUi.displayTags
-                val intro = remember(book.intro) {
-                    HtmlFormatter.formatDisplayText(book.intro).takeIf { it.isNotBlank() }
-                }
                 if (settings.bookshelfShowTag && kindList.isNotEmpty()) {
                     Row(
                         modifier = Modifier
@@ -777,7 +755,9 @@ fun BookItem(
                             }
                             TextCard(
                                 text = label,
-                                backgroundColor = if (colorPair != null && colorPair.bgColor != 0) Color(colorPair.bgColor) else LegadoTheme.colorScheme.surfaceContainerHigh,
+                                backgroundColor = if (colorPair != null && colorPair.bgColor != 0) Color(
+                                    colorPair.bgColor
+                                ) else LegadoTheme.colorScheme.cardPrimaryContainer,
                                 contentColor = if (colorPair != null && colorPair.textColor != 0) Color(colorPair.textColor) else LegadoTheme.colorScheme.primary.copy(alpha = 0.8f),
                                 cornerRadius = 4.dp,
                                 horizontalPadding = 4.dp,
@@ -787,23 +767,30 @@ fun BookItem(
                         }
                     }
                 }
-                if (settings.bookshelfShowIntro && intro != null) {
-                    val maxLines = if (settings.bookshelfIntroMaxLines == 0) Int.MAX_VALUE else settings.bookshelfIntroMaxLines
-                    AppText(
-                        text = intro,
-                        style = LegadoTheme.typography.bodySmall,
-                        color = LegadoTheme.colorScheme.onSurfaceVariant,
-                        maxLines = maxLines,
-                        overflow = TextOverflow.Ellipsis,
-                        modifier = Modifier
-                            .fillMaxWidth()
-                            .padding(vertical = 4.dp)
+                if (showIntro && !showIntroBelowContent) {
+                    BookItemIntro(
+                        intro = intro,
+                        maxLines = settings.bookshelfIntroMaxLines,
+                    )
+                }
+            }
+        } else null,
+        bottomContent = if (showIntroBelowContent) {
+            {
+                GlassCard(
+                    modifier = Modifier.padding(all = 4.dp),
+                    cornerRadius = 4.dp,
+                    containerColor = LegadoTheme.colorScheme.cardContainer
+                ) {
+                    BookItemIntro(
+                        intro = intro,
+                        maxLines = settings.bookshelfIntroMaxLines,
+                        modifier = Modifier.padding(horizontal = 8.dp),
                     )
                 }
             }
         } else null,
-        bottomContent = null,
-        extra = if (layoutMode == 0 && !isCompact && settings.showBookIntro && settings.bookshelfShowLatestChapter) {
+        extra = if (showListDetails && settings.bookshelfShowLatestChapter) {
             {
                 if (settings.showLastUpdateTime && !book.isLocal) {
                     AppText(
@@ -823,23 +810,30 @@ fun BookItem(
                 )
             }
         } else null,
-        titleSmallFont = titleSmallFont,
-        titleCenter = titleCenter,
         titleMaxLines = titleMaxLines,
         coverShadow = coverShadow,
-        accessibilityLabel = bookAccessibilityLabel(
-            name = book.name,
-            author = book.author,
-            unreadText?.let { "$it ${stringResource(R.string.is_unread)}" },
-            if (isUpdating) stringResource(R.string.loading) else null,
-            book.durChapterTitle,
-            book.latestChapterTitle,
-            matchedSourceLabel,
-            bookTypeLabel,
-        ),
-        coverWidth = if (layoutMode == 0) settings.bookshelfListCoverWidth else settings.bookshelfGridCoverWidth,
+        accessibilityLabel = accessibilityLabel,
+        coverWidth = settings.bookshelfListCoverWidth,
         onClick = onClick,
-        onLongClick = onLongClick
+        onLongClick = onLongClick,
+    )
+}
+
+@Composable
+private fun BookItemIntro(
+    intro: String,
+    maxLines: Int,
+    modifier: Modifier = Modifier,
+) {
+    AppText(
+        text = intro,
+        style = LegadoTheme.typography.bodySmall,
+        color = LegadoTheme.colorScheme.onSurfaceVariant,
+        maxLines = if (maxLines == 0) Int.MAX_VALUE else maxLines,
+        overflow = TextOverflow.Ellipsis,
+        modifier = modifier
+            .fillMaxWidth()
+            .padding(vertical = 4.dp),
     )
 }
 

+ 10 - 0
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt

@@ -448,6 +448,16 @@ fun BookshelfConfigSheet(
                                     onUpdate { it.copy(bookshelfShowIntro = value) }
                                 }
                             )
+                            AnimatedVisibility(visible = settings.bookshelfShowIntro && layoutMode == 0) {
+                                CompactSwitchSettingItem(
+                                    title = stringResource(R.string.show_synopsis_below_content),
+                                    checked = settings.bookshelfListIntroBelowContent,
+                                    color = LegadoTheme.colorScheme.surface,
+                                    onCheckedChange = { value ->
+                                        onUpdate { it.copy(bookshelfListIntroBelowContent = value) }
+                                    }
+                                )
+                            }
                             AnimatedVisibility(
                                 visible = settings.bookshelfShowIntro
                             ) {

+ 0 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -863,8 +863,6 @@ fun BookshelfScreen(
                                         ?: emptyList(),
                                     countText = countText,
                                     isCompact = uiState.settings.bookshelfLayoutCompact,
-                                    titleSmallFont = uiState.settings.bookshelfTitleSmallFont,
-                                    titleCenter = uiState.settings.bookshelfTitleCenter,
                                     titleMaxLines = uiState.settings.bookshelfTitleMaxLines,
                                     coverShadow = uiState.settings.bookshelfCoverShadow,
                                     onClick = {

+ 1 - 2
app/src/main/java/io/legado/app/ui/theme/ThemeColorSchemeOverride.kt

@@ -1,7 +1,6 @@
 package io.legado.app.ui.theme
 
 import androidx.compose.animation.animateColor
-import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.FastOutSlowInEasing
 import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.tween
@@ -72,7 +71,7 @@ fun ColorScheme.toLegadoColorScheme(
         tertiaryFixedDim = tertiaryFixedDim,
         onTertiaryFixed = onTertiaryFixed,
         onTertiaryFixedVariant = onTertiaryFixedVariant,
-        cardContainer = primaryContainer.copy(alpha = 0.5f),
+        cardContainer = surfaceContainerLow,
         onCardContainer = primary,
         onSheetContent = surface,
         cardPrimaryContainer = primaryContainer,

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

@@ -2328,6 +2328,7 @@
     <string name="night_card_bg_color">夜间卡片背景颜色</string>
     <string name="compact_details">精简详情</string>
     <string name="show_more_info">显示更多信息</string>
+    <string name="show_synopsis_below_content">简介显示在封面和信息下方</string>
     <string name="show_latest_chapter">显示最新章节</string>
     <string name="show_synopsis">显示简介</string>
     <string name="synopsis_lines">简介行数</string>

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

@@ -2106,6 +2106,7 @@
     <string name="show_lines_synopsis">顯示簡介 %1$d 行</string>
     <string name="show_menu_icon">顯示選單項目圖標</string>
     <string name="show_more_info">顯示更多資訊</string>
+    <string name="show_synopsis_below_content">簡介顯示在封面和資訊下方</string>
     <string name="show_original">顯示原文</string>
     <string name="show_password">顯示密碼</string>
     <string name="show_select_menu_icon">展示圖標</string>

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

@@ -2244,6 +2244,7 @@
     <string name="night_card_bg_color">夜間卡片背景色</string>
     <string name="compact_details">精簡詳情</string>
     <string name="show_more_info">顯示更多資訊</string>
+    <string name="show_synopsis_below_content">簡介顯示在封面和資訊下方</string>
     <string name="show_latest_chapter">顯示最新章節</string>
     <string name="show_synopsis">顯示簡介</string>
     <string name="synopsis_lines">簡介行數</string>

+ 1 - 0
app/src/main/res/values/strings.xml

@@ -2352,6 +2352,7 @@
     <string name="night_card_bg_color">Night Card Background Color</string>
     <string name="compact_details">Compact Details</string>
     <string name="show_more_info">Show More Info</string>
+    <string name="show_synopsis_below_content">Show Synopsis Below Cover and Details</string>
     <string name="show_latest_chapter">Show Latest Chapter</string>
     <string name="show_synopsis">Show Synopsis</string>
     <string name="synopsis_lines">Synopsis Lines</string>

+ 5 - 2
app/src/test/java/io/legado/app/data/repository/BookshelfSettingsMappingTest.kt

@@ -8,8 +8,8 @@ import org.junit.Test
 class BookshelfSettingsMappingTest {
 
     @Test
-    fun `Bookshelf 46 键写读映射逐字段对应`() {
-        assertEquals(46, BookshelfSettings().toPrefMap().size)
+    fun `Bookshelf 47 键写读映射逐字段对应`() {
+        assertEquals(47, BookshelfSettings().toPrefMap().size)
         bookshelfMappingSamples().forEach { expected ->
             assertEquals(expected.expectedPrefMap(), expected.toPrefMap())
             assertEquals(
@@ -66,6 +66,7 @@ private fun bookshelfMappingSamples(): List<BookshelfSettings> {
         bookshelfFolderLayoutListLandscape = 117,
         bookshelfGridLayout = 118,
         bookshelfLayoutCompact = false,
+        bookshelfListIntroBelowContent = false,
         bookshelfShowDivider = false,
         bookshelfTitleSmallFont = false,
         bookshelfTitleCenter = false,
@@ -97,6 +98,7 @@ private fun bookshelfMappingSamples(): List<BookshelfSettings> {
         base.copy(showBookshelfFastScroller = true),
         base.copy(shouldShowExpandButton = true),
         base.copy(bookshelfLayoutCompact = true),
+        base.copy(bookshelfListIntroBelowContent = true),
         base.copy(bookshelfShowDivider = true),
         base.copy(bookshelfTitleSmallFont = true),
         base.copy(bookshelfTitleCenter = true),
@@ -139,6 +141,7 @@ private fun BookshelfSettings.expectedPrefMap(): Map<String, Any?> = mapOf(
     PreferKey.bookshelfFolderLayoutListLandscape to bookshelfFolderLayoutListLandscape,
     PreferKey.bookshelfGridLayout to bookshelfGridLayout,
     PreferKey.bookshelfLayoutCompact to bookshelfLayoutCompact,
+    PreferKey.bookshelfListIntroBelowContent to bookshelfListIntroBelowContent,
     PreferKey.bookshelfShowDivider to bookshelfShowDivider,
     PreferKey.bookshelfTitleSmallFont to bookshelfTitleSmallFont,
     PreferKey.bookshelfTitleCenter to bookshelfTitleCenter,