diff --git a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt index 545ce7448..464cac2bb 100644 --- a/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt +++ b/app/src/main/java/io/legado/app/ui/main/MainNavGraph.kt @@ -404,7 +404,16 @@ fun MainActivity.mainEntryProvider( RssSortRouteScreen( sourceUrl = route.sourceUrl, initialSortUrl = route.sortUrl, + initialSearchKey = route.key, onBackClick = { onNavigateBack() }, + onSearch = { key -> + onNavigateToRoute( + MainRouteRssSort( + sourceUrl = route.sourceUrl, + key = key + ) + ) + }, onOpenRead = { title, origin, link, openUrl -> if (link?.contains("@js:") == true) { onNavigateToRoute( diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortRouteScreen.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortRouteScreen.kt index 5034ca3db..fe1f735f1 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssSortRouteScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortRouteScreen.kt @@ -29,17 +29,23 @@ import androidx.compose.ui.res.stringResource fun RssSortRouteScreen( sourceUrl: String?, initialSortUrl: String?, + initialSearchKey: String?, onBackClick: () -> Unit, + onSearch: (String) -> Unit, onOpenRead: (title: String?, origin: String, link: String?, openUrl: String?) -> Unit, viewModel: RssSortViewModel = koinViewModel() ) { val context = LocalContext.current val scope = rememberCoroutineScope() - var sortList by remember(sourceUrl) { mutableStateOf>>(emptyList()) } - var articleStyle by remember(sourceUrl) { mutableIntStateOf(0) } - var redirectPolicy by remember(sourceUrl) { mutableStateOf(RedirectPolicy.ALLOW_ALL) } - var screenTitle by remember(sourceUrl) { mutableStateOf("") } + var sortList by remember(sourceUrl, initialSortUrl, initialSearchKey) { + mutableStateOf>>(emptyList()) + } + 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 errorText = stringResource(R.string.error) @@ -51,9 +57,9 @@ fun RssSortRouteScreen( withContext(Dispatchers.IO) { viewModel.initDataSource(sourceUrl) } - sortList = viewModel.loadSorts() + sortList = viewModel.loadSorts(initialSortUrl, initialSearchKey) articleStyle = viewModel.currentArticleStyle() - screenTitle = viewModel.rssSource?.sourceName.orEmpty() + screenTitle = initialSearchKey ?: viewModel.rssSource?.sourceName.orEmpty() redirectPolicy = RedirectPolicy.fromString(viewModel.rssSource?.redirectPolicy) } @@ -65,7 +71,7 @@ fun RssSortRouteScreen( } } - LaunchedEffect(sourceUrl) { + LaunchedEffect(sourceUrl, initialSortUrl, initialSearchKey) { reloadSourceState() } @@ -73,12 +79,15 @@ fun RssSortRouteScreen( title = screenTitle.ifBlank { stringResource(R.string.rss) }, sortList = sortList, preferredSortUrl = initialSortUrl, + searchKey = initialSearchKey, + hasSearch = !viewModel.rssSource?.searchUrl.isNullOrBlank(), hasLogin = !viewModel.rssSource?.loginUrl.isNullOrBlank(), redirectPolicy = redirectPolicy, showReadRecordSheet = showReadRecordSheet, readRecords = readRecords, sourceVariableSheet = sourceVariableSheet, onBackClick = onBackClick, + onSearch = onSearch, onLogin = { context.startActivity { putExtra("type", "rssSource") @@ -88,7 +97,7 @@ fun RssSortRouteScreen( onRefreshSort = { scope.launch { viewModel.clearSortCache() - sortList = viewModel.loadSorts() + sortList = viewModel.loadSorts(initialSortUrl, initialSearchKey) } }, onSetSourceVariable = { @@ -157,7 +166,7 @@ fun RssSortRouteScreen( }, pagerContent = { _, sort, paddingValues -> val pageViewModel: RssArticlesViewModel = koinViewModel( - key = "rss_${viewModel.url}_${sort.first}_${sort.second}" + key = "rss_${viewModel.url}_${sort.first}_${sort.second}_${initialSearchKey.orEmpty()}" ) RssArticlesPage( sortName = sort.first, @@ -166,6 +175,7 @@ fun RssSortRouteScreen( rssUrl = viewModel.url, rssSource = viewModel.rssSource, viewModel = pageViewModel, + searchKey = initialSearchKey, paddingValues = paddingValues, onRead = { article -> viewModel.read(article) diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortScreen.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortScreen.kt index 9629dc236..da5eeed14 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssSortScreen.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortScreen.kt @@ -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.AppTextField 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.SmallPlainButton import io.legado.app.ui.widget.components.button.series.SmallToggleButton @@ -85,12 +86,15 @@ fun RssSortScreen( title: String, sortList: List>, preferredSortUrl: String?, + searchKey: String?, + hasSearch: Boolean, hasLogin: Boolean, redirectPolicy: RedirectPolicy, showReadRecordSheet: Boolean, readRecords: List, sourceVariableSheet: RssSourceVariableSheetState?, onBackClick: () -> Unit, + onSearch: (String) -> Unit, onLogin: () -> Unit, onRefreshSort: () -> Unit, onSetSourceVariable: () -> Unit, @@ -120,15 +124,25 @@ fun RssSortScreen( val tabTitles = sortList.map { it.first } 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 showGroupMenu by remember { mutableStateOf(false) } - BackHandler(enabled = showMainMenu || showRedirectMenu || showGroupMenu) { + BackHandler(enabled = showMainMenu || showSearchSheet || showRedirectMenu || showGroupMenu) { showMainMenu = false + showSearchSheet = false showRedirectMenu = false showGroupMenu = false } + fun submitSearch(query: String) { + val normalized = query.trim() + if (normalized.isEmpty()) return + showSearchSheet = false + onSearch(normalized) + } + LaunchedEffect(sortList.size) { if (sortList.isEmpty()) return@LaunchedEffect val safeIndex = pagerState.currentPage.coerceIn(0, sortList.lastIndex) @@ -147,6 +161,13 @@ fun RssSortScreen( TopBarNavigationButton(onClick = onBackClick, imageVector = AppIcons.Back) }, actions = { + if (hasSearch) { + TopBarActionButton( + onClick = { showSearchSheet = true }, + imageVector = AppIcons.Search, + contentDescription = stringResource(R.string.search) + ) + } TopBarActionButton( onClick = { showMainMenu = true }, imageVector = AppIcons.MoreVert, @@ -328,6 +349,44 @@ fun RssSortScreen( onDismissRequest = onDismissSourceVariable, 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 diff --git a/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt index 70372fafd..2bdbd7b75 100644 --- a/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt @@ -9,6 +9,9 @@ import io.legado.app.data.entities.RssReadRecord import io.legado.app.data.entities.RssSource import io.legado.app.help.source.removeSortCache 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 splitties.init.appCtx @@ -32,10 +35,32 @@ class RssSortViewModel(application: Application) : BaseViewModel(application) { } } - suspend fun loadSorts(): List> { + suspend fun loadSorts(sortUrl: String? = null, searchKey: String? = null): List> { + 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() } + private fun parseSortUrl(sortUrl: String): List> { + return try { + if (sortUrl.isJsonObject()) { + GSONStrict.fromJsonObject>(sortUrl) + .getOrThrow() + .map { it.key to it.value } + } else { + listOf("" to sortUrl) + } + } catch (_: Exception) { + listOf("" to sortUrl) + } + } + fun currentArticleStyle(): Int = rssSource?.articleStyle ?: 0 fun switchLayout() {