[优化] 修复封面进入时尺寸错误的问题,添加分割线选项
This commit is contained in:
@@ -91,11 +91,14 @@ object BookCover {
|
||||
val cacheKey = "$randomPath-${isNight}"
|
||||
|
||||
// 从缓存中获取,如果没有则解码并缓存
|
||||
return randomDrawableCache.getOrPut(cacheKey) {
|
||||
val drawable = randomDrawableCache.getOrPut(cacheKey) {
|
||||
kotlin.runCatching {
|
||||
BitmapUtils.decodeBitmap(randomPath, 600, 900)!!.toDrawable(appCtx.resources)
|
||||
}.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 bookshelfShowDivider by prefDelegate(PreferKey.bookshelfShowDivider, true)
|
||||
|
||||
/**
|
||||
* 书架标题小字体
|
||||
*/
|
||||
|
||||
@@ -75,6 +75,8 @@ fun BookshelfItem(
|
||||
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.aspectRatio(5f / 7f)
|
||||
.then(
|
||||
if (coverShadow) Modifier.shadow(
|
||||
4.dp,
|
||||
@@ -83,7 +85,7 @@ fun BookshelfItem(
|
||||
)
|
||||
.clip(MaterialTheme.shapes.extraSmall) // 先阴影后裁剪
|
||||
) {
|
||||
cover(Modifier.fillMaxWidth())
|
||||
cover(Modifier.fillMaxSize())
|
||||
if (gridStyle == 1) {
|
||||
Text(
|
||||
text = title,
|
||||
@@ -184,7 +186,7 @@ fun BookshelfItem(
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isCompact) HorizontalDivider(
|
||||
if (BookshelfConfig.bookshelfShowDivider) HorizontalDivider(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
thickness = 0.5.dp,
|
||||
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f)
|
||||
@@ -205,78 +207,68 @@ fun BookGroupCover(
|
||||
.clip(RoundedCornerShape(4.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainer)
|
||||
) {
|
||||
if (coverPath != null) {
|
||||
BookCover(
|
||||
name = "",
|
||||
author = "",
|
||||
path = coverPath,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
ignoreUseDefaultCover = true
|
||||
)
|
||||
} else {
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.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()
|
||||
)
|
||||
}
|
||||
Column(modifier = Modifier.fillMaxSize()) {
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(1.dp)
|
||||
) {
|
||||
books.getOrNull(0)?.let {
|
||||
BookCover(
|
||||
name = it.name,
|
||||
author = it.author,
|
||||
path = it.getDisplayCover(),
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
}
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(1.dp)
|
||||
) {
|
||||
books.getOrNull(2)?.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()
|
||||
)
|
||||
}
|
||||
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()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Row(modifier = Modifier.weight(1f)) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
.padding(1.dp)
|
||||
) {
|
||||
books.getOrNull(2)?.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(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,
|
||||
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 {
|
||||
CompactSwitchSettingItem(
|
||||
title = "紧凑模式",
|
||||
@@ -126,6 +140,13 @@ fun BookshelfConfigSheet(
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it }
|
||||
)
|
||||
CompactSwitchSettingItem(
|
||||
title = "显示分隔线",
|
||||
checked = BookshelfConfig.bookshelfShowDivider,
|
||||
imageVector = Icons.Default.ViewCompact,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
onCheckedChange = { BookshelfConfig.bookshelfShowDivider = it }
|
||||
)
|
||||
}
|
||||
|
||||
// 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(
|
||||
title = "标题最大行数",
|
||||
value = BookshelfConfig.bookshelfTitleMaxLines.toFloat(),
|
||||
|
||||
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Update
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -37,10 +38,10 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.core.graphics.withSave
|
||||
import coil.compose.AsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import io.legado.app.model.BookCover
|
||||
import io.legado.app.ui.config.coverConfig.CoverConfig
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import org.koin.compose.koinInject
|
||||
import io.legado.app.model.BookCover as BookCoverModel
|
||||
|
||||
@Composable
|
||||
fun BookCover(
|
||||
@@ -60,34 +61,23 @@ fun BookCover(
|
||||
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
|
||||
val finalPath = if (useDefault) null else path
|
||||
|
||||
// 获取随机封面路径(字符串),增加配置项作为 Key,确保设置更改后立即刷新
|
||||
val randomPath = remember(
|
||||
name, author, path, isNight,
|
||||
CoverConfig.defaultCover,
|
||||
CoverConfig.defaultCoverDark
|
||||
) {
|
||||
BookCover.getRandomDefaultPath(
|
||||
BookCoverModel.getRandomDefaultPath(
|
||||
seed = name ?: author ?: path ?: "",
|
||||
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()
|
||||
|
||||
var isOnlineCoverLoaded by remember(path) { mutableStateOf(false) }
|
||||
LaunchedEffect(finalPath) {
|
||||
isOnlineCoverLoaded = false
|
||||
}
|
||||
|
||||
Box(
|
||||
modifier = modifier
|
||||
@@ -104,49 +94,46 @@ fun BookCover(
|
||||
} else Modifier
|
||||
)
|
||||
) {
|
||||
// 如果没有自定义随机封面,显示书本图标作为底图
|
||||
// TODO:暂时先用猫猫头
|
||||
/*
|
||||
if (!hasCustomDefault) {
|
||||
Icon(
|
||||
Icons.Default.Book,
|
||||
if (hasCustomDefault) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(randomPath)
|
||||
.crossfade(true)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.secondary,
|
||||
modifier = Modifier
|
||||
.size(32.dp)
|
||||
.align(Alignment.Center)
|
||||
imageLoader = koinInject(),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize()
|
||||
)
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
// 1. 封面图层
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(finalPath ?: randomPath)
|
||||
.placeholder(randomDrawable)
|
||||
.error(randomDrawable)
|
||||
.crossfade(true)
|
||||
.setParameter("sourceOrigin", sourceOrigin)
|
||||
.setParameter("loadOnlyWifi", CoverConfig.loadCoverOnlyWifi)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
imageLoader = koinInject(),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onSuccess = {
|
||||
if (finalPath != null) {
|
||||
if (finalPath != null) {
|
||||
AsyncImage(
|
||||
model = ImageRequest.Builder(context)
|
||||
.data(finalPath)
|
||||
.crossfade(true)
|
||||
.setParameter("sourceOrigin", sourceOrigin)
|
||||
.setParameter("loadOnlyWifi", CoverConfig.loadCoverOnlyWifi)
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
imageLoader = koinInject(),
|
||||
contentScale = ContentScale.Crop,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
onSuccess = {
|
||||
isOnlineCoverLoaded = true
|
||||
onLoadFinish?.invoke()
|
||||
},
|
||||
onError = {
|
||||
isOnlineCoverLoaded = false
|
||||
onLoadFinish?.invoke()
|
||||
}
|
||||
onLoadFinish?.invoke()
|
||||
},
|
||||
onError = {
|
||||
isOnlineCoverLoaded = false
|
||||
)
|
||||
} else {
|
||||
LaunchedEffect(Unit) {
|
||||
onLoadFinish?.invoke()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// 2. 文字叠加层:当没有书籍自身封面(或加载失败)时,在随机底图上绘制书名/作者
|
||||
if (!isOnlineCoverLoaded) {
|
||||
CoverTextOverlay(
|
||||
name = name,
|
||||
@@ -155,7 +142,6 @@ fun BookCover(
|
||||
)
|
||||
}
|
||||
|
||||
// 3. 角标 (Badge)
|
||||
if (!badgeText.isNullOrEmpty()) {
|
||||
TextCard(
|
||||
text = badgeText,
|
||||
@@ -218,7 +204,6 @@ private fun CoverTextOverlay(
|
||||
val textPaint = TextPaint(paint).apply {
|
||||
textAlign = Paint.Align.LEFT
|
||||
}
|
||||
|
||||
val layout = StaticLayout.Builder
|
||||
.obtain(name, 0, name.length, textPaint, maxWidth)
|
||||
.setAlignment(Layout.Alignment.ALIGN_CENTER)
|
||||
|
||||
Reference in New Issue
Block a user