[优化] 优化书架搜索筛选逻辑,可以在书架筛选书源,现在输入关键词时回车触发搜索

This commit is contained in:
HapeLee
2026-04-23 13:21:37 +08:00
parent f8473077dc
commit 2c63332403
12 changed files with 285 additions and 82 deletions
@@ -75,6 +75,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -107,6 +108,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -136,6 +138,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -165,6 +168,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -199,6 +203,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -234,6 +239,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -264,6 +270,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -284,7 +291,9 @@ interface BookDao {
)
fun flowBookShelfByUserGroup(group: Long): Flow<List<BookShelfItem>>
@Query("SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'")
@Query(
"SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%' or originName like '%'||:key||'%'"
)
fun flowSearch(key: String): Flow<List<Book>>
@Query(
@@ -293,6 +302,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -308,7 +318,7 @@ interface BookDao {
`order`,
canUpdate
FROM books
WHERE name like '%'||:key||'%' or author like '%'||:key||'%'
WHERE name like '%'||:key||'%' or author like '%'||:key||'%' or originName like '%'||:key||'%'
"""
)
fun flowBookShelfSearch(key: String): Flow<List<BookShelfItem>>
@@ -322,6 +332,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -352,6 +363,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -381,6 +393,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -410,6 +423,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -439,6 +453,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -468,6 +483,7 @@ interface BookDao {
bookUrl,
name,
author,
originName,
coverUrl,
customCoverUrl,
durChapterTitle,
@@ -582,4 +598,4 @@ interface BookDao {
@Query("delete from books where type & ${BookType.notShelf} > 0")
fun deleteNotShelfBook()
}
}
@@ -13,6 +13,7 @@ import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource
import io.legado.app.help.config.AppConfig
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.localBook.LocalBook
import io.legado.app.model.localBook.TextFile
@@ -274,7 +275,7 @@ object BookHelp {
book: Book,
bookChapter: BookChapter,
content: String,
concurrency: Int = AppConfig.threadCount
concurrency: Int = OtherConfig.threadCount
) = coroutineScope {
flowImages(bookChapter, content).onEachParallel(concurrency) { mSrc ->
saveImage(bookSource, book, mSrc, bookChapter)
@@ -673,4 +674,4 @@ object BookHelp {
.replace(regexOther, "")
}
}
}
@@ -19,6 +19,7 @@ import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.isTrue
import io.legado.app.utils.mapAsync
import kotlinx.coroutines.ensureActive
@@ -96,7 +97,7 @@ object BookChapterList {
for (urlStr in chapterData.second) {
emit(urlStr)
}
}.mapAsync(AppConfig.threadCount) { urlStr ->
}.mapAsync(OtherConfig.threadCount) { urlStr ->
val analyzeUrl = AnalyzeUrl(
mUrl = urlStr,
source = bookSource,
@@ -285,4 +286,4 @@ object BookChapterList {
}
}
}
}
@@ -19,6 +19,7 @@ import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext
import io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setNextChapterUrl
import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.ui.config.otherConfig.OtherConfig
import io.legado.app.utils.HtmlFormatter
import io.legado.app.utils.NetworkUtils
import io.legado.app.utils.mapAsync
@@ -105,7 +106,7 @@ object BookContent {
for (urlStr in contentData.second) {
emit(urlStr)
}
}.mapAsync(AppConfig.threadCount) { urlStr ->
}.mapAsync(OtherConfig.threadCount) { urlStr ->
val analyzeUrl = AnalyzeUrl(
mUrl = urlStr,
source = bookSource,
@@ -241,7 +241,7 @@ private fun CacheScreen(
) { dismiss ->
state.groupList.forEach { group ->
RoundDropdownMenuItem(
text = "${stringResource(R.string.group)}: ${group.groupName}",
text = "${group.groupName}",
isSelected = group.groupId == state.groupId,
onClick = {
dismiss()
@@ -394,6 +394,8 @@ fun BookItem(
titleCenter: Boolean = true,
titleMaxLines: Int = 2,
coverShadow: Boolean = false,
isSearchMode: Boolean = false,
searchKey: String = "",
onClick: () -> Unit,
onLongClick: () -> Unit
) {
@@ -409,6 +411,15 @@ fun BookItem(
} else {
null
}
val matchedSourceLabel = if (
isSearchMode &&
searchKey.isNotBlank() &&
book.originName.contains(searchKey, ignoreCase = true)
) {
book.originName
} else {
null
}
BookshelfItem(
isGrid = layoutMode != 0,
@@ -425,7 +436,7 @@ fun BookItem(
modifier = modifier,
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew,
leftBottomText = bookTypeLabel
leftBottomText = matchedSourceLabel ?: bookTypeLabel
)
},
title = book.name,
@@ -9,6 +9,7 @@ data class BookShelfItem(
val bookUrl: String,
val name: String,
val author: String,
val originName: String,
val coverUrl: String?,
val customCoverUrl: String?,
val durChapterTitle: String?,
@@ -69,9 +69,7 @@ import io.legado.app.base.BaseRuleEvent
import io.legado.app.data.appDb
import io.legado.app.data.entities.BookGroup
import io.legado.app.ui.about.AppLogSheet
import io.legado.app.ui.book.cache.CacheActivity
import io.legado.app.ui.book.info.GroupSelectSheet
import io.legado.app.ui.book.search.SearchActivity
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.ThemeResolver
@@ -82,6 +80,7 @@ import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
@@ -92,7 +91,6 @@ import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
import io.legado.app.utils.readText
import io.legado.app.utils.startActivity
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -109,8 +107,10 @@ fun BookshelfScreen(
viewModel: BookshelfViewModel = koinViewModel(),
onBookClick: (BookShelfItem) -> Unit,
onBookLongClick: (BookShelfItem) -> Unit,
onNavigateToSearch: (String) -> Unit,
onNavigateToRemoteImport: () -> Unit,
onNavigateToLocalImport: () -> Unit
onNavigateToLocalImport: () -> Unit,
onNavigateToCache: (Long) -> Unit
) {
val context = LocalContext.current
val uiState by viewModel.uiState.collectAsState()
@@ -182,8 +182,8 @@ fun BookshelfScreen(
pageCount = { uiState.groups.size }
)
LaunchedEffect(uiState.groups) {
if (uiState.groups.isNotEmpty()) {
LaunchedEffect(uiState.groups, uiState.isSearch) {
if (!uiState.isSearch && uiState.groups.isNotEmpty()) {
val savedGroupId = BookshelfConfig.saveTabPosition
val savedGroupIndex = uiState.groups.indexOfFirst { it.groupId == savedGroupId }
if (savedGroupIndex >= 0 && savedGroupIndex != pagerState.currentPage) {
@@ -208,8 +208,16 @@ fun BookshelfScreen(
}
}
val currentGroupId =
val currentTabGroupId =
uiState.groups.getOrNull(pagerState.currentPage)?.groupId ?: BookGroup.IdAll
val searchGroupExists = uiState.allGroups.any { it.groupId == uiState.selectedGroupId }
val currentGroupId = if (uiState.isSearch && searchGroupExists) {
uiState.selectedGroupId
} else {
currentTabGroupId
}
val isUsingStandaloneSearchGroup = uiState.isSearch &&
uiState.groups.none { it.groupId == currentGroupId }
val currentGroupBookCountFlow = remember(currentGroupId) {
appDb.bookDao.flowBookShelfByGroup(currentGroupId).map { it.size }
}
@@ -266,28 +274,33 @@ fun BookshelfScreen(
}
}
val baseTitle = when (bookGroupStyle) {
1 -> {
uiState.groups.getOrNull(pagerState.currentPage)?.groupName
?: stringResource(R.string.bookshelf)
}
2 if uiState.groups.isNotEmpty() -> {
val currentGroupName = uiState.allGroups.firstOrNull { it.groupId == currentGroupId }?.groupName
?: uiState.groups.getOrNull(pagerState.currentPage)?.groupName
val baseTitle = when {
uiState.isSearch && bookGroupStyle == 0 -> stringResource(R.string.bookshelf)
uiState.isSearch -> currentGroupName ?: stringResource(R.string.bookshelf)
bookGroupStyle == 1 -> currentGroupName ?: stringResource(R.string.bookshelf)
bookGroupStyle == 2 && uiState.groups.isNotEmpty() -> {
if (isInFolderRoot) stringResource(R.string.bookshelf)
else uiState.groups.getOrNull(pagerState.currentPage)?.groupName
?: stringResource(R.string.bookshelf)
else currentGroupName ?: stringResource(R.string.bookshelf)
}
else -> stringResource(R.string.bookshelf)
}
val title = if (isEditMode) {
"$baseTitle ${selectedBookUrls.size} / $currentGroupBookCount"
"$currentGroupBookCount · ${selectedBookUrls.size} "
} else if (uiState.upBooksCount > 0) {
"$baseTitle (${uiState.upBooksCount})"
} else {
baseTitle
}
val subtitle = if (isEditMode) {
"${stringResource(R.string.all)}${stringResource(R.string.book_count, allGroupsBookCount)}"
if (bookGroupStyle == 0) {
"${allGroupsBookCount}"
} else {
"${currentGroupName ?: stringResource(R.string.bookshelf)} · 共${allGroupsBookCount}"
}
} else {
null
}
@@ -317,9 +330,28 @@ fun BookshelfScreen(
state = uiState,
onBackClick = if (isEditMode) exitEditMode else null,
backNavigationIcon = if (isEditMode) AppIcons.Close else AppIcons.Back,
showSearchAction = !isEditMode,
onSearchToggle = { context.startActivity<SearchActivity>() },
showSearchAction = true,
onSearchToggle = { active ->
viewModel.setSearchMode(active)
if (!active && uiState.selectedGroupId != currentTabGroupId) {
viewModel.changeGroup(currentTabGroupId)
}
},
onSearchQueryChange = { viewModel.setSearchKey(it) },
onSearchSubmit = { rawQuery ->
rawQuery.trim()
.takeIf { it.isNotEmpty() }
?.let(onNavigateToSearch)
},
searchTrailingIcon = {
if (uiState.searchKey.isNotEmpty()) {
TopBarActionButton(
onClick = { viewModel.setSearchKey("") },
imageVector = AppIcons.Close,
contentDescription = stringResource(R.string.clear)
)
}
},
topBarActions = {
if (isEditMode) {
TopBarActionButton(
@@ -393,9 +425,7 @@ fun BookshelfScreen(
onClick = {
val groupId =
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
context.startActivity<CacheActivity> {
putExtra("groupId", groupId)
}
onNavigateToCache(groupId)
dismiss()
},
leadingIcon = { Icon(Icons.Default.Download, null) }
@@ -463,11 +493,19 @@ fun BookshelfScreen(
RoundDropdownMenuItem(
text = group.groupName,
onClick = {
if (uiState.isSearch) {
viewModel.changeGroup(group.groupId)
}
scope.launch { pagerState.animateScrollToPage(index) }
dismiss()
},
trailingIcon = {
if (selectedTabIndex == index) {
val isSelected = if (uiState.isSearch) {
uiState.selectedGroupId == group.groupId
} else {
selectedTabIndex == index
}
if (isSelected) {
Icon(
Icons.Default.Check,
null,
@@ -477,6 +515,59 @@ fun BookshelfScreen(
}
)
}
if (uiState.isSearch) {
val allGroup = uiState.allGroups.firstOrNull {
it.groupId == BookGroup.IdAll
}
val hiddenGroups = uiState.allGroups.filter {
!it.show && it.groupId != BookGroup.IdAll
}
if (allGroup != null || hiddenGroups.isNotEmpty()) {
PillHeaderDivider(
title = "${stringResource(R.string.all)} / ${stringResource(R.string.hide)}"
)
allGroup?.let { group ->
RoundDropdownMenuItem(
text = group.groupName,
onClick = {
viewModel.changeGroup(group.groupId)
dismiss()
},
trailingIcon = {
if (uiState.selectedGroupId == group.groupId) {
Icon(
Icons.Default.Check,
null,
modifier = Modifier.size(18.dp)
)
}
}
)
}
hiddenGroups.forEach { group ->
RoundDropdownMenuItem(
text = group.groupName,
onClick = {
viewModel.changeGroup(group.groupId)
dismiss()
},
trailingIcon = {
if (uiState.selectedGroupId == group.groupId) {
Icon(
Icons.Default.Check,
null,
modifier = Modifier.size(18.dp)
)
}
}
)
}
}
}
}
}
}
@@ -487,7 +578,11 @@ fun BookshelfScreen(
) { paddingValues ->
var isRefreshing by remember { mutableStateOf(false) }
val pullToRefreshState = rememberPullToRefreshState()
val currentGroup = uiState.groups.getOrNull(pagerState.currentPage)
val currentGroup = if (uiState.isSearch) {
uiState.allGroups.firstOrNull { it.groupId == currentGroupId }
} else {
uiState.groups.getOrNull(pagerState.currentPage)
}
val pullToRefreshEnabled = (currentGroup?.enableRefresh ?: true) && !isEditMode
Box(
@@ -511,7 +606,7 @@ fun BookshelfScreen(
targetState = isInFolderRoot,
label = "FolderTransition"
) { isRoot ->
if (bookGroupStyle == 2 && isRoot) {
if (bookGroupStyle == 2 && isRoot && !isUsingStandaloneSearchGroup) {
val folderColumns =
if (bookshelfLayoutMode == 0) bookshelfLayoutList else bookshelfLayoutGrid
val isGridMode = bookshelfLayoutMode != 0
@@ -576,32 +671,50 @@ fun BookshelfScreen(
}
}
} else {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize(),
beyondViewportPageCount = 3,
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
) { pageIndex ->
val group = uiState.groups.getOrNull(pageIndex)
if (group != null) {
val booksFlow = remember(group.groupId) {
viewModel.getBooksFlow(group.groupId)
}
val books by booksFlow.collectAsState(emptyList())
BookshelfPage(
paddingValues = paddingValues,
books = books,
uiState = uiState,
bookshelfLayoutMode = bookshelfLayoutMode,
bookshelfLayoutGrid = bookshelfLayoutGrid,
if (isUsingStandaloneSearchGroup) {
BookshelfPage(
paddingValues = paddingValues,
books = uiState.items,
uiState = uiState,
bookshelfLayoutMode = bookshelfLayoutMode,
bookshelfLayoutGrid = bookshelfLayoutGrid,
bookshelfLayoutList = bookshelfLayoutList,
isEditMode = isEditMode,
selectedBookUrls = selectedBookUrls,
onToggleBookSelection = { toggleBookSelection(it.bookUrl) },
onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
onBookClick = onBookClick,
onBookLongClick = onBookLongClick
)
} else {
HorizontalPager(
state = pagerState,
modifier = Modifier.fillMaxSize(),
beyondViewportPageCount = 3,
key = { if (it < uiState.groups.size) uiState.groups[it].groupId else it }
) { pageIndex ->
val group = uiState.groups.getOrNull(pageIndex)
if (group != null) {
val booksFlow = remember(group.groupId) {
viewModel.getBooksFlow(group.groupId)
}
val books by booksFlow.collectAsState(emptyList())
BookshelfPage(
paddingValues = paddingValues,
books = books,
uiState = uiState,
bookshelfLayoutMode = bookshelfLayoutMode,
bookshelfLayoutGrid = bookshelfLayoutGrid,
bookshelfLayoutList = bookshelfLayoutList,
isEditMode = isEditMode,
selectedBookUrls = selectedBookUrls,
onToggleBookSelection = { toggleBookSelection(it.bookUrl) },
onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
onBookClick = onBookClick,
onBookLongClick = onBookLongClick
)
}
}
}
}
}
@@ -716,19 +829,34 @@ fun BookshelfPage(
isEditMode: Boolean,
selectedBookUrls: Set<String>,
onToggleBookSelection: (BookShelfItem) -> Unit,
onGlobalSearch: () -> Unit,
onBookClick: (BookShelfItem) -> Unit,
onBookLongClick: (BookShelfItem) -> Unit
) {
if (books.isEmpty()) {
EmptyMessage(
modifier = Modifier
.fillMaxSize()
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
),
messageResId = R.string.bookshelf_empty
)
if (uiState.isSearch) {
EmptyMessage(
modifier = Modifier
.fillMaxSize()
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
),
message = "没有书籍,尝试全局搜索",
buttonText = "全局搜索",
onButtonClick = onGlobalSearch
)
} else {
EmptyMessage(
modifier = Modifier
.fillMaxSize()
.padding(
top = paddingValues.calculateTopPadding(),
bottom = paddingValues.calculateBottomPadding()
),
messageResId = R.string.bookshelf_empty
)
}
return
}
@@ -766,6 +894,8 @@ fun BookshelfPage(
titleCenter = BookshelfConfig.bookshelfTitleCenter,
titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
coverShadow = BookshelfConfig.bookshelfCoverShadow,
isSearchMode = uiState.isSearch,
searchKey = uiState.searchKey,
onClick = {
if (isEditMode) {
onToggleBookSelection(book)
@@ -10,9 +10,11 @@ data class BookshelfUiState(
override val isSearch: Boolean = false,
override val isLoading: Boolean = false,
val groups: List<BookGroup> = emptyList(),
val allGroups: List<BookGroup> = emptyList(),
val groupPreviews: Map<Long, List<BookShelfItem>> = emptyMap(),
val groupBookCounts: Map<Long, Int> = emptyMap(),
val selectedGroupIndex: Int = 0,
val selectedGroupId: Long = BookGroup.IdAll,
val loadingText: String? = null,
val upBooksCount: Int = 0,
val updatingBooks: Set<String> = emptySet()
@@ -97,6 +97,7 @@ class BookshelfViewModel(
private val groupIdFlow = MutableStateFlow(BookshelfConfig.saveTabPosition)
private val searchKeyFlow = MutableStateFlow("")
private val searchModeFlow = MutableStateFlow(false)
private val refreshTrigger = MutableStateFlow(0)
private val loadingTextFlow = MutableStateFlow<String?>(null)
@@ -207,19 +208,27 @@ class BookshelfViewModel(
}
}.distinctUntilChanged().flowOn(Dispatchers.Default)
private val internalStateFlow = combine(
private val coreInternalStateFlow = combine(
groupIdFlow,
searchKeyFlow,
searchModeFlow,
loadingTextFlow,
updatingBooksFlow,
updatingBooksFlow
) { groupId, searchKey, isSearchMode, loadingText, updatingBooks ->
InternalState(groupId, searchKey, isSearchMode, loadingText, updatingBooks, 0)
}
private val internalStateFlow = combine(
coreInternalStateFlow,
upBooksCountFlow
) { groupId, searchKey, loadingText, updatingBooks, upBooksCount ->
InternalState(groupId, searchKey, loadingText, updatingBooks, upBooksCount)
) { core, upBooksCount ->
core.copy(upBooksCount = upBooksCount)
}
data class InternalState(
val groupId: Long,
val searchKey: String,
val isSearchMode: Boolean,
val loadingText: String?,
val updatingBooks: Set<String>,
val upBooksCount: Int
@@ -228,29 +237,27 @@ class BookshelfViewModel(
val uiState: StateFlow<BookshelfUiState> = combine(
booksFlow,
groupsFlow,
allGroupsFlow,
groupPreviewsFlow,
internalStateFlow
) { books, groups, previews, internal ->
val filteredBooks = if (internal.searchKey.isEmpty()) {
) { books, groups, allGroups, previews, internal ->
val filteredBooks = if (!internal.isSearchMode || internal.searchKey.isBlank()) {
books
} else {
books.filter {
it.name.contains(
internal.searchKey,
true
) || it.author.contains(internal.searchKey, true)
}
books.filter { it.matchesSearchKey(internal.searchKey) }
}
BookshelfUiState(
items = filteredBooks,
groups = groups,
allGroups = allGroups,
groupPreviews = previews.previews,
groupBookCounts = previews.counts,
selectedGroupIndex = groups.indexOfFirst { it.groupId == internal.groupId }
.coerceAtLeast(0),
selectedGroupId = internal.groupId,
searchKey = internal.searchKey,
isSearch = internal.searchKey.isNotEmpty(),
isSearch = internal.isSearchMode,
isLoading = internal.loadingText != null,
loadingText = internal.loadingText,
upBooksCount = internal.upBooksCount,
@@ -333,16 +340,15 @@ class BookshelfViewModel(
return combine(
appDb.bookDao.flowBookShelfByGroup(groupId),
searchKeyFlow,
searchModeFlow,
groupsFlow,
refreshTrigger
) { books, searchKey, groups, _ ->
) { books, searchKey, isSearchMode, groups, _ ->
val group = groups.find { it.groupId == groupId }
val filtered = if (searchKey.isEmpty()) {
val filtered = if (!isSearchMode || searchKey.isBlank()) {
books
} else {
books.filter {
it.name.contains(searchKey, true) || it.author.contains(searchKey, true)
}
books.filter { it.matchesSearchKey(searchKey) }
}
sortBooks(filtered, group)
}.distinctUntilChanged().flowOn(Dispatchers.Default)
@@ -359,6 +365,13 @@ class BookshelfViewModel(
searchKeyFlow.value = key
}
fun setSearchMode(active: Boolean) {
searchModeFlow.value = active
if (!active) {
searchKeyFlow.value = ""
}
}
fun refresh() {
refreshTrigger.value++
}
@@ -764,4 +777,10 @@ class BookshelfViewModel(
}
}
private fun BookShelfItem.matchesSearchKey(searchKey: String): Boolean {
return name.contains(searchKey, true) ||
author.contains(searchKey, true) ||
originName.contains(searchKey, true)
}
}
@@ -18,12 +18,15 @@ import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.button.SmallTonalTextButton
import io.legado.app.ui.widget.components.text.AnimatedTextLine
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@@ -32,6 +35,9 @@ fun EmptyMessage(
message: String,
modifier: Modifier = Modifier,
isLoading: Boolean = false,
buttonText: String? = null,
buttonImageVector: ImageVector = AppIcons.Search,
onButtonClick: (() -> Unit)? = null,
faces: List<String> = listOf(
"(;′⌒`)", "(つ﹏⊂)", "(•̀ᴗ•́)و", "(๑•́ ₃ •̀๑)",
"(눈‸눈)", "(ಥ﹏ಥ)", "(。•́︿•̀。)"
@@ -77,6 +83,15 @@ fun EmptyMessage(
softWrap = true,
modifier = Modifier.widthIn(max = 240.dp)
)
if (buttonText != null && onButtonClick != null) {
Spacer(modifier = Modifier.height(8.dp))
SmallTonalTextButton(
onClick = onButtonClick,
text = buttonText,
imageVector = buttonImageVector
)
}
}
}
@@ -85,6 +100,9 @@ fun EmptyMessage(
@StringRes messageResId: Int,
modifier: Modifier = Modifier,
isLoading: Boolean = false,
buttonText: String? = null,
buttonImageVector: ImageVector = AppIcons.Search,
onButtonClick: (() -> Unit)? = null,
faces: List<String> = listOf(
"(;′⌒`)", "(つ﹏⊂)", "(•̀ᴗ•́)و", "(๑•́ ₃ •̀๑)",
"(눈‸눈)", "(ಥ﹏ಥ)", "(。•́︿•̀。)"
@@ -97,8 +115,11 @@ fun EmptyMessage(
message = message,
modifier = modifier,
isLoading = isLoading,
buttonText = buttonText,
buttonImageVector = buttonImageVector,
onButtonClick = onButtonClick,
faces = faces,
faceTextSize = faceTextSize,
onFaceClick = onFaceClick
)
}
}
@@ -169,7 +169,7 @@ fun RoundDropdownMenuItem(
Text(
modifier = Modifier.widthIn(max = 200.dp),
text = text,
style = LegadoTheme.typography.bodyMediumEmphasized,
style = LegadoTheme.typography.labelLargeEmphasized,
color = contentColor
)
}