Add search bar

This commit is contained in:
2024-10-24 07:37:12 +00:00
committed by GitHub
parent a1f8f57447
commit 4334346632
9 changed files with 148 additions and 0 deletions

View File

@@ -181,3 +181,59 @@ class GMetaInfos {
ApiResult<GMeta>.fromJson(value as Map<String, dynamic>,
(json) => GMeta.fromJson(json as Map<String, dynamic>)))));
}
@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<Tag> 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<String, dynamic> json) =>
_$GMetaSearchInfoFromJson(json);
Map<String, dynamic> toJson() => _$GMetaSearchInfoToJson(this);
String get preferredTitle => prefs.getBool("useTitleJpn") == true
? titleJpn.isEmpty
? title
: titleJpn
: title;
}

View File

@@ -134,3 +134,45 @@ Map<String, dynamic> _$GalleryDataToJson(GalleryData instance) =>
'tags': instance.tags,
'pages': instance.pages,
};
GMetaSearchInfo _$GMetaSearchInfoFromJson(Map<String, dynamic> 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<dynamic>)
.map((e) => Tag.fromJson(e as Map<String, dynamic>))
.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<String, dynamic> _$GMetaSearchInfoToJson(GMetaSearchInfo instance) =>
<String, dynamic>{
'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,
};

View File

@@ -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<void> 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<void> checkSessionInfo() async {

View File

@@ -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;

View File

@@ -119,6 +119,7 @@ class _GalleriesPage extends State<GalleriesPage>
});
_translatedTag = widget.translatedTag;
listener.on("user_logined", _onStateChanged);
listener.on("meilisearch_enabled", _onStateChanged);
super.initState();
}
@@ -202,6 +203,7 @@ class _GalleriesPage extends State<GalleriesPage>
icon: const Icon(Icons.sort),
itemBuilder: (context) =>
[PopupMenuItem(child: sortByGidMenu)]),
buildSearchButton(context),
buildThemeModeIcon(context),
buildMoreVertSettingsButon(context),
]),
@@ -226,6 +228,7 @@ class _GalleriesPage extends State<GalleriesPage>
_pagingController.dispose();
_tagCancel?.cancel();
listener.removeEventListener("user_logined", _onStateChanged);
listener.removeEventListener("meilisearch_enabled", _onStateChanged);
super.dispose();
}
}

View File

@@ -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);

View File

@@ -192,6 +192,7 @@ class _TaskManagerPage extends State<TaskManagerPage>
),
title: Text(i18n.taskManager),
actions: [
buildSearchButton(context),
buildThemeModeIcon(context),
buildMoreVertSettingsButon(context),
],

View File

@@ -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:

View File

@@ -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