mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
Update search
This commit is contained in:
74
lib/components/alert_number_form_dialog.dart
Normal file
74
lib/components/alert_number_form_dialog.dart
Normal file
@@ -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<StatefulWidget> createState() => _AlertNumberFormDialog();
|
||||
}
|
||||
|
||||
class _AlertNumberFormDialog extends State<AlertNumberFormDialog> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
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)),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -333,5 +333,9 @@
|
||||
"translationFile": "翻译文件",
|
||||
"translationFileHelp": "包含标签翻译的JSON文件路径。未指定时将从网站上下载。",
|
||||
"thumbnailFormat": "缩略图格式",
|
||||
"thumbnailFormatHelp": "ffmpeg API方式不支持webp格式。"
|
||||
"thumbnailFormatHelp": "ffmpeg API方式不支持webp格式。",
|
||||
"search": "搜索",
|
||||
"maxSearchSuggestions": "最大搜索建议结果数量",
|
||||
"ok": "确定",
|
||||
"changeSettings": "修改设置"
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<StatefulWidget> createState() => _HomePage();
|
||||
}
|
||||
|
||||
class _HomePage extends State<HomePage> 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),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -94,6 +94,12 @@ class _SettingsPage extends State<SettingsPage>
|
||||
},
|
||||
)
|
||||
: Container(),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.search),
|
||||
title: Text(i18n.search),
|
||||
onTap: () {
|
||||
context.push("/settings/search");
|
||||
}),
|
||||
],
|
||||
));
|
||||
},
|
||||
|
||||
83
lib/pages/settings/search.dart
Normal file
83
lib/pages/settings/search.dart
Normal file
@@ -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<StatefulWidget> createState() => _SearchSettingsPage();
|
||||
}
|
||||
|
||||
class _SearchSettingsPage extends State<SearchSettingsPage>
|
||||
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),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user