HapeLee 4 месяцев назад
Родитель
Сommit
b362161b7f

+ 9 - 0
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(

+ 19 - 9
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<List<Pair<String, String>>>(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<List<Pair<String, String>>>(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<SourceLoginActivity> {
                 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)

+ 60 - 1
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<Pair<String, String>>,
     preferredSortUrl: String?,
+    searchKey: String?,
+    hasSearch: Boolean,
     hasLogin: Boolean,
     redirectPolicy: RedirectPolicy,
     showReadRecordSheet: Boolean,
     readRecords: List<RssReadRecord>,
     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

+ 26 - 1
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<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()
     }
 
+    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 switchLayout() {