mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
Update
This commit is contained in:
@@ -6,6 +6,7 @@ import 'package:retrofit/retrofit.dart';
|
||||
import 'api_result.dart';
|
||||
import 'gallery.dart';
|
||||
import 'status.dart';
|
||||
import 'tags.dart';
|
||||
import 'token.dart';
|
||||
import 'user.dart';
|
||||
|
||||
@@ -113,6 +114,12 @@ abstract class _EHApi {
|
||||
{@Query("all") bool? all,
|
||||
@Query("offset") int? offset,
|
||||
@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 {
|
||||
@@ -145,4 +152,8 @@ class EHApi extends __EHApi {
|
||||
Future<ApiResult<EhFiles>> getFiles(List<String> tokens) {
|
||||
return _getFiles(tokens.join(","));
|
||||
}
|
||||
|
||||
Future<ApiResult<Tags>> getTags(List<int> ids) {
|
||||
return _getTags(ids.join(","));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -530,6 +530,70 @@ class __EHApi implements _EHApi {
|
||||
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) {
|
||||
if (T != dynamic &&
|
||||
!(requestOptions.responseType == ResponseType.bytes ||
|
||||
|
||||
15
lib/api/tags.dart
Normal file
15
lib/api/tags.dart
Normal 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>))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:eh_downloader_flutter/globals.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import '../api/gallery.dart';
|
||||
import 'gallery_basic_info.dart';
|
||||
@@ -12,6 +13,16 @@ class GalleryInfo extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _GalleryInfo extends State<GalleryInfo> {
|
||||
void showNsfwChanged(dynamic _) {
|
||||
setState(() {});
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
listener.on("showNsfwChanged", showNsfwChanged);
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
bool useMobile = MediaQuery.of(context).size.width <= 810;
|
||||
@@ -19,12 +30,24 @@ class _GalleryInfo extends State<GalleryInfo> {
|
||||
return SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(minHeight: constraints.maxHeight),
|
||||
child: useMobile ? Column(children: [
|
||||
GalleryBasicInfo(widget.gData.meta, widget.gData.pages.first),
|
||||
],
|
||||
) : Column(children: [
|
||||
GalleryInfoDesktop(widget.gData),
|
||||
],)));
|
||||
child: useMobile
|
||||
? Column(
|
||||
children: [
|
||||
GalleryBasicInfo(
|
||||
widget.gData.meta, widget.gData.pages.first),
|
||||
],
|
||||
)
|
||||
: Column(
|
||||
children: [
|
||||
GalleryInfoDesktop(widget.gData),
|
||||
],
|
||||
)));
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
listener.removeEventListener("showNsfwChanged", showNsfwChanged);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import '../api/gallery.dart';
|
||||
import 'tags.dart';
|
||||
import 'thumbnail.dart';
|
||||
|
||||
class GalleryInfoDesktop extends StatelessWidget {
|
||||
@@ -45,7 +46,7 @@ class GalleryInfoDesktop extends StatelessWidget {
|
||||
SelectableText(gData.meta.uploader),
|
||||
])),
|
||||
const VerticalDivider(indent: 10, endIndent: 10),
|
||||
Expanded(child: Column(children: [])),
|
||||
Expanded(child: TagsPanel(gData.tags)),
|
||||
const VerticalDivider(indent: 10, endIndent: 10),
|
||||
SizedBox(width: 150, child: Column(children: [])),
|
||||
])),
|
||||
|
||||
80
lib/components/tags.dart
Normal file
80
lib/components/tags.dart
Normal 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]!)));
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio/dio.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/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@@ -18,6 +19,7 @@ import 'config/shared_preferences.dart';
|
||||
import 'config/windows.dart';
|
||||
import 'main.dart';
|
||||
import 'platform/path.dart';
|
||||
import 'tags.dart';
|
||||
import 'utils.dart';
|
||||
|
||||
final dio = Dio()
|
||||
@@ -111,8 +113,10 @@ EHApi get api {
|
||||
|
||||
final AuthInfo auth = AuthInfo();
|
||||
final Path platformPath = Path();
|
||||
final TagsInfo tags = TagsInfo();
|
||||
final GlobalKey<ScaffoldMessengerState> rootScaffoldMessengerKey =
|
||||
GlobalKey<ScaffoldMessengerState>();
|
||||
final EventListener listener = EventListener();
|
||||
|
||||
enum MoreVertSettings {
|
||||
setServerUrl,
|
||||
@@ -159,6 +163,24 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
|
||||
value: MoreVertSettings.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;
|
||||
}
|
||||
|
||||
@@ -219,6 +241,7 @@ final _authLog = Logger("AuthLog");
|
||||
|
||||
void clearAllStates(BuildContext context) {
|
||||
auth.clear();
|
||||
tags.clear();
|
||||
checkAuth(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
"useTitleJpn": "Use Japanese title first",
|
||||
"showNsfw": "Show NSFW image by default",
|
||||
"read": "Read",
|
||||
"download": "Download"
|
||||
"download": "Download",
|
||||
"colon": ":"
|
||||
}
|
||||
|
||||
@@ -25,5 +25,6 @@
|
||||
"useTitleJpn": "优先使用日语标题",
|
||||
"showNsfw": "默认显示NSFW图片",
|
||||
"read": "阅读",
|
||||
"download": "下载"
|
||||
"download": "下载",
|
||||
"colon": ":"
|
||||
}
|
||||
|
||||
34
lib/tags.dart
Normal file
34
lib/tags.dart
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user