[新增] 书籍信息显示和文件夹数目显示

This commit is contained in:
HapeLee
2026-04-08 01:44:59 +08:00
parent 37c80e1230
commit 191f639b29
8 changed files with 144 additions and 80 deletions
@@ -23,18 +23,15 @@ class RemoteBookRepository(
) {
suspend fun getWebDav(book: Book): RemoteBookWebDav? {
val remoteUrl = book.getRemoteUrl() ?: return null
val analyzeUrl = AnalyzeUrl(remoteUrl)
val remoteUrl = book.getRemoteUrl()
val serverId = remoteUrl?.let { AnalyzeUrl(it).serverID }
// 优先从书籍 origin 中解析 serverID
val serverId = analyzeUrl.serverID
if (serverId != null && serverId != AppConst.DEFAULT_WEBDAV_ID) {
appDb.serverDao.get(serverId)?.getWebDavConfig()?.let {
return RemoteBookWebDav(it.url, Authorization(it), serverId)
}
}
// 如果没有指定 serverID,尝试使用目前选择的 WebDAV 服务器
val currentServerId = AppConfig.remoteServerId
if (currentServerId != AppConst.DEFAULT_WEBDAV_ID) {
appDb.serverDao.get(currentServerId)?.getWebDavConfig()?.let {
@@ -42,7 +39,6 @@ class RemoteBookRepository(
}
}
// 最后回退到默认的 WebDAV 配置
return AppWebDav.defaultBookWebDav
}
@@ -43,6 +43,11 @@ object BookshelfConfig {
*/
var showTip by prefDelegate(PreferKey.showTip, false)
/**
* 鏄惁鏄剧ず鍒嗙粍鍐呬功绫嶆暟閲?
*/
var showBookCount by prefDelegate(PreferKey.showBookCount, true)
/**
* 是否在列表中显示最后更新时间
*/
@@ -31,9 +31,11 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.constant.BookType
import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.cover.BookCover
import io.legado.app.ui.widget.components.cover.BookshelfCover
import io.legado.app.ui.widget.components.text.AppText
@@ -201,6 +203,7 @@ fun BookshelfItem(
fun BookGroupCover(
books: List<BookShelfItem>,
coverPath: String? = null,
leftBottomText: String? = null,
modifier: Modifier = Modifier
) {
Box(
@@ -284,6 +287,20 @@ fun BookGroupCover(
}
}
}
if (!leftBottomText.isNullOrEmpty()) {
TextCard(
text = leftBottomText,
backgroundColor = LegadoTheme.colorScheme.cardContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(2.dp),
cornerRadius = 4.dp,
horizontalPadding = 4.dp,
verticalPadding = 0.dp
)
}
}
}
@@ -291,6 +308,7 @@ fun BookGroupCover(
fun BookGroupItemGrid(
group: BookGroup,
previewBooks: List<BookShelfItem>,
countText: String? = null,
gridStyle: Int = 0,
titleSmallFont: Boolean = false,
titleCenter: Boolean = true,
@@ -304,7 +322,14 @@ fun BookGroupItemGrid(
isGrid = true,
gridStyle = gridStyle,
isCompact = false,
cover = { BookGroupCover(books = previewBooks, coverPath = group.cover, modifier = it) },
cover = {
BookGroupCover(
books = previewBooks,
coverPath = group.cover,
leftBottomText = countText,
modifier = it
)
},
title = group.groupName,
modifier = modifier,
titleSmallFont = titleSmallFont,
@@ -320,6 +345,7 @@ fun BookGroupItemGrid(
fun BookGroupItemList(
group: BookGroup,
previewBooks: List<BookShelfItem>,
countText: String? = null,
isCompact: Boolean = false,
titleSmallFont: Boolean = false,
titleCenter: Boolean = true,
@@ -335,6 +361,7 @@ fun BookGroupItemList(
isCompact = isCompact,
cover = { BookGroupCover(books = previewBooks, coverPath = group.cover, modifier = it) },
title = group.groupName,
subTitle = countText,
titleSmallFont = titleSmallFont,
titleCenter = titleCenter,
titleMaxLines = titleMaxLines,
@@ -360,6 +387,17 @@ fun BookItem(
onLongClick: () -> Unit
) {
val unreadCount = book.getUnreadChapterNum()
val bookTypeLabel = if (BookshelfConfig.showTip) {
when {
book.isAudio -> stringResource(R.string.audio)
book.isImage -> stringResource(R.string.manga)
(book.type and BookType.webFile) > 0 -> stringResource(R.string.web_file)
book.isLocal -> stringResource(R.string.local)
else -> stringResource(R.string.noval)
}
} else {
null
}
BookshelfItem(
isGrid = layoutMode != 0,
@@ -373,7 +411,8 @@ fun BookItem(
isUpdating = isUpdating,
modifier = modifier,
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew,
leftBottomText = bookTypeLabel
)
},
title = book.name,
@@ -9,18 +9,6 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.Sort
import androidx.compose.material.icons.automirrored.filled.ViewList
import androidx.compose.material.icons.filled.BlurOn
import androidx.compose.material.icons.filled.History
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.SortByAlpha
import androidx.compose.material.icons.filled.Style
import androidx.compose.material.icons.filled.TextFormat
import androidx.compose.material.icons.filled.ViewCompact
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
@@ -67,7 +55,6 @@ fun BookshelfConfigSheet(
selectedValue = BookshelfConfig.bookGroupStyle.toString(),
displayEntries = stringArrayResource(R.array.group_style),
entryValues = Array(stringArrayResource(R.array.group_style).size) { it.toString() },
imageVector = Icons.Default.Style,
onValueChange = { BookshelfConfig.bookGroupStyle = it.toInt() }
)
@@ -77,7 +64,6 @@ fun BookshelfConfigSheet(
selectedValue = BookshelfConfig.bookshelfSort.toString(),
displayEntries = stringArrayResource(R.array.bookshelf_px_array),
entryValues = Array(stringArrayResource(R.array.bookshelf_px_array).size) { it.toString() },
imageVector = Icons.AutoMirrored.Filled.Sort,
onValueChange = { BookshelfConfig.bookshelfSort = it.toInt() }
)
@@ -90,7 +76,6 @@ fun BookshelfConfigSheet(
stringResource(R.string.descending_order)
),
entryValues = arrayOf("0", "1"),
imageVector = Icons.Default.SortByAlpha,
onValueChange = { BookshelfConfig.bookshelfSortOrder = it.toInt() }
)
@@ -105,7 +90,6 @@ fun BookshelfConfigSheet(
selectedValue = layoutMode.toString(),
displayEntries = arrayOf("列表", "网格"),
entryValues = arrayOf("0", "1"),
imageVector = Icons.AutoMirrored.Filled.ViewList,
onValueChange = {
if (isLandscape) BookshelfConfig.bookshelfLayoutModeLandscape = it.toInt()
else BookshelfConfig.bookshelfLayoutModePortrait = it.toInt()
@@ -123,7 +107,6 @@ fun BookshelfConfigSheet(
selectedValue = BookshelfConfig.bookshelfGridLayout.toString(),
displayEntries = stringArrayResource(R.array.bookshelf_grid_layout),
entryValues = Array(stringArrayResource(R.array.bookshelf_grid_layout).size) { it.toString() },
imageVector = Icons.Default.ViewCompact,
onValueChange = { BookshelfConfig.bookshelfGridLayout = it.toInt() }
)
@@ -144,7 +127,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = "标题小字体",
checked = BookshelfConfig.bookshelfTitleSmallFont,
imageVector = Icons.Default.TextFormat,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfTitleSmallFont = it }
)
@@ -167,7 +149,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = "紧凑模式",
checked = BookshelfConfig.bookshelfLayoutCompact,
imageVector = Icons.Default.ViewCompact,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfLayoutCompact = it }
)
@@ -175,7 +156,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = "显示分隔线",
checked = BookshelfConfig.bookshelfShowDivider,
imageVector = Icons.Default.ViewCompact,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfShowDivider = it }
)
@@ -207,7 +187,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = "封面阴影",
checked = BookshelfConfig.bookshelfCoverShadow,
imageVector = Icons.Default.BlurOn,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.bookshelfCoverShadow = it }
)
@@ -216,7 +195,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = stringResource(R.string.show_unread),
checked = BookshelfConfig.showUnread,
imageVector = Icons.Default.Notifications,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.showUnread = it }
)
@@ -231,15 +209,20 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = stringResource(R.string.show_tip),
checked = BookshelfConfig.showTip,
imageVector = Icons.Default.Info,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.showTip = it }
)
CompactSwitchSettingItem(
title = stringResource(R.string.show_book_count),
checked = BookshelfConfig.showBookCount,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.showBookCount = it }
)
CompactSwitchSettingItem(
title = stringResource(R.string.show_last_update_time),
checked = BookshelfConfig.showLastUpdateTime,
imageVector = Icons.Default.History,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.showLastUpdateTime = it }
)
@@ -261,7 +244,6 @@ fun BookshelfConfigSheet(
CompactSwitchSettingItem(
title = stringResource(R.string.show_bookshelf_tab_menu),
checked = BookshelfConfig.shouldShowExpandButton,
imageVector = Icons.Default.Menu,
color = MaterialTheme.colorScheme.surface,
onCheckedChange = { BookshelfConfig.shouldShowExpandButton = it }
)
@@ -400,8 +400,10 @@ fun BookshelfScreen(
label = "FolderTransition"
) { isRoot ->
if (bookGroupStyle == 2 && isRoot) {
val folderColumns =
if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid
FastScrollLazyVerticalGrid(
columns = GridCells.Fixed(bookshelfLayoutGrid.coerceAtLeast(1)),
columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)),
modifier = Modifier.fillMaxSize(),
contentPadding = adaptiveContentPadding(
top = paddingValues.calculateTopPadding(),
@@ -414,11 +416,19 @@ fun BookshelfScreen(
itemsIndexed(
uiState.groups,
key = { _, it -> it.groupId }) { index, group ->
val countText = if (BookshelfConfig.showBookCount) {
uiState.groupBookCounts[group.groupId]?.let {
stringResource(R.string.book_count, it)
}
} else {
null
}
if (bookshelfLayoutMode == 0) {
BookGroupItemList(
group = group,
previewBooks = uiState.groupPreviews[group.groupId]
?: emptyList(),
countText = countText,
isCompact = BookshelfConfig.bookshelfLayoutCompact,
titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
titleCenter = BookshelfConfig.bookshelfTitleCenter,
@@ -434,6 +444,7 @@ fun BookshelfScreen(
group = group,
previewBooks = uiState.groupPreviews[group.groupId]
?: emptyList(),
countText = countText,
gridStyle = BookshelfConfig.bookshelfGridLayout,
titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
titleCenter = BookshelfConfig.bookshelfTitleCenter,
@@ -11,6 +11,7 @@ data class BookshelfUiState(
override val isLoading: Boolean = false,
val groups: List<BookGroup> = emptyList(),
val groupPreviews: Map<Long, List<BookShelfItem>> = emptyMap(),
val groupBookCounts: Map<Long, Int> = emptyMap(),
val selectedGroupIndex: Int = 0,
val loadingText: String? = null,
val updatingBooks: Set<String> = emptySet()
@@ -126,6 +126,11 @@ class BookshelfViewModel(
.distinctUntilChanged()
.flowOn(Dispatchers.Default)
private data class GroupPreviewState(
val previews: Map<Long, List<BookShelfItem>>,
val counts: Map<Long, Int>
)
@OptIn(ExperimentalCoroutinesApi::class)
val booksFlow = combine(groupIdFlow, refreshTrigger) { groupId, _ -> groupId }
.flatMapLatest { groupId ->
@@ -138,59 +143,68 @@ class BookshelfViewModel(
private val groupPreviewsFlow =
combine(groupsFlow, allBooksFlow, refreshTrigger) { groups, allBooks, _ ->
if (BookshelfConfig.bookGroupStyle in 2..3) {
groups.associate { group ->
val groupBooks = when (group.groupId) {
BookGroup.IdRoot -> {
val sumUserGroupIds = groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.text) > 0 &&
(book.type and BookType.local) == 0 &&
(sumUserGroupIds and book.group) == 0L
if (BookshelfConfig.bookGroupStyle in 2..3) {
val previews = HashMap<Long, List<BookShelfItem>>(groups.size)
val counts = HashMap<Long, Int>(groups.size)
groups.forEach { group ->
val groupBooks = when (group.groupId) {
BookGroup.IdRoot -> {
val sumUserGroupIds =
groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.text) > 0 &&
(book.type and BookType.local) == 0 &&
(sumUserGroupIds and book.group) == 0L
}
}
}
BookGroup.IdAll -> allBooks
BookGroup.IdLocal -> allBooks.filter { (it.type and BookType.local) > 0 }
BookGroup.IdAudio -> allBooks.filter { (it.type and BookType.audio) > 0 }
BookGroup.IdNetNone -> {
val sumUserGroupIds = groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.audio) == 0 &&
(book.type and BookType.local) == 0 &&
(sumUserGroupIds and book.group) == 0L
BookGroup.IdAll -> allBooks
BookGroup.IdLocal -> allBooks.filter { (it.type and BookType.local) > 0 }
BookGroup.IdAudio -> allBooks.filter { (it.type and BookType.audio) > 0 }
BookGroup.IdNetNone -> {
val sumUserGroupIds =
groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.audio) == 0 &&
(book.type and BookType.local) == 0 &&
(sumUserGroupIds and book.group) == 0L
}
}
}
BookGroup.IdLocalNone -> {
val sumUserGroupIds = groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.local) > 0 &&
(sumUserGroupIds and book.group) == 0L
BookGroup.IdLocalNone -> {
val sumUserGroupIds =
groups.filter { it.groupId > 0 }.sumOf { it.groupId }
allBooks.filter { book ->
(book.type and BookType.local) > 0 &&
(sumUserGroupIds and book.group) == 0L
}
}
}
BookGroup.IdManga -> allBooks.filter { (it.type and BookType.image) > 0 }
BookGroup.IdText -> allBooks.filter { (it.type and BookType.text) > 0 }
BookGroup.IdError -> allBooks.filter { (it.type and BookType.updateError) > 0 }
BookGroup.IdUnread -> allBooks.filter { it.durChapterIndex == 0 && it.durChapterPos == 0 }
BookGroup.IdReading -> allBooks.filter { it.totalChapterNum > 0 && it.durChapterIndex > 0 && it.durChapterIndex < it.totalChapterNum - 1 }
BookGroup.IdReadFinished -> allBooks.filter { it.totalChapterNum > 0 && it.durChapterIndex >= it.totalChapterNum - 1 }
else -> allBooks.filter { (it.group and group.groupId) != 0L }
BookGroup.IdManga -> allBooks.filter { (it.type and BookType.image) > 0 }
BookGroup.IdText -> allBooks.filter { (it.type and BookType.text) > 0 }
BookGroup.IdError -> allBooks.filter { (it.type and BookType.updateError) > 0 }
BookGroup.IdUnread -> allBooks.filter { it.durChapterIndex == 0 && it.durChapterPos == 0 }
BookGroup.IdReading -> allBooks.filter { it.totalChapterNum > 0 && it.durChapterIndex > 0 && it.durChapterIndex < it.totalChapterNum - 1 }
BookGroup.IdReadFinished -> allBooks.filter { it.totalChapterNum > 0 && it.durChapterIndex >= it.totalChapterNum - 1 }
else -> allBooks.filter { (it.group and group.groupId) != 0L }
}
counts[group.groupId] = groupBooks.size
val sortedBooks = sortBooks(groupBooks, group)
val booksWithCover = sortedBooks.filter { it.getDisplayCover() != null }
val result = if (booksWithCover.size >= 4) {
booksWithCover.take(4)
} else {
(booksWithCover + sortedBooks.filter { it.getDisplayCover() == null }).take(
4
)
}
previews[group.groupId] = result
}
val sortedBooks = sortBooks(groupBooks, group)
val booksWithCover = sortedBooks.filter { it.getDisplayCover() != null }
val result = if (booksWithCover.size >= 4) {
booksWithCover.take(4)
} else {
(booksWithCover + sortedBooks.filter { it.getDisplayCover() == null }).take(4)
}
group.groupId to result
GroupPreviewState(previews, counts)
} else {
GroupPreviewState(emptyMap(), emptyMap())
}
} else {
emptyMap()
}
}.distinctUntilChanged().flowOn(Dispatchers.Default)
}.distinctUntilChanged().flowOn(Dispatchers.Default)
private val internalStateFlow = combine(
groupIdFlow,
@@ -228,7 +242,8 @@ class BookshelfViewModel(
BookshelfUiState(
items = filteredBooks,
groups = groups,
groupPreviews = previews,
groupPreviews = previews.previews,
groupBookCounts = previews.counts,
selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
.coerceAtLeast(0),
searchKey = internal.searchKey,
@@ -24,6 +24,7 @@ fun BookshelfCover(
isUpdating: Boolean = false,
badgeText: String? = null,
showBadgeDot: Boolean = false,
leftBottomText: String? = null,
sourceOrigin: String? = null,
onLoadFinish: (() -> Unit)? = null
) {
@@ -52,6 +53,20 @@ fun BookshelfCover(
)
}
if (!leftBottomText.isNullOrEmpty()) {
TextCard(
text = leftBottomText,
backgroundColor = LegadoTheme.colorScheme.cardContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer,
modifier = Modifier
.align(Alignment.BottomStart)
.padding(2.dp),
cornerRadius = 4.dp,
horizontalPadding = 4.dp,
verticalPadding = 0.dp
)
}
if (isUpdating) {
LinearProgressIndicator(
modifier = Modifier