[优化] 发现页可以在添加进书架后立即标记,与增加发现页内分组搜索

This commit is contained in:
HapeLee
2025-12-09 01:10:32 +08:00
parent 8cdd5ca482
commit b7323b54ed
4 changed files with 46 additions and 16 deletions
@@ -93,7 +93,9 @@ import io.legado.app.ui.widget.components.AnimatedTextButton
import io.legado.app.ui.widget.components.AnimatedTextLine import io.legado.app.ui.widget.components.AnimatedTextLine
import io.legado.app.ui.widget.components.Cover import io.legado.app.ui.widget.components.Cover
import io.legado.app.ui.widget.components.EmptyMessageView import io.legado.app.ui.widget.components.EmptyMessageView
import io.legado.app.ui.widget.components.SearchBarSection
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import org.koin.androidx.compose.koinViewModel import org.koin.androidx.compose.koinViewModel
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight") @SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
@@ -216,6 +218,7 @@ fun ExploreShowScreen(
if (showKindSheet) { if (showKindSheet) {
val scrollState = rememberScrollState() val scrollState = rememberScrollState()
var kindQuery by remember { mutableStateOf("") }
val sheetState = rememberModalBottomSheetState( val sheetState = rememberModalBottomSheetState(
skipPartiallyExpanded = true, skipPartiallyExpanded = true,
confirmValueChange = { newValue -> confirmValueChange = { newValue ->
@@ -231,16 +234,30 @@ fun ExploreShowScreen(
.fillMaxWidth() .fillMaxWidth()
.heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f) .heightIn(max = LocalConfiguration.current.screenHeightDp.dp * 0.8f)
.verticalScroll(scrollState) .verticalScroll(scrollState)
.padding(12.dp)
) { ) {
Text(
"选择分类", var kindQuery by remember { mutableStateOf("") }
style = MaterialTheme.typography.titleMedium,
modifier = Modifier.padding(bottom = 8.dp) SearchBarSection(
query = kindQuery,
onQueryChange = { kindQuery = it },
placeholder = "选择或搜索分类",
backgroundColor = MaterialTheme.colorScheme.surfaceContainerHigh
) )
FlowRow(modifier = Modifier.fillMaxWidth()) { val filteredKinds = remember(kindQuery, kinds) {
kinds.forEach { kind -> if (kindQuery.isBlank()) kinds
else kinds.filter { kind ->
kind.title.contains(kindQuery, ignoreCase = true) ||
(kind.url?.contains(kindQuery, ignoreCase = true) == true)
}
}
FlowRow(
modifier = Modifier.fillMaxWidth()
.padding(16.dp)
) {
filteredKinds.forEach { kind ->
KindListItem( KindListItem(
kind = kind, kind = kind,
currentTitle = selectedTitle ?: title, currentTitle = selectedTitle ?: title,
@@ -295,10 +312,9 @@ fun ExploreShowScreen(
items = books, items = books,
key = { it.bookUrl } key = { it.bookUrl }
) { book -> ) { book ->
val shelfState = viewModel.getCurrentBookShelfState(book)
ExploreBookGridItem( ExploreBookGridItem(
book = book, book = book,
shelfState = shelfState, shelfState = viewModel.getBookShelfStateFlow(book),
onClick = { onBookClick(book) }, onClick = { onBookClick(book) },
modifier = Modifier.animateItem() modifier = Modifier.animateItem()
) )
@@ -325,7 +341,7 @@ fun ExploreShowScreen(
val shelfState = viewModel.getCurrentBookShelfState(book) val shelfState = viewModel.getCurrentBookShelfState(book)
ExploreBookItem( ExploreBookItem(
book = book, book = book,
shelfState = shelfState, shelfState = viewModel.getBookShelfStateFlow(book),
onClick = { onBookClick(book) }, onClick = { onBookClick(book) },
modifier = Modifier.animateItem() modifier = Modifier.animateItem()
) )
@@ -372,7 +388,7 @@ fun KindListItem(
modifier = Modifier modifier = Modifier
.then( .then(
if (!isClickable) Modifier.fillMaxWidth() if (!isClickable) Modifier.fillMaxWidth()
else Modifier.fillMaxWidth(1 / 3f) else Modifier.fillMaxWidth(1f / 3f)
) )
.padding(horizontal = 4.dp) .padding(horizontal = 4.dp)
) )
@@ -472,10 +488,11 @@ fun ExploreTopBar(
@Composable @Composable
fun ExploreBookItem( fun ExploreBookItem(
book: SearchBook, book: SearchBook,
shelfState: BookShelfState, shelfState: Flow<BookShelfState>,
onClick: () -> Unit, onClick: () -> Unit,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val shelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
val badge: (@Composable RowScope.() -> Unit)? val badge: (@Composable RowScope.() -> Unit)?
= when (shelfState) { = when (shelfState) {
@@ -584,10 +601,12 @@ fun ExploreBookItem(
fun ExploreBookGridItem( fun ExploreBookGridItem(
book: SearchBook, book: SearchBook,
onClick: () -> Unit, onClick: () -> Unit,
shelfState: BookShelfState, shelfState: Flow<BookShelfState>,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
val shelfState by shelfState.collectAsState(initial = BookShelfState.NOT_IN_SHELF)
val badgeText: String? = when (shelfState) { val badgeText: String? = when (shelfState) {
BookShelfState.IN_SHELF -> "已在书架" BookShelfState.IN_SHELF -> "已在书架"
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍" BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
@@ -11,11 +11,14 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.model.BookShelfState import io.legado.app.model.BookShelfState
import io.legado.app.ui.book.search.BookKey import io.legado.app.ui.book.search.BookKey
import io.legado.app.utils.exploreLayoutGrid import io.legado.app.utils.exploreLayoutGrid
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import splitties.init.appCtx import splitties.init.appCtx
@@ -198,6 +201,12 @@ class ExploreShowViewModel(
} }
} }
fun getBookShelfStateFlow(item: SearchBook): Flow<BookShelfState> {
return _bookshelf.map { shelf ->
getBookShelfState(item, shelf)
}.distinctUntilChanged()
}
private fun getBookShelfState(item: SearchBook, shelf: Set<BookKey>): BookShelfState { private fun getBookShelfState(item: SearchBook, shelf: Set<BookKey>): BookShelfState {
val exactMatch = shelf.any { it.name == item.name && it.author == item.author && it.url == item.bookUrl } val exactMatch = shelf.any { it.name == item.name && it.author == item.author && it.url == item.bookUrl }
if (exactMatch) return BookShelfState.IN_SHELF if (exactMatch) return BookShelfState.IN_SHELF
@@ -6,6 +6,7 @@ import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut import androidx.compose.animation.fadeOut
import androidx.compose.animation.togetherWith import androidx.compose.animation.togetherWith
import androidx.compose.material3.ContainedLoadingIndicator
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.LoadingIndicator
import androidx.compose.material3.Text import androidx.compose.material3.Text
@@ -36,7 +37,7 @@ fun AnimatedTextButton(
label = "ButtonLoading" label = "ButtonLoading"
) { loading -> ) { loading ->
if (loading) { if (loading) {
LoadingIndicator() ContainedLoadingIndicator()
} else { } else {
Text(text) Text(text)
} }
@@ -24,14 +24,15 @@ import androidx.compose.ui.unit.dp
fun SearchBarSection( fun SearchBarSection(
query: String, query: String,
onQueryChange: (String) -> Unit, onQueryChange: (String) -> Unit,
placeholder: String = "搜索书名" placeholder: String = "搜索书名",
backgroundColor: Color = MaterialTheme.colorScheme.surfaceContainerLow
) { ) {
Surface( Surface(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 8.dp), .padding(horizontal = 16.dp, vertical = 8.dp),
shape = RoundedCornerShape(32.dp), shape = RoundedCornerShape(32.dp),
color = MaterialTheme.colorScheme.surfaceContainerLow color = backgroundColor
) { ) {
TextField( TextField(