[优化] 修复封面进入时尺寸错误的问题,添加分割线选项
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user