选择书源时可搜素

This commit is contained in:
HapeLee
2026-05-14 00:55:48 +08:00
parent f5971024e7
commit 4d3ae1e537
4 changed files with 193 additions and 172 deletions
@@ -34,7 +34,6 @@ import androidx.compose.material.icons.outlined.Image
import androidx.compose.material.icons.outlined.Settings
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -71,6 +70,7 @@ import io.legado.app.ui.book.changesource.ChangeBookSourceComposeViewModel
import io.legado.app.ui.book.changesource.ChangeSourceConfig
import io.legado.app.ui.book.changesource.ChangeSourceMigrationOptionsSheet
import io.legado.app.ui.book.group.GroupEditSheet
import io.legado.app.ui.book.search.ScopeSelectSheet
import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.theme.LegadoTheme
@@ -298,7 +298,6 @@ fun ChangeSourceSheet(
var loadingAction by remember { mutableStateOf(false) }
var showOptionsMenu by rememberSaveable { mutableStateOf(false) }
var showFilterSheet by rememberSaveable { mutableStateOf(false) }
var scopeSheetTab by rememberSaveable { mutableIntStateOf(0) }
val bookAddedToShelfText = stringResource(R.string.book_added_to_shelf)
val editSourceResult = rememberLauncherForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
@@ -603,93 +602,22 @@ fun ChangeSourceSheet(
}
)
AppModalBottomSheet(
ScopeSelectSheet(
show = showFilterSheet,
onDismissRequest = { showFilterSheet = false },
title = stringResource(R.string.search_select_group),
endAction = {
MediumIconButton(
onClick = {
viewModel.startSearch()
showFilterSheet = false
},
imageVector = Icons.Default.Check
)
isAll = scopeState.isAll,
onSelectAll = { viewModel.selectAllScope() },
groups = groups,
selectedGroups = scopeState.displayNames,
onToggleGroup = { viewModel.toggleScopeGroup(it) },
sources = enabledSources,
selectedSources = scopeState.sourceUrls,
onToggleSource = { viewModel.toggleScopeSource(it) },
isSourceScope = scopeState.isSource,
onConfirm = {
viewModel.startSearch()
showFilterSheet = false
}
) {
Column {
SelectionItemCard(
title = stringResource(R.string.all_source),
isSelected = scopeState.isAll,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
viewModel.selectAllScope()
}
)
Spacer(modifier = Modifier.height(8.dp))
AppTabRow(
tabTitles = listOf(
stringResource(R.string.group),
stringResource(R.string.book_source),
),
selectedTabIndex = scopeSheetTab,
onTabSelected = { scopeSheetTab = it },
)
Spacer(modifier = Modifier.height(6.dp))
if (scopeSheetTab == 0) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(groups, key = { it }) {
val selected = !scopeState.isSource && scopeState.displayNames.contains(it)
SelectionItemCard(
title = it,
isSelected = selected,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
viewModel.toggleScopeGroup(it)
}
)
}
}
} else {
if (enabledSources.isEmpty()) {
Text(
text = stringResource(R.string.search_empty),
style = LegadoTheme.typography.bodyMedium,
color = LegadoTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
)
} else {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(enabledSources, key = { it.bookSourceUrl }) {
val selected = scopeState.sourceUrls.contains(it.bookSourceUrl)
SelectionItemCard(
title = it.bookSourceName,
subtitle = it.bookSourceGroup?.takeIf { group -> group.isNotBlank() },
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
isSelected = selected,
inSelectionMode = true,
onToggleSelection = {
viewModel.toggleScopeSource(it)
}
)
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
}
}
)
}
@@ -0,0 +1,165 @@
package io.legado.app.ui.book.search
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Check
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import io.legado.app.R
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.MediumIconButton
import io.legado.app.ui.widget.components.card.SelectionItemCard
import io.legado.app.ui.widget.components.modalBottomSheet.AppModalBottomSheet
import io.legado.app.ui.widget.components.tabRow.AppTabRow
@Composable
fun ScopeSelectSheet(
show: Boolean,
onDismissRequest: () -> Unit,
isAll: Boolean,
onSelectAll: () -> Unit,
groups: List<String>,
selectedGroups: Collection<String>,
onToggleGroup: (String) -> Unit,
sources: List<BookSourcePart>,
selectedSources: Collection<String>,
onToggleSource: (BookSourcePart) -> Unit,
isSourceScope: Boolean = false,
title: String = stringResource(R.string.search_select_group),
onConfirm: (() -> Unit)? = null,
) {
var scopeSheetTab by rememberSaveable(show) { mutableIntStateOf(if (isSourceScope) 1 else 0) }
var filterText by rememberSaveable(show) { mutableStateOf("") }
val filteredGroups = remember(groups, filterText) {
if (filterText.isBlank()) groups else groups.filter { it.contains(filterText, ignoreCase = true) }
}
val filteredSources = remember(sources, filterText) {
if (filterText.isBlank()) sources else sources.filter {
it.bookSourceName.contains(filterText, ignoreCase = true) ||
it.bookSourceGroup?.contains(filterText, ignoreCase = true) == true ||
it.bookSourceUrl.contains(filterText, ignoreCase = true)
}
}
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest,
title = title,
endAction = onConfirm?.let {
{
MediumIconButton(
onClick = it,
imageVector = Icons.Default.Check
)
}
}
) {
Column {
SearchBar(
query = filterText,
onQueryChange = { filterText = it },
placeholder = stringResource(R.string.screen),
autoFocus = false
)
Spacer(modifier = Modifier.height(8.dp))
SelectionItemCard(
title = stringResource(R.string.all_source),
isSelected = isAll,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
onSelectAll()
}
)
Spacer(modifier = Modifier.height(8.dp))
AppTabRow(
tabTitles = listOf(
stringResource(R.string.group),
stringResource(R.string.book_source),
),
selectedTabIndex = scopeSheetTab,
onTabSelected = { scopeSheetTab = it },
)
Spacer(modifier = Modifier.height(6.dp))
if (scopeSheetTab == 0) {
LazyColumn(
modifier = Modifier.heightIn(max = 480.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(filteredGroups, key = { it }) { groupName ->
val selected = !isSourceScope && selectedGroups.contains(groupName)
SelectionItemCard(
title = groupName,
isSelected = selected,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
onToggleGroup(groupName)
}
)
}
}
} else {
if (filteredSources.isEmpty()) {
Text(
text = stringResource(R.string.search_empty),
style = LegadoTheme.typography.bodyMedium,
color = LegadoTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
)
} else {
LazyColumn(
modifier = Modifier.heightIn(max = 480.dp),
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(filteredSources, key = { it.bookSourceUrl }) { source ->
val selected = selectedSources.contains(source.bookSourceUrl)
SelectionItemCard(
title = source.bookSourceName,
subtitle = source.bookSourceGroup?.takeIf { group -> group.isNotBlank() },
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
isSelected = selected,
inSelectionMode = true,
onToggleSelection = {
onToggleSource(source)
}
)
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
}
}
}
@@ -39,7 +39,6 @@ import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
@@ -80,6 +79,7 @@ import io.legado.app.ui.widget.components.topbar.TopBarAnimatedActionButton
import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
import io.legado.app.ui.widget.components.icon.AppIcon
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.book.search.ScopeSelectSheet
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
import io.legado.app.ui.widget.components.tabRow.AppTabRow
import io.legado.app.ui.widget.components.text.AppText
@@ -104,7 +104,6 @@ fun SearchScreen(
val listState = rememberLazyListState()
val lifecycleOwner = LocalLifecycleOwner.current
var queryInput by rememberSaveable { mutableStateOf(state.query) }
var scopeSheetTab by rememberSaveable { mutableIntStateOf(0) }
var ignoreNextDebouncedQuery by rememberSaveable { mutableStateOf<String?>(null) }
val showSuggestionPanel = state.showSuggestions
val latestQuery by rememberUpdatedState(state.query)
@@ -128,12 +127,6 @@ fun SearchScreen(
}
}
LaunchedEffect(state.showScopeSheet, state.isSourceScope) {
if (state.showScopeSheet) {
scopeSheetTab = if (state.isSourceScope) 1 else 0
}
}
LaunchedEffect(Unit) {
snapshotFlow { queryInput }
.distinctUntilChanged()
@@ -505,85 +498,19 @@ fun SearchScreen(
}
)
AppModalBottomSheet(
ScopeSelectSheet(
show = state.showScopeSheet,
onDismissRequest = { viewModel.onIntent(SearchIntent.SetScopeSheetVisible(false)) },
title = stringResource(R.string.search_select_group),
) {
Column {
SelectionItemCard(
title = stringResource(R.string.all_source),
isSelected = state.isAllScope,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
viewModel.onIntent(SearchIntent.SelectAllScope)
}
)
Spacer(modifier = Modifier.height(8.dp))
AppTabRow(
tabTitles = listOf(
stringResource(R.string.group),
stringResource(R.string.book_source),
),
selectedTabIndex = scopeSheetTab,
onTabSelected = { scopeSheetTab = it },
)
Spacer(modifier = Modifier.height(6.dp))
if (scopeSheetTab == 0) {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(state.enabledGroups, key = { it }) {
val selected = !state.isSourceScope && state.scopeDisplayNames.contains(it)
SelectionItemCard(
title = it,
isSelected = selected,
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
inSelectionMode = true,
onToggleSelection = {
viewModel.onIntent(SearchIntent.ToggleScopeGroup(it))
}
)
}
}
} else {
if (state.enabledSources.isEmpty()) {
Text(
text = stringResource(R.string.search_empty),
style = LegadoTheme.typography.bodyMedium,
color = LegadoTheme.colorScheme.onSurfaceVariant,
textAlign = TextAlign.Center,
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 12.dp),
)
} else {
LazyColumn(
verticalArrangement = Arrangement.spacedBy(4.dp)
) {
items(state.enabledSources, key = { it.bookSourceUrl }) {
val selected = state.selectedScopeSourceUrls.contains(it.bookSourceUrl)
SelectionItemCard(
title = it.bookSourceName,
subtitle = it.bookSourceGroup?.takeIf { group -> group.isNotBlank() },
containerColor = LegadoTheme.colorScheme.surface.copy(alpha = 0.6f),
isSelected = selected,
inSelectionMode = true,
onToggleSelection = {
viewModel.onIntent(SearchIntent.ToggleScopeSource(it))
}
)
}
}
}
}
Spacer(modifier = Modifier.height(20.dp))
}
}
isAll = state.isAllScope,
onSelectAll = { viewModel.onIntent(SearchIntent.SelectAllScope) },
groups = state.enabledGroups,
selectedGroups = state.scopeDisplayNames,
onToggleGroup = { viewModel.onIntent(SearchIntent.ToggleScopeGroup(it)) },
sources = state.enabledSources,
selectedSources = state.selectedScopeSourceUrls,
onToggleSource = { viewModel.onIntent(SearchIntent.ToggleScopeSource(it)) },
isSourceScope = state.isSourceScope
)
AppModalBottomSheet(
show = state.showTypeSheet,
@@ -26,6 +26,7 @@ sealed interface BookshelfOverlay {
data object LogSheet : BookshelfOverlay
data object GroupMenu : BookshelfOverlay
data object GroupSelectSheet : BookshelfOverlay
data class GroupEditSheet(val groupId: Long) : BookshelfOverlay
data object BatchDownloadConfirmDialog : BookshelfOverlay
}