fix: 换源功能多项修复

- getDbSearchBooks: 使用 SearchScope 解析分组,多分组回退全部查询,加入 checkAuthor 判断
- searchSource: 移除 per-book try-catch,出错书源整体跳过
- 单章换源: 添加 getDbSearchBooks 历史搜索数据支持
- 单章换源: 显示更多信息启用时按字数排序
- 单章换源: 分组选择改为确认后再搜索(draft selection 模式)
- 两个换源界面: 搜索开始前预设 totalSourceCount
This commit is contained in:
HapeLee committed 2026-06-15 23:41:21 +08:00
1 parent ae56520750
commit 6dded05b20
4 files changed
+84 -21

No files matched your search

@@ -151,7 +151,6 @@ class ChangeSourceSearchUseCase(
when { when {
loadInfo || loadToc || loadWordCount -> { loadInfo || loadToc || loadWordCount -> {
val book = searchBook.toBook() val book = searchBook.toBook()
try {
val wordCountSearchBook = loadBookInfo( val wordCountSearchBook = loadBookInfo(
source, source,
book, book,
@@ -162,10 +161,6 @@ class ChangeSourceSearchUseCase(
contentProcessor contentProcessor
) )
processedBooks.add(wordCountSearchBook ?: book.toSearchBook()) processedBooks.add(wordCountSearchBook ?: book.toSearchBook())
} catch (e: Throwable) {
if (e is CancellationException) throw e
processedBooks.add(searchBook)
}
} }
else -> { else -> {
@@ -108,14 +108,22 @@ class ChangeBookSourceComposeViewModel(
} }
private fun getDbSearchBooks(): List<SearchBook> { private fun getDbSearchBooks(): List<SearchBook> {
val scopeRaw = ChangeSourceConfig.searchScope val searchScope = SearchScope(ChangeSourceConfig.searchScope)
val group = when {
searchScope.isAll() || searchScope.isSource() -> ""
else -> {
val names = searchScope.displayNames
if (names.size == 1) names.first() else ""
}
}
val bookAuthor = if (checkAuthor) bookAuthor else ""
return if (screenKey.isEmpty()) { return if (screenKey.isEmpty()) {
io.legado.app.data.appDb.searchBookDao.changeSourceByGroup( io.legado.app.data.appDb.searchBookDao.changeSourceByGroup(
bookName, bookAuthor, scopeRaw bookName, bookAuthor, group
) )
} else { } else {
io.legado.app.data.appDb.searchBookDao.changeSourceSearch( io.legado.app.data.appDb.searchBookDao.changeSourceSearch(
bookName, bookAuthor, screenKey, scopeRaw bookName, bookAuthor, screenKey, group
) )
} }
} }
@@ -128,7 +136,7 @@ class ChangeBookSourceComposeViewModel(
searchResults.clear() searchResults.clear()
bookMap.clear() bookMap.clear()
tocMap.clear() tocMap.clear()
totalSourceCount = 0 totalSourceCount = searchScope.getBookSourceParts().size
_changeSourceProgress.value = 0 to "" _changeSourceProgress.value = 0 to ""
_searchDataFlow.value = emptyList() _searchDataFlow.value = emptyList()
@@ -33,6 +33,7 @@ class ChangeChapterSourceViewModel(
val effects = _effects.asSharedFlow() val effects = _effects.asSharedFlow()
// Internal state // Internal state
private val chapterNumRegex = "^\\[(\\d+)]".toRegex()
private var searchJob: Job? = null private var searchJob: Job? = null
private var oldBook: Book? = null private var oldBook: Book? = null
private var chapterIndex: Int = 0 private var chapterIndex: Int = 0
@@ -82,8 +83,43 @@ class ChangeChapterSourceViewModel(
selectedSourceName = "", selectedSourceName = "",
) )
} }
if (searchJob?.isActive != true) {
viewModelScope.launch {
val dbBooks = getDbSearchBooks()
if (dbBooks.isNotEmpty()) {
searchResults.clear()
searchResults.addAll(dbBooks)
searchResults.forEach { bookMap[it.primaryStr()] = it }
filterResults()
} else {
startSearch() startSearch()
} }
}
}
}
private fun getDbSearchBooks(): List<SearchBook> {
val name = oldBook?.name ?: return emptyList()
val author = oldBook?.author ?: return emptyList()
val searchScope = SearchScope(ChangeSourceConfig.searchScope)
val group = when {
searchScope.isAll() || searchScope.isSource() -> ""
else -> {
val names = searchScope.displayNames
if (names.size == 1) names.first() else ""
}
}
val bookAuthor = if (ChangeSourceConfig.checkAuthor) author else ""
return if (screenKey.isEmpty()) {
io.legado.app.data.appDb.searchBookDao.changeSourceByGroup(
name, bookAuthor, group
)
} else {
io.legado.app.data.appDb.searchBookDao.changeSourceSearch(
name, bookAuthor, screenKey, group
)
}
}
fun onIntent(intent: ChangeChapterSourceIntent) { fun onIntent(intent: ChangeChapterSourceIntent) {
when (intent) { when (intent) {
@@ -182,7 +218,7 @@ class ChangeChapterSourceViewModel(
is ChangeChapterSourceIntent.SelectAllScope -> { is ChangeChapterSourceIntent.SelectAllScope -> {
searchScope.update("") searchScope.update("")
saveScope() updateScopeState()
} }
is ChangeChapterSourceIntent.ToggleScopeGroup -> { is ChangeChapterSourceIntent.ToggleScopeGroup -> {
@@ -196,7 +232,7 @@ class ChangeChapterSourceViewModel(
selected.add(intent.groupName) selected.add(intent.groupName)
} }
searchScope.update(selected.toList()) searchScope.update(selected.toList())
saveScope() updateScopeState()
} }
is ChangeChapterSourceIntent.ToggleScopeSource -> { is ChangeChapterSourceIntent.ToggleScopeSource -> {
@@ -219,11 +255,12 @@ class ChangeChapterSourceViewModel(
} }
searchScope.updateSources(selectedSources) searchScope.updateSources(selectedSources)
} }
saveScope() updateScopeState()
} }
is ChangeChapterSourceIntent.ApplyScope -> { is ChangeChapterSourceIntent.ApplyScope -> {
startSearch() ChangeSourceConfig.searchScope = searchScope.toString()
refreshResults()
} }
} }
} }
@@ -233,6 +270,13 @@ class ChangeChapterSourceViewModel(
stopSearch() stopSearch()
searchResults.clear() searchResults.clear()
bookMap.clear() bookMap.clear()
val scope = SearchScope(ChangeSourceConfig.searchScope)
_uiState.update {
it.copy(
totalSourceCount = scope.getBookSourceParts().size,
searchProgress = 0 to "",
)
}
filterResults() filterResults()
searchJob = viewModelScope.launch { searchJob = viewModelScope.launch {
@@ -284,8 +328,15 @@ class ChangeChapterSourceViewModel(
private fun refreshResults() { private fun refreshResults() {
searchResults.clear() searchResults.clear()
bookMap.clear() bookMap.clear()
val dbBooks = getDbSearchBooks()
if (dbBooks.isNotEmpty()) {
searchResults.addAll(dbBooks)
searchResults.forEach { bookMap[it.primaryStr()] = it }
filterResults()
} else {
startSearch() startSearch()
} }
}
private fun filterResults() { private fun filterResults() {
val filtered = if (screenKey.isEmpty()) { val filtered = if (screenKey.isEmpty()) {
@@ -295,20 +346,31 @@ class ChangeChapterSourceViewModel(
it.name.contains(screenKey) || it.originName.contains(screenKey) it.name.contains(screenKey) || it.originName.contains(screenKey)
} }
} }
// Sort by score val comparator = if (_uiState.value.loadWordCount) {
val sorted = filtered.sortedWith( compareByDescending<SearchBook> { ObservableSourceConfig.getBookScore(it) }
.thenByDescending { io.legado.app.help.config.SourceConfig.getSourceScore(it.origin) }
.thenByDescending { it.chapterWordCount > 1000 }
.thenByDescending { getChapterNum(it.chapterWordCountText) }
.thenByDescending { it.chapterWordCount }
.thenBy { it.originOrder }
} else {
compareByDescending<SearchBook> { ObservableSourceConfig.getBookScore(it) } compareByDescending<SearchBook> { ObservableSourceConfig.getBookScore(it) }
.thenByDescending { io.legado.app.help.config.SourceConfig.getSourceScore(it.origin) } .thenByDescending { io.legado.app.help.config.SourceConfig.getSourceScore(it.origin) }
.thenBy { it.originOrder } .thenBy { it.originOrder }
) }
_uiState.update { _uiState.update {
it.copy( it.copy(
searchResults = sorted.toImmutableList(), searchResults = filtered.sortedWith(comparator).toImmutableList(),
bookMap = bookMap.toMap() bookMap = bookMap.toMap()
) )
} }
} }
private fun getChapterNum(text: String?): Int {
if (text.isNullOrBlank()) return 0
return chapterNumRegex.find(text)?.groupValues?.get(1)?.toIntOrNull() ?: 0
}
private fun selectSource(searchBook: SearchBook) { private fun selectSource(searchBook: SearchBook) {
val book = searchBook.toBook() val book = searchBook.toBook()
_uiState.update { _uiState.update {
@@ -376,8 +438,7 @@ class ChangeChapterSourceViewModel(
} }
} }
private fun saveScope() { private fun updateScopeState() {
ChangeSourceConfig.searchScope = searchScope.toString()
_uiState.update { _uiState.update {
it.copy( it.copy(
scopeState = ScopeUiState( scopeState = ScopeUiState(
@@ -388,7 +449,6 @@ class ChangeChapterSourceViewModel(
) )
) )
} }
refreshResults()
} }
fun bookScoreFlow(searchBook: SearchBook) = ObservableSourceConfig.bookScoreFlow(searchBook) fun bookScoreFlow(searchBook: SearchBook) = ObservableSourceConfig.bookScoreFlow(searchBook)
@@ -156,7 +156,7 @@ fun ChangeChapterSourceSheet(
selectedSources = state.scopeState.sourceUrls, selectedSources = state.scopeState.sourceUrls,
onToggleSource = { onIntent(ChangeChapterSourceIntent.ToggleScopeSource(it)) }, onToggleSource = { onIntent(ChangeChapterSourceIntent.ToggleScopeSource(it)) },
isSourceScope = state.scopeState.isSource, isSourceScope = state.scopeState.isSource,
onConfirm = { onApplyScope = { selection ->
onIntent(ChangeChapterSourceIntent.ApplyScope) onIntent(ChangeChapterSourceIntent.ApplyScope)
showFilterSheet = false showFilterSheet = false
} }