diff --git a/lib/components/alert_number_form_dialog.dart b/lib/components/alert_number_form_dialog.dart new file mode 100644 index 0000000..1e8a3a2 --- /dev/null +++ b/lib/components/alert_number_form_dialog.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import '../globals.dart'; +import './number_field.dart'; + +class AlertNumberFormDialog extends StatefulWidget { + const AlertNumberFormDialog(this.confKey, + {this.title, + this.initial, + this.decoration, + this.max, + this.min, + this.errorMsg, + super.key}); + final Widget? title; + final int? initial; + final InputDecoration? decoration; + final int? max; + final int? min; + final String? errorMsg; + final String confKey; + @override + State createState() => _AlertNumberFormDialog(); +} + +class _AlertNumberFormDialog extends State { + final _formKey = GlobalKey(); + int? now; + @override + void initState() { + now = prefs.getInt(widget.confKey) ?? widget.initial; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context)!; + return AlertDialog( + title: widget.title ?? Text(i18n.changeSettings), + content: Form( + key: _formKey, + child: NumberFormField( + initialValue: now, + decoration: widget.decoration, + onChanged: (s) { + setState(() { + now = s; + }); + }, + max: widget.max, + min: widget.min, + errorMsg: widget.errorMsg, + )), + actions: [ + TextButton( + onPressed: now != null + ? () { + prefs.setInt(widget.confKey, now!); + listener + .tryEmit("settings_updated", (widget.confKey, now!)); + context.pop(); + } + : null, + child: Text(i18n.ok)), + TextButton( + onPressed: () { + context.pop(); + }, + child: Text(i18n.cancel)), + ], + ); + } +} diff --git a/lib/globals.dart b/lib/globals.dart index 2433361..65a497a 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -12,6 +12,7 @@ import 'package:flutter_datetime_picker_plus/flutter_datetime_picker_plus.dart' import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; +import 'package:meilisearch/meilisearch.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'api/client.dart'; @@ -312,26 +313,34 @@ Widget buildMoreVertSettingsButon(BuildContext context) { Widget buildSearchButton(BuildContext context, {bool openGallery = true}) { return auth.meilisearch != null - ? SearchAnchor(builder: (context, controller) { - return IconButton( - onPressed: () { - controller.openView(); - }, - icon: const Icon(Icons.search)); - }, suggestionsBuilder: (context, controller) async { - final c = auth.meiliSearchClient!; - final re = await c.index("gmeta").search(controller.text); - return re.asSearchResult().hits.map((e) { - final m = GMetaSearchInfo.fromJson(e); - return ListTile( - title: Text(m.preferredTitle), - onTap: () { - controller.closeView(m.preferredTitle); - if (openGallery) context.push("/gallery/${m.gid}"); - }, - ); - }).toList(); - }) + ? SearchAnchor( + builder: (context, controller) { + return IconButton( + onPressed: () { + controller.openView(); + }, + icon: const Icon(Icons.search)); + }, + suggestionsBuilder: (context, controller) async { + if (controller.text.isEmpty) return []; + final c = auth.meiliSearchClient!; + final max = prefs.getInt("maxSearchSuggestions") ?? 100; + final re = await c + .index("gmeta") + .search(controller.text, SearchQuery(limit: max)); + return re.asSearchResult().hits.map((e) { + final m = GMetaSearchInfo.fromJson(e); + return ListTile( + title: Text(m.preferredTitle), + onTap: () { + controller.closeView(controller.text); + if (openGallery) context.push("/gallery/${m.gid}"); + }, + ); + }).toList(); + }, + isFullScreen: true, + ) : Container(); } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 3e8fd18..90ae8f4 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -333,5 +333,9 @@ "translationFile": "Tnanslation file", "translationFileHelp": "The path to json file contains tag's translation. Will download from website if this is empty.", "thumbnailFormat": "Thumbnail format", - "thumbnailFormatHelp": "Webp is not supported in ffmpeg API method." + "thumbnailFormatHelp": "Webp is not supported in ffmpeg API method.", + "search": "Search", + "maxSearchSuggestions": "Maximum results of search suggestions", + "ok": "Ok", + "changeSettings": "Change settings" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9200b87..2ab89b6 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -333,5 +333,9 @@ "translationFile": "翻译文件", "translationFileHelp": "包含标签翻译的JSON文件路径。未指定时将从网站上下载。", "thumbnailFormat": "缩略图格式", - "thumbnailFormatHelp": "ffmpeg API方式不支持webp格式。" + "thumbnailFormatHelp": "ffmpeg API方式不支持webp格式。", + "search": "搜索", + "maxSearchSuggestions": "最大搜索建议结果数量", + "ok": "确定", + "changeSettings": "修改设置" } diff --git a/lib/main.dart b/lib/main.dart index 9597ab5..deaa126 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,6 +27,7 @@ import 'pages/login.dart'; import 'pages/settings.dart'; import 'pages/settings/cache.dart'; import 'pages/settings/display.dart'; +import 'pages/settings/search.dart'; import 'pages/settings/server.dart'; import 'pages/settings/server_url.dart'; import 'pages/settings/user.dart'; @@ -337,6 +338,10 @@ final _router = GoRouter( return NewUpdateTagTranslationTaskPage(); }); }), + GoRoute( + path: SearchSettingsPage.routeName, + builder: (context, state) => SearchSettingsPage(key: state.pageKey), + ), ], observers: [ _NavigatorObserver(), diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 1823ef4..d9d9b01 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -1,6 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:logging/logging.dart'; import '../globals.dart'; @@ -65,16 +64,35 @@ class HomeDrawer extends StatelessWidget { } } -class HomePage extends HookWidget with IsTopWidget { +class HomePage extends StatefulWidget { const HomePage({super.key}); static const String routeName = '/'; + @override + State createState() => _HomePage(); +} + +class _HomePage extends State with ThemeModeWidget, IsTopWidget2 { + void _onStateChanged(dynamic _) { + setState(() {}); + } + + @override + void initState() { + listener.on("meilisearch_enabled", _onStateChanged); + super.initState(); + } + + @override + void dispose() { + listener.removeEventListener("meilisearch_enabled", _onStateChanged); + super.dispose(); + } + @override Widget build(BuildContext context) { tryInitApi(context); - var mode = useState(MainApp.of(context).themeMode); - mode.value = MainApp.of(context).themeMode; if (isTop(context)) { setCurrentTitle("", Theme.of(context).primaryColor.value, usePrefix: true); @@ -84,17 +102,7 @@ class HomePage extends HookWidget with IsTopWidget { title: Text(AppLocalizations.of(context)!.titleBar), actions: [ buildSearchButton(context), - IconButton( - onPressed: () { - final n = themeModeNext(mode.value); - MainApp.of(context).changeThemeMode(n); - mode.value = n; - }, - icon: Icon(mode.value == ThemeMode.system - ? Icons.brightness_auto - : mode.value == ThemeMode.dark - ? Icons.dark_mode - : Icons.light_mode)), + buildThemeModeIcon(context), buildMoreVertSettingsButon(context), ], ), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index a25e960..bb3c24c 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -94,6 +94,12 @@ class _SettingsPage extends State }, ) : Container(), + ListTile( + leading: const Icon(Icons.search), + title: Text(i18n.search), + onTap: () { + context.push("/settings/search"); + }), ], )); }, diff --git a/lib/pages/settings/search.dart b/lib/pages/settings/search.dart new file mode 100644 index 0000000..aa32678 --- /dev/null +++ b/lib/pages/settings/search.dart @@ -0,0 +1,83 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; +import '../../components/alert_number_form_dialog.dart'; +import '../../globals.dart'; + +final _log = Logger("SearchSettingsPage"); + +class SearchSettingsPage extends StatefulWidget { + const SearchSettingsPage({super.key}); + + static const String routeName = '/settings/search'; + + @override + State createState() => _SearchSettingsPage(); +} + +class _SearchSettingsPage extends State + with ThemeModeWidget, IsTopWidget2 { + void _onStateChanged(dynamic _) { + setState(() {}); + } + + @override + void initState() { + listener.on("settings_updated", _onStateChanged); + super.initState(); + } + + @override + void dispose() { + listener.removeEventListener("settings_updated", _onStateChanged); + super.dispose(); + } + + Widget _buildMain(BuildContext context) { + final i18n = AppLocalizations.of(context)!; + return SingleChildScrollView( + child: Column(mainAxisSize: MainAxisSize.min, children: [ + ListTile( + title: Text(i18n.maxSearchSuggestions), + onTap: () => showDialog( + context: context, + builder: (context) => AlertNumberFormDialog("maxSearchSuggestions", + initial: 100, + min: 1, + max: 1000, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.maxSearchSuggestions, + ))), + subtitle: + Text((prefs.getInt("maxSearchSuggestions") ?? 100).toString()), + ) + ])); + } + + @override + Widget build(BuildContext context) { + final i18n = AppLocalizations.of(context)!; + if (isTop(context)) { + setCurrentTitle("${i18n.settings} - ${i18n.search}", + Theme.of(context).primaryColor.value); + } + return Scaffold( + appBar: AppBar( + leading: IconButton( + onPressed: () { + context.canPop() ? context.pop() : context.go("/settings"); + }, + icon: const Icon(Icons.arrow_back), + ), + title: Text(i18n.search), + actions: [ + buildThemeModeIcon(context), + buildMoreVertSettingsButon(context), + ], + ), + body: _buildMain(context), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 84bf576..695f37c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -308,14 +308,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.0" - flutter_hooks: - dependency: "direct main" - description: - name: flutter_hooks - sha256: cde36b12f7188c85286fba9b38cc5a902e7279f36dd676967106c041dc9dde70 - url: "https://pub.dev" - source: hosted - version: "0.20.5" flutter_lints: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 9d462c3..639f1d2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,6 @@ dependencies: flutter: sdk: flutter flutter_datetime_picker_plus: ^2.2.0 - flutter_hooks: ^0.20.0 flutter_localizations: sdk: flutter flutter_web_plugins: