[优化] 发现页可以在添加进书架后立即标记,与增加发现页内分组搜索
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
Reference in New Issue
Block a user