From 79e1ca9bbebc12a684e6b34b92a4d05e7023770d Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 10 Aug 2024 07:33:29 +0000 Subject: [PATCH] Add import task --- lib/api/client.dart | 12 + lib/api/client.g.dart | 88 ++++++ lib/api/status.dart | 6 + lib/api/status.g.dart | 4 + lib/api/task.dart | 59 ++++ lib/api/task.g.dart | 53 ++++ lib/components/task.dart | 8 + lib/dialog/new_export_zip_task_page.dart | 1 - lib/dialog/new_import_task_page.dart | 332 +++++++++++++++++++++++ lib/l10n/app_en.arb | 5 +- lib/l10n/app_zh.arb | 5 +- lib/main.dart | 18 ++ lib/pages/create_root_user.dart | 74 +++-- lib/pages/login.dart | 56 ++-- lib/pages/settings/server_url.dart | 12 +- lib/pages/task_manager.dart | 6 + pubspec.lock | 195 +++++++------ 17 files changed, 793 insertions(+), 141 deletions(-) create mode 100644 lib/dialog/new_import_task_page.dart diff --git a/lib/api/client.dart b/lib/api/client.dart index c5cec64..75856df 100644 --- a/lib/api/client.dart +++ b/lib/api/client.dart @@ -308,12 +308,24 @@ abstract class _EHApi { {@Part(name: 'cfg') ExportZipConfig? cfg, @Part(name: "type") String t = "export_zip", @CancelRequest() CancelToken? cancel}); + @PUT('/task') + @MultiPart() + Future> createImportTask( + @Part(name: "gid") int gid, + @Part(name: "token") String token, { + @Part(name: "cfg") ImportConfig? cfg, + @Part(name: "type") String t = "import", + @CancelRequest() CancelToken? cancel, + }); @GET('/task/download_cfg') Future> getDefaultDownloadConfig( {@CancelRequest() CancelToken? cancel}); @GET('/task/export_zip_cfg') Future> getDefaultExportZipConfig( {@CancelRequest() CancelToken? cancel}); + @GET('/task/import_cfg') + Future> getDefaultImportConfig( + {@CancelRequest() CancelToken? cancel}); } class EHApi extends __EHApi { diff --git a/lib/api/client.g.dart b/lib/api/client.g.dart index 62b5f2d..2c76277 100644 --- a/lib/api/client.g.dart +++ b/lib/api/client.g.dart @@ -1460,6 +1460,61 @@ class __EHApi implements _EHApi { return value; } + @override + Future> createImportTask( + int gid, + String token, { + ImportConfig? cfg, + String t = "import", + CancelToken? cancel, + }) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + final _data = FormData(); + _data.fields.add(MapEntry( + 'gid', + gid.toString(), + )); + _data.fields.add(MapEntry( + 'token', + token, + )); + _data.fields.add(MapEntry( + 'cfg', + jsonEncode(cfg ?? {}), + )); + _data.fields.add(MapEntry( + 'type', + t, + )); + final _result = await _dio + .fetch>(_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 value = ApiResult.fromJson( + _result.data!, + (json) => Task.fromJson(json as Map), + ); + return value; + } + @override Future> getDefaultDownloadConfig( {CancelToken? cancel}) async { @@ -1526,6 +1581,39 @@ class __EHApi implements _EHApi { return value; } + @override + Future> getDefaultImportConfig( + {CancelToken? cancel}) async { + final _extra = {}; + final queryParameters = {}; + queryParameters.removeWhere((k, v) => v == null); + final _headers = {}; + const Map? _data = null; + final _result = await _dio.fetch>( + _setStreamType>(Options( + method: 'GET', + headers: _headers, + extra: _extra, + ) + .compose( + _dio.options, + '/task/import_cfg', + queryParameters: queryParameters, + data: _data, + cancelToken: cancel, + ) + .copyWith( + baseUrl: _combineBaseUrls( + _dio.options.baseUrl, + baseUrl, + )))); + final value = ApiResult.fromJson( + _result.data!, + (json) => DefaultImportConfig.fromJson(json as Map), + ); + return value; + } + RequestOptions _setStreamType(RequestOptions requestOptions) { if (T != dynamic && !(requestOptions.responseType == ResponseType.bytes || diff --git a/lib/api/status.dart b/lib/api/status.dart index 47b8213..bd32a79 100644 --- a/lib/api/status.dart +++ b/lib/api/status.dart @@ -27,6 +27,8 @@ class ServerStatus { this.meilisearch, required this.noUser, required this.isDocker, + required this.ffprobeBinaryEnabled, + required this.libzipEnabled, }); @JsonKey(name: 'ffmpeg_api_enabled') @@ -40,6 +42,10 @@ class ServerStatus { final bool noUser; @JsonKey(name: 'is_docker') final bool isDocker; + @JsonKey(name: 'ffprobe_binary_enabled') + final bool ffprobeBinaryEnabled; + @JsonKey(name: 'libzip_enabled') + final bool libzipEnabled; factory ServerStatus.fromJson(Map json) => _$ServerStatusFromJson(json); diff --git a/lib/api/status.g.dart b/lib/api/status.g.dart index 6d87cc1..c3fc875 100644 --- a/lib/api/status.g.dart +++ b/lib/api/status.g.dart @@ -28,6 +28,8 @@ ServerStatus _$ServerStatusFromJson(Map json) => ServerStatus( json['meilisearch'] as Map), noUser: json['no_user'] as bool, isDocker: json['is_docker'] as bool, + ffprobeBinaryEnabled: json['ffprobe_binary_enabled'] as bool, + libzipEnabled: json['libzip_enabled'] as bool, ); Map _$ServerStatusToJson(ServerStatus instance) => @@ -38,4 +40,6 @@ Map _$ServerStatusToJson(ServerStatus instance) => 'meilisearch': instance.meilisearch, 'no_user': instance.noUser, 'is_docker': instance.isDocker, + 'ffprobe_binary_enabled': instance.ffprobeBinaryEnabled, + 'libzip_enabled': instance.libzipEnabled, }; diff --git a/lib/api/task.dart b/lib/api/task.dart index d85264e..34c17c6 100644 --- a/lib/api/task.dart +++ b/lib/api/task.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'config.dart'; part 'task.g.dart'; @@ -346,3 +347,61 @@ class ExportZipConfig { _$ExportZipConfigFromJson(json); Map toJson() => _$ExportZipConfigToJson(this); } + +enum ImportSize { + @JsonValue(0) + original, + @JsonValue(780) + x780, + @JsonValue(980) + x980, + @JsonValue(1280) + resampled, + @JsonValue(1600) + x1600, + @JsonValue(2400) + x2400, +} + +@JsonSerializable() +class ImportConfig { + ImportConfig( + this.importPath, { + this.size = ImportSize.original, + this.maxImportImgCount, + this.mpv, + this.method, + this.removePreviousGallery, + }); + @JsonKey(name: 'max_import_img_count') + int? maxImportImgCount; + bool? mpv; + ImportMethod? method; + @JsonKey(name: 'remove_previous_gallery') + bool? removePreviousGallery; + @JsonKey(name: 'import_path') + String importPath; + ImportSize size; + factory ImportConfig.fromJson(Map json) => + _$ImportConfigFromJson(json); + Map toJson() => _$ImportConfigToJson(this); +} + +@JsonSerializable() +class DefaultImportConfig { + DefaultImportConfig({ + this.maxImportImgCount, + this.method, + this.mpv, + this.removePreviousGallery, + }); + @JsonKey(name: 'max_import_img_count') + int? maxImportImgCount; + ImportMethod? method; + bool? mpv; + @JsonKey(name: 'remove_previous_gallery') + bool? removePreviousGallery; + factory DefaultImportConfig.fromJson(Map json) => + _$DefaultImportConfigFromJson(json); + Map toJson() => _$DefaultImportConfigToJson(this); +} diff --git a/lib/api/task.g.dart b/lib/api/task.g.dart index a28694d..e6ae6de 100644 --- a/lib/api/task.g.dart +++ b/lib/api/task.g.dart @@ -205,3 +205,56 @@ Map _$ExportZipConfigToJson(ExportZipConfig instance) => 'max_length': instance.maxLength, 'export_ad': instance.exportAd, }; + +ImportConfig _$ImportConfigFromJson(Map json) => ImportConfig( + json['import_path'] as String, + size: $enumDecodeNullable(_$ImportSizeEnumMap, json['size']) ?? + ImportSize.original, + maxImportImgCount: (json['max_import_img_count'] as num?)?.toInt(), + mpv: json['mpv'] as bool?, + method: $enumDecodeNullable(_$ImportMethodEnumMap, json['method']), + removePreviousGallery: json['remove_previous_gallery'] as bool?, + ); + +Map _$ImportConfigToJson(ImportConfig instance) => + { + 'max_import_img_count': instance.maxImportImgCount, + 'mpv': instance.mpv, + 'method': _$ImportMethodEnumMap[instance.method], + 'remove_previous_gallery': instance.removePreviousGallery, + 'import_path': instance.importPath, + 'size': _$ImportSizeEnumMap[instance.size]!, + }; + +const _$ImportSizeEnumMap = { + ImportSize.original: 0, + ImportSize.x780: 780, + ImportSize.x980: 980, + ImportSize.resampled: 1280, + ImportSize.x1600: 1600, + ImportSize.x2400: 2400, +}; + +const _$ImportMethodEnumMap = { + ImportMethod.copy: 0, + ImportMethod.copyThenDelete: 1, + ImportMethod.move: 2, + ImportMethod.keep: 3, +}; + +DefaultImportConfig _$DefaultImportConfigFromJson(Map json) => + DefaultImportConfig( + maxImportImgCount: (json['max_import_img_count'] as num?)?.toInt(), + method: $enumDecodeNullable(_$ImportMethodEnumMap, json['method']), + mpv: json['mpv'] as bool?, + removePreviousGallery: json['remove_previous_gallery'] as bool?, + ); + +Map _$DefaultImportConfigToJson( + DefaultImportConfig instance) => + { + 'max_import_img_count': instance.maxImportImgCount, + 'method': _$ImportMethodEnumMap[instance.method], + 'mpv': instance.mpv, + 'remove_previous_gallery': instance.removePreviousGallery, + }; diff --git a/lib/components/task.dart b/lib/components/task.dart index b9e7168..6734536 100644 --- a/lib/components/task.dart +++ b/lib/components/task.dart @@ -90,6 +90,14 @@ class _TaskView extends State { return Text("${i18n.exportZipTask} $title", maxLines: 1, overflow: TextOverflow.ellipsis); } + if (typ == TaskType.import) { + final gid = widget.task.base.gid; + final title = tasks.meta.containsKey(gid) + ? tasks.meta[gid]!.preferredTitle + : gid.toString(); + return Text("${i18n.importTask} $title", + maxLines: 1, overflow: TextOverflow.ellipsis); + } return Container(); } diff --git a/lib/dialog/new_export_zip_task_page.dart b/lib/dialog/new_export_zip_task_page.dart index 396f00d..6a94e49 100644 --- a/lib/dialog/new_export_zip_task_page.dart +++ b/lib/dialog/new_export_zip_task_page.dart @@ -4,7 +4,6 @@ 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/gallery.dart'; import '../components/labeled_checkbox.dart'; import '../components/number_field.dart'; import '../globals.dart'; diff --git a/lib/dialog/new_import_task_page.dart b/lib/dialog/new_import_task_page.dart new file mode 100644 index 0000000..2195677 --- /dev/null +++ b/lib/dialog/new_import_task_page.dart @@ -0,0 +1,332 @@ +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/config.dart'; +import '../api/task.dart'; +import '../components/labeled_checkbox.dart'; +import '../components/number_field.dart'; +import '../globals.dart'; +import '../utils/parse_url.dart'; + +final _log = Logger("NewImportTaskPage"); + +class NewImportTaskPage extends StatefulWidget { + const NewImportTaskPage({super.key, this.gid, this.token}); + final int? gid; + final String? token; + + static const routeName = "/dialog/new_import_task"; + + @override + State createState() => _NewImportTaskPage(); +} + +class _NewImportTaskPage extends State { + final _formKey = GlobalKey(); + late TextEditingController _urlController; + late TextEditingController _gidController; + late TextEditingController _tokenController; + late ImportConfig _cfg; + int? _gid; + String? _token; + bool _ok = false; + bool _isCreating = false; + CancelToken? _cancel; + CancelToken? _cancel2; + bool _fetched = false; + DefaultImportConfig? _dftCfg; + bool _useCfg = false; + + @override + void initState() { + _urlController = TextEditingController( + text: widget.gid != null && widget.token != null + ? "https://e-hentai.org/g/${widget.gid}/${widget.token}/" + : ""); + _gidController = TextEditingController(text: widget.gid?.toString()); + _tokenController = TextEditingController(text: widget.token); + _cfg = ImportConfig(""); + _gid = widget.gid; + _token = widget.token; + super.initState(); + } + + @override + void dispose() { + _urlController.dispose(); + _gidController.dispose(); + _tokenController.dispose(); + _cancel?.cancel(); + _cancel2?.cancel(); + super.dispose(); + } + + Widget _buildWithVecticalPadding(Widget child) { + return Container( + padding: const EdgeInsets.symmetric(vertical: 8), + child: child, + ); + } + + Future create() async { + if (_gid == null || _token == null || _token!.isEmpty) { + return; + } + try { + _cancel = CancelToken(); + setState(() { + _isCreating = true; + }); + final cfg = + _useCfg ? _cfg : ImportConfig(_cfg.importPath, size: _cfg.size); + (await api.createImportTask(_gid!, _token!, 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; + }); + } + } + } + + Future fetchDefaultCfg() async { + _fetched = true; + try { + _cancel2 = CancelToken(); + _dftCfg = (await api.getDefaultImportConfig(cancel: _cancel2)).unwrap(); + } catch (e) { + if (!_cancel2!.isCancelled) { + _log.warning("Failed to fetch default import config:", e); + } + } + } + + @override + Widget build(BuildContext context) { + tryInitApi(context); + if (_ok) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + context.canPop() ? context.pop() : context.go("/task_manager"); + }); + _ok = false; + } + if (!_fetched) fetchDefaultCfg(); + 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.createImportTask, + 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( + controller: _urlController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.galleryURL, + ), + onChanged: (value) { + final match = parseGalleryUrl(value); + if (match != null) { + setState(() { + _gidController.text = match.$1.toString(); + _tokenController.text = match.$2; + _gid = match.$1; + _token = match.$2; + }); + } + }, + )), + _buildWithVecticalPadding(NumberFormField( + controller: _gidController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.gid, + ), + min: 0, + onChanged: (int? value) { + setState(() { + _gid = value; + }); + if (_token != null && _token!.isNotEmpty) { + _urlController.text = + "https://e-hentai.org/g/$_gid/$_token/"; + } + }, + )), + _buildWithVecticalPadding(TextFormField( + controller: _tokenController, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.galleryToken, + ), + onChanged: (String value) { + setState(() { + _token = value; + }); + if (_gid != null && _token!.isNotEmpty) { + _urlController.text = + "https://e-hentai.org/g/$_gid/$_token/"; + } + }, + )), + _buildWithVecticalPadding(TextFormField( + initialValue: _cfg.importPath, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.importPath, + ), + onChanged: (val) { + setState(() { + _cfg.importPath = val; + }); + }, + )), + _buildWithVecticalPadding( + DropdownButtonFormField( + items: ImportSize.values + .map((e) => DropdownMenuItem( + value: e, child: Text(e.name))) + .toList(), + onChanged: (v) { + if (v != null) { + setState(() { + _cfg.size = v; + }); + } + }, + value: _cfg.size, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.importSize, + ))), + _buildWithVecticalPadding(LabeledCheckbox( + value: _useCfg, + onChanged: (value) { + if (value != null) { + setState(() { + _useCfg = value; + if (_useCfg) { + if (_dftCfg != null) { + _cfg.maxImportImgCount = + _dftCfg?.maxImportImgCount; + _cfg.method = _dftCfg?.method; + _cfg.mpv = _dftCfg?.mpv; + _cfg.removePreviousGallery = + _dftCfg?.removePreviousGallery; + } else { + _cfg = ImportConfig(_cfg.importPath, + size: _cfg.size); + } + } + }); + } + }, + label: Text(i18n.overwriteDefaultConfig), + )), + _useCfg + ? _buildWithVecticalPadding( + DropdownButtonFormField( + items: ImportMethod.values + .map((e) => DropdownMenuItem( + value: e, + child: Text(e.localText(context)))) + .toList(), + onChanged: (v) { + if (v != null) { + setState(() { + _cfg.method = v; + }); + } + }, + value: _cfg.method, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.importMethod, + ))) + : Container(), + _useCfg + ? _buildWithVecticalPadding(NumberFormField( + min: 1, + initialValue: _cfg!.maxImportImgCount, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.maxImportImgCount, + ), + onChanged: (s) { + setState(() { + _cfg!.maxImportImgCount = s; + }); + })) + : Container(), + _useCfg + ? _buildWithVecticalPadding(LabeledCheckbox( + value: _cfg!.mpv ?? false, + onChanged: (b) { + if (b != null) { + setState(() { + _cfg!.mpv = b; + }); + } + }, + label: Text(i18n.mpv))) + : Container(), + _useCfg + ? _buildWithVecticalPadding(LabeledCheckbox( + value: _cfg!.removePreviousGallery ?? false, + onChanged: (b) { + if (b != null) { + setState(() { + _cfg!.removePreviousGallery = b; + }); + } + }, + label: Text(i18n.removePreviousGallery))) + : Container(), + _buildWithVecticalPadding(ElevatedButton( + onPressed: _gid != null && + _token != null && + _token!.isNotEmpty && + !_isCreating && + _cfg.importPath.isNotEmpty + ? () { + create(); + } + : null, + child: Text(i18n.create))), + ], + )))); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index eab6e62..bd7e210 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -266,5 +266,8 @@ "leftOrTop": "Left / Top", "center": "Center", "rightOrBottom": "Right / Bottom", - "thumbnailAlignHelp": "This only works if the scaling method is to touch container from outside/inside." + "thumbnailAlignHelp": "This only works if the scaling method is to touch container from outside/inside.", + "createImportTask": "Create import task", + "importPath": "Path to the file/directory to import", + "importSize": "Image size" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 7e24ebe..36dc90a 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -266,5 +266,8 @@ "leftOrTop": "左/上", "center": "居中", "rightOrBottom": "右/下", - "thumbnailAlignHelp": "仅当缩放方法是缩放以填满/适合容器时生效。" + "thumbnailAlignHelp": "仅当缩放方法是缩放以填满/适合容器时生效。", + "createImportTask": "创建导入任务", + "importPath": "需要导入的文件/文件夹路径", + "importSize": "图像大小" } diff --git a/lib/main.dart b/lib/main.dart index 1039c29..0ba10c2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,6 +12,7 @@ import 'dialog/edit_user_page.dart'; import 'dialog/gallery_details_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_user_page.dart'; import 'dialog/task_page.dart'; import 'globals.dart'; @@ -268,6 +269,23 @@ final _router = GoRouter( return NewExportZipTaskPage(gid: gid); }); }), + GoRoute( + path: NewImportTaskPage.routeName, + pageBuilder: (context, state) { + int? gid; + String? token; + if (state.uri.queryParameters.containsKey("gid")) { + gid = int.tryParse(state.uri.queryParameters["gid"]!); + } + if (state.uri.queryParameters.containsKey("token")) { + token = state.uri.queryParameters["token"]!; + } + return DialogPage( + key: state.pageKey, + builder: (context) { + return NewImportTaskPage(gid: gid, token: token); + }); + }), ], ); diff --git a/lib/pages/create_root_user.dart b/lib/pages/create_root_user.dart index 45e72c8..577b7f5 100644 --- a/lib/pages/create_root_user.dart +++ b/lib/pages/create_root_user.dart @@ -163,9 +163,13 @@ class _CreateRootUserPage extends State _log.warning( "Failed to set skipCreateRootUser."); } else { - context.canPop() - ? context.pop() - : context.go("/"); + if (context.mounted) { + context.canPop() + ? context.pop() + : context.go("/"); + } else { + _log.warning("Context not mounted."); + } } }).catchError((e) { _log.warning( @@ -183,42 +187,58 @@ class _CreateRootUserPage extends State _createRootUser(_username, _password) .then((re) { if (re) { - clearAllStates(context); - context.canPop() - ? context.pop() - : context.go("/"); - } else { - if (!_createdUser) { + if (context.mounted) { + clearAllStates(context); context.canPop() ? context.pop() : context.go("/"); + } else { + _log.warning("Context not mounted."); + } + } else { + if (!_createdUser) { + if (context.mounted) { + context.canPop() + ? context.pop() + : context.go("/"); + } else { + _log.warning("Context not mounted."); + } return; } + if (context.mounted) { + final snackBar = SnackBar( + content: Text( + AppLocalizations.of(context)! + .incorrectUserPassword)); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + setState(() { + _isCreated = false; + }); + } else { + _log.warning("Context not mounted."); + } + } + }).catchError((e) { + _log.severe( + "Failed to create root user:", e); + final isNetworkError = e is! (int, String); + if (context.mounted) { final snackBar = SnackBar( - content: Text( - AppLocalizations.of(context)! - .incorrectUserPassword)); + content: Text(isNetworkError + ? AppLocalizations.of(context)! + .networkError + : AppLocalizations.of(context)! + .internalError)); ScaffoldMessenger.of(context) .showSnackBar(snackBar); setState(() { _isCreated = false; }); + } else { + _log.warning("Context not mounted."); } - }).catchError((e) { - _log.severe( - "Failed to create root user:", e); - final isNetworkError = e is! (int, String); - final snackBar = SnackBar( - content: Text(isNetworkError - ? AppLocalizations.of(context)! - .networkError - : AppLocalizations.of(context)! - .internalError)); - ScaffoldMessenger.of(context) - .showSnackBar(snackBar); - setState(() { - _isCreated = false; - }); }); } : null, diff --git a/lib/pages/login.dart b/lib/pages/login.dart index afdb224..d61b5c0 100644 --- a/lib/pages/login.dart +++ b/lib/pages/login.dart @@ -108,7 +108,11 @@ class _LoginPageState extends State auth.checkAuth().then((re) { _checkAuth = false; if (re) { - build.go("/"); + if (build.mounted) { + build.go("/"); + } else { + _log.warning("Context not mounted."); + } } }).catchError((e) { _log.severe("Failed to check auth info:", e); @@ -190,33 +194,45 @@ class _LoginPageState extends State }); login(_username, _password).then((re) { if (re) { - clearAllStates(context); - context.canPop() - ? context.pop() - : context.go("/"); + if (context.mounted) { + clearAllStates(context); + context.canPop() + ? context.pop() + : context.go("/"); + } else { + _log.warning("Context not mounted."); + } } else { + if (context.mounted) { + final snackBar = SnackBar( + content: Text( + i18n.incorrectUserPassword)); + ScaffoldMessenger.of(context) + .showSnackBar(snackBar); + setState(() { + _isLogin = false; + }); + } else { + _log.warning("Context not mounted."); + } + } + }).catchError((e) { + _log.severe("Failed to login:", e); + final isNetworkError = + e is! (int, String); + if (context.mounted) { final snackBar = SnackBar( - content: Text( - i18n.incorrectUserPassword)); + content: Text(isNetworkError + ? i18n.networkError + : i18n.internalError)); ScaffoldMessenger.of(context) .showSnackBar(snackBar); setState(() { _isLogin = false; }); + } else { + _log.warning("Context not mounted."); } - }).catchError((e) { - _log.severe("Failed to login:", e); - final isNetworkError = - e is! (int, String); - final snackBar = SnackBar( - content: Text(isNetworkError - ? i18n.networkError - : i18n.internalError)); - ScaffoldMessenger.of(context) - .showSnackBar(snackBar); - setState(() { - _isLogin = false; - }); }); } : null, diff --git a/lib/pages/settings/server_url.dart b/lib/pages/settings/server_url.dart index 2b668d2..0de11c1 100644 --- a/lib/pages/settings/server_url.dart +++ b/lib/pages/settings/server_url.dart @@ -131,10 +131,14 @@ class _ServerUrlSettingsPage extends State .setString('baseUrl', _serverUrl + _apiPath) .then((re) { if (re) { - tryInitApi(context); - context.canPop() - ? context.pop() - : context.go("/"); + if (context.mounted) { + tryInitApi(context); + context.canPop() + ? context.pop() + : context.go("/"); + } else { + _log.warning("Context not mounted."); + } } }); } diff --git a/lib/pages/task_manager.dart b/lib/pages/task_manager.dart index 1634151..32b7aa7 100644 --- a/lib/pages/task_manager.dart +++ b/lib/pages/task_manager.dart @@ -217,6 +217,10 @@ class _TaskManagerPage extends State value: TaskType.exportZip, child: Text(i18n.createExportZipTask), ), + PopupMenuItem( + value: TaskType.import, + child: Text(i18n.createImportTask), + ), ]; }, onSelected: (TaskType type) { @@ -224,6 +228,8 @@ class _TaskManagerPage extends State context.push("/dialog/new_download_task"); } else if (type == TaskType.exportZip) { context.push("/dialog/new_export_zip_task"); + } else if (type == TaskType.import) { + context.push("/dialog/new_import_task"); } }); } diff --git a/pubspec.lock b/pubspec.lock index c76f46e..d41bf3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,26 +5,31 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" archive: dependency: transitive description: name: archive - sha256: "6bd38d335f0954f5fad9c79e614604fbf03a0e5b975923dd001b6ea965ef5b4b" + sha256: cb6a278ef2dbb298455e1a713bda08524a175630ec643a242c399c932a0a1f7d url: "https://pub.dev" source: hosted - version: "3.6.0" + version: "3.6.1" args: dependency: transitive description: @@ -85,18 +90,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "1414d6d733a85d8ad2f1dfcb3ea7945759e35a123cb99ccfac75d0758f75edfa" + sha256: dd09dd4e2b078992f42aac7f1a622f01882a8492fef08486b27ddde929c19f04 url: "https://pub.dev" source: hosted - version: "2.4.10" + version: "2.4.12" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -206,26 +211,26 @@ packages: dependency: transitive description: name: device_info_plus - sha256: eead12d1a1ed83d8283ab4c2f3fca23ac4082f29f25f29dff0f758f57d06ec91 + sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074 url: "https://pub.dev" source: hosted - version: "10.1.0" + version: "10.1.2" device_info_plus_platform_interface: dependency: transitive description: name: device_info_plus_platform_interface - sha256: d3b01d5868b50ae571cd1dc6e502fc94d956b665756180f7b16ead09e836fd64 + sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" dio: dependency: "direct main" description: name: dio - sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5" + sha256: e17f6b3097b8c51b72c74c9f071a605c47bcc8893839bd66732457a5ebe73714 url: "https://pub.dev" source: hosted - version: "5.4.3+1" + version: "5.5.0+1" dio_cookie_manager: dependency: "direct main" description: @@ -234,6 +239,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.1" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "36c5b2d79eb17cdae41e974b7a8284fec631651d2a6f39a8a2ff22327e90aeac" + url: "https://pub.dev" + source: hosted + version: "1.0.1" enum_flag: dependency: "direct main" description: @@ -354,26 +367,26 @@ packages: dependency: "direct main" description: name: go_router - sha256: abec47eb8c8c36ebf41d0a4c64dbbe7f956e39a012b3aafc530e951bdc12fe3f + sha256: ddc16d34b0d74cb313986918c0f0885a7ba2fc24d8fb8419de75f0015144ccfe url: "https://pub.dev" source: hosted - version: "14.1.4" + version: "14.2.3" graphs: dependency: transitive description: name: graphs - sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 + sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_multi_server: dependency: transitive description: @@ -426,10 +439,10 @@ packages: dependency: transitive description: name: irondash_engine_context - sha256: e8398cca5e28dc280c87b8c35a6ff4e15be844eabec51e713631f83903563681 + sha256: cd7b769db11a2b5243b037c8a9b1ecaef02e1ae27a2d909ffa78c1dad747bb10 url: "https://pub.dev" source: hosted - version: "0.5.3" + version: "0.5.4" irondash_message_channel: dependency: transitive description: @@ -474,18 +487,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -510,6 +523,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" markdown: dependency: transitive description: @@ -530,18 +551,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: @@ -570,26 +591,26 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0 + sha256: a75164ade98cb7d24cfd0a13c6408927c6b217fa60dee5a7ff5c116a58f28918 url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.0.2" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e + sha256: ac1f4a4847f1ade8e6a87d1f39f5d7c67490738642e2542f559ec38c37489a66 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" palette_generator: dependency: "direct main" description: name: palette_generator - sha256: eb7082b4b97487ebc65b3ad3f6f0b7489b96e76840381ed0e06a46fe7ffd4068 + sha256: d50fbcd69abb80c5baec66d700033b1a320108b1aa17a5961866a12c0abb7c0c url: "https://pub.dev" source: hosted - version: "0.3.3+3" + version: "0.3.3+4" path: dependency: "direct main" description: @@ -602,18 +623,18 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 + sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "9c96da072b421e98183f9ea7464898428e764bc0ce5567f27ec8693442e72514" + sha256: "490539678396d4c3c0b06efdaab75ae60675c3e0c66f72bc04c2e2c1e0e2abeb" url: "https://pub.dev" source: hosted - version: "2.2.5" + version: "2.2.9" path_provider_foundation: dependency: transitive description: @@ -642,10 +663,10 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.3.0" percent_indicator: dependency: "direct main" description: @@ -682,10 +703,10 @@ packages: dependency: transitive description: name: platform - sha256: "12220bb4b65720483f8fa9450b4332347737cf8213dd2840d8b2c823e47243ec" + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -738,10 +759,10 @@ packages: dependency: "direct dev" description: name: retrofit_generator - sha256: a962be21403c2ecdd82d06c863340cff759642ae6ecac5a2c74a9a60377592c3 + sha256: "6bb40ca26b3746ac213e7908755299c7d067b79ab7550a647f5894f6957d240b" url: "https://pub.dev" source: hosted - version: "8.1.0" + version: "8.1.2" screen_retriever: dependency: transitive description: @@ -754,58 +775,58 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: d3bbe5553a986e83980916ded2f0b435ef2e1893dfaa29d5a7a790d0eca12180 + sha256: c272f9cabca5a81adc9b0894381e9c1def363e980f960fa903c604c471b22f68 url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "93d0ec9dd902d85f326068e6a899487d1f65ffcd5798721a95330b26c8131577" + sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974 url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "0a8a893bf4fd1152f93fec03a415d11c27c74454d96e2318a7ac38dd18683ab7" + sha256: "776786cff96324851b656777648f36ac772d88bc4c669acff97b7fce5de3c849" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.5.1" shared_preferences_linux: dependency: transitive description: name: shared_preferences_linux - sha256: "9f2cbcf46d4270ea8be39fa156d86379077c8a5228d9dfdb1164ae0bb93f1faa" + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_platform_interface: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shared_preferences_web: dependency: transitive description: name: shared_preferences_web - sha256: "9aee1089b36bd2aafe06582b7d7817fd317ef05fc30e6ba14bff247d0933042a" + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.2" shared_preferences_windows: dependency: transitive description: name: shared_preferences_windows - sha256: "841ad54f3c8381c480d0c9b508b89a34036f512482c407e6df7a9c4aa2ef8f59" + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.1" shelf: dependency: transitive description: @@ -887,18 +908,18 @@ packages: dependency: transitive description: name: sqlite3 - sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295 + sha256: fde692580bee3379374af1f624eb3e113ab2865ecb161dbe2d8ac2de9735dbdb url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.5" sqlite3_flutter_libs: dependency: "direct main" description: name: sqlite3_flutter_libs - sha256: "1e62698dc1ab396152ccaf3b3990d826244e9f3c8c39b51805f209adcd6dbea3" + sha256: "62bbb4073edbcdf53f40c80775f33eea01d301b7b81417e5b3fb7395416258c1" url: "https://pub.dev" source: hosted - version: "0.5.22" + version: "0.5.24" stack_trace: dependency: transitive description: @@ -935,26 +956,26 @@ packages: dependency: "direct main" description: name: super_clipboard - sha256: "17978c3dc7b6ebf588a8007448f668ad6aa64733b60ed2ae18152113cca39fa2" + sha256: "74098001413e075cc53dee72b68c32eaffc10709df41806800393abaa6dac9d5" url: "https://pub.dev" source: hosted - version: "0.8.16" + version: "0.8.19" super_context_menu: dependency: "direct main" description: name: super_context_menu - sha256: "33f189fd508716d03daa277d890a8b079f691e3e3577d99d81c1e23b2a9968c7" + sha256: c21e8a2d5a2f304c5acf56b78c8fd21a36f83ea9aded513529de022dc0a077f3 url: "https://pub.dev" source: hosted - version: "0.8.16" + version: "0.8.19" super_native_extensions: dependency: transitive description: name: super_native_extensions - sha256: d2d1259c7e43966173c394ef64475e612bd5822095f6cd1008eb978175ce2f0d + sha256: c24676825c9f3ae844676a843d45ad186f2270539ffe72be4277753e46d14e29 url: "https://pub.dev" source: hosted - version: "0.8.16" + version: "0.8.19" synchronized: dependency: transitive description: @@ -975,10 +996,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" timing: dependency: transitive description: @@ -1031,10 +1052,10 @@ packages: dependency: transitive description: name: uuid - sha256: "814e9e88f21a176ae1359149021870e87f7cddaf633ab678a5d2b0bff7fd1ba8" + sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.4.2" vector_math: dependency: transitive description: @@ -1047,10 +1068,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.4" watcher: dependency: transitive description: @@ -1071,34 +1092,34 @@ packages: dependency: transitive description: name: web_socket - sha256: "24301d8c293ce6fe327ffe6f59d8fd8834735f0ec36e4fd383ec7ff8a64aa078" + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" url: "https://pub.dev" source: hosted - version: "0.1.5" + version: "0.1.6" web_socket_channel: dependency: "direct main" description: name: web_socket_channel - sha256: a2d56211ee4d35d9b344d9d4ce60f362e4f5d1aafb988302906bd732bc731276 + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.1" win32: dependency: transitive description: name: win32 - sha256: a79dbe579cb51ecd6d30b17e0cae4e0ea15e2c0e66f69ad4198f22a6789e94f4 + sha256: "015002c060f1ae9f41a818f2d5640389cc05283e368be19dc8d77cecb43c40c9" url: "https://pub.dev" source: hosted - version: "5.5.1" + version: "5.5.3" win32_registry: dependency: transitive description: name: win32_registry - sha256: "10589e0d7f4e053f2c61023a31c9ce01146656a70b7b7f0828c0b46d7da2a9bb" + sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.1.4" window_manager: dependency: "direct main" description: @@ -1132,5 +1153,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.4.0 <4.0.0" + dart: ">=3.5.0-259.0.dev <4.0.0" flutter: ">=3.22.0"