fix: 搜索界面问题
This commit is contained in:
@@ -509,7 +509,8 @@ fun SearchScreen(
|
||||
},
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
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 searchScope = SearchScope(AppConfig.searchScope)
|
||||
private val searchControl = BookSearchControl()
|
||||
private val searchResultBooks = LinkedHashMap<String, SearchBook>()
|
||||
private val searchResultBooks = LinkedHashMap<SearchResultKey, SearchBook>()
|
||||
|
||||
private var searchJob: Job? = null
|
||||
private var currentSearchPage = 1
|
||||
private var resultCountBeforeCurrentPage = 0
|
||||
private var wasSearching = false
|
||||
|
||||
init {
|
||||
@@ -483,6 +484,7 @@ class SearchViewModel(
|
||||
private fun startSearch(keyword: String, page: Int) {
|
||||
searchJob?.cancel()
|
||||
searchControl.resume()
|
||||
resultCountBeforeCurrentPage = searchResultBooks.size
|
||||
wasSearching = true
|
||||
searchJob = viewModelScope.launch {
|
||||
try {
|
||||
@@ -517,10 +519,8 @@ class SearchViewModel(
|
||||
}
|
||||
|
||||
is SearchRunEvent.Progress -> {
|
||||
event.removedBookUrls.forEach { searchResultBooks.remove(it) }
|
||||
event.upsertBooks.forEach { book ->
|
||||
searchResultBooks[book.bookUrl] = book
|
||||
}
|
||||
removeSearchResults(event.removedBookUrls)
|
||||
mergeSearchResults(event.upsertBooks)
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
results = buildSearchResultItems(
|
||||
@@ -542,9 +542,16 @@ class SearchViewModel(
|
||||
} else {
|
||||
null
|
||||
}
|
||||
val hasNewPageResults = searchResultBooks.size > resultCountBeforeCurrentPage
|
||||
val exactSearchShouldStopAfterFirstPage =
|
||||
state.matchMode == MatchMode.EXACT &&
|
||||
currentSearchPage == 1 &&
|
||||
searchResultBooks.size <= EXACT_SEARCH_SINGLE_PAGE_RESULT_THRESHOLD
|
||||
state.copy(
|
||||
isSearching = false,
|
||||
hasMore = event.hasMore,
|
||||
hasMore = event.hasMore &&
|
||||
hasNewPageResults &&
|
||||
!exactSearchShouldStopAfterFirstPage,
|
||||
emptyScopeAction = emptyAction,
|
||||
)
|
||||
}
|
||||
@@ -697,7 +704,9 @@ class SearchViewModel(
|
||||
private fun buildSearchResultItems(
|
||||
shelf: Set<BookShelfKey>,
|
||||
): 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(
|
||||
@@ -749,4 +758,57 @@ class SearchViewModel(
|
||||
private fun emitEffect(effect: SearchEffect) {
|
||||
_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.draw.clip
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.font.FontWeight
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import io.legado.app.R
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.domain.model.BookShelfState
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
@@ -53,6 +55,7 @@ fun SearchBookListItem(
|
||||
sharedTransitionScope: SharedTransitionScope? = null,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope? = null,
|
||||
sharedCoverKey: String? = null,
|
||||
sourceCount: Int? = null,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
@@ -106,12 +109,25 @@ fun SearchBookListItem(
|
||||
.weight(1f)
|
||||
.align(Alignment.CenterVertically)
|
||||
) {
|
||||
AppText(
|
||||
text = book.name,
|
||||
style = LegadoTheme.typography.titleSmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
AppText(
|
||||
text = book.name,
|
||||
modifier = Modifier.weight(1f),
|
||||
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 {
|
||||
AppText(
|
||||
|
||||
@@ -1724,6 +1724,7 @@
|
||||
<string name="search_empty_scope_switch_all">%1$s分组搜索结果为空,是否切换到全部分组?</string>
|
||||
<string name="search_type">搜索类型</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_source_grouped">来源分组</string>
|
||||
<string name="download_cache_config">下载与缓存</string>
|
||||
|
||||
@@ -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_type">Search type</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_source_grouped">Source grouped</string>
|
||||
<string name="download_cache_config">Download & Cache</string>
|
||||
|
||||
Reference in New Issue
Block a user