fix: RSS搜索框

This commit is contained in:
HapeLee
2026-06-14 00:21:14 +08:00
parent fc096c92e8
commit b362161b7f
4 changed files with 114 additions and 11 deletions
@@ -404,7 +404,16 @@ fun MainActivity.mainEntryProvider(
RssSortRouteScreen( RssSortRouteScreen(
sourceUrl = route.sourceUrl, sourceUrl = route.sourceUrl,
initialSortUrl = route.sortUrl, initialSortUrl = route.sortUrl,
initialSearchKey = route.key,
onBackClick = { onNavigateBack() }, onBackClick = { onNavigateBack() },
onSearch = { key ->
onNavigateToRoute(
MainRouteRssSort(
sourceUrl = route.sourceUrl,
key = key
)
)
},
onOpenRead = { title, origin, link, openUrl -> onOpenRead = { title, origin, link, openUrl ->
if (link?.contains("@js:") == true) { if (link?.contains("@js:") == true) {
onNavigateToRoute( onNavigateToRoute(
@@ -29,17 +29,23 @@ import androidx.compose.ui.res.stringResource
fun RssSortRouteScreen( fun RssSortRouteScreen(
sourceUrl: String?, sourceUrl: String?,
initialSortUrl: String?, initialSortUrl: String?,
initialSearchKey: String?,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onSearch: (String) -> Unit,
onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit, onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit,
viewModel: RssSortViewModel = koinViewModel() viewModel: RssSortViewModel = koinViewModel()
) { ) {
val context = LocalContext.current val context = LocalContext.current
val scope = rememberCoroutineScope() val scope = rememberCoroutineScope()
var sortList by remember(sourceUrl) { mutableStateOf<List<Pair<String, String>>>(emptyList()) } var sortList by remember(sourceUrl, initialSortUrl, initialSearchKey) {
var articleStyle by remember(sourceUrl) { mutableIntStateOf(0) } mutableStateOf<List<Pair<String, String>>>(emptyList())
var redirectPolicy by remember(sourceUrl) { mutableStateOf(RedirectPolicy.ALLOW_ALL) } }
var screenTitle by remember(sourceUrl) { mutableStateOf("") } var articleStyle by remember(sourceUrl, initialSortUrl, initialSearchKey) { mutableIntStateOf(0) }
var redirectPolicy by remember(sourceUrl, initialSortUrl, initialSearchKey) {
mutableStateOf(RedirectPolicy.ALLOW_ALL)
}
var screenTitle by remember(sourceUrl, initialSortUrl, initialSearchKey) { mutableStateOf("") }
val setSourceVariableText = stringResource(R.string.set_source_variable) val setSourceVariableText = stringResource(R.string.set_source_variable)
val errorText = stringResource(R.string.error) val errorText = stringResource(R.string.error)
@@ -51,9 +57,9 @@ fun RssSortRouteScreen(
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
viewModel.initDataSource(sourceUrl) viewModel.initDataSource(sourceUrl)
} }
sortList = viewModel.loadSorts() sortList = viewModel.loadSorts(initialSortUrl, initialSearchKey)
articleStyle = viewModel.currentArticleStyle() articleStyle = viewModel.currentArticleStyle()
screenTitle = viewModel.rssSource?.sourceName.orEmpty() screenTitle = initialSearchKey ?: viewModel.rssSource?.sourceName.orEmpty()
redirectPolicy = RedirectPolicy.fromString(viewModel.rssSource?.redirectPolicy) redirectPolicy = RedirectPolicy.fromString(viewModel.rssSource?.redirectPolicy)
} }
@@ -65,7 +71,7 @@ fun RssSortRouteScreen(
} }
} }
LaunchedEffect(sourceUrl) { LaunchedEffect(sourceUrl, initialSortUrl, initialSearchKey) {
reloadSourceState() reloadSourceState()
} }
@@ -73,12 +79,15 @@ fun RssSortRouteScreen(
title = screenTitle.ifBlank { stringResource(R.string.rss) }, title = screenTitle.ifBlank { stringResource(R.string.rss) },
sortList = sortList, sortList = sortList,
preferredSortUrl = initialSortUrl, preferredSortUrl = initialSortUrl,
searchKey = initialSearchKey,
hasSearch = !viewModel.rssSource?.searchUrl.isNullOrBlank(),
hasLogin = !viewModel.rssSource?.loginUrl.isNullOrBlank(), hasLogin = !viewModel.rssSource?.loginUrl.isNullOrBlank(),
redirectPolicy = redirectPolicy, redirectPolicy = redirectPolicy,
showReadRecordSheet = showReadRecordSheet, showReadRecordSheet = showReadRecordSheet,
readRecords = readRecords, readRecords = readRecords,
sourceVariableSheet = sourceVariableSheet, sourceVariableSheet = sourceVariableSheet,
onBackClick = onBackClick, onBackClick = onBackClick,
onSearch = onSearch,
onLogin = { onLogin = {
context.startActivity<SourceLoginActivity> { context.startActivity<SourceLoginActivity> {
putExtra("type", "rssSource") putExtra("type", "rssSource")
@@ -88,7 +97,7 @@ fun RssSortRouteScreen(
onRefreshSort = { onRefreshSort = {
scope.launch { scope.launch {
viewModel.clearSortCache() viewModel.clearSortCache()
sortList = viewModel.loadSorts() sortList = viewModel.loadSorts(initialSortUrl, initialSearchKey)
} }
}, },
onSetSourceVariable = { onSetSourceVariable = {
@@ -157,7 +166,7 @@ fun RssSortRouteScreen(
}, },
pagerContent = { _, sort, paddingValues -> pagerContent = { _, sort, paddingValues ->
val pageViewModel: RssArticlesViewModel = koinViewModel( val pageViewModel: RssArticlesViewModel = koinViewModel(
key = "rss_${viewModel.url}_${sort.first}_${sort.second}" key = "rss_${viewModel.url}_${sort.first}_${sort.second}_${initialSearchKey.orEmpty()}"
) )
RssArticlesPage( RssArticlesPage(
sortName = sort.first, sortName = sort.first,
@@ -166,6 +175,7 @@ fun RssSortRouteScreen(
rssUrl = viewModel.url, rssUrl = viewModel.url,
rssSource = viewModel.rssSource, rssSource = viewModel.rssSource,
viewModel = pageViewModel, viewModel = pageViewModel,
searchKey = initialSearchKey,
paddingValues = paddingValues, paddingValues = paddingValues,
onRead = { article -> onRead = { article ->
viewModel.read(article) viewModel.read(article)
@@ -54,6 +54,7 @@ import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.AppScaffold import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.AppTextField
import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.SearchBar
import io.legado.app.ui.widget.components.button.series.MediumPlainButton import io.legado.app.ui.widget.components.button.series.MediumPlainButton
import io.legado.app.ui.widget.components.button.series.SmallPlainButton import io.legado.app.ui.widget.components.button.series.SmallPlainButton
import io.legado.app.ui.widget.components.button.series.SmallToggleButton import io.legado.app.ui.widget.components.button.series.SmallToggleButton
@@ -85,12 +86,15 @@ fun RssSortScreen(
title: String, title: String,
sortList: List<Pair<String, String>>, sortList: List<Pair<String, String>>,
preferredSortUrl: String?, preferredSortUrl: String?,
searchKey: String?,
hasSearch: Boolean,
hasLogin: Boolean, hasLogin: Boolean,
redirectPolicy: RedirectPolicy, redirectPolicy: RedirectPolicy,
showReadRecordSheet: Boolean, showReadRecordSheet: Boolean,
readRecords: List<RssReadRecord>, readRecords: List<RssReadRecord>,
sourceVariableSheet: RssSourceVariableSheetState?, sourceVariableSheet: RssSourceVariableSheetState?,
onBackClick: () -> Unit, onBackClick: () -> Unit,
onSearch: (String) -> Unit,
onLogin: () -> Unit, onLogin: () -> Unit,
onRefreshSort: () -> Unit, onRefreshSort: () -> Unit,
onSetSourceVariable: () -> Unit, onSetSourceVariable: () -> Unit,
@@ -120,15 +124,25 @@ fun RssSortScreen(
val tabTitles = sortList.map { it.first } val tabTitles = sortList.map { it.first }
var showMainMenu by remember { mutableStateOf(false) } var showMainMenu by remember { mutableStateOf(false) }
var showSearchSheet by remember { mutableStateOf(false) }
var searchQuery by remember(searchKey) { mutableStateOf(searchKey.orEmpty()) }
var showRedirectMenu by remember { mutableStateOf(false) } var showRedirectMenu by remember { mutableStateOf(false) }
var showGroupMenu by remember { mutableStateOf(false) } var showGroupMenu by remember { mutableStateOf(false) }
BackHandler(enabled = showMainMenu || showRedirectMenu || showGroupMenu) { BackHandler(enabled = showMainMenu || showSearchSheet || showRedirectMenu || showGroupMenu) {
showMainMenu = false showMainMenu = false
showSearchSheet = false
showRedirectMenu = false showRedirectMenu = false
showGroupMenu = false showGroupMenu = false
} }
fun submitSearch(query: String) {
val normalized = query.trim()
if (normalized.isEmpty()) return
showSearchSheet = false
onSearch(normalized)
}
LaunchedEffect(sortList.size) { LaunchedEffect(sortList.size) {
if (sortList.isEmpty()) return@LaunchedEffect if (sortList.isEmpty()) return@LaunchedEffect
val safeIndex = pagerState.currentPage.coerceIn(0, sortList.lastIndex) val safeIndex = pagerState.currentPage.coerceIn(0, sortList.lastIndex)
@@ -147,6 +161,13 @@ fun RssSortScreen(
TopBarNavigationButton(onClick = onBackClick, imageVector = AppIcons.Back) TopBarNavigationButton(onClick = onBackClick, imageVector = AppIcons.Back)
}, },
actions = { actions = {
if (hasSearch) {
TopBarActionButton(
onClick = { showSearchSheet = true },
imageVector = AppIcons.Search,
contentDescription = stringResource(R.string.search)
)
}
TopBarActionButton( TopBarActionButton(
onClick = { showMainMenu = true }, onClick = { showMainMenu = true },
imageVector = AppIcons.MoreVert, imageVector = AppIcons.MoreVert,
@@ -328,6 +349,44 @@ fun RssSortScreen(
onDismissRequest = onDismissSourceVariable, onDismissRequest = onDismissSourceVariable,
onSave = onSaveSourceVariable onSave = onSaveSourceVariable
) )
RssSearchSheet(
show = showSearchSheet,
query = searchQuery,
onQueryChange = { searchQuery = it },
onSearch = ::submitSearch,
onDismissRequest = { showSearchSheet = false }
)
}
@Composable
private fun RssSearchSheet(
show: Boolean,
query: String,
onQueryChange: (String) -> Unit,
onSearch: (String) -> Unit,
onDismissRequest: () -> Unit
) {
AppModalBottomSheet(
show = show,
onDismissRequest = onDismissRequest,
title = stringResource(R.string.search),
endAction = {
SmallPlainButton(
onClick = { onSearch(query) },
icon = AppIcons.Search,
contentDescription = stringResource(R.string.search)
)
}
) {
SearchBar(
query = query,
onQueryChange = onQueryChange,
onSearch = onSearch,
placeholder = stringResource(R.string.search_content),
modifier = Modifier.fillMaxWidth()
)
}
} }
@Composable @Composable
@@ -9,6 +9,9 @@ import io.legado.app.data.entities.RssReadRecord
import io.legado.app.data.entities.RssSource import io.legado.app.data.entities.RssSource
import io.legado.app.help.source.removeSortCache import io.legado.app.help.source.removeSortCache
import io.legado.app.help.source.sortUrls import io.legado.app.help.source.sortUrls
import io.legado.app.utils.GSONStrict
import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.isJsonObject
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import splitties.init.appCtx import splitties.init.appCtx
@@ -32,10 +35,32 @@ class RssSortViewModel(application: Application) : BaseViewModel(application) {
} }
} }
suspend fun loadSorts(): List<Pair<String, String>> { suspend fun loadSorts(sortUrl: String? = null, searchKey: String? = null): List<Pair<String, String>> {
if (searchKey != null) {
return rssSource?.searchUrl?.takeIf { it.isNotBlank() }?.let {
listOf("搜索" to it)
}.orEmpty()
}
sortUrl?.takeIf { it.isNotBlank() }?.let { url ->
return parseSortUrl(url)
}
return rssSource?.sortUrls().orEmpty() return rssSource?.sortUrls().orEmpty()
} }
private fun parseSortUrl(sortUrl: String): List<Pair<String, String>> {
return try {
if (sortUrl.isJsonObject()) {
GSONStrict.fromJsonObject<Map<String, String>>(sortUrl)
.getOrThrow()
.map { it.key to it.value }
} else {
listOf("" to sortUrl)
}
} catch (_: Exception) {
listOf("" to sortUrl)
}
}
fun currentArticleStyle(): Int = rssSource?.articleStyle ?: 0 fun currentArticleStyle(): Int = rssSource?.articleStyle ?: 0
fun switchLayout() { fun switchLayout() {