fix: 搜索界面问题

This commit is contained in:
HapeLee committed 2026-06-14 00:21:14 +08:00
1 parent 318bef3649
commit fc096c92e8
5 files changed
+95 -14

No files matched your search

@@ -509,7 +509,8 @@ fun SearchScreen(
}, },
sharedTransitionScope = sharedTransitionScope, sharedTransitionScope = sharedTransitionScope,
animatedVisibilityScope = animatedVisibilityScope, animatedVisibilityScope = animatedVisibilityScope,
sharedCoverKey = sharedCoverKey sharedCoverKey = sharedCoverKey,
sourceCount = item.book.origins.size,
) )
} }
@@ -84,10 +84,11 @@ class SearchViewModel(
private val bookshelfKeys = MutableStateFlow<Set<BookShelfKey>>(emptySet()) private val bookshelfKeys = MutableStateFlow<Set<BookShelfKey>>(emptySet())
private val searchScope = SearchScope(AppConfig.searchScope) private val searchScope = SearchScope(AppConfig.searchScope)
private val searchControl = BookSearchControl() private val searchControl = BookSearchControl()
private val searchResultBooks = LinkedHashMap<String, SearchBook>() private val searchResultBooks = LinkedHashMap<SearchResultKey, SearchBook>()
private var searchJob: Job? = null private var searchJob: Job? = null
private var currentSearchPage = 1 private var currentSearchPage = 1
private var resultCountBeforeCurrentPage = 0
private var wasSearching = false private var wasSearching = false
init { init {
@@ -483,6 +484,7 @@ class SearchViewModel(
private fun startSearch(keyword: String, page: Int) { private fun startSearch(keyword: String, page: Int) {
searchJob?.cancel() searchJob?.cancel()
searchControl.resume() searchControl.resume()
resultCountBeforeCurrentPage = searchResultBooks.size
wasSearching = true wasSearching = true
searchJob = viewModelScope.launch { searchJob = viewModelScope.launch {
try { try {
@@ -517,10 +519,8 @@ class SearchViewModel(
} }
is SearchRunEvent.Progress -> { is SearchRunEvent.Progress -> {
event.removedBookUrls.forEach { searchResultBooks.remove(it) } removeSearchResults(event.removedBookUrls)
event.upsertBooks.forEach { book -> mergeSearchResults(event.upsertBooks)
searchResultBooks[book.bookUrl] = book
}
_uiState.update { _uiState.update {
it.copy( it.copy(
results = buildSearchResultItems( results = buildSearchResultItems(
@@ -542,9 +542,16 @@ class SearchViewModel(
} else { } else {
null null
} }
val hasNewPageResults = searchResultBooks.size > resultCountBeforeCurrentPage
val exactSearchShouldStopAfterFirstPage =
state.matchMode == MatchMode.EXACT &&
currentSearchPage == 1 &&
searchResultBooks.size <= EXACT_SEARCH_SINGLE_PAGE_RESULT_THRESHOLD
state.copy( state.copy(
isSearching = false, isSearching = false,
hasMore = event.hasMore, hasMore = event.hasMore &&
hasNewPageResults &&
!exactSearchShouldStopAfterFirstPage,
emptyScopeAction = emptyAction, emptyScopeAction = emptyAction,
) )
} }
@@ -697,7 +704,9 @@ class SearchViewModel(
private fun buildSearchResultItems( private fun buildSearchResultItems(
shelf: Set<BookShelfKey>, shelf: Set<BookShelfKey>,
): List<SearchResultItemUi> { ): List<SearchResultItemUi> {
return searchResultBooks.values.toList().toSearchResultItems(shelf) return searchResultBooks.values
.sortedWithSearchPriority(_uiState.value.committedQuery, _uiState.value.matchMode)
.toSearchResultItems(shelf)
} }
private fun List<SearchResultItemUi>.withShelfState( private fun List<SearchResultItemUi>.withShelfState(
@@ -749,4 +758,57 @@ class SearchViewModel(
private fun emitEffect(effect: SearchEffect) { private fun emitEffect(effect: SearchEffect) {
_effects.tryEmit(effect) _effects.tryEmit(effect)
} }
private fun mergeSearchResults(books: List<SearchBook>) {
books.forEach { book ->
val key = SearchResultKey(book.name, book.author)
val currentBook = searchResultBooks[key]
if (currentBook == null) {
searchResultBooks[key] = book
} else {
book.origins.forEach { origin -> currentBook.addOrigin(origin) }
}
}
}
private fun removeSearchResults(bookUrls: List<String>) {
if (bookUrls.isEmpty()) return
val removedUrls = bookUrls.toHashSet()
searchResultBooks.entries.removeAll { (_, book) -> book.bookUrl in removedUrls }
}
private fun Collection<SearchBook>.sortedWithSearchPriority(
keyword: String,
matchMode: MatchMode,
): List<SearchBook> {
val equalBooks = arrayListOf<SearchBook>()
val tagsBooks = arrayListOf<SearchBook>()
val containsBooks = arrayListOf<SearchBook>()
val otherBooks = arrayListOf<SearchBook>()
forEach { book ->
when {
book.name.equals(keyword, ignoreCase = true) ||
book.author.equals(keyword, ignoreCase = true) -> equalBooks.add(book)
book.kind?.contains(keyword, ignoreCase = true) == true -> tagsBooks.add(book)
book.name.contains(keyword, ignoreCase = true) ||
book.author.contains(keyword, ignoreCase = true) -> containsBooks.add(book)
matchMode == MatchMode.DEFAULT -> otherBooks.add(book)
}
}
return buildList(size) {
addAll(equalBooks.sortedByDescending { it.origins.size })
addAll(tagsBooks.sortedByDescending { it.origins.size })
addAll(containsBooks.sortedByDescending { it.origins.size })
addAll(otherBooks)
}
}
private data class SearchResultKey(
val name: String,
val author: String,
)
private companion object {
const val EXACT_SEARCH_SINGLE_PAGE_RESULT_THRESHOLD = 3
}
} }
@@ -28,9 +28,11 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.domain.model.BookShelfState import io.legado.app.domain.model.BookShelfState
import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme
@@ -53,6 +55,7 @@ fun SearchBookListItem(
sharedTransitionScope: SharedTransitionScope? = null, sharedTransitionScope: SharedTransitionScope? = null,
animatedVisibilityScope: AnimatedVisibilityScope? = null, animatedVisibilityScope: AnimatedVisibilityScope? = null,
sharedCoverKey: String? = null, sharedCoverKey: String? = null,
sourceCount: Int? = null,
) { ) {
Row( Row(
modifier = modifier modifier = modifier
@@ -106,12 +109,25 @@ fun SearchBookListItem(
.weight(1f) .weight(1f)
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
) { ) {
AppText( Row(verticalAlignment = Alignment.CenterVertically) {
text = book.name, AppText(
style = LegadoTheme.typography.titleSmall, text = book.name,
maxLines = 1, modifier = Modifier.weight(1f),
overflow = TextOverflow.Ellipsis, style = LegadoTheme.typography.titleSmall,
) maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
sourceCount?.let { count ->
Spacer(modifier = Modifier.width(8.dp))
TextCard(
text = stringResource(R.string.search_book_source_count, count),
cornerRadius = 4.dp,
horizontalPadding = 4.dp,
verticalPadding = 2.dp
)
}
}
Row { Row {
AppText( AppText(
@@ -1724,6 +1724,7 @@
<string name="search_empty_scope_switch_all">%1$s分组搜索结果为空,是否切换到全部分组?</string> <string name="search_empty_scope_switch_all">%1$s分组搜索结果为空,是否切换到全部分组?</string>
<string name="search_type">搜索类型</string> <string name="search_type">搜索类型</string>
<string name="search_has_more">上拉或点击按钮加载更多</string> <string name="search_has_more">上拉或点击按钮加载更多</string>
<string name="search_book_source_count">%1$d</string>
<string name="search_layout_list">列表布局</string> <string name="search_layout_list">列表布局</string>
<string name="search_layout_source_grouped">来源分组</string> <string name="search_layout_source_grouped">来源分组</string>
<string name="download_cache_config">下载与缓存</string> <string name="download_cache_config">下载与缓存</string>
+1
View File
@@ -1760,6 +1760,7 @@
<string name="search_empty_scope_switch_all">%1$s group search returned no results. Switch to all groups?</string> <string name="search_empty_scope_switch_all">%1$s group search returned no results. Switch to all groups?</string>
<string name="search_type">Search type</string> <string name="search_type">Search type</string>
<string name="search_has_more">Pull up or tap button to load more</string> <string name="search_has_more">Pull up or tap button to load more</string>
<string name="search_book_source_count">%1$d</string>
<string name="search_layout_list">List layout</string> <string name="search_layout_list">List layout</string>
<string name="search_layout_source_grouped">Source grouped</string> <string name="search_layout_source_grouped">Source grouped</string>
<string name="download_cache_config">Download &amp; Cache</string> <string name="download_cache_config">Download &amp; Cache</string>