fix: 搜索界面问题
This commit is contained in:
5 files changed
+89
-8
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,13 +109,26 @@ fun SearchBookListItem(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
.align(Alignment.CenterVertically)
|
.align(Alignment.CenterVertically)
|
||||||
) {
|
) {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
AppText(
|
AppText(
|
||||||
text = book.name,
|
text = book.name,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
style = LegadoTheme.typography.titleSmall,
|
style = LegadoTheme.typography.titleSmall,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
overflow = TextOverflow.Ellipsis,
|
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(
|
||||||
text = book.author,
|
text = book.author,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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 & Cache</string>
|
<string name="download_cache_config">Download & Cache</string>
|
||||||
|
|||||||
Reference in new issue
Block a user