diff --git a/lib/api/client.dart b/lib/api/client.dart index b759827..6924cda 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -340,6 +340,13 @@ abstract class _EHApi { @Part(name: "type") String t = "import", @CancelRequest() CancelToken? cancel, }); + @PUT('/task') + @MultiPart() + Future> createUpdateTagTranslationTask({ + @Part(name: "cfg") UpdateTagTranslationConfig? cfg, + @Part(name: "type") String t = "update_tag_translation", + @CancelRequest() CancelToken? cancel, + }); @GET('/task/download_cfg') Future> getDefaultDownloadConfig( {@CancelRequest() CancelToken? cancel}); diff --git a/lib/api/client.g.dart b/lib/api/client.g.dart index 0f47af7..b42c50d 100644 --- a/lib/api/client.g.dart +++ b/lib/api/client.g.dart @@ -1911,6 +1911,57 @@ class __EHApi implements _EHApi { return _value; } + @override + Future> createUpdateTagTranslationTask({ + UpdateTagTranslationConfig? cfg, + String t = "update_tag_translation", + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + final _data = FormData(); + _data.fields.add(MapEntry( + 'cfg', + jsonEncode(cfg ?? {}), + )); + _data.fields.add(MapEntry( + 'type', + t, + )); + final _options = _setStreamType>(Options( + method: 'PUT', + headers: _headers, + extra: _extra, + contentType: 'multipart/form-data', + ) + .compose( + _dio.options, + '/task', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + ))); + final _result = await _dio.fetch>(_options); + late ApiResult _value; + try { + _value = ApiResult.fromJson( + _result.data!, + (json) => Task.fromJson(json as Map), + ); + } on Object catch (e, s) { + errorLogger?.logError(e, s, _options); + rethrow; + } + return _value; + } + @override Future> getDefaultDownloadConfig( {CancelToken? cancel}) async { diff --git a/lib/api/task.dart b/lib/api/task.dart index edf4651..c5c244c 100644 --- a/lib/api/task.dart +++ b/lib/api/task.dart @@ -433,3 +433,12 @@ class DefaultImportConfig { _$DefaultImportConfigFromJson(json); Map toJson() => _$DefaultImportConfigToJson(this); } + +@JsonSerializable() +class UpdateTagTranslationConfig { + UpdateTagTranslationConfig({this.file}); + String? file; + factory UpdateTagTranslationConfig.fromJson(Map json) => + _$UpdateTagTranslationConfigFromJson(json); + Map toJson() => _$UpdateTagTranslationConfigToJson(this); +} diff --git a/lib/api/task.g.dart b/lib/api/task.g.dart index 0d06b70..3686047 100644 --- a/lib/api/task.g.dart +++ b/lib/api/task.g.dart @@ -273,3 +273,15 @@ Map _$DefaultImportConfigToJson( 'mpv': instance.mpv, 'remove_previous_gallery': instance.removePreviousGallery, }; + +UpdateTagTranslationConfig _$UpdateTagTranslationConfigFromJson( + Map json) => + UpdateTagTranslationConfig( + file: json['file'] as String?, + ); + +Map _$UpdateTagTranslationConfigToJson( + UpdateTagTranslationConfig instance) => + { + 'file': instance.file, + }; diff --git a/lib/dialog/new_update_tag_translation_task_page.dart b/lib/dialog/new_update_tag_translation_task_page.dart new file mode 100644 index 0000000..529d721 --- /dev/null +++ b/lib/dialog/new_update_tag_translation_task_page.dart @@ -0,0 +1,128 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:logging/logging.dart'; +import '../api/task.dart'; +import '../globals.dart'; + +final _log = Logger("NewUpdateTagTranslationTaskPage"); + +class NewUpdateTagTranslationTaskPage extends StatefulWidget { + const NewUpdateTagTranslationTaskPage({super.key}); + + static const routeName = "/dialog/new_update_tag_translation_task"; + + @override + State createState() => + _NewUpdateTagTranslationTaskPage(); +} + +class _NewUpdateTagTranslationTaskPage + extends State { + final _formKey = GlobalKey(); + bool _ok = false; + bool _isCreating = false; + CancelToken? _cancel; + String _file = ""; + + @override + void dispose() { + _cancel?.cancel(); + super.dispose(); + } + + Widget _buildWithVecticalPadding(Widget child) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: child, + ); + } + + Future create() async { + try { + _cancel = CancelToken(); + setState(() { + _isCreating = true; + }); + final cfg = + _file.isEmpty ? null : UpdateTagTranslationConfig(file: _file); + (await api.createUpdateTagTranslationTask(cfg: cfg, cancel: _cancel)) + .unwrap(); + _ok = true; + if (!_cancel!.isCancelled) { + setState(() { + _isCreating = false; + }); + } + } catch (e) { + if (!_cancel!.isCancelled) { + _log.warning("Failed to create import task:", e); + setState(() { + _isCreating = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + tryInitApi(context); + if (_ok) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + context.canPop() ? context.pop() : context.go("/task_manager"); + }); + _ok = false; + } + final i18n = AppLocalizations.of(context)!; + final maxWidth = MediaQuery.of(context).size.width; + return Container( + padding: maxWidth < 400 + ? const EdgeInsets.symmetric(vertical: 20, horizontal: 5) + : const EdgeInsets.all(20), + width: maxWidth < 810 ? null : 800, + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)), + child: SingleChildScrollView( + child: Form( + key: _formKey, + child: Column( + children: [ + Stack( + alignment: Alignment.center, + children: [ + Text( + i18n.createUpdateTagTranslationTask, + style: Theme.of(context).textTheme.headlineSmall, + ), + Align( + alignment: Alignment.centerRight, + child: IconButton( + onPressed: () => context.canPop() + ? context.pop() + : context.go("/task_manager"), + icon: const Icon(Icons.close), + )), + ], + ), + _buildWithVecticalPadding(TextFormField( + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.translationFile, + helperText: i18n.translationFileHelp, + helperMaxLines: 3, + ), + onChanged: (value) { + _file = value; + }, + )), + _buildWithVecticalPadding(ElevatedButton( + onPressed: _isCreating + ? null + : () { + create(); + }, + child: Text(i18n.create))), + ], + )))); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 653610c..e05ccb9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -328,5 +328,8 @@ "share": "Share", "failedChangeExpireTime": "Failed to change expired time: ", "failedShareGallery": "Failed to share gallery: ", - "updateTagTranslation": "Update tag's translation" + "updateTagTranslation": "Update tag's translation", + "createUpdateTagTranslationTask": "Create update tag's translation task", + "translationFile": "Tnanslation file", + "translationFileHelp": "The path to json file contains tag's translation. Will download from website if this is empty." } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index c8cff9b..16e43d8 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -328,5 +328,8 @@ "share": "分享", "failedChangeExpireTime": "修改过期时间失败:", "failedShareGallery": "分享画廊失败:", - "updateTagTranslation": "更新标签的翻译" + "updateTagTranslation": "更新标签的翻译", + "createUpdateTagTranslationTask": "创建更新标签翻译的任务", + "translationFile": "翻译文件", + "translationFileHelp": "包含标签翻译的JSON文件路径。未指定时将从网站上下载。" } diff --git a/lib/main.dart b/lib/main.dart index 29604a1..9597ab5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,6 +14,7 @@ import 'dialog/gallery_share_page.dart'; import 'dialog/new_download_task_page.dart'; import 'dialog/new_export_zip_task_page.dart'; import 'dialog/new_import_task_page.dart'; +import 'dialog/new_update_tag_translation_task_page.dart'; import 'dialog/new_user_page.dart'; import 'dialog/task_page.dart'; import 'globals.dart'; @@ -327,6 +328,15 @@ final _router = GoRouter( return "/"; } }), + GoRoute( + path: NewUpdateTagTranslationTaskPage.routeName, + pageBuilder: (context, state) { + return DialogPage( + key: state.pageKey, + builder: (context) { + return NewUpdateTagTranslationTaskPage(); + }); + }), ], observers: [ _NavigatorObserver(), diff --git a/lib/pages/task_manager.dart b/lib/pages/task_manager.dart index 32b7aa7..adb2b4c 100644 --- a/lib/pages/task_manager.dart +++ b/lib/pages/task_manager.dart @@ -221,6 +221,10 @@ class _TaskManagerPage extends State value: TaskType.import, child: Text(i18n.createImportTask), ), + PopupMenuItem( + value: TaskType.updateTagTranslation, + child: Text(i18n.createUpdateTagTranslationTask), + ), ]; }, onSelected: (TaskType type) { @@ -230,6 +234,8 @@ class _TaskManagerPage extends State context.push("/dialog/new_export_zip_task"); } else if (type == TaskType.import) { context.push("/dialog/new_import_task"); + } else if (type == TaskType.updateTagTranslation) { + context.push("/dialog/new_update_tag_translation_task"); } }); }