Add ability to manage search sources. Close #174

This commit is contained in:
2025-02-09 19:29:51 +08:00
parent 17ef17ca5b
commit df4263f969
6 changed files with 120 additions and 25 deletions

View File

@@ -323,7 +323,8 @@
"Cloudflare verification required": "需要Cloudflare验证", "Cloudflare verification required": "需要Cloudflare验证",
"Success": "成功", "Success": "成功",
"Compressing": "压缩中", "Compressing": "压缩中",
"Exporting": "导出中" "Exporting": "导出中",
"Search Sources": "搜索源"
}, },
"zh_TW": { "zh_TW": {
"Home": "首頁", "Home": "首頁",
@@ -649,6 +650,7 @@
"Cloudflare verification required": "需要Cloudflare驗證", "Cloudflare verification required": "需要Cloudflare驗證",
"Success": "成功", "Success": "成功",
"Compressing": "壓縮中", "Compressing": "壓縮中",
"Exporting": "匯出中" "Exporting": "匯出中",
"Search Sources": "搜索源"
} }
} }

View File

@@ -126,6 +126,7 @@ class _Settings with ChangeNotifier {
'explore_pages': [], 'explore_pages': [],
'categories': [], 'categories': [],
'favorites': [], 'favorites': [],
'searchSources': null,
'showFavoriteStatusOnTile': true, 'showFavoriteStatusOnTile': true,
'showHistoryStatusOnTile': false, 'showHistoryStatusOnTile': false,
'blockedWords': [], 'blockedWords': [],

View File

@@ -42,11 +42,16 @@ Future<void> init() async {
await ComicSource.init().wait(); await ComicSource.init().wait();
await LocalManager().init().wait(); await LocalManager().init().wait();
CacheManager().setLimitSize(appdata.settings['cacheSize']); CacheManager().setLimitSize(appdata.settings['cacheSize']);
if (appdata.settings['searchSources'] == null) {
appdata.settings['searchSources'] = ComicSource.all()
.where((e) => e.searchPageData != null)
.map((e) => e.key)
.toList();
}
if (App.isAndroid) { if (App.isAndroid) {
handleLinks(); handleLinks();
} }
FlutterError.onError = (details) { FlutterError.onError = (details) {
Log.error( Log.error("Unhandled Exception", "${details.exception}\n${details.stack}");
"Unhandled Exception", "${details.exception}\n${details.stack}");
}; };
} }

View File

@@ -2,6 +2,7 @@ import "package:flutter/material.dart";
import 'package:shimmer_animation/shimmer_animation.dart'; import 'package:shimmer_animation/shimmer_animation.dart';
import "package:venera/components/components.dart"; import "package:venera/components/components.dart";
import "package:venera/foundation/app.dart"; import "package:venera/foundation/app.dart";
import "package:venera/foundation/appdata.dart";
import "package:venera/foundation/comic_source/comic_source.dart"; import "package:venera/foundation/comic_source/comic_source.dart";
import "package:venera/pages/search_result_page.dart"; import "package:venera/pages/search_result_page.dart";
import "package:venera/utils/translations.dart"; import "package:venera/utils/translations.dart";
@@ -24,7 +25,18 @@ class _AggregatedSearchPageState extends State<AggregatedSearchPage> {
@override @override
void initState() { void initState() {
sources = ComicSource.all().where((e) => e.searchPageData != null).toList(); var all = ComicSource.all()
.where((e) => e.searchPageData != null)
.map((e) => e.key)
.toList();
var settings = appdata.settings['searchSources'] as List;
var sources = <String>[];
for (var source in settings) {
if (all.contains(source)) {
sources.add(source);
}
}
this.sources = sources.map((e) => ComicSource.find(e)!).toList();
_keyword = widget.keyword; _keyword = widget.keyword;
controller = SearchBarController( controller = SearchBarController(
currentText: widget.keyword, currentText: widget.keyword,

View File

@@ -10,12 +10,14 @@ import 'package:venera/foundation/comic_source/comic_source.dart';
import 'package:venera/foundation/state_controller.dart'; import 'package:venera/foundation/state_controller.dart';
import 'package:venera/pages/aggregated_search_page.dart'; import 'package:venera/pages/aggregated_search_page.dart';
import 'package:venera/pages/search_result_page.dart'; import 'package:venera/pages/search_result_page.dart';
import 'package:venera/pages/settings/settings_page.dart';
import 'package:venera/utils/app_links.dart'; import 'package:venera/utils/app_links.dart';
import 'package:venera/utils/ext.dart'; import 'package:venera/utils/ext.dart';
import 'package:venera/utils/tags_translation.dart'; import 'package:venera/utils/tags_translation.dart';
import 'package:venera/utils/translations.dart'; import 'package:venera/utils/translations.dart';
import 'comic_page.dart'; import 'comic_page.dart';
import 'comic_source_page.dart';
class SearchPage extends StatefulWidget { class SearchPage extends StatefulWidget {
const SearchPage({super.key}); const SearchPage({super.key});
@@ -27,8 +29,13 @@ class SearchPage extends StatefulWidget {
class _SearchPageState extends State<SearchPage> { class _SearchPageState extends State<SearchPage> {
late final SearchBarController controller; late final SearchBarController controller;
late List<String> searchSources;
String searchTarget = ""; String searchTarget = "";
SearchPageData get currentSearchPageData =>
ComicSource.find(searchTarget)!.searchPageData!;
bool aggregatedSearch = false; bool aggregatedSearch = false;
var focusNode = FocusNode(); var focusNode = FocusNode();
@@ -139,31 +146,85 @@ class _SearchPageState extends State<SearchPage> {
@override @override
void initState() { void initState() {
findSearchSources();
var defaultSearchTarget = appdata.settings['defaultSearchTarget']; var defaultSearchTarget = appdata.settings['defaultSearchTarget'];
if (defaultSearchTarget == "_aggregated_") { if (defaultSearchTarget == "_aggregated_") {
aggregatedSearch = true; aggregatedSearch = true;
searchTarget = ComicSource.all().where((e) => e.searchPageData != null)
.toList().first.key;
} else if (defaultSearchTarget != null && } else if (defaultSearchTarget != null &&
ComicSource.find(defaultSearchTarget) != null) { searchSources.contains(defaultSearchTarget)) {
searchTarget = defaultSearchTarget; searchTarget = defaultSearchTarget;
} else {
searchTarget = ComicSource.all().first.key;
} }
controller = SearchBarController( controller = SearchBarController(
onSearch: search, onSearch: search,
); );
appdata.settings.addListener(updateSearchSourcesIfNeeded);
super.initState(); super.initState();
} }
@override @override
void dispose() { void dispose() {
focusNode.dispose(); focusNode.dispose();
appdata.settings.removeListener(updateSearchSourcesIfNeeded);
super.dispose(); super.dispose();
} }
void findSearchSources() {
var all = ComicSource.all()
.where((e) => e.searchPageData != null)
.map((e) => e.key)
.toList();
var settings = appdata.settings['searchSources'] as List;
var sources = <String>[];
for (var source in settings) {
if (all.contains(source)) {
sources.add(source);
}
}
searchSources = sources;
if (!searchSources.contains(searchTarget)) {
searchTarget = searchSources.firstOrNull ?? "";
}
}
void updateSearchSourcesIfNeeded() {
var old = searchSources;
findSearchSources();
if (old.isEqualsTo(searchSources)) {
return;
}
setState(() {});
}
void manageSearchSources() {
showPopUpWidget(App.rootContext, setSearchSourcesWidget());
}
Widget buildEmpty() {
var msg = "No Search Sources".tl;
msg += '\n';
VoidCallback onTap;
if (ComicSource.isEmpty) {
msg += "Please add some sources".tl;
onTap = () {
context.to(() => ComicSourcePage());
};
} else {
msg += "Please check your settings".tl;
onTap = manageSearchSources;
}
return NetworkError(
message: msg,
retry: onTap,
withAppbar: true,
buttonText: "Manage".tl,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (searchSources.isEmpty) {
return buildEmpty();
}
return Scaffold( return Scaffold(
body: SmoothCustomScrollView( body: SmoothCustomScrollView(
slivers: buildSlivers().toList(), slivers: buildSlivers().toList(),
@@ -192,8 +253,7 @@ class _SearchPageState extends State<SearchPage> {
} }
Widget buildSearchTarget() { Widget buildSearchTarget() {
var sources = var sources = searchSources.map((e) => ComicSource.find(e)!).toList();
ComicSource.all().where((e) => e.searchPageData != null).toList();
return SliverToBoxAdapter( return SliverToBoxAdapter(
child: Container( child: Container(
width: double.infinity, width: double.infinity,
@@ -205,6 +265,10 @@ class _SearchPageState extends State<SearchPage> {
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
leading: const Icon(Icons.search), leading: const Icon(Icons.search),
title: Text("Search in".tl), title: Text("Search in".tl),
trailing: IconButton(
icon: const Icon(Icons.settings),
onPressed: manageSearchSources,
),
), ),
Wrap( Wrap(
spacing: 8, spacing: 8,
@@ -231,11 +295,6 @@ class _SearchPageState extends State<SearchPage> {
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
aggregatedSearch = value ?? false; aggregatedSearch = value ?? false;
if (!aggregatedSearch &&
appdata.settings['defaultSearchTarget'] ==
"_aggregated_") {
searchTarget = sources.first.key;
}
}); });
}, },
), ),
@@ -247,9 +306,7 @@ class _SearchPageState extends State<SearchPage> {
} }
void useDefaultOptions() { void useDefaultOptions() {
final searchOptions = final searchOptions = currentSearchPageData.searchOptions ?? [];
ComicSource.find(searchTarget)!.searchPageData!.searchOptions ??
<SearchOptions>[];
options = searchOptions.map((e) => e.defaultValue).toList(); options = searchOptions.map((e) => e.defaultValue).toList();
} }
@@ -260,9 +317,7 @@ class _SearchPageState extends State<SearchPage> {
var children = <Widget>[]; var children = <Widget>[];
final searchOptions = final searchOptions = currentSearchPageData.searchOptions ?? [];
ComicSource.find(searchTarget)!.searchPageData!.searchOptions ??
<SearchOptions>[];
if (searchOptions.length != options.length) { if (searchOptions.length != options.length) {
useDefaultOptions(); useDefaultOptions();
} }
@@ -396,7 +451,9 @@ class _SearchPageState extends State<SearchPage> {
Text( Text(
subTitle, subTitle,
style: TextStyle( style: TextStyle(
fontSize: 14, color: Theme.of(context).colorScheme.outline), fontSize: 14,
color: Theme.of(context).colorScheme.outline,
),
) )
], ],
), ),

View File

@@ -40,6 +40,10 @@ class _ExploreSettingsState extends State<ExploreSettings> {
title: "Network Favorite Pages".tl, title: "Network Favorite Pages".tl,
builder: setFavoritesPagesWidget, builder: setFavoritesPagesWidget,
).toSliver(), ).toSliver(),
_PopupWindowSetting(
title: "Search Sources".tl,
builder: setSearchSourcesWidget,
).toSliver(),
_SwitchSetting( _SwitchSetting(
title: "Show favorite status on comic tile".tl, title: "Show favorite status on comic tile".tl,
settingKey: "showFavoriteStatusOnTile", settingKey: "showFavoriteStatusOnTile",
@@ -211,3 +215,17 @@ Widget setFavoritesPagesWidget() {
pages: pages, pages: pages,
); );
} }
Widget setSearchSourcesWidget() {
var pages = <String, String>{};
for (var c in ComicSource.all()) {
if (c.searchPageData != null) {
pages[c.key] = c.name;
}
}
return _MultiPagesFilter(
title: "Search Sources".tl,
settingsIndex: "searchSources",
pages: pages,
);
}