[优化] 优化书架搜索筛选逻辑,可以在书架筛选书源,现在输入关键词时回车触发搜索
This commit is contained in:
@@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user