标签问题

This commit is contained in:
HapeLee committed 2026-05-17 02:00:48 +08:00
1 parent f0bbd4697f
commit 207b00189f
11 files changed
+230 -133

No files matched your search

@@ -98,7 +98,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
where type & ${BookType.text} > 0 where type & ${BookType.text} > 0
and type & ${BookType.local} = 0 and type & ${BookType.local} = 0
@@ -134,7 +135,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
""" """
@@ -167,7 +169,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE type & ${BookType.audio} > 0 WHERE type & ${BookType.audio} > 0
""" """
@@ -200,7 +203,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE type & ${BookType.local} > 0 WHERE type & ${BookType.local} > 0
""" """
@@ -238,7 +242,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
where type & ${BookType.audio} = 0 and type & ${BookType.local} = 0 where type & ${BookType.audio} = 0 and type & ${BookType.local} = 0
and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0 and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
@@ -277,7 +282,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
where type & ${BookType.local} > 0 where type & ${BookType.local} > 0
and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0 and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0
@@ -311,7 +317,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE (`group` & :group) > 0 WHERE (`group` & :group) > 0
""" """
@@ -346,7 +353,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE name like '%'||:key||'%' or author like '%'||:key||'%' or originName like '%'||:key||'%' WHERE name like '%'||:key||'%' or author like '%'||:key||'%' or originName like '%'||:key||'%'
""" """
@@ -379,7 +387,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
where type & ${BookType.updateError} > 0 where type & ${BookType.updateError} > 0
order by durChapterTime desc order by durChapterTime desc
@@ -413,7 +422,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE durChapterIndex = 0 AND durChapterPos = 0 WHERE durChapterIndex = 0 AND durChapterPos = 0
""" """
@@ -446,7 +456,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
""" """
@@ -479,7 +490,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE totalChapterNum > 0 AND durChapterIndex > 0 AND durChapterIndex < totalChapterNum - 1 WHERE totalChapterNum > 0 AND durChapterIndex > 0 AND durChapterIndex < totalChapterNum - 1
""" """
@@ -512,7 +524,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE type & ${BookType.image} > 0 WHERE type & ${BookType.image} > 0
""" """
@@ -545,7 +558,8 @@ interface BookDao {
`order`, `order`,
canUpdate, canUpdate,
ifnull(customIntro, intro) as intro, ifnull(customIntro, intro) as intro,
kind kind,
wordCount
FROM books FROM books
WHERE type & ${BookType.text} > 0 WHERE type & ${BookType.text} > 0
""" """
@@ -721,7 +735,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
LIMIT 10 LIMIT 10
@@ -736,7 +750,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.text} > 0 AND type & ${BookType.local} = 0 WHERE type & ${BookType.text} > 0 AND type & ${BookType.local} = 0
AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0
@@ -754,7 +768,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.local} > 0 WHERE type & ${BookType.local} > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -770,7 +784,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.audio} > 0 WHERE type & ${BookType.audio} > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -786,7 +800,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.audio} = 0 AND type & ${BookType.local} = 0 WHERE type & ${BookType.audio} = 0 AND type & ${BookType.local} = 0
AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0
@@ -803,7 +817,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.local} > 0 WHERE type & ${BookType.local} > 0
AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0 AND ((SELECT COALESCE(SUM(groupId), 0) FROM book_groups WHERE groupId > 0) & `group`) = 0
@@ -820,7 +834,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.image} > 0 WHERE type & ${BookType.image} > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -836,7 +850,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.text} > 0 WHERE type & ${BookType.text} > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -852,7 +866,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE type & ${BookType.updateError} > 0 WHERE type & ${BookType.updateError} > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -868,7 +882,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE durChapterIndex = 0 AND durChapterPos = 0 WHERE durChapterIndex = 0 AND durChapterPos = 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -884,7 +898,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE totalChapterNum > 0 AND durChapterIndex > 0 AND durChapterIndex < totalChapterNum - 1 WHERE totalChapterNum > 0 AND durChapterIndex > 0 AND durChapterIndex < totalChapterNum - 1
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -900,7 +914,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -916,7 +930,7 @@ interface BookDao {
durChapterPos, latestChapterTitle, latestChapterTime, durChapterPos, latestChapterTitle, latestChapterTime,
lastCheckCount, totalChapterNum, durChapterIndex, lastCheckCount, totalChapterNum, durChapterIndex,
type, `group`, `order`, canUpdate, type, `group`, `order`, canUpdate,
ifnull(customIntro, intro) as intro, kind ifnull(customIntro, intro) as intro, kind, wordCount
FROM books FROM books
WHERE (`group` & :groupId) > 0 WHERE (`group` & :groupId) > 0
ORDER BY durChapterTime DESC ORDER BY durChapterTime DESC
@@ -355,6 +355,9 @@ data class Book(
newBook.type = type newBook.type = type
} }
newBook.readConfig = readConfig newBook.readConfig = readConfig
if (newBook.wordCount.isNullOrBlank()) {
newBook.wordCount = wordCount
}
return newBook return newBook
} }
@@ -2,7 +2,6 @@
package io.legado.app.help.book package io.legado.app.help.book
import io.legado.app.ui.config.otherConfig.OtherConfig
import android.net.Uri import android.net.Uri
import androidx.core.net.toUri import androidx.core.net.toUri
import com.script.buildScriptBindings import com.script.buildScriptBindings
@@ -19,6 +18,7 @@ import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.RuleBigDataHelp import io.legado.app.help.RuleBigDataHelp
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.FileDoc import io.legado.app.utils.FileDoc
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
@@ -27,6 +27,7 @@ import io.legado.app.utils.find
import io.legado.app.utils.inputStream import io.legado.app.utils.inputStream
import io.legado.app.utils.isUri import io.legado.app.utils.isUri
import io.legado.app.utils.normalizeFileName import io.legado.app.utils.normalizeFileName
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import splitties.init.appCtx import splitties.init.appCtx
import java.io.File import java.io.File
@@ -306,6 +307,44 @@ fun Book.upType() {
} }
} }
fun Book.upKind() {
val fileSizePattern = Regex("""^\d[\d,.]*\s*(b|kb|M|G|T)$""", RegexOption.IGNORE_CASE)
val wordCountPattern = Regex(""".*?\d.*字$""")
val kinds = kind?.splitNotBlank(",", "\n").orEmpty()
.filter {
it.isNotBlank() && !fileSizePattern.matches(it.trim()) && !wordCountPattern.matches(
it.trim()
)
}
.toMutableList()
if (isLocal) {
// 添加格式
val typeName = getBookTypeName()
if (typeName != "未知类型" && !kinds.contains(typeName)) {
kinds.add(0, typeName)
}
// 添加大小
try {
val size = FileDoc.fromFile(bookUrl).size
if (size > 0) {
kinds.add(io.legado.app.utils.ConvertUtils.formatFileSize(size))
}
} catch (e: Exception) {
// ignore
}
}
// 添加字数
wordCount?.let {
if (it.isNotBlank()) {
kinds.add(it)
}
}
kind = kinds.distinct().joinToString(",")
}
fun Book.sync(oldBook: Book) { fun Book.sync(oldBook: Book) {
val curBook = appDb.bookDao.getBook(oldBook.bookUrl)!! val curBook = appDb.bookDao.getBook(oldBook.bookUrl)!!
durChapterTime = curBook.durChapterTime durChapterTime = curBook.durChapterTime
@@ -344,6 +383,10 @@ fun Book.updateTo(newBook: Book): Book {
newBook.type = type newBook.type = type
} }
newBook.readConfig = readConfig newBook.readConfig = readConfig
if (newBook.wordCount.isNullOrBlank()) {
newBook.wordCount = wordCount
}
newBook.upKind()
val variableMap = variableMap.toMutableMap() val variableMap = variableMap.toMutableMap()
variableMap.keys.removeIf { variableMap.keys.removeIf {
newBook.hasVariable(it) newBook.hasVariable(it)
@@ -1,6 +1,5 @@
package io.legado.app.model.localBook package io.legado.app.model.localBook
import io.legado.app.ui.config.otherConfig.OtherConfig
import android.net.Uri import android.net.Uri
import android.util.Base64 import android.util.Base64
import androidx.core.net.toUri import androidx.core.net.toUri
@@ -36,11 +35,13 @@ import io.legado.app.help.book.isPdf
import io.legado.app.help.book.isUmd import io.legado.app.help.book.isUmd
import io.legado.app.help.book.removeLocalUriCache import io.legado.app.help.book.removeLocalUriCache
import io.legado.app.help.book.simulatedTotalChapterNum import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.book.upKind
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.webdav.WebDav import io.legado.app.lib.webdav.WebDav
import io.legado.app.lib.webdav.WebDavException import io.legado.app.lib.webdav.WebDavException
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.CustomUrl import io.legado.app.model.analyzeRule.CustomUrl
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.FileDoc import io.legado.app.utils.FileDoc
import io.legado.app.utils.FileUtils import io.legado.app.utils.FileUtils
@@ -252,14 +253,17 @@ object LocalBook {
order = appDb.bookDao.minOrder - 1 order = appDb.bookDao.minOrder - 1
) )
upBookInfo(book) upBookInfo(book)
book.upKind()
appDb.bookDao.insert(book) appDb.bookDao.insert(book)
} else { } else {
deleteBook(book, false) deleteBook(book, false)
upBookInfo(book) upBookInfo(book)
book.upKind()
// 触发 isLocalModified // 触发 isLocalModified
book.latestChapterTime = 0 book.latestChapterTime = 0
//已有书籍说明是更新,删除原有目录 //已有书籍说明是更新,删除原有目录
appDb.bookChapterDao.delByBook(bookUrl) appDb.bookChapterDao.delByBook(bookUrl)
appDb.bookDao.update(book)
} }
return book return book
} }
@@ -290,6 +294,7 @@ object LocalBook {
//附加压缩包名称 以便解压文件被删后再解压 //附加压缩包名称 以便解压文件被删后再解压
origin = "${BookType.localTag}::${archiveFileDoc.name}" origin = "${BookType.localTag}::${archiveFileDoc.name}"
addType(BookType.archive) addType(BookType.archive)
upKind()
save() save()
} }
} }
@@ -8,6 +8,7 @@ import io.legado.app.data.entities.TxtTocRule
import io.legado.app.exception.EmptyFileException import io.legado.app.exception.EmptyFileException
import io.legado.app.help.DefaultData import io.legado.app.help.DefaultData
import io.legado.app.help.book.isLocalModified import io.legado.app.help.book.isLocalModified
import io.legado.app.help.book.upKind
import io.legado.app.utils.EncodingDetect import io.legado.app.utils.EncodingDetect
import io.legado.app.utils.MD5Utils import io.legado.app.utils.MD5Utils
import io.legado.app.utils.StringUtils import io.legado.app.utils.StringUtils
@@ -94,6 +95,7 @@ class TextFile(private var book: Book) {
} }
val (toc, wordCount) = analyze(book.tocUrl.toPattern(Pattern.MULTILINE)) val (toc, wordCount) = analyze(book.tocUrl.toPattern(Pattern.MULTILINE))
book.wordCount = StringUtils.wordCountFormat(wordCount) book.wordCount = StringUtils.wordCountFormat(wordCount)
book.upKind()
toc.forEachIndexed { index, bookChapter -> toc.forEachIndexed { index, bookChapter ->
bookChapter.index = index bookChapter.index = index
bookChapter.bookUrl = book.bookUrl bookChapter.bookUrl = book.bookUrl
@@ -36,6 +36,7 @@ import io.legado.app.help.book.isNotShelf
import io.legado.app.help.book.isSameNameAuthor import io.legado.app.help.book.isSameNameAuthor
import io.legado.app.help.book.isWebFile import io.legado.app.help.book.isWebFile
import io.legado.app.help.book.removeType import io.legado.app.help.book.removeType
import io.legado.app.help.book.upKind
import io.legado.app.help.book.updateTo import io.legado.app.help.book.updateTo
import io.legado.app.help.config.LocalConfig import io.legado.app.help.config.LocalConfig
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
@@ -51,8 +52,6 @@ import io.legado.app.model.webBook.WebBook
import io.legado.app.ui.config.coverConfig.CoverConfig import io.legado.app.ui.config.coverConfig.CoverConfig
import io.legado.app.ui.widget.components.cover.buildCoverImageRequest import io.legado.app.ui.widget.components.cover.buildCoverImageRequest
import io.legado.app.utils.ArchiveUtils import io.legado.app.utils.ArchiveUtils
import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.FileDoc
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.ImageSaveUtils import io.legado.app.utils.ImageSaveUtils
import io.legado.app.utils.UrlUtil import io.legado.app.utils.UrlUtil
@@ -759,24 +758,14 @@ class BookInfoViewModel(
private fun refreshMeta(book: Book) { private fun refreshMeta(book: Book) {
execute { execute {
val fileSizePattern = Regex("""^\d[\d,.]*\s*(b|kb|M|G|T)$""", RegexOption.IGNORE_CASE) book.upKind()
val kinds = (book.kind?.splitNotBlank(",", "\n").orEmpty().toList())
.filter { !fileSizePattern.matches(it.trim()) }
.toMutableList()
if (book.isLocal) {
val size = FileDoc.fromFile(book.bookUrl).size
if (size > 0) {
kinds.add(ConvertUtils.formatFileSize(size))
}
}
val finalKinds = kinds.distinct()
val userGroupIds = appDb.bookGroupDao.idsSum val userGroupIds = appDb.bookGroupDao.idsSum
val groupAnd = userGroupIds and book.group val groupAnd = userGroupIds and book.group
val hasCustomGroup = book.group > 0L && groupAnd != 0L val hasCustomGroup = book.group > 0L && groupAnd != 0L
val groupNames = appDb.bookGroupDao.getGroupNames(book.group).joinToString(",") val groupNames = appDb.bookGroupDao.getGroupNames(book.group).joinToString(",")
val normalizedGroupNames = groupNames.ifBlank { null } val normalizedGroupNames = groupNames.ifBlank { null }
book.kind = finalKinds.joinToString(",")
appDb.bookDao.update(book) appDb.bookDao.update(book)
val finalKinds = book.kind?.splitNotBlank(",", "\n").orEmpty().toList()
Triple(finalKinds, normalizedGroupNames, hasCustomGroup) Triple(finalKinds, normalizedGroupNames, hasCustomGroup)
}.onSuccess { }.onSuccess {
currentKindLabels = it.first currentKindLabels = it.first
@@ -54,7 +54,6 @@ import io.legado.app.ui.widget.components.cover.BookshelfCover
import io.legado.app.ui.widget.components.cover.CoilBookCover import io.legado.app.ui.widget.components.cover.CoilBookCover
import io.legado.app.ui.widget.components.icon.AppIcon import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.text.AppText import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.toTimeAgo import io.legado.app.utils.toTimeAgo
/** /**
@@ -289,7 +288,7 @@ fun BookshelfItem(
@Composable @Composable
fun BookGroupCover( fun BookGroupCover(
books: List<BookShelfItem>, books: List<BookUiItem>,
coverPath: String? = null, coverPath: String? = null,
leftBottomText: String? = null, leftBottomText: String? = null,
modifier: Modifier = Modifier modifier: Modifier = Modifier
@@ -328,7 +327,7 @@ fun BookGroupCover(
.fillMaxHeight() .fillMaxHeight()
.padding(1.dp) .padding(1.dp)
) { ) {
books.getOrNull(0)?.let { books.getOrNull(0)?.book?.let {
CoilBookCover( CoilBookCover(
name = it.name, name = it.name,
author = it.author, author = it.author,
@@ -343,7 +342,7 @@ fun BookGroupCover(
.fillMaxHeight() .fillMaxHeight()
.padding(1.dp) .padding(1.dp)
) { ) {
books.getOrNull(1)?.let { books.getOrNull(1)?.book?.let {
CoilBookCover( CoilBookCover(
name = it.name, name = it.name,
author = it.author, author = it.author,
@@ -360,7 +359,7 @@ fun BookGroupCover(
.fillMaxHeight() .fillMaxHeight()
.padding(1.dp) .padding(1.dp)
) { ) {
books.getOrNull(2)?.let { books.getOrNull(2)?.book?.let {
CoilBookCover( CoilBookCover(
name = it.name, name = it.name,
author = it.author, author = it.author,
@@ -375,7 +374,7 @@ fun BookGroupCover(
.fillMaxHeight() .fillMaxHeight()
.padding(1.dp) .padding(1.dp)
) { ) {
books.getOrNull(3)?.let { books.getOrNull(3)?.book?.let {
CoilBookCover( CoilBookCover(
name = it.name, name = it.name,
author = it.author, author = it.author,
@@ -406,7 +405,7 @@ fun BookGroupCover(
@Composable @Composable
fun BookGroupItemGrid( fun BookGroupItemGrid(
group: BookGroupUi, group: BookGroupUi,
previewBooks: List<BookShelfItem>, previewBooks: List<BookUiItem>,
countText: String? = null, countText: String? = null,
gridStyle: Int = 0, gridStyle: Int = 0,
titleSmallFont: Boolean = false, titleSmallFont: Boolean = false,
@@ -443,7 +442,7 @@ fun BookGroupItemGrid(
@Composable @Composable
fun BookGroupItemList( fun BookGroupItemList(
group: BookGroupUi, group: BookGroupUi,
previewBooks: List<BookShelfItem>, previewBooks: List<BookUiItem>,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
countText: String? = null, countText: String? = null,
@@ -467,7 +466,7 @@ fun BookGroupItemList(
) )
return return
} }
val firstBookName = previewBooks.firstOrNull()?.name val firstBookName = previewBooks.firstOrNull()?.book?.name
val descAnnotated = if (firstBookName != null) { val descAnnotated = if (firstBookName != null) {
buildAnnotatedString { buildAnnotatedString {
append("最近阅读:") append("最近阅读:")
@@ -499,7 +498,7 @@ fun BookGroupItemList(
@Composable @Composable
fun BookGroupItemHorizontalCovers( fun BookGroupItemHorizontalCovers(
group: BookGroupUi, group: BookGroupUi,
previewBooks: List<BookShelfItem>, previewBooks: List<BookUiItem>,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
countText: String? = null, countText: String? = null,
@@ -563,7 +562,8 @@ fun BookGroupItemHorizontalCovers(
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
val coverCount = BookshelfConfig.bookshelfGroupCoverCount val coverCount = BookshelfConfig.bookshelfGroupCoverCount
previewBooks.take(coverCount).forEach { book -> previewBooks.take(coverCount).forEach { bookUi ->
val book = bookUi.book
Box( Box(
modifier = Modifier modifier = Modifier
.weight(1f) .weight(1f)
@@ -597,7 +597,7 @@ fun BookGroupItemHorizontalCovers(
@OptIn(ExperimentalSharedTransitionApi::class) @OptIn(ExperimentalSharedTransitionApi::class)
@Composable @Composable
fun BookItem( fun BookItem(
book: BookShelfItem, bookUi: BookUiItem,
layoutMode: Int, layoutMode: Int,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isSelected: Boolean = false, isSelected: Boolean = false,
@@ -616,6 +616,7 @@ fun BookItem(
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: (() -> Unit)? onLongClick: (() -> Unit)?
) { ) {
val book = bookUi.book
val unreadCount = book.getUnreadChapterNum() val unreadCount = book.getUnreadChapterNum()
val unreadText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null val unreadText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null
val bookTypeLabel = if (BookshelfConfig.showTip) { val bookTypeLabel = if (BookshelfConfig.showTip) {
@@ -684,10 +685,10 @@ fun BookItem(
desc = book.durChapterTitle ?: "", desc = book.durChapterTitle ?: "",
columnContent = if (layoutMode == 0 && !isCompact && BookshelfConfig.showBookIntro) { columnContent = if (layoutMode == 0 && !isCompact && BookshelfConfig.showBookIntro) {
{ {
val kindList = book.kind?.splitNotBlank(",", "\n")?.filter { it.isNotBlank() } val kindList = bookUi.displayTags
val intro = book.intro?.takeIf { it.isNotBlank() } val intro = book.intro?.takeIf { it.isNotBlank() }
val customTagColors = if (ThemeConfig.enableCustomTagColors) ThemeConfig.getCustomTagColors() else emptyList() val customTagColors = if (ThemeConfig.enableCustomTagColors) ThemeConfig.getCustomTagColors() else emptyList()
if (BookshelfConfig.bookshelfShowTag && !kindList.isNullOrEmpty()) { if (BookshelfConfig.bookshelfShowTag && kindList.isNotEmpty()) {
Row( Row(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
@@ -3,6 +3,9 @@ package io.legado.app.ui.main.bookshelf
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import io.legado.app.constant.BookType import io.legado.app.constant.BookType
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.utils.splitNotBlank
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlin.math.max import kotlin.math.max
@Stable @Stable
@@ -27,7 +30,8 @@ data class BookShelfItem(
val order: Int, val order: Int,
val canUpdate: Boolean = true, val canUpdate: Boolean = true,
val intro: String? = null, val intro: String? = null,
val kind: String? = null val kind: String? = null,
val wordCount: String? = null
) { ) {
fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
@@ -41,8 +45,41 @@ data class BookShelfItem(
val isNew: Boolean get() = lastCheckCount > 0 val isNew: Boolean get() = lastCheckCount > 0
fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0)
/**
* 将 DTO 转换为专为 Compose 设计的 UI 状态
*/
fun toUiItem(): BookUiItem {
val tagList = mutableListOf<String>()
kind?.splitNotBlank(",", "\n")?.filter { it.isNotBlank() }?.let {
tagList.addAll(it)
}
if (!wordCount.isNullOrBlank() && !tagList.contains(wordCount)) {
tagList.add(wordCount)
}
return BookUiItem(
book = this,
displayTags = tagList.toImmutableList()
)
}
}
/**
* 理想实现:专为 UI 设计的状态类
*/
@Stable
data class BookUiItem(
val book: BookShelfItem,
val displayTags: ImmutableList<String>
) {
fun matches(key: String): Boolean {
return book.name.contains(key, true) ||
book.author.contains(key, true) ||
book.originName.contains(key, true) ||
displayTags.any { it.contains(key, true) }
}
} }
fun BookShelfItem.toLightBook() = Book( fun BookShelfItem.toLightBook() = Book(
@@ -64,5 +101,7 @@ fun BookShelfItem.toLightBook() = Book(
type = type, type = type,
group = group, group = group,
order = order, order = order,
canUpdate = canUpdate canUpdate = canUpdate,
wordCount = wordCount,
kind = kind
) )
@@ -847,7 +847,7 @@ fun BookshelfScreen(
uiState = uiState, uiState = uiState,
selectedBookUrls = selectedBookUrls, selectedBookUrls = selectedBookUrls,
canReorderBooks = false, canReorderBooks = false,
onToggleBookSelection = { toggleBookSelection(it.bookUrl) }, onToggleBookSelection = { toggleBookSelection(it.book.bookUrl) },
draggingBooks = null, draggingBooks = null,
pendingSavedBooks = null, pendingSavedBooks = null,
onDragStarted = {}, onDragStarted = {},
@@ -890,7 +890,7 @@ fun BookshelfScreen(
uiState = uiState, uiState = uiState,
selectedBookUrls = selectedBookUrls, selectedBookUrls = selectedBookUrls,
canReorderBooks = canReorderBooks, canReorderBooks = canReorderBooks,
onToggleBookSelection = { toggleBookSelection(it.bookUrl) }, onToggleBookSelection = { toggleBookSelection(it.book.bookUrl) },
draggingBooks = if (isSelectedGroup) { draggingBooks = if (isSelectedGroup) {
uiState.draggingBooks uiState.draggingBooks
} else { } else {
@@ -1241,15 +1241,15 @@ private data class BookshelfEditStickySummary(
@Composable @Composable
fun BookshelfPage( fun BookshelfPage(
paddingValues: PaddingValues, paddingValues: PaddingValues,
books: ImmutableList<BookShelfItem>, books: ImmutableList<BookUiItem>,
uiState: BookshelfUiState, uiState: BookshelfUiState,
selectedBookUrls: ImmutableSet<String>, selectedBookUrls: ImmutableSet<String>,
canReorderBooks: Boolean, canReorderBooks: Boolean,
onToggleBookSelection: (BookShelfItem) -> Unit, onToggleBookSelection: (BookUiItem) -> Unit,
draggingBooks: ImmutableList<BookShelfItem>?, draggingBooks: ImmutableList<BookUiItem>?,
pendingSavedBooks: ImmutableList<BookShelfItem>?, pendingSavedBooks: ImmutableList<BookUiItem>?,
onDragStarted: (ImmutableList<BookShelfItem>) -> Unit, onDragStarted: (ImmutableList<BookUiItem>) -> Unit,
onMoveBook: (fromIndex: Int, toIndex: Int, currentBooks: ImmutableList<BookShelfItem>) -> Unit, onMoveBook: (fromIndex: Int, toIndex: Int, currentBooks: ImmutableList<BookUiItem>) -> Unit,
onDragFinished: () -> Unit, onDragFinished: () -> Unit,
onGlobalSearch: () -> Unit, onGlobalSearch: () -> Unit,
onBookClick: (BookShelfItem) -> Unit, onBookClick: (BookShelfItem) -> Unit,
@@ -1367,15 +1367,15 @@ fun BookshelfPage(
horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
showFastScroll = showFastScroll showFastScroll = showFastScroll
) { ) {
items(displayBooks, key = { it.bookUrl }) { book -> items(displayBooks, key = { it.book.bookUrl }) { bookUi ->
val isSelected = selectedBookUrls.contains(book.bookUrl) val isSelected = selectedBookUrls.contains(bookUi.book.bookUrl)
ReorderableItem( ReorderableItem(
state = reorderableState, state = reorderableState,
key = book.bookUrl, key = bookUi.book.bookUrl,
enabled = canReorderBooks enabled = canReorderBooks
) { ) {
BookItem( BookItem(
book = book, bookUi = bookUi,
modifier = Modifier.then( modifier = Modifier.then(
if (canReorderBooks) { if (canReorderBooks) {
Modifier.longPressDraggableHandle( Modifier.longPressDraggableHandle(
@@ -1399,7 +1399,7 @@ fun BookshelfPage(
isSelected = isSelected, isSelected = isSelected,
gridStyle = bookItemGridStyle, gridStyle = bookItemGridStyle,
isCompact = bookItemIsCompact, isCompact = bookItemIsCompact,
isUpdating = uiState.updatingBooks.contains(book.bookUrl), isUpdating = uiState.updatingBooks.contains(bookUi.book.bookUrl),
titleSmallFont = bookItemTitleSmallFont, titleSmallFont = bookItemTitleSmallFont,
titleCenter = bookItemTitleCenter, titleCenter = bookItemTitleCenter,
titleMaxLines = bookItemTitleMaxLines, titleMaxLines = bookItemTitleMaxLines,
@@ -1408,12 +1408,12 @@ fun BookshelfPage(
searchKey = uiState.searchKey, searchKey = uiState.searchKey,
sharedTransitionScope = sharedTransitionScope, sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
sharedCoverKey = if (isCurrentPage) bookCoverSharedElementKey(book.bookUrl) else null, sharedCoverKey = if (isCurrentPage) bookCoverSharedElementKey(bookUi.book.bookUrl) else null,
onClick = { onClick = {
if (uiState.isEditMode) { if (uiState.isEditMode) {
onToggleBookSelection(book) onToggleBookSelection(bookUi)
} else { } else {
onBookClick(book) onBookClick(bookUi.book)
} }
}, },
onLongClick = if (canReorderBooks) { onLongClick = if (canReorderBooks) {
@@ -1421,9 +1421,9 @@ fun BookshelfPage(
} else { } else {
{ {
if (uiState.isEditMode) { if (uiState.isEditMode) {
onToggleBookSelection(book) onToggleBookSelection(bookUi)
} else { } else {
onBookLongClick(book) onBookLongClick(bookUi.book)
} }
} }
} }
@@ -33,7 +33,7 @@ sealed interface BookshelfOverlay {
@Stable @Stable
data class BookshelfUiState( data class BookshelfUiState(
override val items: ImmutableList<BookShelfItem> = persistentListOf(), override val items: ImmutableList<BookUiItem> = persistentListOf(),
override val selectedIds: ImmutableSet<Any> = persistentSetOf(), override val selectedIds: ImmutableSet<Any> = persistentSetOf(),
override val searchKey: String = "", override val searchKey: String = "",
override val isSearch: Boolean = false, override val isSearch: Boolean = false,
@@ -41,7 +41,7 @@ data class BookshelfUiState(
val isInitialLoading: Boolean = true, val isInitialLoading: Boolean = true,
val groups: ImmutableList<BookGroupUi> = persistentListOf(), val groups: ImmutableList<BookGroupUi> = persistentListOf(),
val allGroups: ImmutableList<BookGroupUi> = persistentListOf(), val allGroups: ImmutableList<BookGroupUi> = persistentListOf(),
val groupPreviews: ImmutableMap<Long, ImmutableList<BookShelfItem>> = persistentMapOf(), val groupPreviews: ImmutableMap<Long, ImmutableList<BookUiItem>> = persistentMapOf(),
val groupBookCounts: ImmutableMap<Long, Int> = persistentMapOf(), val groupBookCounts: ImmutableMap<Long, Int> = persistentMapOf(),
val currentGroupBookCount: Int = 0, val currentGroupBookCount: Int = 0,
val allBooksCount: Int = 0, val allBooksCount: Int = 0,
@@ -61,7 +61,7 @@ data class BookshelfUiState(
val title: String = "", val title: String = "",
val subtitle: String? = null, val subtitle: String? = null,
val currentGroupName: String? = null, val currentGroupName: String? = null,
val draggingBooks: ImmutableList<BookShelfItem>? = null, val draggingBooks: ImmutableList<BookUiItem>? = null,
val pendingSavedBooks: ImmutableList<BookShelfItem>? = null, val pendingSavedBooks: ImmutableList<BookUiItem>? = null,
val allGroupBooks: ImmutableMap<Long, ImmutableList<BookShelfItem>> = persistentMapOf() val allGroupBooks: ImmutableMap<Long, ImmutableList<BookUiItem>> = persistentMapOf()
) : ListUiState<BookShelfItem> ) : ListUiState<BookUiItem>
@@ -117,8 +117,8 @@ class BookshelfViewModel(
private val isInFolderRootFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle == 2) private val isInFolderRootFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle == 2)
private val isRefreshingFlow = MutableStateFlow(false) private val isRefreshingFlow = MutableStateFlow(false)
private val bookGroupStyleFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle) private val bookGroupStyleFlow = MutableStateFlow(BookshelfConfig.bookGroupStyle)
private val draggingBooksFlow = MutableStateFlow<List<BookShelfItem>?>(null) private val draggingBooksFlow = MutableStateFlow<List<BookUiItem>?>(null)
private val pendingSavedBooksFlow = MutableStateFlow<List<BookShelfItem>?>(null) private val pendingSavedBooksFlow = MutableStateFlow<List<BookUiItem>?>(null)
private val isInitialLoadingFlow = MutableStateFlow(true) private val isInitialLoadingFlow = MutableStateFlow(true)
private data class BookshelfSortConfig( private data class BookshelfSortConfig(
@@ -170,7 +170,7 @@ class BookshelfViewModel(
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
private data class GroupPreviewState( private data class GroupPreviewState(
val previews: ImmutableMap<Long, ImmutableList<BookShelfItem>>, val previews: ImmutableMap<Long, ImmutableList<BookUiItem>>,
val counts: ImmutableMap<Long, Int>, val counts: ImmutableMap<Long, Int>,
val allBookCount: Int val allBookCount: Int
) )
@@ -189,7 +189,7 @@ class BookshelfViewModel(
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfGroupSelectorState()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), BookshelfGroupSelectorState())
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
val booksFlow = groupIdFlow val booksFlow: Flow<List<BookUiItem>> = groupIdFlow
.flatMapLatest { groupId -> .flatMapLatest { groupId ->
combine( combine(
appDb.bookDao.flowBookShelfByGroup(groupId), appDb.bookDao.flowBookShelfByGroup(groupId),
@@ -200,12 +200,12 @@ class BookshelfViewModel(
list, list,
groups.find { it.groupId == groupId }, groups.find { it.groupId == groupId },
sortConfig sortConfig
) ).map { it.toUiItem() }
} }
}.distinctUntilChanged().flowOn(Dispatchers.Default) }.distinctUntilChanged().flowOn(Dispatchers.Default)
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private val allGroupBooksFlow: StateFlow<Map<Long, List<BookShelfItem>>> = combine( private val allGroupBooksFlow: StateFlow<Map<Long, List<BookUiItem>>> = combine(
groupsFlow, sortConfigFlow groupsFlow, sortConfigFlow
) { groups, sortConfig -> ) { groups, sortConfig ->
groups to sortConfig groups to sortConfig
@@ -215,7 +215,7 @@ class BookshelfViewModel(
} else { } else {
val flows = groups.map { group -> val flows = groups.map { group ->
appDb.bookDao.flowBookShelfByGroup(group.groupId).map { books -> appDb.bookDao.flowBookShelfByGroup(group.groupId).map { books ->
group.groupId to sortBooks(books, group, sortConfig) group.groupId to sortBooks(books, group, sortConfig).map { it.toUiItem() }
} }
} }
combine(flows) { it.toMap() } combine(flows) { it.toMap() }
@@ -224,13 +224,13 @@ class BookshelfViewModel(
.flowOn(Dispatchers.Default) .flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyMap())
private val allGroupBooksImmutableFlow: StateFlow<ImmutableMap<Long, ImmutableList<BookShelfItem>>> = private val allGroupBooksImmutableFlow: StateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>> =
allGroupBooksFlow.map { map -> allGroupBooksFlow.map { map ->
map.mapValues { it.value.toImmutableList() }.toImmutableMap() map.mapValues { it.value.toImmutableList() }.toImmutableMap()
}.flowOn(Dispatchers.Default) }.flowOn(Dispatchers.Default)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf()) .stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), persistentMapOf())
private val visibleBooksFlow = combine( private val visibleBooksFlow: Flow<List<BookUiItem>> = combine(
booksFlow, booksFlow,
searchKeyFlow, searchKeyFlow,
searchModeFlow searchModeFlow
@@ -254,7 +254,7 @@ class BookshelfViewModel(
selectedBookUrlsFlow, selectedBookUrlsFlow,
visibleBooksFlow visibleBooksFlow
) { selectedBookUrls, visibleBooks -> ) { selectedBookUrls, visibleBooks ->
val visibleBookUrls = visibleBooks.mapTo(hashSetOf()) { it.bookUrl } val visibleBookUrls = visibleBooks.mapTo(hashSetOf()) { it.book.bookUrl }
selectedBookUrls.intersect(visibleBookUrls) selectedBookUrls.intersect(visibleBookUrls)
}.distinctUntilChanged() }.distinctUntilChanged()
@@ -281,11 +281,11 @@ class BookshelfViewModel(
} }
val previewFlow = appDb.bookDao.flowGroupPreview(group.groupId) val previewFlow = appDb.bookDao.flowGroupPreview(group.groupId)
combine(countFlow, previewFlow) { count, preview -> combine(countFlow, previewFlow) { count, preview ->
Triple(group.groupId, count, preview) Triple(group.groupId, count, preview.map { it.toUiItem() })
} }
} }
combine(groupFlows) { results -> combine(groupFlows) { results ->
var previews = persistentMapOf<Long, ImmutableList<BookShelfItem>>() var previews = persistentMapOf<Long, ImmutableList<BookUiItem>>()
var counts = persistentMapOf<Long, Int>() var counts = persistentMapOf<Long, Int>()
var allBookCount = 0 var allBookCount = 0
results.forEach { (groupId, count, preview) -> results.forEach { (groupId, count, preview) ->
@@ -344,8 +344,8 @@ class BookshelfViewModel(
val isInFolderRoot: Boolean, val isInFolderRoot: Boolean,
val isRefreshing: Boolean, val isRefreshing: Boolean,
val bookGroupStyle: Int, val bookGroupStyle: Int,
val draggingBooks: List<BookShelfItem>?, val draggingBooks: List<BookUiItem>?,
val pendingSavedBooks: List<BookShelfItem>? val pendingSavedBooks: List<BookUiItem>?
) )
private val editStateFlow = combine( private val editStateFlow = combine(
@@ -387,7 +387,8 @@ class BookshelfViewModel(
GroupPreviewState(persistentMapOf(), persistentMapOf(), 0) GroupPreviewState(persistentMapOf(), persistentMapOf(), 0)
) )
private val allGroupBooksStateFlow = MutableStateFlow<ImmutableMap<Long, ImmutableList<BookShelfItem>>>( private val allGroupBooksStateFlow =
MutableStateFlow<ImmutableMap<Long, ImmutableList<BookUiItem>>>(
persistentMapOf() persistentMapOf()
) )
@@ -411,7 +412,7 @@ class BookshelfViewModel(
} }
private data class BookshelfDataCore( private data class BookshelfDataCore(
val books: List<BookShelfItem>, val books: List<BookUiItem>,
val groups: List<BookGroup>, val groups: List<BookGroup>,
val allGroups: List<BookGroup>, val allGroups: List<BookGroup>,
val previews: GroupPreviewState, val previews: GroupPreviewState,
@@ -419,12 +420,12 @@ class BookshelfViewModel(
) )
private data class BookshelfDataState( private data class BookshelfDataState(
val books: List<BookShelfItem>, val books: List<BookUiItem>,
val groups: List<BookGroupUi>, val groups: List<BookGroupUi>,
val allGroups: List<BookGroupUi>, val allGroups: List<BookGroupUi>,
val previews: GroupPreviewState, val previews: GroupPreviewState,
val internal: InternalState, val internal: InternalState,
val allGroupBooks: ImmutableMap<Long, ImmutableList<BookShelfItem>> val allGroupBooks: ImmutableMap<Long, ImmutableList<BookUiItem>>
) )
val uiState: StateFlow<BookshelfUiState> = combine( val uiState: StateFlow<BookshelfUiState> = combine(
@@ -540,14 +541,14 @@ class BookshelfViewModel(
} }
private fun filterBooks( private fun filterBooks(
books: List<BookShelfItem>, books: List<BookUiItem>,
searchKey: String, searchKey: String,
isSearchMode: Boolean isSearchMode: Boolean
): List<BookShelfItem> { ): List<BookUiItem> {
return if (!isSearchMode || searchKey.isBlank()) { return if (!isSearchMode || searchKey.isBlank()) {
books books
} else { } else {
books.filter { it.matchesSearchKey(searchKey) } books.filter { it.matches(searchKey) }
} }
} }
@@ -673,11 +674,11 @@ class BookshelfViewModel(
} }
fun selectAllVisible() { fun selectAllVisible() {
selectedBookUrlsFlow.value = uiState.value.items.mapTo(hashSetOf()) { it.bookUrl } selectedBookUrlsFlow.value = uiState.value.items.mapTo(hashSetOf()) { it.book.bookUrl }
} }
fun invertVisibleSelection() { fun invertVisibleSelection() {
val visibleBookUrls = uiState.value.items.mapTo(hashSetOf()) { it.bookUrl } val visibleBookUrls = uiState.value.items.mapTo(hashSetOf()) { it.book.bookUrl }
selectedBookUrlsFlow.value = visibleBookUrls - selectedBookUrlsFlow.value selectedBookUrlsFlow.value = visibleBookUrls - selectedBookUrlsFlow.value
} }
@@ -719,13 +720,13 @@ class BookshelfViewModel(
} }
} }
fun saveBookOrder(reorderedBooks: List<BookShelfItem>) { fun saveBookOrder(reorderedBooks: List<BookUiItem>) {
if (reorderedBooks.isEmpty()) return if (reorderedBooks.isEmpty()) return
val isDescending = BookshelfConfig.bookshelfSortOrder == 1 val isDescending = BookshelfConfig.bookshelfSortOrder == 1
val maxOrder = reorderedBooks.size val maxOrder = reorderedBooks.size
execute { execute {
val updates = reorderedBooks.mapIndexedNotNull { index, book -> val updates = reorderedBooks.mapIndexedNotNull { index, bookUi ->
appDb.bookDao.getBook(book.bookUrl)?.apply { appDb.bookDao.getBook(bookUi.book.bookUrl)?.apply {
order = if (isDescending) maxOrder - index else index + 1 order = if (isDescending) maxOrder - index else index + 1
} }
} }
@@ -756,19 +757,19 @@ class BookshelfViewModel(
} }
} }
fun refreshBooks(books: List<BookShelfItem>) { fun refreshBooks(books: List<BookUiItem>) {
if (isRefreshingFlow.value) return if (isRefreshingFlow.value) return
isRefreshingFlow.value = true isRefreshingFlow.value = true
val limit = BookshelfConfig.bookshelfRefreshingLimit val limit = BookshelfConfig.bookshelfRefreshingLimit
val list = if (limit > 0) books.take(limit) else books val list = if (limit > 0) books.take(limit) else books
enqueueTocUpdate(list, resetRefreshWhenIdle = true) enqueueTocUpdate(list.map { it.book }, resetRefreshWhenIdle = true)
} }
fun startDraggingBooks(books: List<BookShelfItem>) { fun startDraggingBooks(books: List<BookUiItem>) {
draggingBooksFlow.value = books draggingBooksFlow.value = books
} }
fun moveDraggingBook(fromIndex: Int, toIndex: Int, fallbackBooks: List<BookShelfItem>) { fun moveDraggingBook(fromIndex: Int, toIndex: Int, fallbackBooks: List<BookUiItem>) {
if (fromIndex == toIndex) return if (fromIndex == toIndex) return
val sourceBooks = draggingBooksFlow.value ?: fallbackBooks val sourceBooks = draggingBooksFlow.value ?: fallbackBooks
if (fromIndex !in sourceBooks.indices || toIndex !in sourceBooks.indices) return if (fromIndex !in sourceBooks.indices || toIndex !in sourceBooks.indices) return
@@ -778,19 +779,19 @@ class BookshelfViewModel(
} }
fun finishDraggingBooks() { fun finishDraggingBooks() {
val reorderedBooks = draggingBooksFlow.value ?: return val reorderedUiBooks = draggingBooksFlow.value ?: return
pendingSavedBooksFlow.value = reorderedBooks pendingSavedBooksFlow.value = reorderedUiBooks
draggingBooksFlow.value = null draggingBooksFlow.value = null
saveBookOrder(reorderedBooks) saveBookOrder(reorderedUiBooks)
} }
private fun syncDragState(books: List<BookShelfItem>, canReorderBooks: Boolean) { private fun syncDragState(books: List<BookUiItem>, canReorderBooks: Boolean) {
if (!canReorderBooks) { if (!canReorderBooks) {
clearDragState() clearDragState()
return return
} }
val pending = pendingSavedBooksFlow.value ?: return val pending = pendingSavedBooksFlow.value ?: return
if (books.map { it.bookUrl } == pending.map { it.bookUrl }) { if (books.map { it.book.bookUrl } == pending.map { it.book.bookUrl }) {
pendingSavedBooksFlow.value = null pendingSavedBooksFlow.value = null
} }
} }
@@ -811,10 +812,10 @@ class BookshelfViewModel(
} }
} }
fun upToc(books: List<BookShelfItem>) { fun upToc(books: List<BookUiItem>) {
val limit = BookshelfConfig.bookshelfRefreshingLimit val limit = BookshelfConfig.bookshelfRefreshingLimit
val list = if (limit > 0) books.take(limit) else books val list = if (limit > 0) books.take(limit) else books
enqueueTocUpdate(list, resetRefreshWhenIdle = false) enqueueTocUpdate(list.map { it.book }, resetRefreshWhenIdle = false)
} }
private fun enqueueTocUpdate( private fun enqueueTocUpdate(
@@ -1088,7 +1089,7 @@ class BookshelfViewModel(
} }
} }
fun exportToUri(uri: Uri, items: List<BookShelfItem>) { fun exportToUri(uri: Uri, items: List<BookUiItem>) {
execute { execute {
context.contentResolver.openOutputStream(uri)?.use { out -> context.contentResolver.openOutputStream(uri)?.use { out ->
val writer = JsonWriter(OutputStreamWriter(out, "UTF-8")) val writer = JsonWriter(OutputStreamWriter(out, "UTF-8"))
@@ -1096,11 +1097,11 @@ class BookshelfViewModel(
writer.beginArray() writer.beginArray()
items.forEach { items.forEach {
val bookMap = hashMapOf<String, String?>() val bookMap = hashMapOf<String, String?>()
bookMap["name"] = it.name bookMap["name"] = it.book.name
bookMap["author"] = it.author bookMap["author"] = it.book.author
// intro is not in BookShelfItem, fetch from DB if needed or skip // intro is not in BookShelfItem, fetch from DB if needed or skip
// For now, let's keep it simple and skip intro or fetch it // For now, let's keep it simple and skip intro or fetch it
val fullBook = appDb.bookDao.getBook(it.bookUrl) val fullBook = appDb.bookDao.getBook(it.book.bookUrl)
bookMap["intro"] = fullBook?.getDisplayIntro() bookMap["intro"] = fullBook?.getDisplayIntro()
GSON.toJson(bookMap, bookMap::class.java, writer) GSON.toJson(bookMap, bookMap::class.java, writer)
} }
@@ -1114,14 +1115,14 @@ class BookshelfViewModel(
} }
} }
fun uploadBookshelf(items: List<BookShelfItem>) { fun uploadBookshelf(items: List<BookUiItem>) {
execute { execute {
val json = withContext(Dispatchers.Default) { val json = withContext(Dispatchers.Default) {
val list = items.map { val list = items.map {
val bookMap = hashMapOf<String, String?>() val bookMap = hashMapOf<String, String?>()
bookMap["name"] = it.name bookMap["name"] = it.book.name
bookMap["author"] = it.author bookMap["author"] = it.book.author
val fullBook = appDb.bookDao.getBook(it.bookUrl) val fullBook = appDb.bookDao.getBook(it.book.bookUrl)
bookMap["intro"] = fullBook?.getDisplayIntro() bookMap["intro"] = fullBook?.getDisplayIntro()
bookMap bookMap
} }
@@ -1149,7 +1150,7 @@ class BookshelfViewModel(
} }
} }
fun exportBookshelf(items: List<BookShelfItem>?, success: (file: File) -> Unit) { fun exportBookshelf(items: List<BookUiItem>?, success: (file: File) -> Unit) {
execute { execute {
items?.let { items?.let {
val path = "${context.filesDir}/books.json" val path = "${context.filesDir}/books.json"
@@ -1161,9 +1162,9 @@ class BookshelfViewModel(
writer.beginArray() writer.beginArray()
items.forEach { items.forEach {
val bookMap = hashMapOf<String, String?>() val bookMap = hashMapOf<String, String?>()
bookMap["name"] = it.name bookMap["name"] = it.book.name
bookMap["author"] = it.author bookMap["author"] = it.book.author
val fullBook = appDb.bookDao.getBook(it.bookUrl) val fullBook = appDb.bookDao.getBook(it.book.bookUrl)
bookMap["intro"] = fullBook?.getDisplayIntro() bookMap["intro"] = fullBook?.getDisplayIntro()
GSON.toJson(bookMap, bookMap::class.java, writer) GSON.toJson(bookMap, bookMap::class.java, writer)
} }