This commit is contained in:
HapeLee
2026-05-28 00:15:01 +08:00
parent 02dca3edc1
commit 68c16cbc5a
2 changed files with 126 additions and 100 deletions
@@ -59,13 +59,11 @@ import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil.ImageLoader
import coil.compose.AsyncImage
import io.legado.app.R
import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book
@@ -73,7 +71,6 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.SearchBook
import io.legado.app.help.book.isLocal
import io.legado.app.help.config.AppConfig
import io.legado.app.model.BookCover
import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.main.homepage.modules.BannerModule
import io.legado.app.ui.theme.LegadoTheme
@@ -93,8 +90,8 @@ import io.legado.app.ui.widget.components.button.series.SmallTonalButton
import io.legado.app.ui.widget.components.card.GlassCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.image.cover.BookCoverImage
import io.legado.app.ui.widget.components.image.cover.CoilBookCover
import io.legado.app.ui.widget.components.image.cover.buildCoverImageRequest
import io.legado.app.ui.widget.components.log.AppLogSheet
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
@@ -192,10 +189,7 @@ private fun BookInfoScreenContent(
} else {
Box(modifier = Modifier.fillMaxSize()) {
BookInfoBackdrop(
bookUrl = book.bookUrl,
coverUrl = book.coverUrl,
customCoverUrl = book.customCoverUrl,
origin = book.origin,
book = book,
)
AppPullToRefresh(
modifier = Modifier.fillMaxSize(),
@@ -479,26 +473,8 @@ private fun BookInfoTopBarActions(
@Composable
private fun BookInfoBackdrop(
bookUrl: String,
coverUrl: String?,
customCoverUrl: String?,
origin: String,
book: Book,
) {
val useDefaultCover = AppConfig.useDefaultCover || customCoverUrl == "use_default_cover"
val isNight = AppConfig.isNightTheme
val cover = remember(bookUrl, customCoverUrl, coverUrl, isNight) {
if (useDefaultCover) {
BookCover.getRandomDefaultPath(bookUrl, isNight)
} else {
if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
}
}
val sourceOrigin = if (!useDefaultCover) origin else null
val loadOnlyWifi = CoverConfig.loadCoverOnlyWifi
val context = LocalContext.current
val imageLoader = koinInject<ImageLoader>()
val seedOverlay = lerp(
LegadoTheme.colorScheme.secondaryContainer,
LegadoTheme.seedColor,
@@ -506,32 +482,22 @@ private fun BookInfoBackdrop(
)
Box(modifier = Modifier.fillMaxSize()) {
Crossfade(
targetState = cover,
targetState = book,
animationSpec = tween(800),
label = "BackdropCrossfade"
) { currentCover ->
if (!currentCover.isNullOrBlank()) {
val backdropRequest = remember(currentCover, sourceOrigin, loadOnlyWifi, context) {
buildCoverImageRequest(
context = context,
data = currentCover,
sourceOrigin = sourceOrigin,
loadOnlyWifi = loadOnlyWifi,
crossfade = true,
memoryCacheKey = currentCover,
)
}
AsyncImage(
model = backdropRequest,
imageLoader = imageLoader,
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.height(480.dp)
.blur(24.dp),
contentScale = ContentScale.Crop,
)
}
) { currentBook ->
BookCoverImage(
name = currentBook.name,
author = currentBook.author,
path = currentBook.getDisplayCover(),
sourceOrigin = currentBook.origin,
modifier = Modifier
.fillMaxWidth()
.height(480.dp)
.blur(24.dp),
contentScale = ContentScale.Crop,
showLoadingPlaceholder = false,
)
}
Box(
modifier = Modifier
@@ -52,6 +52,97 @@ import io.legado.app.model.BookCover as BookCoverModel
private const val SharedCoverRadiusCacheMaxSize = 256
private val sharedCoverRadiusCache = mutableStateMapOf<String, Dp>()
@Composable
fun BookCoverImage(
name: String?,
author: String?,
path: String?,
modifier: Modifier = Modifier,
sourceOrigin: String? = null,
ignoreUseDefaultCover: Boolean = false,
showLoadingPlaceholder: Boolean = true,
contentScale: ContentScale = ContentScale.Crop,
onLoadFinish: (() -> Unit)? = null,
onSuccess: (() -> Unit)? = null,
onError: (() -> Unit)? = null,
sharedCoverKey: String? = null,
) {
val context = LocalContext.current
val isNight = isSystemInDarkTheme()
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
val finalPath = if (useDefault) null else path
val randomPath = remember(name, author, path, isNight) {
BookCoverModel.getRandomDefaultPath(
seed = name ?: author ?: path ?: "",
isNight = isNight
)
}
val hasCustomDefault = !randomPath.isNullOrBlank()
var isOnlineCoverLoaded by remember(finalPath) {
mutableStateOf(sharedCoverKey != null && finalPath != null)
}
LaunchedEffect(finalPath) {
if (finalPath == null) {
isOnlineCoverLoaded = false
}
}
Box(modifier = modifier) {
if (hasCustomDefault && !isOnlineCoverLoaded) {
AsyncImage(
model = buildCoverImageRequest(
context = context,
data = randomPath,
sourceOrigin = null,
loadOnlyWifi = false,
crossfade = showLoadingPlaceholder,
memoryCacheKey = randomPath,
),
contentDescription = null,
imageLoader = koinInject(),
contentScale = contentScale,
modifier = Modifier.fillMaxSize()
)
}
if (finalPath != null) {
AsyncImage(
model = buildCoverImageRequest(
context = context,
data = finalPath,
sourceOrigin = sourceOrigin,
loadOnlyWifi = CoverConfig.loadCoverOnlyWifi,
crossfade = showLoadingPlaceholder,
memoryCacheKey = finalPath,
),
contentDescription = null,
imageLoader = koinInject(),
contentScale = contentScale,
modifier = Modifier.fillMaxSize(),
onSuccess = {
isOnlineCoverLoaded = true
onSuccess?.invoke()
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onError?.invoke()
onLoadFinish?.invoke()
}
)
} else {
LaunchedEffect(Unit) {
onLoadFinish?.invoke()
}
}
}
}
// 改成BookCover
@OptIn(ExperimentalSharedTransitionApi::class)
@Composable
fun CoilBookCover(
@@ -68,7 +159,6 @@ fun CoilBookCover(
animatedVisibilityScope: AnimatedVisibilityScope? = null,
sharedCoverKey: String? = null,
) {
val context = LocalContext.current
val isNight = isSystemInDarkTheme()
val useDefault = !ignoreUseDefaultCover && CoverConfig.useDefaultCover
@@ -126,54 +216,24 @@ fun CoilBookCover(
)
.clip(shape)
) {
Box(modifier = Modifier.fillMaxSize()) {
if (hasCustomDefault && !isOnlineCoverLoaded) {
AsyncImage(
model = buildCoverImageRequest(
context = context,
data = randomPath,
sourceOrigin = null,
loadOnlyWifi = false,
crossfade = showLoadingPlaceholder,
memoryCacheKey = randomPath,
),
contentDescription = null,
imageLoader = koinInject(),
contentScale = ContentScale.Crop,
modifier = Modifier
.fillMaxSize()
)
}
if (finalPath != null) {
AsyncImage(
model = buildCoverImageRequest(
context = context,
data = finalPath,
sourceOrigin = sourceOrigin,
loadOnlyWifi = CoverConfig.loadCoverOnlyWifi,
crossfade = showLoadingPlaceholder,
memoryCacheKey = finalPath,
),
contentDescription = null,
imageLoader = koinInject(),
contentScale = ContentScale.Crop,
modifier = Modifier.fillMaxSize(),
onSuccess = {
isOnlineCoverLoaded = true
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onLoadFinish?.invoke()
}
)
} else {
LaunchedEffect(Unit) {
onLoadFinish?.invoke()
}
}
}
BookCoverImage(
name = name,
author = author,
path = path,
modifier = Modifier.fillMaxSize(),
sourceOrigin = sourceOrigin,
ignoreUseDefaultCover = ignoreUseDefaultCover,
showLoadingPlaceholder = showLoadingPlaceholder,
onSuccess = {
isOnlineCoverLoaded = true
onLoadFinish?.invoke()
},
onError = {
isOnlineCoverLoaded = false
onLoadFinish?.invoke()
},
sharedCoverKey = sharedCoverKey
)
if (showLoadingPlaceholder && !isOnlineCoverLoaded) {
if (!hasCustomDefault) {