Add NSFW marking functionality

This commit is contained in:
2023-11-30 13:20:33 +08:00
parent abb95ba1ba
commit a154261acf
7 changed files with 246 additions and 12 deletions

View File

@@ -170,6 +170,27 @@ abstract class _EHApi {
@Query("max_length") int? maxLength,
@Query("export_ad") bool? exportAd,
@CancelRequest() CancelToken? cancel});
@POST('/filemeta')
@MultiPart()
Future<ApiResult<dynamic>> updateGalleryFileMeta(@Part(name: "gid") int gid,
{@Part(name: "is_nsfw") bool? isNsfw,
@Part(name: "is_ad") bool? isAd,
@Part(name: "excludes") String? excludes,
@CancelRequest() CancelToken? cancel});
@POST('/filemeta')
@MultiPart()
Future<ApiResult<dynamic>> updateFileMeta(@Part(name: "token") String token,
{@Part(name: "is_nsfw") bool? isNsfw,
@Part(name: "is_ad") bool? isAd,
@CancelRequest() CancelToken? cancel});
@POST('/filemeta')
@MultiPart()
Future<ApiResult<dynamic>> updateFilesMeta(
@Part(name: "tokens") String tokens,
{@Part(name: "is_nsfw") bool? isNsfw,
@Part(name: "is_ad") bool? isAd,
@CancelRequest() CancelToken? cancel});
}
class EHApi extends __EHApi {

View File

@@ -688,6 +688,175 @@ class __EHApi implements _EHApi {
return httpResponse;
}
@override
Future<ApiResult<dynamic>> updateGalleryFileMeta(
int gid, {
bool? isNsfw,
bool? isAd,
String? excludes,
CancelToken? cancel,
}) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
_data.fields.add(MapEntry(
'gid',
gid.toString(),
));
if (isNsfw != null) {
_data.fields.add(MapEntry(
'is_nsfw',
isNsfw.toString(),
));
}
if (isAd != null) {
_data.fields.add(MapEntry(
'is_ad',
isAd.toString(),
));
}
if (excludes != null) {
_data.fields.add(MapEntry(
'excludes',
excludes,
));
}
final _result = await _dio
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<dynamic>>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/filemeta',
queryParameters: queryParameters,
data: _data,
cancelToken: cancel,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
))));
final value = ApiResult<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
return value;
}
@override
Future<ApiResult<dynamic>> updateFileMeta(
String token, {
bool? isNsfw,
bool? isAd,
CancelToken? cancel,
}) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
_data.fields.add(MapEntry(
'token',
token,
));
if (isNsfw != null) {
_data.fields.add(MapEntry(
'is_nsfw',
isNsfw.toString(),
));
}
if (isAd != null) {
_data.fields.add(MapEntry(
'is_ad',
isAd.toString(),
));
}
final _result = await _dio
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<dynamic>>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/filemeta',
queryParameters: queryParameters,
data: _data,
cancelToken: cancel,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
))));
final value = ApiResult<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
return value;
}
@override
Future<ApiResult<dynamic>> updateFilesMeta(
String tokens, {
bool? isNsfw,
bool? isAd,
CancelToken? cancel,
}) async {
const _extra = <String, dynamic>{};
final queryParameters = <String, dynamic>{};
queryParameters.removeWhere((k, v) => v == null);
final _headers = <String, dynamic>{};
final _data = FormData();
_data.fields.add(MapEntry(
'tokens',
tokens,
));
if (isNsfw != null) {
_data.fields.add(MapEntry(
'is_nsfw',
isNsfw.toString(),
));
}
if (isAd != null) {
_data.fields.add(MapEntry(
'is_ad',
isAd.toString(),
));
}
final _result = await _dio
.fetch<Map<String, dynamic>>(_setStreamType<ApiResult<dynamic>>(Options(
method: 'POST',
headers: _headers,
extra: _extra,
contentType: 'multipart/form-data',
)
.compose(
_dio.options,
'/filemeta',
queryParameters: queryParameters,
data: _data,
cancelToken: cancel,
)
.copyWith(
baseUrl: _combineBaseUrls(
_dio.options.baseUrl,
baseUrl,
))));
final value = ApiResult<dynamic>.fromJson(
_result.data!,
(json) => json as dynamic,
);
return value;
}
RequestOptions _setStreamType<T>(RequestOptions requestOptions) {
if (T != dynamic &&
!(requestOptions.responseType == ResponseType.bytes ||

View File

@@ -162,6 +162,7 @@ class GalleryData {
final GMeta meta;
final List<Tag> tags;
final List<ExtendedPMeta> pages;
bool get isAllNsfw => pages.every((page) => page.isNsfw);
factory GalleryData.fromJson(Map<String, dynamic> json) =>
_$GalleryDataFromJson(json);

View File

@@ -27,6 +27,12 @@ class GalleryPage extends StatefulWidget {
@override
State<GalleryPage> createState() => _GalleryPage();
// ignore: library_private_types_in_public_api
static _GalleryPage of(BuildContext context) =>
context.findAncestorStateOfType<_GalleryPage>()!;
// ignore: library_private_types_in_public_api
static _GalleryPage? maybeOf(BuildContext context) =>
context.findAncestorStateOfType<_GalleryPage>();
}
class _GalleryPage extends State<GalleryPage>
@@ -37,7 +43,24 @@ class _GalleryPage extends State<GalleryPage>
EhFiles? _files;
Object? _error;
CancelToken? _cancel;
CancelToken? _markAsNsfwCancel;
bool _isLoading = false;
bool? get isAllNsfw => _data?.isAllNsfw;
Future<void> markGalleryAsNsfw(bool isNsfw) async {
try {
_markAsNsfwCancel = CancelToken();
(await api.updateGalleryFileMeta(_gid,
isNsfw: isNsfw, cancel: _markAsNsfwCancel))
.unwrap();
if (!_cancel!.isCancelled) {
_fetchData();
}
} catch (e) {
if (!_cancel!.isCancelled) {
_log.severe("Failed to mark gallery $_gid:", e);
}
}
}
Future<void> _fetchData() async {
try {
@@ -124,6 +147,7 @@ class _GalleryPage extends State<GalleryPage>
@override
void dispose() {
_cancel?.cancel();
_markAsNsfwCancel?.cancel();
super.dispose();
}
}

View File

@@ -20,6 +20,7 @@ import 'auth.dart';
import 'config/base.dart';
import 'config/shared_preferences.dart';
import 'config/windows.dart';
import 'gallery.dart';
import 'main.dart';
import 'platform/clipboard.dart';
import 'platform/display.dart';
@@ -133,6 +134,8 @@ enum MoreVertSettings {
setServerUrl,
createRootUser,
settings,
markAsNsfw,
markAsNonNsfw,
}
void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
@@ -146,6 +149,12 @@ void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
case MoreVertSettings.settings:
context.push("/settings");
break;
case MoreVertSettings.markAsNsfw:
GalleryPage.maybeOf(context)?.markGalleryAsNsfw(true);
break;
case MoreVertSettings.markAsNonNsfw:
GalleryPage.maybeOf(context)?.markGalleryAsNsfw(false);
break;
default:
break;
}
@@ -183,11 +192,7 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
onChanged: (value) {
if (value != null) {
prefs.setBool("showNsfw", value);
try {
listener.emit("showNsfwChanged", null);
} catch (e) {
// Do nothing.
}
listener.tryEmit("showNsfwChanged", null);
setState(() {
showNsfw = value;
});
@@ -205,11 +210,7 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
onChanged: (value) {
if (value != null) {
prefs.setBool("displayAd", value);
try {
listener.emit("displayAdChanged", null);
} catch (e) {
// Do nothing.
}
listener.tryEmit("displayAdChanged", null);
setState(() {
displayAd = value;
});
@@ -218,6 +219,20 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
title: Text(AppLocalizations.of(context)!.displayAd),
),
)));
if (path == "/gallery/:gid") {
list.add(const PopupMenuDivider());
final isAllNsfw = GalleryPage.of(context).isAllNsfw;
if (isAllNsfw != null) {
list.add(PopupMenuItem(
value: isAllNsfw
? MoreVertSettings.markAsNonNsfw
: MoreVertSettings.markAsNsfw,
child: Text(isAllNsfw
? AppLocalizations.of(context)!.markAsNonNsfw
: AppLocalizations.of(context)!.markAsNsfw),
));
}
}
return list;
}

View File

@@ -87,5 +87,7 @@
"downloadZipFailed": "Failed to download ZIP file.",
"rating": "Rating",
"preventScreenCapture": "Prevent screen capture",
"seeMoreInfo": "See more information"
"seeMoreInfo": "See more information",
"markAsNsfw": "Mark as NSFW",
"markAsNonNsfw": "Mark as non-NSFW"
}

View File

@@ -87,5 +87,7 @@
"downloadZipFailed": "Zip文件下载失败。",
"rating": "评分",
"preventScreenCapture": "防止截屏",
"seeMoreInfo": "显示更多信息"
"seeMoreInfo": "显示更多信息",
"markAsNsfw": "标记为NSFW",
"markAsNonNsfw": "标记为非NSFW"
}