Ver Fonte

fix: 分离书源标签和用户标签

HapeLee há 3 meses atrás
pai
commit
f738de869a

+ 33 - 16
app/src/main/java/io/legado/app/data/dao/BookDao.kt

@@ -113,6 +113,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         where type & ${BookType.text} > 0
         where type & ${BookType.text} > 0
@@ -154,6 +155,7 @@ interface BookDao {
         canUpdate,
         canUpdate,
         ifnull(customIntro, intro) as intro,
         ifnull(customIntro, intro) as intro,
         kind,
         kind,
+        customTag,
         wordCount
         wordCount
     FROM books
     FROM books
     WHERE $PUBLIC_BOOK_FILTER
     WHERE $PUBLIC_BOOK_FILTER
@@ -189,6 +191,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.audio} > 0
         WHERE type & ${BookType.audio} > 0
@@ -224,6 +227,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE type & ${BookType.local} > 0
         WHERE type & ${BookType.local} > 0
@@ -264,6 +268,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             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
@@ -305,6 +310,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         where type & ${BookType.local} > 0
         where type & ${BookType.local} > 0
@@ -341,6 +347,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE (`group` & :group) > 0
         WHERE (`group` & :group) > 0
@@ -378,9 +385,11 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             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||'%'
+            or kind like '%'||:key||'%' or customTag like '%'||:key||'%')
         AND $PUBLIC_BOOK_FILTER
         AND $PUBLIC_BOOK_FILTER
         """
         """
     )
     )
@@ -413,6 +422,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         where type & ${BookType.updateError} > 0 
         where type & ${BookType.updateError} > 0 
@@ -449,6 +459,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE durChapterIndex = 0 AND durChapterPos = 0
         WHERE durChapterIndex = 0 AND durChapterPos = 0
@@ -484,6 +495,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
@@ -521,6 +533,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 1
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 1
@@ -558,6 +571,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 0
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 0
@@ -593,6 +607,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             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
@@ -628,6 +643,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE type & ${BookType.image} > 0
         WHERE type & ${BookType.image} > 0
@@ -663,6 +679,7 @@ interface BookDao {
             canUpdate,
             canUpdate,
             ifnull(customIntro, intro) as intro,
             ifnull(customIntro, intro) as intro,
             kind,
             kind,
+            customTag,
             wordCount
             wordCount
         FROM books 
         FROM books 
         WHERE type & ${BookType.text} > 0
         WHERE type & ${BookType.text} > 0
@@ -875,7 +892,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE $PUBLIC_BOOK_FILTER
         WHERE $PUBLIC_BOOK_FILTER
         ORDER BY durChapterTime DESC
         ORDER BY durChapterTime DESC
@@ -891,7 +908,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, 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 ($PUBLIC_GROUP_MASK & `group`) = 0
             AND ($PUBLIC_GROUP_MASK & `group`) = 0
@@ -910,7 +927,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.local} > 0
         WHERE type & ${BookType.local} > 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -927,7 +944,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.audio} > 0
         WHERE type & ${BookType.audio} > 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -944,7 +961,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, 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 ($PUBLIC_GROUP_MASK & `group`) = 0
             AND ($PUBLIC_GROUP_MASK & `group`) = 0
@@ -962,7 +979,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.local} > 0
         WHERE type & ${BookType.local} > 0
             AND ($PUBLIC_GROUP_MASK & `group`) = 0
             AND ($PUBLIC_GROUP_MASK & `group`) = 0
@@ -980,7 +997,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.image} > 0
         WHERE type & ${BookType.image} > 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -997,7 +1014,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.text} > 0
         WHERE type & ${BookType.text} > 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1014,7 +1031,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE type & ${BookType.updateError} > 0
         WHERE type & ${BookType.updateError} > 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1031,7 +1048,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE durChapterIndex = 0 AND durChapterPos = 0
         WHERE durChapterIndex = 0 AND durChapterPos = 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1048,7 +1065,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, 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
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1065,7 +1082,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1082,7 +1099,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 1
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 1
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1099,7 +1116,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 0
         WHERE totalChapterNum > 0 AND durChapterIndex >= totalChapterNum - 1 AND canUpdate = 0
             AND $PUBLIC_BOOK_FILTER
             AND $PUBLIC_BOOK_FILTER
@@ -1116,7 +1133,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, wordCount
+            ifnull(customIntro, intro) as intro, kind, customTag, wordCount
         FROM books
         FROM books
         WHERE (`group` & :groupId) > 0
         WHERE (`group` & :groupId) > 0
             AND ((SELECT isPrivate FROM book_groups WHERE groupId = :groupId) = 1 OR $PUBLIC_BOOK_FILTER)
             AND ((SELECT isPrivate FROM book_groups WHERE groupId = :groupId) = 1 OR $PUBLIC_BOOK_FILTER)

+ 13 - 3
app/src/main/java/io/legado/app/help/book/BookExtensions.kt

@@ -15,11 +15,11 @@ import io.legado.app.data.entities.BaseBook
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.BookSource
 import io.legado.app.data.entities.HighlightTagRule
 import io.legado.app.data.entities.HighlightTagRule
-import io.legado.app.ui.book.info.HighlightedTag
 import io.legado.app.exception.NoStackTraceException
 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.book.info.HighlightedTag
 import io.legado.app.ui.config.otherConfig.OtherConfig
 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
@@ -122,9 +122,19 @@ fun Book.contains(word: String?): Boolean {
             || originName.contains(word)
             || originName.contains(word)
             || origin.contains(word)
             || origin.contains(word)
             || kind?.contains(word) == true
             || kind?.contains(word) == true
+            || customTag?.contains(word) == true
             || intro?.contains(word) == true
             || intro?.contains(word) == true
 }
 }
 
 
+fun Book.getSourceTagList(): List<String> =
+    kind?.splitNotBlank(",", "\n").orEmpty().distinct()
+
+fun Book.getCustomTagList(): List<String> =
+    customTag?.splitNotBlank(",", "\n").orEmpty().distinct()
+
+fun Book.getDisplayTagList(): List<String> =
+    (getCustomTagList() + getSourceTagList()).distinct()
+
 /**
 /**
  * 仅在目标bookUrl未被其他书占用,或判定为同一本书时,允许迁移主键。
  * 仅在目标bookUrl未被其他书占用,或判定为同一本书时,允许迁移主键。
  */
  */
@@ -439,7 +449,7 @@ fun applyTagGroupRules(
 
 
     val updatedBooks = mutableListOf<Book>()
     val updatedBooks = mutableListOf<Book>()
     for (book in books) {
     for (book in books) {
-        val kinds = book.kind?.splitNotBlank(",", "\n").orEmpty()
+        val kinds = book.getDisplayTagList()
         var newGroupMask = 0L
         var newGroupMask = 0L
         for ((rule, regex) in compiledRules) {
         for ((rule, regex) in compiledRules) {
             if (kinds.any { regex.containsMatchIn(it) }) {
             if (kinds.any { regex.containsMatchIn(it) }) {
@@ -496,7 +506,7 @@ fun applyTagGroupRulesForBook(book: Book) {
     }
     }
 
 
     val allRuleGroupMask = groupCache.values.fold(0L) { acc, id -> acc or id }
     val allRuleGroupMask = groupCache.values.fold(0L) { acc, id -> acc or id }
-    val kinds = book.kind?.splitNotBlank(",", "\n").orEmpty()
+    val kinds = book.getDisplayTagList()
     var newGroupMask = 0L
     var newGroupMask = 0L
     for ((rule, regex) in compiledRules) {
     for ((rule, regex) in compiledRules) {
         if (kinds.any { regex.containsMatchIn(it) }) {
         if (kinds.any { regex.containsMatchIn(it) }) {

+ 5 - 5
app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt

@@ -31,14 +31,15 @@ import io.legado.app.exception.NoBooksDirException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.exception.NoStackTraceException
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.BookHelp
 import io.legado.app.help.book.addType
 import io.legado.app.help.book.addType
+import io.legado.app.help.book.getDisplayTagList
 import io.legado.app.help.book.getExportFileName
 import io.legado.app.help.book.getExportFileName
 import io.legado.app.help.book.isLocal
 import io.legado.app.help.book.isLocal
 import io.legado.app.help.book.isNotShelf
 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.parseHighlightedTags
 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.upKind
-import io.legado.app.help.book.parseHighlightedTags
 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
@@ -60,7 +61,6 @@ import io.legado.app.utils.ImageSaveUtils
 import io.legado.app.utils.UrlUtil
 import io.legado.app.utils.UrlUtil
 import io.legado.app.utils.fromJsonArray
 import io.legado.app.utils.fromJsonArray
 import io.legado.app.utils.postEvent
 import io.legado.app.utils.postEvent
-import io.legado.app.utils.splitNotBlank
 import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toastOnUi
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.collections.immutable.toImmutableList
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CancellationException
@@ -69,14 +69,14 @@ import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.async
 import kotlinx.coroutines.async
 import kotlinx.coroutines.awaitAll
 import kotlinx.coroutines.awaitAll
+import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.callbackFlow
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.combine
@@ -889,7 +889,7 @@ class BookInfoViewModel(
             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 }
             appDb.bookDao.update(book)
             appDb.bookDao.update(book)
-            val finalKinds = book.kind?.splitNotBlank(",", "\n").orEmpty().toList()
+            val finalKinds = book.getDisplayTagList()
             val enabledRules = appDb.highlightTagRuleDao.getEnabled()
             val enabledRules = appDb.highlightTagRuleDao.getEnabled()
             val (highlighted, regular) = parseHighlightedTags(finalKinds, enabledRules)
             val (highlighted, regular) = parseHighlightedTags(finalKinds, enabledRules)
             HighlightMeta(highlighted, regular, normalizedGroupNames, hasCustomGroup)
             HighlightMeta(highlighted, regular, normalizedGroupNames, hasCustomGroup)

+ 14 - 1
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditScreen.kt

@@ -53,7 +53,6 @@ import androidx.compose.ui.zIndex
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.ui.book.changecover.ChangeCoverDialog
 import io.legado.app.ui.book.changecover.ChangeCoverDialog
-import io.legado.app.ui.config.themeConfig.ThemeConfig
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.fadingEdge
 import io.legado.app.ui.theme.fadingEdge
 import io.legado.app.ui.widget.components.AppScaffold
 import io.legado.app.ui.widget.components.AppScaffold
@@ -65,6 +64,7 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 import io.legado.app.ui.widget.components.settingItem.SwitchSettingItem
 import io.legado.app.ui.widget.components.text.AnimatedTextLine
 import io.legado.app.ui.widget.components.text.AnimatedTextLine
+import io.legado.app.ui.widget.components.text.AppText
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
@@ -220,6 +220,15 @@ fun BookInfoEditContent(
             modifier = Modifier.fillMaxWidth()
             modifier = Modifier.fillMaxWidth()
         )
         )
         Spacer(modifier = Modifier.height(8.dp))
         Spacer(modifier = Modifier.height(8.dp))
+        AppTextField(
+            value = uiState.sourceKindList.joinToString(", "),
+            onValueChange = {},
+            readOnly = true,
+            label = stringResource(R.string.source_categories),
+            backgroundColor = LegadoTheme.colorScheme.surfaceInput,
+            modifier = Modifier.fillMaxWidth()
+        )
+        Spacer(modifier = Modifier.height(8.dp))
         KindEditor(
         KindEditor(
             kindList = uiState.kindList,
             kindList = uiState.kindList,
             onKindListChange = { viewModel.onKindListChange(it) },
             onKindListChange = { viewModel.onKindListChange(it) },
@@ -325,6 +334,10 @@ fun KindEditor(
         modifier = Modifier.fillMaxWidth(),
         modifier = Modifier.fillMaxWidth(),
         verticalAlignment = Alignment.CenterVertically
         verticalAlignment = Alignment.CenterVertically
     ) {
     ) {
+        AppText(
+            text = stringResource(R.string.my_tags),
+            modifier = Modifier.padding(end = 8.dp),
+        )
         LazyRow(
         LazyRow(
             state = listState,
             state = listState,
             modifier = Modifier
             modifier = Modifier

+ 8 - 8
app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt

@@ -20,6 +20,7 @@ import io.legado.app.utils.FileUtils
 import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.MD5Utils
 import io.legado.app.utils.externalFiles
 import io.legado.app.utils.externalFiles
 import io.legado.app.utils.inputStream
 import io.legado.app.utils.inputStream
+import io.legado.app.utils.splitNotBlank
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -38,6 +39,7 @@ data class BookInfoEditUiState(
     val coverUrl: String? = null,
     val coverUrl: String? = null,
     val intro: String? = null,
     val intro: String? = null,
     val remark: String? = null,
     val remark: String? = null,
+    val sourceKindList: List<String> = emptyList(),
     val kindList: List<String> = emptyList(),
     val kindList: List<String> = emptyList(),
     val originalKindList: List<String> = emptyList(),
     val originalKindList: List<String> = emptyList(),
     val selectedType: BookInfoEditType = BookInfoEditType.TEXT,
     val selectedType: BookInfoEditType = BookInfoEditType.TEXT,
@@ -59,19 +61,17 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
                     it.isAudio -> BookInfoEditType.AUDIO
                     it.isAudio -> BookInfoEditType.AUDIO
                     else -> BookInfoEditType.TEXT
                     else -> BookInfoEditType.TEXT
                 }
                 }
-                val kinds =
-                    it.kind?.split(",", "\n")
-                        ?.filter { kind -> kind.isNotBlank() }
-                        ?.distinct()
-                        .orEmpty()
+                val sourceKinds = it.kind?.splitNotBlank(",", "\n").orEmpty().distinct()
+                val customTags = it.customTag?.splitNotBlank(",", "\n").orEmpty().distinct()
                 _uiState.value = BookInfoEditUiState(
                 _uiState.value = BookInfoEditUiState(
                     name = it.name,
                     name = it.name,
                     author = it.author,
                     author = it.author,
                     coverUrl = it.getDisplayCover(),
                     coverUrl = it.getDisplayCover(),
                     intro = it.getDisplayIntro(),
                     intro = it.getDisplayIntro(),
                     remark = it.remark,
                     remark = it.remark,
-                    kindList = kinds,
-                    originalKindList = kinds,
+                    sourceKindList = sourceKinds,
+                    kindList = customTags,
+                    originalKindList = customTags,
                     selectedType = selectedType,
                     selectedType = selectedType,
                     fixedType = it.config.fixedType,
                     fixedType = it.config.fixedType,
                     book = it
                     book = it
@@ -139,7 +139,7 @@ class BookInfoEditViewModel(application: Application) : BaseViewModel(applicatio
                 book.config.fixedType = currentState.fixedType
                 book.config.fixedType = currentState.fixedType
                 book.customCoverUrl = if (currentState.coverUrl == book.coverUrl) null else currentState.coverUrl
                 book.customCoverUrl = if (currentState.coverUrl == book.coverUrl) null else currentState.coverUrl
                 book.customIntro = if (currentState.intro == book.intro) null else currentState.intro
                 book.customIntro = if (currentState.intro == book.intro) null else currentState.intro
-                book.kind = currentState.kindList.joinToString(",")
+                book.customTag = currentState.kindList.joinToString(",").ifBlank { null }
                 BookHelp.updateCacheFolder(oldBook, book)
                 BookHelp.updateCacheFolder(oldBook, book)
 
 
                 if (ReadBook.book?.bookUrl == book.bookUrl) {
                 if (ReadBook.book?.bookUrl == book.bookUrl) {

+ 7 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt

@@ -31,6 +31,7 @@ data class BookShelfItem(
     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 customTag: String? = null,
     val wordCount: String? = null
     val wordCount: String? = null
 ) {
 ) {
     fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
     fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl
@@ -52,9 +53,12 @@ data class BookShelfItem(
      */
      */
     fun toUiItem(): BookUiItem {
     fun toUiItem(): BookUiItem {
         val tagList = mutableListOf<String>()
         val tagList = mutableListOf<String>()
-        kind?.splitNotBlank(",", "\n")?.filter { it.isNotBlank() }?.let {
+        customTag?.splitNotBlank(",", "\n")?.filter { it.isNotBlank() }?.let {
             tagList.addAll(it)
             tagList.addAll(it)
         }
         }
+        kind?.splitNotBlank(",", "\n")?.filter { it.isNotBlank() }?.let {
+            tagList.addAll(it.filterNot(tagList::contains))
+        }
         if (!wordCount.isNullOrBlank() && !tagList.contains(wordCount)) {
         if (!wordCount.isNullOrBlank() && !tagList.contains(wordCount)) {
             tagList.add(wordCount)
             tagList.add(wordCount)
         }
         }
@@ -103,5 +107,6 @@ fun BookShelfItem.toLightBook() = Book(
     order = order,
     order = order,
     canUpdate = canUpdate,
     canUpdate = canUpdate,
     wordCount = wordCount,
     wordCount = wordCount,
-    kind = kind
+    kind = kind,
+    customTag = customTag
 )
 )

+ 3 - 1
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -1056,7 +1056,9 @@ class BookshelfViewModel(
     private fun BookShelfItem.matchesSearchKey(searchKey: String): Boolean {
     private fun BookShelfItem.matchesSearchKey(searchKey: String): Boolean {
         return name.contains(searchKey, true) ||
         return name.contains(searchKey, true) ||
                 author.contains(searchKey, true) ||
                 author.contains(searchKey, true) ||
-                originName.contains(searchKey, true)
+                originName.contains(searchKey, true) ||
+                kind?.contains(searchKey, true) == true ||
+                customTag?.contains(searchKey, true) == true
     }
     }
 
 
 }
 }

+ 2 - 0
app/src/main/res/values-zh-rCN/strings.xml

@@ -2472,4 +2472,6 @@
     <string name="hidden_items">已隐藏项目</string>
     <string name="hidden_items">已隐藏项目</string>
     <string name="merged_in_multiline">多行模式下将合并展示</string>
     <string name="merged_in_multiline">多行模式下将合并展示</string>
     <string name="apply">应用</string>
     <string name="apply">应用</string>
+    <string name="source_categories">书源分类</string>
+    <string name="my_tags">我的标签</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values-zh-rHK/strings.xml

@@ -2380,4 +2380,6 @@
     <string name="collapsed_menu">折疊選單</string>
     <string name="collapsed_menu">折疊選單</string>
     <string name="hidden_items">已隱藏項目</string>
     <string name="hidden_items">已隱藏項目</string>
     <string name="merged_in_multiline">多行模式下將合併顯示</string>
     <string name="merged_in_multiline">多行模式下將合併顯示</string>
+    <string name="source_categories">書源分類</string>
+    <string name="my_tags">我的標籤</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values-zh-rTW/strings.xml

@@ -2385,4 +2385,6 @@
     <string name="collapsed_menu">折疊選單</string>
     <string name="collapsed_menu">折疊選單</string>
     <string name="hidden_items">已隱藏項目</string>
     <string name="hidden_items">已隱藏項目</string>
     <string name="merged_in_multiline">多行模式下將合併顯示</string>
     <string name="merged_in_multiline">多行模式下將合併顯示</string>
+    <string name="source_categories">書源分類</string>
+    <string name="my_tags">我的標籤</string>
 </resources>
 </resources>

+ 2 - 0
app/src/main/res/values/strings.xml

@@ -2477,4 +2477,6 @@
     <string name="hidden_items">Hidden Items</string>
     <string name="hidden_items">Hidden Items</string>
     <string name="merged_in_multiline">Merged in multiline mode</string>
     <string name="merged_in_multiline">Merged in multiline mode</string>
     <string name="apply">Apply</string>
     <string name="apply">Apply</string>
+    <string name="source_categories">Source categories</string>
+    <string name="my_tags">My tags</string>
 </resources>
 </resources>

+ 15 - 0
app/src/test/java/io/legado/app/help/book/BookExtensionsTest.kt

@@ -1,11 +1,26 @@
 package io.legado.app.help.book
 package io.legado.app.help.book
 
 
+import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.HighlightTagRule
 import io.legado.app.data.entities.HighlightTagRule
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.Test
 
 
 class BookExtensionsTest {
 class BookExtensionsTest {
 
 
+    @Test
+    fun displayTags_keepsCustomAndSourceTagsSeparateAndDeduplicated() {
+        val book = Book(
+            kind = "奇幻,连载",
+            customTag = "收藏,奇幻",
+        )
+
+        assertEquals(listOf("奇幻", "连载"), book.getSourceTagList())
+        assertEquals(listOf("收藏", "奇幻"), book.getCustomTagList())
+        assertEquals(listOf("收藏", "奇幻", "连载"), book.getDisplayTagList())
+        assertEquals("奇幻,连载", book.kind)
+        assertEquals("收藏,奇幻", book.customTag)
+    }
+
     @Test
     @Test
     fun parseHighlightedTags_ordersMatchesByRuleOrder() {
     fun parseHighlightedTags_ordersMatchesByRuleOrder() {
         val rules = listOf(
         val rules = listOf(