[优化] 修复封面进入时尺寸错误的问题,添加分割线选项

This commit is contained in:
HapeLee
2026-03-27 01:30:13 +08:00
parent 75a3752079
commit 63e1db17b1
5 changed files with 130 additions and 139 deletions
@@ -91,11 +91,14 @@ object BookCover {
val cacheKey = "$randomPath-${isNight}" val cacheKey = "$randomPath-${isNight}"
// 从缓存中获取,如果没有则解码并缓存 // 从缓存中获取,如果没有则解码并缓存
return randomDrawableCache.getOrPut(cacheKey) { val drawable = randomDrawableCache.getOrPut(cacheKey) {
kotlin.runCatching { kotlin.runCatching {
BitmapUtils.decodeBitmap(randomPath, 600, 900)!!.toDrawable(appCtx.resources) BitmapUtils.decodeBitmap(randomPath, 600, 900)!!.toDrawable(appCtx.resources)
}.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null)) }.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null))
} }
// 返回克隆的实例并 mutate,防止多个 View 共享状态(如 bounds)导致显示异常
return drawable.constantState?.newDrawable()?.mutate() ?: drawable
} }
/** /**
@@ -98,6 +98,11 @@ object BookshelfConfig {
*/ */
var bookshelfLayoutCompact by prefDelegate(PreferKey.bookshelfLayoutCompact, false) var bookshelfLayoutCompact by prefDelegate(PreferKey.bookshelfLayoutCompact, false)
/**
* 是否显示书架分隔线
*/
var bookshelfShowDivider by prefDelegate(PreferKey.bookshelfShowDivider, true)
/** /**
* 书架标题小字体 * 书架标题小字体
*/ */
@@ -75,6 +75,8 @@ fun BookshelfItem(
Box( Box(
modifier = Modifier modifier = Modifier
.fillMaxWidth()
.aspectRatio(5f / 7f)
.then( .then(
if (coverShadow) Modifier.shadow( if (coverShadow) Modifier.shadow(
4.dp, 4.dp,
@@ -83,7 +85,7 @@ fun BookshelfItem(
) )
.clip(MaterialTheme.shapes.extraSmall) // 先阴影后裁剪 .clip(MaterialTheme.shapes.extraSmall) // 先阴影后裁剪
) { ) {
cover(Modifier.fillMaxWidth()) cover(Modifier.fillMaxSize())
if (gridStyle == 1) { if (gridStyle == 1) {
Text( Text(
text = title, text = title,
@@ -184,7 +186,7 @@ fun BookshelfItem(
} }
} }
} }
if (!isCompact) HorizontalDivider( if (BookshelfConfig.bookshelfShowDivider) HorizontalDivider(
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
thickness = 0.5.dp, thickness = 0.5.dp,
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f) color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)
@@ -205,78 +207,68 @@ fun BookGroupCover(
.clip(RoundedCornerShape(4.dp)) .clip(RoundedCornerShape(4.dp))
.background(MaterialTheme.colorScheme.surfaceContainer) .background(MaterialTheme.colorScheme.surfaceContainer)
) { ) {
if (coverPath != null) { Column(modifier = Modifier.fillMaxSize()) {
BookCover( Row(modifier = Modifier.weight(1f)) {
name = "", Box(
author = "", modifier = Modifier
path = coverPath, .weight(1f)
modifier = Modifier.fillMaxSize(), .fillMaxHeight()
ignoreUseDefaultCover = true .padding(1.dp)
) ) {
} else { books.getOrNull(0)?.let {
Column(modifier = Modifier.fillMaxSize()) { BookCover(
Row(modifier = Modifier.weight(1f)) { name = it.name,
Box( author = it.author,
modifier = Modifier path = it.getDisplayCover(),
.weight(1f) modifier = Modifier.fillMaxSize()
.fillMaxHeight() )
.padding(1.dp)
) {
books.getOrNull(0)?.let {
BookCover(
name = it.name,
author = it.author,
path = it.getDisplayCover(),
modifier = Modifier.fillMaxSize()
)
}
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.padding(1.dp)
) {
books.getOrNull(1)?.let {
BookCover(
name = it.name,
author = it.author,
path = it.getDisplayCover(),
modifier = Modifier.fillMaxSize()
)
}
} }
} }
Row(modifier = Modifier.weight(1f)) { Box(
Box( modifier = Modifier
modifier = Modifier .weight(1f)
.weight(1f) .fillMaxHeight()
.fillMaxHeight() .padding(1.dp)
.padding(1.dp) ) {
) { books.getOrNull(1)?.let {
books.getOrNull(2)?.let { BookCover(
BookCover( name = it.name,
name = it.name, author = it.author,
author = it.author, path = it.getDisplayCover(),
path = it.getDisplayCover(), modifier = Modifier.fillMaxSize()
modifier = Modifier.fillMaxSize() )
)
}
} }
Box( }
modifier = Modifier }
.weight(1f) Row(modifier = Modifier.weight(1f)) {
.fillMaxHeight() Box(
.padding(1.dp) modifier = Modifier
) { .weight(1f)
books.getOrNull(3)?.let { .fillMaxHeight()
BookCover( .padding(1.dp)
name = it.name, ) {
author = it.author, books.getOrNull(2)?.let {
path = it.getDisplayCover(), BookCover(
modifier = Modifier.fillMaxSize() name = it.name,
) author = it.author,
} path = it.getDisplayCover(),
modifier = Modifier.fillMaxSize()
)
}
}
Box(
modifier = Modifier
.weight(1f)
.fillMaxHeight()
.padding(1.dp)
) {
books.getOrNull(3)?.let {
BookCover(
name = it.name,
author = it.author,
path = it.getDisplayCover(),
modifier = Modifier.fillMaxSize()
)
} }
} }
} }
@@ -118,6 +118,20 @@ fun BookshelfConfigSheet(
imageVector = Icons.Default.ViewCompact, imageVector = Icons.Default.ViewCompact,
onValueChange = { BookshelfConfig.bookshelfGridLayout = it.toInt() } onValueChange = { BookshelfConfig.bookshelfGridLayout = it.toInt() }
) )
CompactSwitchSettingItem(
title = "标题小字体",
checked = BookshelfConfig.bookshelfTitleSmallFont,
imageVector = Icons.Default.TextFormat,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfTitleSmallFont = it }
)
CompactSwitchSettingItem(
title = "标题居中",
checked = BookshelfConfig.bookshelfTitleCenter,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfTitleCenter = it }
)
} else { } else {
CompactSwitchSettingItem( CompactSwitchSettingItem(
title = "紧凑模式", title = "紧凑模式",
@@ -126,6 +140,13 @@ fun BookshelfConfigSheet(
color = MaterialTheme.colorScheme.surface, color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it } onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it }
) )
CompactSwitchSettingItem(
title = "显示分隔线",
checked = BookshelfConfig.bookshelfShowDivider,
imageVector = Icons.Default.ViewCompact,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfShowDivider = it }
)
} }
// Grid Count // Grid Count
@@ -142,21 +163,6 @@ fun BookshelfConfigSheet(
} }
) )
CompactSwitchSettingItem(
title = "标题小字体",
checked = BookshelfConfig.bookshelfTitleSmallFont,
imageVector = Icons.Default.TextFormat,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfTitleSmallFont = it }
)
CompactSwitchSettingItem(
title = "标题居中",
checked = BookshelfConfig.bookshelfTitleCenter,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfTitleCenter = it }
)
CompactSliderSettingItem( CompactSliderSettingItem(
title = "标题最大行数", title = "标题最大行数",
value = BookshelfConfig.bookshelfTitleMaxLines.toFloat(), value = BookshelfConfig.bookshelfTitleMaxLines.toFloat(),
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Update import androidx.compose.material.icons.filled.Update
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
@@ -37,10 +38,10 @@ import androidx.compose.ui.unit.dp
import androidx.core.graphics.withSave import androidx.core.graphics.withSave
import coil.compose.AsyncImage import coil.compose.AsyncImage
import coil.request.ImageRequest import coil.request.ImageRequest
import io.legado.app.model.BookCover
import io.legado.app.ui.config.coverConfig.CoverConfig import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.widget.components.card.TextCard import io.legado.app.ui.widget.components.card.TextCard
import org.koin.compose.koinInject import org.koin.compose.koinInject
import io.legado.app.model.BookCover as BookCoverModel
@Composable @Composable
fun BookCover( fun BookCover(
@@ -60,34 +61,23 @@ fun BookCover(
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
val finalPath = if (useDefault) null else path val finalPath = if (useDefault) null else path
// 获取随机封面路径(字符串),增加配置项作为 Key,确保设置更改后立即刷新
val randomPath = remember( val randomPath = remember(
name, author, path, isNight, name, author, path, isNight,
CoverConfig.defaultCover, CoverConfig.defaultCover,
CoverConfig.defaultCoverDark CoverConfig.defaultCoverDark
) { ) {
BookCover.getRandomDefaultPath( BookCoverModel.getRandomDefaultPath(
seed = name ?: author ?: path ?: "", seed = name ?: author ?: path ?: "",
isNight = isNight isNight = isNight
) )
} }
// 获取随机封面 Drawable,用于占位图
val randomDrawable = remember(
name, author, path, isNight,
CoverConfig.defaultCover,
CoverConfig.defaultCoverDark
) {
BookCover.getRandomDefaultDrawable(
seed = name ?: author ?: path ?: "",
isNight = isNight
)
}
// 检查是否有自定义随机封面设置
val hasCustomDefault = !randomPath.isNullOrBlank() val hasCustomDefault = !randomPath.isNullOrBlank()
var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) } var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
LaunchedEffect(finalPath) {
isOnlineCoverLoaded = false
}
Box( Box(
modifier = modifier modifier = modifier
@@ -104,49 +94,46 @@ fun BookCover(
} else Modifier } else Modifier
) )
) { ) {
// 如果没有自定义随机封面,显示书本图标作为底图 if (hasCustomDefault) {
// TODO:暂时先用猫猫头 AsyncImage(
/* model = ImageRequest.Builder(context)
if (!hasCustomDefault) { .data(randomPath)
Icon( .crossfade(true)
Icons.Default.Book, .build(),
contentDescription = null, contentDescription = null,
tint = MaterialTheme.colorScheme.secondary, imageLoader = koinInject(),
modifier = Modifier contentScale = ContentScale.Crop,
.size(32.dp) modifier = Modifier.fillMaxSize()
.align(Alignment.Center)
) )
} }
*/
if (finalPath != null) {
// 1. 封面图层 AsyncImage(
AsyncImage( model = ImageRequest.Builder(context)
model = ImageRequest.Builder(context) .data(finalPath)
.data(finalPath ?: randomPath) .crossfade(true)
.placeholder(randomDrawable) .setParameter("sourceOrigin", sourceOrigin)
.error(randomDrawable) .setParameter("loadOnlyWifi", CoverConfig.loadCoverOnlyWifi)
.crossfade(true) .build(),
.setParameter("sourceOrigin", sourceOrigin) contentDescription = null,
.setParameter("loadOnlyWifi", CoverConfig.loadCoverOnlyWifi) imageLoader = koinInject(),
.build(), contentScale = ContentScale.Crop,
contentDescription = null, modifier = Modifier.fillMaxSize(),
imageLoader = koinInject(), onSuccess = {
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
onSuccess = {
if (finalPath != null) {
isOnlineCoverLoaded = true isOnlineCoverLoaded = true
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onLoadFinish?.invoke()
} }
onLoadFinish?.invoke() )
}, } else {
onError = { LaunchedEffect(Unit) {
isOnlineCoverLoaded = false
onLoadFinish?.invoke() onLoadFinish?.invoke()
} }
) }
// 2. 文字叠加层:当没有书籍自身封面(或加载失败)时,在随机底图上绘制书名/作者
if (!isOnlineCoverLoaded) { if (!isOnlineCoverLoaded) {
CoverTextOverlay( CoverTextOverlay(
name = name, name = name,
@@ -155,7 +142,6 @@ fun BookCover(
) )
} }
// 3. 角标 (Badge)
if (!badgeText.isNullOrEmpty()) { if (!badgeText.isNullOrEmpty()) {
TextCard( TextCard(
text = badgeText, text = badgeText,
@@ -218,7 +204,6 @@ private fun CoverTextOverlay(
val textPaint = TextPaint(paint).apply { val textPaint = TextPaint(paint).apply {
textAlign = Paint.Align.LEFT textAlign = Paint.Align.LEFT
} }
val layout = StaticLayout.Builder val layout = StaticLayout.Builder
.obtain(name, 0, name.length, textPaint, maxWidth) .obtain(name, 0, name.length, textPaint, maxWidth)
.setAlignment(Layout.Alignment.ALIGN_CENTER) .setAlignment(Layout.Alignment.ALIGN_CENTER)