This commit is contained in:
2023-09-04 18:22:09 +08:00
parent 20b38ba6da
commit e1233f35bd
10 changed files with 262 additions and 9 deletions

View File

@@ -6,6 +6,7 @@ import 'package:retrofit/retrofit.dart';
import 'api_result.dart'; import 'api_result.dart';
import 'gallery.dart'; import 'gallery.dart';
import 'status.dart'; import 'status.dart';
import 'tags.dart';
import 'token.dart'; import 'token.dart';
import 'user.dart'; import 'user.dart';
@@ -113,6 +114,12 @@ abstract class _EHApi {
{@Query("all") bool? all, {@Query("all") bool? all,
@Query("offset") int? offset, @Query("offset") int? offset,
@Query("limit") int? limit}); @Query("limit") int? limit});
@GET('/tag/{id}')
// ignore: unused_element
Future<ApiResult<Tags>> _getTags(@Path("id") String id);
@GET('/tag/rows')
Future<ApiResult<List<Tag>>> getRowTags();
} }
class EHApi extends __EHApi { class EHApi extends __EHApi {
@@ -145,4 +152,8 @@ class EHApi extends __EHApi {
Future<ApiResult<EhFiles>> getFiles(List<String> tokens) { Future<ApiResult<EhFiles>> getFiles(List<String> tokens) {
return _getFiles(tokens.join(",")); return _getFiles(tokens.join(","));
} }
Future<ApiResult<Tags>> getTags(List<int> ids) {
return _getTags(ids.join(","));
}
} }

View File

@@ -530,6 +530,70 @@ class __EHApi implements _EHApi {
return value; return value;
} }
@override
Future<ApiResult<Tags>> _getTags(String id) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final Map<String, dynamic>? _data = null;
final _result = await _dio
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<Tags>>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/tag/${id}',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
))));
final value = ApiResult<Tags>.fromJson(
_result.data!,
(json) => Tags.fromJson(json as Map<String, dynamic>),
);
return value;
}
@override
Future<ApiResult<List<Tag>>> getRowTags() async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
final _headers = <String, dynamic>{};
final Map<String, dynamic>? _data = null;
final _result = await _dio.fetch<Map<String, dynamic>>(
_setStreamType<ApiResult<List<Tag>>>(Options(
method: 'GET',
headers: _headers,
extra: _extra,
)
.compose(
_dio.options,
'/tag/rows',
queryParameters: queryParameters,
data: _data,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
))));
final value = ApiResult<List<Tag>>.fromJson(
_result.data!,
(json) => json is List<dynamic>
? json
.map<Tag>((i) => Tag.fromJson(i as Map<String, dynamic>))
.toList()
: List.empty(),
);
return value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) { RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic && if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes || !(requestOptions.responseType == ResponseType.bytes ||

15
lib/api/tags.dart Normal file
View File

@@ -0,0 +1,15 @@
import 'api_result.dart';
import 'gallery.dart';
class Tags {
const Tags({required this.tags});
final Map<String, ApiResult<Tag>> tags;
factory Tags.fromJson(Map<String, dynamic> json) => Tags(
tags: (json).map(
(k, e) => MapEntry(
k,
ApiResult<Tag>.fromJson(e as Map<String, dynamic>,
(e) => Tag.fromJson(e as Map<String, dynamic>))),
),
);
}

View File

@@ -1,3 +1,4 @@
import 'package:eh_downloader_flutter/globals.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../api/gallery.dart'; import '../api/gallery.dart';
import 'gallery_basic_info.dart'; import 'gallery_basic_info.dart';
@@ -12,6 +13,16 @@ class GalleryInfo extends StatefulWidget {
} }
class _GalleryInfo extends State<GalleryInfo> { class _GalleryInfo extends State<GalleryInfo> {
void showNsfwChanged(dynamic _) {
setState(() {});
}
@override
void initState() {
listener.on("showNsfwChanged", showNsfwChanged);
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool useMobile = MediaQuery.of(context).size.width <= 810; bool useMobile = MediaQuery.of(context).size.width <= 810;
@@ -19,12 +30,24 @@ class _GalleryInfo extends State<GalleryInfo> {
return SingleChildScrollView( return SingleChildScrollView(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints(minHeight: constraints.maxHeight), constraints: BoxConstraints(minHeight: constraints.maxHeight),
child: useMobile ? Column(children: [ child: useMobile
GalleryBasicInfo(widget.gData.meta, widget.gData.pages.first), ? Column(
], children: [
) : Column(children: [ GalleryBasicInfo(
GalleryInfoDesktop(widget.gData), widget.gData.meta, widget.gData.pages.first),
],))); ],
)
: Column(
children: [
GalleryInfoDesktop(widget.gData),
],
)));
}); });
} }
@override
void dispose() {
listener.removeEventListener("showNsfwChanged", showNsfwChanged);
super.dispose();
}
} }

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../api/gallery.dart'; import '../api/gallery.dart';
import 'tags.dart';
import 'thumbnail.dart'; import 'thumbnail.dart';
class GalleryInfoDesktop extends StatelessWidget { class GalleryInfoDesktop extends StatelessWidget {
@@ -45,7 +46,7 @@ class GalleryInfoDesktop extends StatelessWidget {
SelectableText(gData.meta.uploader), SelectableText(gData.meta.uploader),
])), ])),
const VerticalDivider(indent: 10, endIndent: 10), const VerticalDivider(indent: 10, endIndent: 10),
Expanded(child: Column(children: [])), Expanded(child: TagsPanel(gData.tags)),
const VerticalDivider(indent: 10, endIndent: 10), const VerticalDivider(indent: 10, endIndent: 10),
SizedBox(width: 150, child: Column(children: [])), SizedBox(width: 150, child: Column(children: [])),
])), ])),

80
lib/components/tags.dart Normal file
View File

@@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import '../api/gallery.dart';
import '../globals.dart';
class TagsPanel extends StatefulWidget {
const TagsPanel(this.tags, {Key? key}) : super(key: key);
final List<Tag> tags;
@override
State<TagsPanel> createState() => _TagsPanel();
}
String _getTag(Tag tag) {
final tags = tag.tag.split(":");
if (tags.length < 2) return tag.translated ?? tag.tag;
final name = tags[1]!;
return tag.translated ?? name;
}
class _TagsPanel extends State<TagsPanel> {
List<(String, List<Tag>)>? data;
@override
void initState() {
Map<String, List<Tag>> maps = {};
for (var e in widget.tags) {
final tags = e.tag.split(":");
if (tags.length < 2) {
final list = maps[""] ?? [];
list.add(e);
maps[""] = list;
continue;
}
final name = tags[0];
final list = maps[name] ?? [];
list.add(e);
maps[name] = list;
}
data = [];
maps.forEach((key, value) {
data!.add((key, value));
});
if (tags.rows == null) {
tags.getRows().then((re) {
if (re) setState(() {});
});
}
super.initState();
}
@override
Widget build(BuildContext context) {
final cs = Theme.of(context).colorScheme;
return ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: data!.length,
itemBuilder: (context, index) {
final t = data![index].$1;
final ta = data![index].$2;
final namespace =
"${tags.getTagTranslate(t) ?? t}${AppLocalizations.of(context)!.colon}";
return Wrap(
children: List.generate(ta.length + 1, (index) {
if (index == 0) {
return Container(
margin: const EdgeInsets.all(2),
child: SelectableText(namespace));
} else {
return Container(
margin: const EdgeInsets.all(2),
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(4.0)),
border: Border.all(width: 1, color: cs.primary),
),
child: SelectableText(_getTag(ta[index - 1]!)));
}
}));
});
}
}

View File

@@ -2,6 +2,7 @@ import 'dart:io';
import 'package:cookie_jar/cookie_jar.dart'; import 'package:cookie_jar/cookie_jar.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart';
import 'package:event_listener/event_listener.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
@@ -18,6 +19,7 @@ import 'config/shared_preferences.dart';
import 'config/windows.dart'; import 'config/windows.dart';
import 'main.dart'; import 'main.dart';
import 'platform/path.dart'; import 'platform/path.dart';
import 'tags.dart';
import 'utils.dart'; import 'utils.dart';
final dio = Dio() final dio = Dio()
@@ -111,8 +113,10 @@ EHApi get api {
final AuthInfo auth = AuthInfo(); final AuthInfo auth = AuthInfo();
final Path platformPath = Path(); final Path platformPath = Path();
final TagsInfo tags = TagsInfo();
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey = final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey =
GlobalKey<ScaffoldMessengerState>(); GlobalKey<ScaffoldMessengerState>();
final EventListener listener = EventListener();
enum MoreVertSettings { enum MoreVertSettings {
setServerUrl, setServerUrl,
@@ -159,6 +163,24 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
value: MoreVertSettings.settings, value: MoreVertSettings.settings,
child: Text(AppLocalizations.of(context)!.settings))); child: Text(AppLocalizations.of(context)!.settings)));
} }
var showNsfw = prefs.getBool("showNsfw") ?? false;
list.add(PopupMenuItem(
child: StatefulBuilder(
builder: (context, setState) => CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
value: showNsfw,
onChanged: (value) {
if (value != null) {
prefs.setBool("showNsfw", value);
listener.emit("showNsfwChanged", null);
setState(() {
showNsfw = value;
});
}
},
title: Text(AppLocalizations.of(context)!.showNsfw),
),
)));
return list; return list;
} }
@@ -219,6 +241,7 @@ final _authLog = Logger("AuthLog");
void clearAllStates(BuildContext context) { void clearAllStates(BuildContext context) {
auth.clear(); auth.clear();
tags.clear();
checkAuth(context); checkAuth(context);
} }

View File

@@ -25,5 +25,6 @@
"useTitleJpn": "Use Japanese title first", "useTitleJpn": "Use Japanese title first",
"showNsfw": "Show NSFW image by default", "showNsfw": "Show NSFW image by default",
"read": "Read", "read": "Read",
"download": "Download" "download": "Download",
"colon": ":"
} }

View File

@@ -25,5 +25,6 @@
"useTitleJpn": "优先使用日语标题", "useTitleJpn": "优先使用日语标题",
"showNsfw": "默认显示NSFW图片", "showNsfw": "默认显示NSFW图片",
"read": "阅读", "read": "阅读",
"download": "下载" "download": "下载",
"colon": ":"
} }

34
lib/tags.dart Normal file
View File

@@ -0,0 +1,34 @@
import 'package:logging/logging.dart';
import 'api/gallery.dart';
import 'globals.dart';
final _log = Logger("TagsInfo");
class TagsInfo {
TagsInfo();
List<Tag>? _rows;
List<Tag>? get rows => _rows;
void clear() {
_rows = null;
}
Future<bool> getRows() async {
try {
_rows = (await api.getRowTags()).unwrap();
return true;
} catch (e) {
_log.warning("Failed to load row tags:", e);
_rows = null;
return false;
}
}
String? getTagTranslate(String row) {
final key = "rows:$row";
if (_rows == null) return null;
final tag = _rows!.indexWhere((e) => e.tag == key);
if (tag == -1) return null;
return _rows![tag].translated;
}
}