From 4334346632828bf48743b410d81784915122abce Mon Sep 17 00:00:00 2001 From: lifegpc Date: Thu, 24 Oct 2024 07:37:12 +0000 Subject: [PATCH] Add search bar --- lib/api/gallery.dart | 56 +++++++++++++++++++++++++++++++++++++ lib/api/gallery.g.dart | 42 ++++++++++++++++++++++++++++ lib/auth.dart | 10 +++++++ lib/globals.dart | 26 +++++++++++++++++ lib/pages/galleries.dart | 3 ++ lib/pages/home.dart | 1 + lib/pages/task_manager.dart | 1 + pubspec.lock | 8 ++++++ pubspec.yaml | 1 + 9 files changed, 148 insertions(+) diff --git a/lib/api/gallery.dart b/lib/api/gallery.dart index 97a1c69..4d5a135 100644 --- a/lib/api/gallery.dart +++ b/lib/api/gallery.dart @@ -181,3 +181,59 @@ class GMetaInfos { ApiResult.fromJson(value as Map, (json) => GMeta.fromJson(json as Map))))); } + +@JsonSerializable() +class GMetaSearchInfo { + const GMetaSearchInfo({ + required this.gid, + required this.token, + required this.title, + required this.titleJpn, + required this.category, + required this.uploader, + required this.posted, + required this.filecount, + required this.filesize, + required this.expunged, + required this.rating, + required this.tags, + this.parentGid, + this.parentToken, + this.firstGid, + this.firstToken, + }); + final int gid; + final String token; + final String title; + @JsonKey(name: 'title_jpn') + final String titleJpn; + final String category; + final String uploader; + @JsonKey(fromJson: _fromJson, toJson: _toJson) + final DateTime posted; + final int filecount; + final int filesize; + final bool expunged; + final double rating; + final List tags; + @JsonKey(name: 'parent_gid') + final int? parentGid; + @JsonKey(name: 'parent_token') + final String? parentToken; + @JsonKey(name: 'first_gid') + final int? firstGid; + @JsonKey(name: 'first_token') + final String? firstToken; + + static DateTime _fromJson(int posted) => + DateTime.fromMillisecondsSinceEpoch(posted * 1000); + static int _toJson(DateTime posted) => posted.millisecondsSinceEpoch ~/ 1000; + factory GMetaSearchInfo.fromJson(Map json) => + _$GMetaSearchInfoFromJson(json); + Map toJson() => _$GMetaSearchInfoToJson(this); + String get preferredTitle => prefs.getBool("useTitleJpn") == true + ? titleJpn.isEmpty + ? title + : titleJpn + : title; +} diff --git a/lib/api/gallery.g.dart b/lib/api/gallery.g.dart index 1f437a7..0b9a2ad 100644 --- a/lib/api/gallery.g.dart +++ b/lib/api/gallery.g.dart @@ -134,3 +134,45 @@ Map _$GalleryDataToJson(GalleryData instance) => 'tags': instance.tags, 'pages': instance.pages, }; + +GMetaSearchInfo _$GMetaSearchInfoFromJson(Map json) => + GMetaSearchInfo( + gid: (json['gid'] as num).toInt(), + token: json['token'] as String, + title: json['title'] as String, + titleJpn: json['title_jpn'] as String, + category: json['category'] as String, + uploader: json['uploader'] as String, + posted: GMetaSearchInfo._fromJson((json['posted'] as num).toInt()), + filecount: (json['filecount'] as num).toInt(), + filesize: (json['filesize'] as num).toInt(), + expunged: json['expunged'] as bool, + rating: (json['rating'] as num).toDouble(), + tags: (json['tags'] as List) + .map((e) => Tag.fromJson(e as Map)) + .toList(), + parentGid: (json['parent_gid'] as num?)?.toInt(), + parentToken: json['parent_token'] as String?, + firstGid: (json['first_gid'] as num?)?.toInt(), + firstToken: json['first_token'] as String?, + ); + +Map _$GMetaSearchInfoToJson(GMetaSearchInfo instance) => + { + 'gid': instance.gid, + 'token': instance.token, + 'title': instance.title, + 'title_jpn': instance.titleJpn, + 'category': instance.category, + 'uploader': instance.uploader, + 'posted': GMetaSearchInfo._toJson(instance.posted), + 'filecount': instance.filecount, + 'filesize': instance.filesize, + 'expunged': instance.expunged, + 'rating': instance.rating, + 'tags': instance.tags, + 'parent_gid': instance.parentGid, + 'parent_token': instance.parentToken, + 'first_gid': instance.firstGid, + 'first_token': instance.firstToken, + }; diff --git a/lib/auth.dart b/lib/auth.dart index 721a798..9b096a5 100644 --- a/lib/auth.dart +++ b/lib/auth.dart @@ -1,4 +1,5 @@ import 'package:logging/logging.dart'; +import 'package:meilisearch/meilisearch.dart'; import 'api/status.dart'; import 'api/token.dart'; import 'api/user.dart'; @@ -35,16 +36,25 @@ class AuthInfo { _user?.permissions.has(UserPermission.manageTasks); bool? get canShareGallery => _user?.permissions.has(UserPermission.shareGallery); + MeilisearchInfo? get meilisearch => _status?.meilisearch; + MeiliSearchClient? _meiliSearchClient; + MeiliSearchClient? get meiliSearchClient => _meiliSearchClient; void clear() { _user = null; _status = null; + _meiliSearchClient = null; _token = null; _checked = false; } Future getServerStatus() async { _status = (await api.getStatus()).unwrap(); + if (_status?.meilisearch != null) { + _meiliSearchClient = MeiliSearchClient( + _status!.meilisearch!.host, _status!.meilisearch!.key); + listener.tryEmit("meilisearch_enabled", null); + } } Future checkSessionInfo() async { diff --git a/lib/globals.dart b/lib/globals.dart index 3d56949..2433361 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -15,6 +15,7 @@ import 'package:logging/logging.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:window_manager/window_manager.dart'; import 'api/client.dart'; +import 'api/gallery.dart'; import 'auth.dart'; import 'config/base.dart'; import 'config/shared_preferences.dart'; @@ -309,6 +310,31 @@ 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(); + }) + : Container(); +} + ThemeMode themeModeNext(ThemeMode mode) { if (mode == ThemeMode.system) return ThemeMode.light; if (mode == ThemeMode.dark) return ThemeMode.system; diff --git a/lib/pages/galleries.dart b/lib/pages/galleries.dart index 10f5a8f..5d846bc 100644 --- a/lib/pages/galleries.dart +++ b/lib/pages/galleries.dart @@ -119,6 +119,7 @@ class _GalleriesPage extends State }); _translatedTag = widget.translatedTag; listener.on("user_logined", _onStateChanged); + listener.on("meilisearch_enabled", _onStateChanged); super.initState(); } @@ -202,6 +203,7 @@ class _GalleriesPage extends State icon: const Icon(Icons.sort), itemBuilder: (context) => [PopupMenuItem(child: sortByGidMenu)]), + buildSearchButton(context), buildThemeModeIcon(context), buildMoreVertSettingsButon(context), ]), @@ -226,6 +228,7 @@ class _GalleriesPage extends State _pagingController.dispose(); _tagCancel?.cancel(); listener.removeEventListener("user_logined", _onStateChanged); + listener.removeEventListener("meilisearch_enabled", _onStateChanged); super.dispose(); } } diff --git a/lib/pages/home.dart b/lib/pages/home.dart index 3fac0d7..1823ef4 100644 --- a/lib/pages/home.dart +++ b/lib/pages/home.dart @@ -83,6 +83,7 @@ class HomePage extends HookWidget with IsTopWidget { appBar: AppBar( title: Text(AppLocalizations.of(context)!.titleBar), actions: [ + buildSearchButton(context), IconButton( onPressed: () { final n = themeModeNext(mode.value); diff --git a/lib/pages/task_manager.dart b/lib/pages/task_manager.dart index adb2b4c..df29131 100644 --- a/lib/pages/task_manager.dart +++ b/lib/pages/task_manager.dart @@ -192,6 +192,7 @@ class _TaskManagerPage extends State ), title: Text(i18n.taskManager), actions: [ + buildSearchButton(context), buildThemeModeIcon(context), buildMoreVertSettingsButon(context), ], diff --git a/pubspec.lock b/pubspec.lock index aeb6e9e..84bf576 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -563,6 +563,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.11.1" + meilisearch: + dependency: "direct main" + description: + name: meilisearch + sha256: "0567639a4cccca84bf7c13fc34c9d8a277f896a57388d1afde69ad0084f49a46" + url: "https://pub.dev" + source: hosted + version: "0.16.0" meta: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 10a9389..9d462c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: json_annotation: ^4.9.0 keymap: ^0.0.92 logging: ^1.2.0 + meilisearch: ^0.16.0 mutex: ^3.1.0 package_info_plus: ^8.0.0 palette_generator: ^0.3.3+3