diff --git a/app/src/main/java/io/legado/app/data/dao/BookDao.kt b/app/src/main/java/io/legado/app/data/dao/BookDao.kt index 7a3179040..51d0e741c 100644 --- a/app/src/main/java/io/legado/app/data/dao/BookDao.kt +++ b/app/src/main/java/io/legado/app/data/dao/BookDao.kt @@ -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> - @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> @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> @@ -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() -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/help/book/BookHelp.kt b/app/src/main/java/io/legado/app/help/book/BookHelp.kt index 2bfbb9b20..887eb1940 100644 --- a/app/src/main/java/io/legado/app/help/book/BookHelp.kt +++ b/app/src/main/java/io/legado/app/help/book/BookHelp.kt @@ -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, "") } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt index 01ad6e9ca..d74c0b5ef 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt @@ -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 { } } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt index 0ae753654..68cd2ecfc 100644 --- a/app/src/main/java/io/legado/app/model/webBook/BookContent.kt +++ b/app/src/main/java/io/legado/app/model/webBook/BookContent.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheScreen.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheScreen.kt index 9b0b8d7fd..dc49e96ca 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheScreen.kt @@ -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() diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt index e703e1d0e..20d702c85 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt @@ -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, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt index e7717278d..c73dcb5c4 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt @@ -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?, diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index cf5d94c88..2bbf2ae42 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -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() }, + 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 { - 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, 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) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt index b43f05486..363c6191b 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfUiState.kt @@ -10,9 +10,11 @@ data class BookshelfUiState( override val isSearch: Boolean = false, override val isLoading: Boolean = false, val groups: List = emptyList(), + val allGroups: List = emptyList(), val groupPreviews: Map> = emptyMap(), val groupBookCounts: Map = emptyMap(), val selectedGroupIndex: Int = 0, + val selectedGroupId: Long = BookGroup.IdAll, val loadingText: String? = null, val upBooksCount: Int = 0, val updatingBooks: Set = emptySet() diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index 9513a9eb3..3151ee60a 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -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(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, val upBooksCount: Int @@ -228,29 +237,27 @@ class BookshelfViewModel( val uiState: StateFlow = 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) + } + } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/EmptyMessage.kt b/app/src/main/java/io/legado/app/ui/widget/components/EmptyMessage.kt index a5046502e..2281ccca4 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/EmptyMessage.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/EmptyMessage.kt @@ -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 = 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 = 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 ) -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt b/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt index cbe88fa11..a54186413 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/menuItem/RoundDropdownMenuItem.kt @@ -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 ) }