diff --git a/app/src/main/java/io/legado/app/model/BookCover.kt b/app/src/main/java/io/legado/app/model/BookCover.kt index 73bcc862c..2a4f51180 100644 --- a/app/src/main/java/io/legado/app/model/BookCover.kt +++ b/app/src/main/java/io/legado/app/model/BookCover.kt @@ -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 } /** diff --git a/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt b/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt index 749c4fdcb..47b1051b4 100644 --- a/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt +++ b/app/src/main/java/io/legado/app/ui/config/bookshelfConfig/BookshelfConfig.kt @@ -98,6 +98,11 @@ object BookshelfConfig { */ var bookshelfLayoutCompact by prefDelegate(PreferKey.bookshelfLayoutCompact, false) + /** + * 是否显示书架分隔线 + */ + var bookshelfShowDivider by prefDelegate(PreferKey.bookshelfShowDivider, true) + /** * 书架标题小字体 */ diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt index f76449196..064047b88 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt @@ -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() + ) } } } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt index 8b155347b..4fb175eb3 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfConfigSheet.kt @@ -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(), diff --git a/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt b/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt index 0efaaa69e..89b5c4956 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/cover/BookCover.kt @@ -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)