From ba7bc832bf8dade736e4828f22e9fff53b1f1070 Mon Sep 17 00:00:00 2001 From: wanghongenpin Date: Tue, 2 Jan 2024 23:06:51 +0800 Subject: [PATCH] =?UTF-8?q?=E8=AF=B7=E6=B1=82=E9=87=8D=E5=86=99=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E9=80=89=E6=8B=A9=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/AppDelegate.swift | 30 +++-- ios/Runner/Info.plist | 7 +- ios/Runner/VpnManager.swift | 2 - lib/l10n/app_en.arb | 43 ++++++- lib/l10n/app_zh.arb | 39 ++++++ .../components/request_rewrite_manager.dart | 64 +++++++-- lib/network/handler.dart | 17 ++- lib/network/util/file_read.dart | 24 ++++ .../toolbar/setting/request_rewrite.dart | 98 +++++++------- .../setting/rewrite/rewrite_replace.dart | 121 ++++++++++++++---- .../setting/rewrite/rewrite_update.dart | 62 +++++---- lib/ui/mobile/mobile.dart | 16 +-- lib/ui/mobile/request/history.dart | 3 +- lib/ui/mobile/request/list.dart | 2 +- lib/ui/mobile/request/request_editor.dart | 2 +- lib/ui/mobile/setting/request_rewrite.dart | 104 ++++++++------- .../setting/rewrite/rewrite_replace.dart | 118 +++++++++++++---- .../setting/rewrite/rewrite_update.dart | 62 +++++---- pubspec.lock | 12 +- pubspec.yaml | 4 +- 20 files changed, 600 insertions(+), 230 deletions(-) diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index 3c2b0e8..69eb904 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -34,17 +34,31 @@ import NetworkExtension override func applicationDidEnterBackground(_ application: UIApplication) { -// timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) -// RunLoop.current.add(timer!, forMode: RunLoop.Mode.common) -// bgTask = application.beginBackgroundTask(expirationHandler: nil) + if (!VpnManager.shared.isRunning()) { + return + } + timer = Timer.scheduledTimer(timeInterval: 3, target: self, selector: #selector(timerAction), userInfo: nil, repeats: true) + RunLoop.current.add(timer!, forMode: RunLoop.Mode.common) + bgTask = application.beginBackgroundTask(expirationHandler: nil) } @objc func timerAction() { - print(UIApplication.shared.backgroundTimeRemaining ) - if UIApplication.shared.backgroundTimeRemaining < 60.0 { - let application = UIApplication.shared - bgTask = application.beginBackgroundTask(expirationHandler: nil) - } + print(UIApplication.shared.backgroundTimeRemaining) + let application = UIApplication.shared + if (bgTask != nil) { + application.endBackgroundTask(bgTask!); + bgTask = nil; + } + + if (UIApplication.shared.backgroundTimeRemaining < 60 && VpnManager.shared.isRunning()) { + bgTask = application.beginBackgroundTask(expirationHandler: nil) + } + + if (application.backgroundTimeRemaining <= 0 || application.applicationState == .active) { + timer?.invalidate(); + timer = nil; + } + } override func applicationWillResignActive(_ application: UIApplication) { diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 558abf2..338ce8d 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -2,6 +2,11 @@ + CFBundleLocalizations + + en + zh + BGTaskSchedulerPermittedIdentifiers $(PRODUCT_BUNDLE_IDENTIFIER) @@ -62,6 +67,6 @@ UIViewControllerBasedStatusBarAppearance NSCameraUsageDescription - 扫描二维码 + 扫描二维码 diff --git a/ios/Runner/VpnManager.swift b/ios/Runner/VpnManager.swift index 7840898..9026762 100755 --- a/ios/Runner/VpnManager.swift +++ b/ios/Runner/VpnManager.swift @@ -164,9 +164,7 @@ extension VpnManager{ } loadProviderManager{ - print("stopVPNTunnel") $0?.connection.stopVPNTunnel() - } } diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 0de923b..107a15b 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -32,6 +32,7 @@ "copied": "Copied to clipboard", "cancel": "Cancel", + "close": "Close", "save": "Save", "confirm": "Confirm", "confirmTitle": "Confirm operation", @@ -56,7 +57,45 @@ "responseType": "Response Type", "request": "Request", "response": "Response", - "statusCode": "Status code: ", + "statusCode": "Status code", + + "done": "Done", + "type": "Type", + "enable": "Enable", + "example": "Example: ", + "responseHeader": "Response Header", + "requestHeader": "Request Header", + "requestLine": "Request Line", + "requestMethod": "Request Method", + "param": "Param", + "replaceBodyWith": "Replace Body With:", + "redirectTo": "Redirect To:", + "redirect": "Redirect", + "cannotBeEmpty": "Cannot be empty", + "requestRewriteList": "Request Rewrite List", + "requestRewriteRule": "Request Rewrite Rule", + "requestRewriteEnable": "Enable Request Rewrite", + "action": "Action", + "multiple": "Multiple", + "edit": "Edit", + "disabled": "Disabled", + "requestRewriteDeleteConfirm": "Delete {size} rule(s)?", + "useGuide": "Use Guide", + "pleaseEnter": "Please Enter", + "click": "Click", + "replace": "Replace", + "itemUpdate": "{size} item(s) updated", + "clickEdit": "Click Edit", + "refresh": "Refresh", + "selectFile": "Select file", + "match": "Match", + "value": "Value", + "matchRule": "Match Rule", + "emptyMatchAll": "Empty means match all", + "newBuilt": "New", + "enableSelect": "Enable Select", + "disableSelect": "Disable Select", + "deleteSelect": "Delete Select", "sequence": "All Requests", "domainList": "Domain List", @@ -76,7 +115,7 @@ "copyCurl": "Copy cURL", "delete": "Delete", "rename": "Rename", - "repeat": "Repeat Request", + "repeat": "Resend Request", "editRequest": "Edit and Request", "reSendRequest": "The request has been resent", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 692741a..1849aef 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -32,6 +32,7 @@ "copied": "已复制到剪贴板", "cancel": "取消", + "close": "关闭", "save": "保存", "confirm": "确认", "confirmTitle": "确认操作", @@ -57,6 +58,44 @@ "response": "响应", "statusCode": "状态码:", + "done": "完成", + "type": "类型", + "enable": "启用", + "example": "示例: ", + "responseHeader": "响应头", + "requestHeader": "请求头", + "requestLine": "请求行", + "requestMethod": "请求方法", + "param": "参数", + "replaceBodyWith": "消息体替换为:", + "redirectTo": "重定向到:", + "redirect": "重定向", + "cannotBeEmpty": "不能为空", + "requestRewriteList": "请求重写列表", + "requestRewriteRule": "请求重写规则", + "requestRewriteEnable": "是否启用请求重写", + "action": "行为", + "multiple": "多选", + "edit": "编辑", + "disabled": "禁用", + "requestRewriteDeleteConfirm": "是否删除{size}条规则?", + "useGuide": "使用文档", + "pleaseEnter": "请输入", + "click": "点击", + "replace": "替换", + "itemUpdate": "{size}条修改", + "clickEdit": "点击编辑", + "refresh": "刷新", + "selectFile": "选择文件", + "match": "匹配", + "value": "值", + "matchRule": "匹配规则", + "emptyMatchAll": "为空表示匹配全部", + "newBuilt": "新建", + "enableSelect": "启用选择", + "disableSelect": "禁用选择", + "deleteSelect": "删除选择", + "sequence": "全部请求", "domainList": "域名列表", "domainWhitelist": "域名白名单", diff --git a/lib/network/components/request_rewrite_manager.dart b/lib/network/components/request_rewrite_manager.dart index 1f3d330..6d28dd8 100644 --- a/lib/network/components/request_rewrite_manager.dart +++ b/lib/network/components/request_rewrite_manager.dart @@ -269,7 +269,11 @@ class RequestRewrites { if (rewriteRule?.type == RuleType.requestReplace) { var rewriteItems = await getRewriteItems(rewriteRule!); - rewriteItems.where((item) => item.enabled).forEach((item) => _replaceRequest(request, item)); + for (var item in rewriteItems) { + if (item.enabled) { + await _replaceRequest(request, item); + } + } return; } @@ -321,13 +325,13 @@ class RequestRewrites { } //替换请求 - _replaceRequest(HttpRequest request, RewriteItem item) { + Future _replaceRequest(HttpRequest request, RewriteItem item) async { if (item.type == RewriteType.replaceRequestLine) { request.method = item.method ?? request.method; request.uri = Uri.parse(request.requestUrl).replace(path: item.path, query: item.queryParam).toString(); return; } - _replaceHttpMessage(request, item); + await _replaceHttpMessage(request, item); } /// 查找重写规则 @@ -339,7 +343,11 @@ class RequestRewrites { if (rewriteRule.type == RuleType.responseReplace) { var rewriteItems = await getRewriteItems(rewriteRule); - rewriteItems.where((item) => item.enabled).forEach((item) => _replaceResponse(response, item)); + for (var item in rewriteItems) { + if (item.enabled) { + await _replaceResponse(response, item); + } + } // logger.d('rewrite response $response'); return; } @@ -393,24 +401,33 @@ class RequestRewrites { } //替换相应 - _replaceResponse(HttpResponse response, RewriteItem item) { + Future _replaceResponse(HttpResponse response, RewriteItem item) async { if (item.type == RewriteType.replaceResponseStatus && item.statusCode != null) { response.status = HttpStatus.valueOf(item.statusCode!); return; } - _replaceHttpMessage(response, item); + await _replaceHttpMessage(response, item); } - _replaceHttpMessage(HttpMessage message, RewriteItem item) { + Future _replaceHttpMessage(HttpMessage message, RewriteItem item) async { if (item.type == RewriteType.replaceResponseHeader && item.headers != null) { item.headers?.forEach((key, value) => message.headers.set(key, value)); return; } - if (item.body != null && - (item.type == RewriteType.replaceResponseBody || item.type == RewriteType.replaceRequestBody)) { - message.body = utf8.encode(item.body!); - message.headers.contentLength = message.body!.length; + if (item.type == RewriteType.replaceResponseBody || item.type == RewriteType.replaceRequestBody) { + if (item.bodyType == ReplaceBodyType.file.name) { + if (item.bodyFile == null) return; + + message.body = await FileRead.readFile(item.bodyFile!); + message.headers.contentLength = message.body!.length; + return; + } + + if (item.body != null) { + message.body = utf8.encode(item.body!); + message.headers.contentLength = message.body!.length; + } return; } } @@ -501,6 +518,15 @@ class RequestRewriteRule { } } +enum ReplaceBodyType { + text("文本"), + file("文件"); + + final String label; + + const ReplaceBodyType(this.label); +} + class RewriteItem { bool enabled; RewriteType type; @@ -559,6 +585,14 @@ class RewriteItem { set body(String? body) => values['body'] = body; + String? get bodyType => values['bodyType']; + + set bodyType(String? bodyType) => values['bodyType'] = bodyType; + + String? get bodyFile => values['bodyFile']; + + set bodyFile(String? bodyFile) => values['bodyFile'] = bodyFile; + Map toJson() { return { 'enabled': enabled, @@ -614,4 +648,12 @@ enum RewriteType { static RewriteType fromName(String name) { return values.firstWhere((element) => element.name == name); } + + String getDescribe(bool isCN) { + if (isCN) { + return label; + } + + return name.replaceFirst("replace", "").replaceFirst("Query", ""); + } } diff --git a/lib/network/handler.dart b/lib/network/handler.dart index d7ef286..bee7c56 100644 --- a/lib/network/handler.dart +++ b/lib/network/handler.dart @@ -105,7 +105,7 @@ class HttpProxyChannelHandler extends ChannelHandler { //实现抓包代理转发 if (httpRequest.method != HttpMethod.connect) { - // log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); + log.i("[${channel.id}] ${httpRequest.method.name} ${httpRequest.requestUrl}"); if (HostFilter.filter(httpRequest.hostAndPort?.host)) { await remoteChannel.write(httpRequest); return; @@ -225,6 +225,7 @@ class HttpResponseProxyHandler extends ChannelHandler { try { HttpResponse? response = await scriptManager.runResponseScript(msg); if (response == null) { + channel.close(); return; } msg = response; @@ -235,13 +236,23 @@ class HttpResponseProxyHandler extends ChannelHandler { } //重写响应 - await requestRewrites?.responseRewrite(msg.request?.requestUrl, msg); - + try { + await requestRewrites?.responseRewrite(msg.request?.requestUrl, msg); + } catch (e, t) { + msg.body = "$e".codeUnits; + log.e('[${clientChannel.id}] 响应重写异常 ', error: e, stackTrace: t); + } listener?.onResponse(channelContext, msg); //发送给客户端 await clientChannel.write(msg); } + @override + void exceptionCaught(ChannelContext channelContext, Channel channel, error, {StackTrace? trace}) { + super.exceptionCaught(channelContext, channel, error, trace: trace); + ProxyHelper.exceptionHandler(channelContext, channel, listener, channelContext.currentRequest, error); + } + @override void channelInactive(ChannelContext channelContext, Channel channel) { clientChannel.close(); diff --git a/lib/network/util/file_read.dart b/lib/network/util/file_read.dart index 5ae6dc9..2ea4027 100644 --- a/lib/network/util/file_read.dart +++ b/lib/network/util/file_read.dart @@ -30,4 +30,28 @@ class FileRead { return rootBundle.load(file).then((bateData) => bateData.buffer.asUint8List()); // return File(file).readAsBytes(); } + + static String? _uuid; + + static Future get iosUuid async { + if (_uuid == null) { + var applicationPath = (await getApplicationSupportDirectory()).path; + var uuidPattern = RegExp(r'/Application/([0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12})/'); + var match = uuidPattern.firstMatch(applicationPath); + + _uuid = match?.group(1); + } + return _uuid!; + } + + static Future readFile(String path) async { + if (Platform.isIOS) { + var uuid = await iosUuid; + //ios替换uuid + var uuidPattern = RegExp(r'/Application/[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/'); + path = path.replaceAll(uuidPattern, '/Application/$uuid/'); + } + + return File(path).readAsBytes(); + } } diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index 4f458d1..6930534 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -6,6 +6,7 @@ import 'package:file_selector/file_selector.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/network/util/logger.dart'; @@ -30,18 +31,13 @@ class RequestRewriteWidget extends StatefulWidget { class RequestRewriteState extends State { late ValueNotifier enableNotifier; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); RawKeyboard.instance.addListener(onKeyEvent); enableNotifier = ValueNotifier(widget.requestRewrites.enabled == true); - DesktopMultiWindow.setMethodHandler((call, fromWindowId) async { - print("call.method: ${call.method}"); - if (call.method == 'reloadRequestRewrite') { - await widget.requestRewrites.reloadRequestRewrite(); - setState(() {}); - } - }); } @override @@ -73,7 +69,8 @@ class RequestRewriteState extends State { return Scaffold( backgroundColor: Theme.of(context).dialogBackgroundColor, appBar: AppBar( - title: const Text("请求重写", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + title: + Text(localizations.requestRewrite, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), toolbarHeight: 34, centerTitle: true), body: Padding( @@ -89,7 +86,7 @@ class RequestRewriteState extends State { scale: 0.8, child: SwitchListTile( contentPadding: const EdgeInsets.only(left: 2), - title: const Text('是否启用请求重写'), + title: Text(localizations.requestRewriteEnable), value: enableNotifier.value, onChanged: (value) { enableNotifier.value = value; @@ -101,11 +98,14 @@ class RequestRewriteState extends State { child: Row( mainAxisAlignment: MainAxisAlignment.end, children: [ - IconButton(onPressed: refresh, icon: const Icon(Icons.refresh, color: Colors.blue), tooltip: "刷新"), + IconButton( + onPressed: refresh, + icon: const Icon(Icons.refresh, color: Colors.blue), + tooltip: localizations.refresh), const SizedBox(width: 30), FilledButton.icon( icon: const Icon(Icons.add, size: 18), - label: const Text("添加", style: TextStyle(fontSize: 12)), + label: Text(localizations.add, style: const TextStyle(fontSize: 12)), onPressed: add, ), const SizedBox(width: 20), @@ -113,7 +113,7 @@ class RequestRewriteState extends State { icon: const Icon(Icons.input_rounded, size: 18), style: ElevatedButton.styleFrom(padding: const EdgeInsets.only(left: 20, right: 20)), onPressed: import, - label: const Text("导入"), + label: Text(localizations.import), ) ], )), @@ -149,13 +149,13 @@ class RequestRewriteState extends State { } if (context.mounted) { - FlutterToastr.show("导入成功", context); + FlutterToastr.show(localizations.importSuccess, context); } setState(() {}); } catch (e, t) { logger.e('导入失败 $file', error: e, stackTrace: t); if (context.mounted) { - FlutterToastr.show("导入失败 $e", context); + FlutterToastr.show("${localizations.importFailed} $e", context); } } } @@ -183,6 +183,8 @@ class _RequestRuleListState extends State { Map selected = {}; late List rules; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override initState() { super.initState(); @@ -223,11 +225,11 @@ class _RequestRuleListState extends State { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container(width: 130, padding: const EdgeInsets.only(left: 10), child: const Text("名称")), - const SizedBox(width: 50, child: Text("启用", textAlign: TextAlign.center)), + Container(width: 130, padding: const EdgeInsets.only(left: 10), child: Text(localizations.name)), + SizedBox(width: 50, child: Text(localizations.enable, textAlign: TextAlign.center)), const VerticalDivider(), const Expanded(child: Text("URL")), - const SizedBox(width: 100, child: Text("行为", textAlign: TextAlign.center)), + SizedBox(width: 100, child: Text(localizations.action, textAlign: TextAlign.center)), ], ), const Divider(thickness: 0.5), @@ -249,18 +251,20 @@ class _RequestRuleListState extends State { showGlobalMenu(Offset offset) { showContextMenu(context, offset, items: [ - PopupMenuItem(height: 35, child: const Text("新建"), onTap: () => showEdit()), - PopupMenuItem(height: 35, child: const Text("导出"), onTap: () => export(selected.keys.toList())), + PopupMenuItem(height: 35, child: Text(localizations.newBuilt), onTap: () => showEdit()), + PopupMenuItem(height: 35, child: Text(localizations.export), onTap: () => export(selected.keys.toList())), const PopupMenuDivider(), - PopupMenuItem(height: 35, child: const Text("启用选择"), onTap: () => enableStatus(true)), - PopupMenuItem(height: 35, child: const Text("禁用选择"), onTap: () => enableStatus(false)), + PopupMenuItem(height: 35, child: Text(localizations.enableSelect), onTap: () => enableStatus(true)), + PopupMenuItem(height: 35, child: Text(localizations.disableSelect), onTap: () => enableStatus(false)), const PopupMenuDivider(), - PopupMenuItem(height: 35, child: const Text("删除选择"), onTap: () => removeRewrite(selected.keys.toList())), + PopupMenuItem( + height: 35, child: Text(localizations.deleteSelect), onTap: () => removeRewrite(selected.keys.toList())), ]); } List rows(List list) { var primaryColor = Theme.of(context).primaryColor; + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); return List.generate(list.length, (index) { return InkWell( @@ -317,7 +321,7 @@ class _RequestRuleListState extends State { Text(list[index].url, overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13))), SizedBox( width: 100, - child: Text(list[index].type.label, + child: Text(isCN ? list[index].type.label : list[index].type.name, textAlign: TextAlign.center, style: const TextStyle(fontSize: 13))), ], ))); @@ -346,13 +350,14 @@ class _RequestRuleListState extends State { final XFile xFile = XFile.fromData(utf8.encode(jsonEncode(list)), mimeType: 'json'); await xFile.saveTo(saveLocation); - if (context.mounted) FlutterToastr.show("导出成功", context); + if (context.mounted) FlutterToastr.show(localizations.exportSuccess, context); } //删除 Future removeRewrite(List indexes) async { if (indexes.isEmpty) return; - return showConfirmDialog(context, content: '是否删除${indexes.length}条规则?', onConfirm: () async { + return showConfirmDialog(context, content: localizations.requestRewriteDeleteConfirm(indexes.length), + onConfirm: () async { var list = indexes.toList(); list.sort((a, b) => b.compareTo(a)); for (var value in list) { @@ -363,7 +368,7 @@ class _RequestRuleListState extends State { setState(() { selected.clear(); }); - if (mounted) FlutterToastr.show('删除成功', context); + if (mounted) FlutterToastr.show(localizations.deleteSuccess, context); }); } @@ -398,11 +403,11 @@ class _RequestRuleListState extends State { selected[index] = true; }); showContextMenu(context, details.globalPosition, items: [ - PopupMenuItem(height: 35, child: const Text("编辑"), onTap: () => showEdit(index)), - PopupMenuItem(height: 35, onTap: () => export([index]), child: const Text("导出")), + PopupMenuItem(height: 35, child: Text(localizations.edit), onTap: () => showEdit(index)), + PopupMenuItem(height: 35, onTap: () => export([index]), child: Text(localizations.export)), PopupMenuItem( height: 35, - child: rules[index].enabled ? const Text("禁用") : const Text("启用"), + child: rules[index].enabled ? Text(localizations.disabled) : Text(localizations.enable), onTap: () { rules[index].enabled = !rules[index].enabled; MultiWindow.invokeRefreshRewrite(Operation.update, index: index, rule: rules[index]); @@ -410,7 +415,7 @@ class _RequestRuleListState extends State { const PopupMenuDivider(), PopupMenuItem( height: 35, - child: const Text("删除"), + child: Text(localizations.delete), onTap: () async { await widget.requestRewrites.removeIndex([index]); MultiWindow.invokeRefreshRewrite(Operation.delete, index: index); @@ -446,6 +451,8 @@ class _RuleAddDialogState extends State { late TextEditingController nameInput; late TextEditingController urlInput; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -468,14 +475,15 @@ class _RuleAddDialogState extends State { @override Widget build(BuildContext context) { GlobalKey formKey = GlobalKey(); + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); return AlertDialog( scrollable: true, title: Row(children: [ - const Text("添加请求重写规则", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + Text(localizations.requestRewriteRule, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), const SizedBox(width: 20), Text.rich(TextSpan( - text: '使用文档', + text: localizations.useGuide, style: const TextStyle(color: Colors.blue, fontSize: 14), recognizer: TapGestureRecognizer() ..onTap = () => DesktopMultiWindow.invokeMethod(0, "launchUrl", @@ -495,19 +503,19 @@ class _RuleAddDialogState extends State { builder: (_, bool enable, __) { return SwitchListTile( contentPadding: const EdgeInsets.only(left: 0), - title: const Text('是否启用', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + title: Text(localizations.enable), value: enable, onChanged: (value) => enableNotifier.value = value); }), const SizedBox(height: 5), - textField('名称:', nameInput, '请输入名称'), + textField('${localizations.name}:', nameInput, localizations.pleaseEnter), const SizedBox(height: 10), textField('URL:', urlInput, 'http://www.example.com/api/*', required: true), const SizedBox(height: 10), Row(children: [ - const SizedBox(width: 60, child: Text('行为:')), + SizedBox(width: 60, child: Text('${localizations.action}:')), SizedBox( - width: 100, + width: 150, height: 33, child: DropdownButtonFormField( onSaved: (val) => rule.type = val!, @@ -520,7 +528,8 @@ class _RuleAddDialogState extends State { border: const OutlineInputBorder()), items: RuleType.values .map((e) => DropdownMenuItem( - value: e, child: Text(e.label, style: const TextStyle(fontSize: 13)))) + value: e, + child: Text(isCN ? e.label : e.name, style: const TextStyle(fontSize: 13)))) .toList(), onChanged: (val) { ruleType = val!; @@ -528,18 +537,18 @@ class _RuleAddDialogState extends State { }, )), const SizedBox(width: 10), - TextButton(onPressed: () => showEdit(rule), child: const Text("点击编辑")), + TextButton(onPressed: () => showEdit(rule), child: Text(localizations.clickEdit)), ]), const SizedBox(height: 10), Padding(padding: const EdgeInsets.only(left: 60), child: getDescribe()), ]))), actions: [ - ElevatedButton(child: const Text("关闭"), onPressed: () => Navigator.of(context).pop()), + ElevatedButton(child: Text(localizations.close), onPressed: () => Navigator.of(context).pop()), FilledButton( - child: const Text("保存"), + child: Text(localizations.save), onPressed: () async { if (!(formKey.currentState as FormState).validate()) { - FlutterToastr.show("缺少配置", context, position: FlutterToastr.center); + FlutterToastr.show(localizations.cannotBeEmpty, context, position: FlutterToastr.center); return; } @@ -568,13 +577,16 @@ class _RuleAddDialogState extends State { } Widget getDescribe() { + bool isCN = localizations.localeName == 'zh'; + if (items?.isNotEmpty == true && (ruleType == RuleType.requestReplace || ruleType == RuleType.responseReplace)) { - return Text("替换: ${items?.where((it) => it.enabled).map((e) => e.type.label).join(" ")}", + return Text( + "${localizations.replace}: ${items?.where((it) => it.enabled).map((e) => e.type.getDescribe(isCN)).join(" ")}", style: const TextStyle(color: Colors.grey)); } if (ruleType == RuleType.requestUpdate || ruleType == RuleType.responseUpdate) { - return Text("${items?.length}条修改", style: const TextStyle(color: Colors.grey)); + return Text(localizations.itemUpdate(items?.length ?? 0), style: const TextStyle(color: Colors.grey)); } return const SizedBox(); } diff --git a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart index 3998e23..4252495 100644 --- a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart +++ b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_replace.dart @@ -1,7 +1,9 @@ import 'dart:math'; +import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/ui/component/state_component.dart'; import 'package:network_proxy/ui/component/widgets.dart'; @@ -24,10 +26,12 @@ class _RewriteReplaceState extends State { List items = []; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override initState() { super.initState(); - if (widget.ruleType== RuleType.redirect) { + if (widget.ruleType == RuleType.redirect) { initRewriteItem(RewriteType.redirect, enabled: true); return; } @@ -55,12 +59,14 @@ class _RewriteReplaceState extends State { @override Widget build(BuildContext context) { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + return AlertDialog( titlePadding: const EdgeInsets.all(0), actionsPadding: const EdgeInsets.only(right: 10, bottom: 10), contentPadding: const EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 5), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("关闭")), + TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(localizations.cancel)), TextButton( onPressed: () { var headers = _headerKey.currentState?.getHeaders(); @@ -73,10 +79,10 @@ class _RewriteReplaceState extends State { } Navigator.of(context).pop(items); }, - child: const Text("完成")), + child: Text(localizations.done)), ], title: ListTile( - title: Text(widget.ruleType.label, + title: Text(isCN ? widget.ruleType.label : widget.ruleType.name, textAlign: TextAlign.center, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), subtitle: Text(widget.subtitle.substring(0, min(widget.subtitle.length, 50)), textAlign: TextAlign.center, style: const TextStyle(fontSize: 12, color: Colors.grey))), @@ -91,11 +97,13 @@ class _RewriteReplaceState extends State { if (widget.ruleType == RuleType.responseReplace || widget.ruleType == RuleType.requestReplace) { bool requestEdited = widget.ruleType == RuleType.requestReplace; - List tabs = requestEdited ? ["请求行", "请求头", "请求体"] : ["状态码", "响应头", "响应体"]; + List tabs = requestEdited + ? [localizations.requestLine, localizations.requestHeader, localizations.requestBody] + : [localizations.statusCode, localizations.responseHeader, localizations.responseBody]; return SizedBox( width: 500, - height: 340, + height: 345, child: DefaultTabController( length: tabs.length, initialIndex: tabs.length - 1, @@ -133,15 +141,35 @@ class _RewriteReplaceState extends State { //body Widget body() { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + var rewriteItem = items.firstWhere( (item) => item.type == RewriteType.replaceRequestBody || item.type == RewriteType.replaceResponseBody); return Column(children: [ Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(width: 10), + const SizedBox(width: 5), + Text("${localizations.type}: "), + SizedBox( + width: 90, + child: DropdownButtonFormField( + value: rewriteItem.bodyType ?? ReplaceBodyType.text.name, + focusColor: Colors.transparent, + itemHeight: 48, + decoration: + const InputDecoration(contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none), + items: ReplaceBodyType.values + .map((e) => DropdownMenuItem( + value: e.name, + child: Text(isCN ? e.label : e.name.toUpperCase(), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)))) + .toList(), + onChanged: (val) => setState(() { + rewriteItem.bodyType = val!; + }))), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -151,13 +179,50 @@ class _RewriteReplaceState extends State { })) ])) ]), - const SizedBox(height: 5), - TextFormField( - initialValue: rewriteItem.body, - style: const TextStyle(fontSize: 14), - maxLines: 10, - decoration: decoration('消息体替换为:', hintText: '示例: {"code":"200","data":{}}'), - onChanged: (val) => rewriteItem.body = val) + const SizedBox(height: 10), + if (rewriteItem.bodyType == ReplaceBodyType.file.name) + fileBodyEdit(rewriteItem) + else + TextFormField( + initialValue: rewriteItem.body, + style: const TextStyle(fontSize: 14), + maxLines: 10, + decoration: decoration(localizations.replaceBodyWith, + hintText: '${localizations.example} {"code":"200","data":{}}'), + onChanged: (val) => rewriteItem.body = val) + ]); + } + + Widget fileBodyEdit(RewriteItem item) { + //选择文件 删除 + return Row(children: [ + Expanded( + child: item.bodyFile == null + ? Container(height: 50) + : Container( + padding: const EdgeInsets.all(5), + foregroundDecoration: + BoxDecoration(border: Border.all(color: Theme.of(context).primaryColor, width: 1)), + child: Text(item.bodyFile ?? ''))), + const SizedBox(width: 10), + FilledButton( + onPressed: () async { + var path = await DesktopMultiWindow.invokeMethod(0, "openFile", ''); + if (path == null) { + return; + } + item.bodyFile = path; + setState(() {}); + }, + child: Text(localizations.selectFile, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500))), + const SizedBox(width: 10), + FilledButton( + onPressed: () { + setState(() { + item.bodyFile = null; + }); + }, + child: Text(localizations.delete, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500))), ]); } @@ -168,11 +233,11 @@ class _RewriteReplaceState extends State { return Column(children: [ Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('Header列表'), + const Text('Headers'), const SizedBox(width: 10), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -192,7 +257,7 @@ class _RewriteReplaceState extends State { return Column( children: [ Row(children: [ - const Text('请求方法'), + Text(localizations.requestMethod), const SizedBox(width: 10), SizedBox( width: 120, @@ -213,7 +278,7 @@ class _RewriteReplaceState extends State { })), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -226,9 +291,11 @@ class _RewriteReplaceState extends State { ])), ]), const SizedBox(height: 15), - textField("Path", rewriteItem.path, "示例: /api/v1/user", onChanged: (val) => rewriteItem.values['path'] = val), + textField("Path", rewriteItem.path, "${localizations.example} /api/v1/user", + onChanged: (val) => rewriteItem.values['path'] = val), const SizedBox(height: 15), - textField("URL参数", rewriteItem.queryParam, "示例: id=1&name=2", onChanged: (val) => rewriteItem.queryParam = val), + textField("URL${localizations.param}", rewriteItem.queryParam, "${localizations.example} id=1&name=2", + onChanged: (val) => rewriteItem.queryParam = val), ], ); } @@ -236,14 +303,14 @@ class _RewriteReplaceState extends State { //重定向 Widget redirectEdit(RewriteItem rewriteItem) { return TextFormField( - decoration: decoration('重定向到:', hintText: 'http://www.example.com/api'), + decoration: decoration(localizations.redirectTo, hintText: 'http://www.example.com/api'), maxLines: 3, style: const TextStyle(fontSize: 14), initialValue: rewriteItem.redirectUrl, onChanged: (val) => rewriteItem.redirectUrl = val, validator: (val) { if (val == null || val.trim().isEmpty) { - return '重定向URL不能为空'; + return '${localizations.redirect} URL ${localizations.cannotBeEmpty}'; } return null; }); @@ -275,7 +342,7 @@ class _RewriteReplaceState extends State { padding: const EdgeInsets.all(10), child: Column(children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('状态码'), + Text(localizations.statusCode), const SizedBox(width: 10), SizedBox( width: 100, @@ -292,7 +359,7 @@ class _RewriteReplaceState extends State { )), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -342,6 +409,8 @@ class HeadersState extends State with AutomaticKeepAliveClientMixin { @override bool get wantKeepAlive => true; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -374,7 +443,7 @@ class HeadersState extends State with AutomaticKeepAliveClientMixin { ]; list.add(TextButton( - child: const Text("添加Header", textAlign: TextAlign.center), + child: Text("${localizations.add}Header", textAlign: TextAlign.center), onPressed: () { setState(() { _headers[TextEditingController()] = TextEditingController(); diff --git a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_update.dart b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_update.dart index 4e1ee07..495fa3c 100644 --- a/lib/ui/desktop/toolbar/setting/rewrite/rewrite_update.dart +++ b/lib/ui/desktop/toolbar/setting/rewrite/rewrite_update.dart @@ -3,6 +3,7 @@ import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/component/widgets.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; class RewriteUpdateDialog extends StatefulWidget { final String subtitle; @@ -18,6 +19,8 @@ class RewriteUpdateDialog extends StatefulWidget { class _RewriteUpdateState extends State { List items = []; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -32,19 +35,21 @@ class _RewriteUpdateState extends State { @override Widget build(BuildContext context) { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + return AlertDialog( titlePadding: const EdgeInsets.all(0), actionsPadding: const EdgeInsets.only(right: 10, bottom: 10), contentPadding: const EdgeInsets.only(left: 10, right: 10, top: 0, bottom: 5), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("关闭")), + TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(localizations.cancel)), TextButton( onPressed: () { Navigator.of(context).pop(items); }, - child: const Text("完成")), + child: Text(localizations.done)), ], - title: ListTile(title: Text(widget.ruleType.label, textAlign: TextAlign.center)), + title: ListTile(title: Text(isCN ? widget.ruleType.label : widget.ruleType.name, textAlign: TextAlign.center)), content: SizedBox( height: 380, child: Column( @@ -90,6 +95,8 @@ class _RewriteUpdateAddState extends State { GlobalKey formKey = GlobalKey(); late RewriteItem rewriteItem; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -106,8 +113,8 @@ class _RewriteUpdateAddState extends State { String keyTips = ""; String valueTips = ""; if (isDelete) { - keyTips = "匹配规则"; - valueTips = "为空表示匹配全部"; + keyTips = localizations.matchRule; + valueTips = localizations.emptyMatchAll; } else if (rewriteType == RewriteType.updateQueryParam || rewriteType == RewriteType.updateHeader) { keyTips = rewriteType == RewriteType.updateQueryParam ? "name=123" : "Content-Type: application/json"; valueTips = rewriteType == RewriteType.updateQueryParam ? "name=456" : "Content-Type: application/xml"; @@ -116,21 +123,21 @@ class _RewriteUpdateAddState extends State { var typeList = widget.ruleType == RuleType.requestUpdate ? RewriteType.updateRequest : RewriteType.updateResponse; return AlertDialog( - title: const Text("添加修改", - style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), textAlign: TextAlign.center), + title: Text(localizations.add, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), textAlign: TextAlign.center), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("取消")), + TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(localizations.cancel)), TextButton( onPressed: () { if (!(formKey.currentState as FormState).validate()) { - FlutterToastr.show("缺少配置", context, position: FlutterToastr.center); + FlutterToastr.show(localizations.cannotBeEmpty, context, position: FlutterToastr.center); return; } (formKey.currentState as FormState).save(); rewriteItem.type = rewriteType; Navigator.of(context).pop(rewriteItem); }, - child: const Text("确定")), + child: Text(localizations.confirm)), ], content: SizedBox( width: 320, @@ -140,10 +147,10 @@ class _RewriteUpdateAddState extends State { child: Column(children: [ Row( children: [ - const Text('类型'), - const SizedBox(width: 15), + Text(localizations.type), + const SizedBox(width: 20), SizedBox( - width: 120, + width: 140, child: DropdownButtonFormField( value: rewriteType, focusColor: Colors.transparent, @@ -153,7 +160,7 @@ class _RewriteUpdateAddState extends State { items: typeList .map((e) => DropdownMenuItem( value: e, - child: Text(e.label, + child: Text(e.getDescribe(localizations.localeName == 'zh'), style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)))) .toList(), onChanged: (val) { @@ -164,17 +171,17 @@ class _RewriteUpdateAddState extends State { ], ), const SizedBox(height: 15), - textField(isUpdate ? "匹配" : "名称", rewriteItem.key, keyTips, + textField(isUpdate ? localizations.match : localizations.name, rewriteItem.key, keyTips, required: !isDelete, onSaved: (val) => rewriteItem.key = val), const SizedBox(height: 15), - textField(isUpdate ? "替换" : "值", rewriteItem.value, valueTips, + textField(isUpdate ? localizations.replace : localizations.value, rewriteItem.value, valueTips, onSaved: (val) => rewriteItem.value = val), ])))); } Widget textField(String label, String? val, String hint, {bool required = false, FormFieldSetter? onSaved}) { return Row(children: [ - SizedBox(width: 50, child: Text(label)), + SizedBox(width: 60, child: Text(label)), Expanded( child: TextFormField( initialValue: val, @@ -209,6 +216,8 @@ class UpdateList extends StatefulWidget { } class _UpdateListState extends State { + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -229,10 +238,10 @@ class _UpdateListState extends State { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container(width: 130, padding: const EdgeInsets.only(left: 10), child: const Text("类型")), - const SizedBox(width: 50, child: Text("启用", textAlign: TextAlign.center)), + Container(width: 130, padding: const EdgeInsets.only(left: 10), child: Text(localizations.type)), + SizedBox(width: 50, child: Text(localizations.enable, textAlign: TextAlign.center)), const VerticalDivider(), - const Expanded(child: Text("修改")), + Expanded(child: Text(localizations.modify)), ], ), const Divider(thickness: 0.5), @@ -244,6 +253,7 @@ class _UpdateListState extends State { List rows(List list) { var primaryColor = Theme.of(context).primaryColor; + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); return List.generate(list.length, (index) { return InkWell( @@ -267,7 +277,9 @@ class _UpdateListState extends State { padding: const EdgeInsets.all(5), child: Row( children: [ - SizedBox(width: 130, child: Text(list[index].type.label, style: const TextStyle(fontSize: 13))), + SizedBox( + width: 130, + child: Text(list[index].type.getDescribe(isCN), style: const TextStyle(fontSize: 13))), SizedBox( width: 40, child: SwitchWidget( @@ -301,7 +313,7 @@ class _UpdateListState extends State { showContextMenu(context, details.globalPosition, items: [ PopupMenuItem( height: 35, - child: const Text("编辑"), + child: Text(localizations.edit), onTap: () async { showDialog( context: context, @@ -315,15 +327,15 @@ class _UpdateListState extends State { }), PopupMenuItem( height: 35, - child: widget.items[index].enabled ? const Text("禁用") : const Text("启用"), + child: widget.items[index].enabled ? Text(localizations.disabled) : Text(localizations.enable), onTap: () => widget.items[index].enabled = !widget.items[index].enabled), const PopupMenuDivider(), PopupMenuItem( height: 35, - child: const Text("删除"), + child: Text(localizations.delete), onTap: () async { widget.items.removeAt(index); - if (mounted) FlutterToastr.show('删除成功', context); + if (mounted) FlutterToastr.show(localizations.deleteSuccess, context); }), ]).then((value) { setState(() { diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index 04d6ed0..5dd80d9 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -205,16 +205,14 @@ class MobileHomeState extends State implements EventListener, Li String content = isCN ? '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n\n' - '1. 请求重写增加 修改请求,可根据正则替换;\n' - '2. 请求重写批量导入、导出;\n' - '3. 支持WebSocket抓包;\n' - '4. 安卓支持小窗口模式;\n' - '5. 优化curl导入;\n' - '6. 支持head请求,修复手机端请求重写切换应用恢复原始的请求问题;' + '1. 增加多语言支持;\n' + '2. 请求重写支持文件选择;\n' + '3. 抓包详情页面Headers默认展开配置;\n' : 'Tips:By default, HTTPS packet capture will not be enabled. Please install the certificate before enabling HTTPS packet capture。\n\n' '1. Increase multilingual support;\n' - '2. Details page Headers Expanded Config;\n' - ; + '2. Request Rewrite support file selection;\n' + '3. Details page Headers Expanded Config;\n' + ; showAlertDialog(isCN ? '更新内容V1.0.6' : "Update content V1.0.6", content, () { widget.appConfiguration.upgradeNoticeV7 = false; widget.appConfiguration.flushConfig(); @@ -248,7 +246,7 @@ class MobileHomeState extends State implements EventListener, Li onClose.call(); Navigator.pop(context); }, - child: const Text('关闭')) + child: Text(localizations.cancel)) ], title: Text(title, style: const TextStyle(fontSize: 18)), content: Text(content)); diff --git a/lib/ui/mobile/request/history.dart b/lib/ui/mobile/request/history.dart index 8cf2ecc..08098c5 100644 --- a/lib/ui/mobile/request/history.dart +++ b/lib/ui/mobile/request/history.dart @@ -15,6 +15,7 @@ import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'package:network_proxy/storage/histories.dart'; import 'package:network_proxy/ui/component/utils.dart'; +import 'package:network_proxy/ui/mobile/mobile.dart'; import 'package:network_proxy/ui/mobile/request/list.dart'; import 'package:share_plus/share_plus.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -44,7 +45,7 @@ class _MobileHistoryState extends State { return futureWidget(HistoryStorage.instance, (data) { List children = []; - var container = RequestListState.container; + var container = MobileHomeState.requestStateKey.currentState!.container; if (container.isNotEmpty == true && !_sessionSaved) { //当前会话未保存,是否保存当前会话 children.add(buildSaveSession(data, container)); diff --git a/lib/ui/mobile/request/list.dart b/lib/ui/mobile/request/list.dart index 4cec07d..06105ec 100644 --- a/lib/ui/mobile/request/list.dart +++ b/lib/ui/mobile/request/list.dart @@ -32,7 +32,7 @@ class RequestListState extends State { final GlobalKey domainListKey = GlobalKey(); //请求列表容器 - static List container = []; + List container = []; AppLocalizations get localizations => AppLocalizations.of(context)!; diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index d924ccf..051374c 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -129,7 +129,7 @@ class RequestEditorState extends State with SingleTickerPro builder: (_, value, __) => _HttpWidget( key: responseKey, title: Row(children: [ - Text(localizations.statusCode, style: const TextStyle(fontWeight: FontWeight.w500)), + Text("${localizations.statusCode}: ", style: const TextStyle(fontWeight: FontWeight.w500)), const SizedBox(width: 10), Text(response?.status.toString() ?? "", style: const TextStyle(color: Colors.blue)) ]), diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index 4fd7428..24d5ede 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'package:file_selector/file_selector.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/network/util/logger.dart'; @@ -27,6 +28,8 @@ class MobileRequestRewrite extends StatefulWidget { class _MobileRequestRewriteState extends State { bool enabled = false; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -46,25 +49,27 @@ class _MobileRequestRewriteState extends State { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(centerTitle: true, title: const Text("请求重写列表", style: TextStyle(fontSize: 16))), + appBar: AppBar( + centerTitle: true, title: Text(localizations.requestRewriteList, style: const TextStyle(fontSize: 16))), body: Container( padding: const EdgeInsets.all(10), child: Column( children: [ Row( children: [ - const Text("是否启用请求重写"), + Text(localizations.requestRewriteEnable), SwitchWidget(value: enabled, scale: 0.8, onChanged: (val) => enabled = val), ], ), Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - FilledButton.icon(icon: const Icon(Icons.add, size: 18), onPressed: add, label: const Text("添加")), + FilledButton.icon( + icon: const Icon(Icons.add, size: 18), onPressed: add, label: Text(localizations.add)), const SizedBox(width: 10), FilledButton.icon( icon: const Icon(Icons.input_rounded, size: 18), style: ElevatedButton.styleFrom(padding: const EdgeInsets.only(left: 20, right: 20)), onPressed: import, - label: const Text("导入"), + label: Text(localizations.import), ), ]), const SizedBox(height: 10), @@ -86,18 +91,18 @@ class _MobileRequestRewriteState extends State { for (var item in json) { var rule = RequestRewriteRule.formJson(item); var items = (item['items'] as List).map((e) => RewriteItem.fromJson(e)).toList(); - widget.requestRewrites.addRule(rule, items); + await widget.requestRewrites.addRule(rule, items); } widget.requestRewrites.flushRequestRewriteConfig(); if (context.mounted) { - FlutterToastr.show("导入成功", context); + FlutterToastr.show(localizations.importSuccess, context); } setState(() {}); } catch (e, t) { logger.e('导入失败 $file', error: e, stackTrace: t); if (context.mounted) { - FlutterToastr.show("导入失败 $e", context); + FlutterToastr.show("${localizations.importFailed} $e", context); } } } @@ -128,6 +133,8 @@ class _RequestRuleListState extends State { bool multiple = false; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override initState() { super.initState(); @@ -159,11 +166,11 @@ class _RequestRuleListState extends State { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container(width: 80, padding: const EdgeInsets.only(left: 10), child: const Text("名称")), - const SizedBox(width: 30, child: Text("启用", textAlign: TextAlign.center)), + Container(width: 80, padding: const EdgeInsets.only(left: 10), child: Text(localizations.name)), + SizedBox(width: 46, child: Text(localizations.enable, textAlign: TextAlign.center)), const VerticalDivider(), const Expanded(child: Text("URL")), - const SizedBox(width: 60, child: Text("行为", textAlign: TextAlign.center)), + SizedBox(width: 60, child: Text(localizations.action, textAlign: TextAlign.center)), ], ), const Divider(thickness: 0.5), @@ -199,12 +206,12 @@ class _RequestRuleListState extends State { }); }, icon: const Icon(Icons.share, size: 18), - label: const Text("导出")), + label: Text(localizations.export)), const SizedBox(width: 15), TextButton.icon( onPressed: () => removeRewrite(), icon: const Icon(Icons.delete, size: 18), - label: const Text("删除")), + label: Text(localizations.delete)), const SizedBox(width: 15), TextButton.icon( onPressed: () { @@ -214,14 +221,14 @@ class _RequestRuleListState extends State { }); }, icon: const Icon(Icons.cancel, size: 18), - label: const Text("取消")), + label: Text(localizations.cancel)), ])))) ]); } List rows(List list) { var primaryColor = Theme.of(context).primaryColor; - + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); return List.generate(list.length, (index) { return InkWell( highlightColor: Colors.transparent, @@ -263,7 +270,7 @@ class _RequestRuleListState extends State { child: Text(list[index].name ?? "", overflow: TextOverflow.ellipsis, style: const TextStyle(fontSize: 13))), SizedBox( - width: 20, + width: 35, child: SwitchWidget( scale: 0.65, value: list[index].enabled, @@ -275,7 +282,7 @@ class _RequestRuleListState extends State { Expanded(child: Text(list[index].url, style: const TextStyle(fontSize: 13))), SizedBox( width: 60, - child: Text(list[index].type.label, + child: Text(isCN ? list[index].type.label : list[index].type.name, textAlign: TextAlign.center, style: const TextStyle(fontSize: 13))), ], ))); @@ -295,7 +302,7 @@ class _RequestRuleListState extends State { builder: (ctx) { return Wrap(alignment: WrapAlignment.center, children: [ BottomSheetItem( - text: '多选', + text: localizations.multiple, onPressed: () { setState(() { multiple = true; @@ -303,7 +310,7 @@ class _RequestRuleListState extends State { }), const Divider(thickness: 0.5), BottomSheetItem( - text: "编辑", + text: localizations.edit, onPressed: () async { var rule = widget.requestRewrites.rules[index]; var rewriteItems = await widget.requestRewrites.getRewriteItems(rule); @@ -318,21 +325,21 @@ class _RequestRuleListState extends State { }); }), const Divider(thickness: 0.5), - BottomSheetItem(text: "分享", onPressed: () => export([index])), + BottomSheetItem(text: localizations.share, onPressed: () => export([index])), const Divider(thickness: 0.5, height: 1), BottomSheetItem( - text: rules[index].enabled ? "禁用" : "启用", + text: rules[index].enabled ? localizations.disabled : localizations.enable, onPressed: () { rules[index].enabled = !rules[index].enabled; changed = true; }), const Divider(thickness: 0.5), BottomSheetItem( - text: "删除", + text: localizations.delete, onPressed: () async { await widget.requestRewrites.removeIndex([index]); widget.requestRewrites.flushRequestRewriteConfig(); - if (mounted) FlutterToastr.show('删除成功', context); + if (mounted) FlutterToastr.show(localizations.deleteSuccess, context); }), Container(color: Theme.of(context).hoverColor, height: 8), TextButton( @@ -340,7 +347,7 @@ class _RequestRuleListState extends State { height: 50, width: double.infinity, padding: const EdgeInsets.only(top: 10), - child: const Text("取消", textAlign: TextAlign.center)), + child: Text(localizations.cancel, textAlign: TextAlign.center)), onPressed: () { Navigator.of(context).pop(); }), @@ -377,7 +384,8 @@ class _RequestRuleListState extends State { //删除 Future removeRewrite() async { if (selected.isEmpty) return; - return showConfirmDialog(context, content: '是否删除${selected.length}条规则?', onConfirm: () async { + return showConfirmDialog(context, content: localizations.requestRewriteDeleteConfirm(selected.length), + onConfirm: () async { var list = selected.toList(); list.sort((a, b) => b.compareTo(a)); for (var value in list) { @@ -388,7 +396,7 @@ class _RequestRuleListState extends State { multiple = false; selected.clear(); }); - if (mounted) FlutterToastr.show('删除成功', context); + if (mounted) FlutterToastr.show(localizations.deleteSuccess, context); }); } } @@ -414,6 +422,8 @@ class _RewriteRuleState extends State { late TextEditingController nameInput; late TextEditingController urlInput; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -437,15 +447,15 @@ class _RewriteRuleState extends State { @override Widget build(BuildContext context) { GlobalKey formKey = GlobalKey(); + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); return Scaffold( appBar: AppBar( - centerTitle: true, title: Row(children: [ - const Text("请求重写规则", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), - const SizedBox(width: 20), + Text(localizations.requestRewrite, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + const SizedBox(width: 15), Text.rich(TextSpan( - text: '使用文档', + text: localizations.useGuide, style: const TextStyle(color: Colors.blue, fontSize: 14), recognizer: TapGestureRecognizer() ..onTap = () => launchUrl(Uri.parse( @@ -453,10 +463,10 @@ class _RewriteRuleState extends State { ]), actions: [ TextButton( - child: const Text("保存"), + child: Text(localizations.save), onPressed: () async { if (!(formKey.currentState as FormState).validate()) { - FlutterToastr.show("缺少配置", context, position: FlutterToastr.center); + FlutterToastr.show(localizations.cannotBeEmpty, context, position: FlutterToastr.center); return; } @@ -475,7 +485,7 @@ class _RewriteRuleState extends State { } requestRewrites.flushRequestRewriteConfig(); if (mounted) { - FlutterToastr.show("保存请求重写规则成功", context); + FlutterToastr.show(localizations.saveSuccess, context); Navigator.of(context).pop(rule); } }) @@ -491,19 +501,20 @@ class _RewriteRuleState extends State { builder: (_, bool enable, __) { return SwitchListTile( contentPadding: const EdgeInsets.only(left: 0), - title: const Text('是否启用', - style: TextStyle(fontWeight: FontWeight.w500), textAlign: TextAlign.start), + title: Text(localizations.enable, textAlign: TextAlign.start), value: enable, onChanged: (value) => enableNotifier.value = value); }), - textField('名称:', nameInput, '请输入名称'), + textField('${localizations.name}:', nameInput, localizations.pleaseEnter), textField('URL:', urlInput, 'http://www.example.com/api/*', required: true, keyboardType: TextInputType.url), Row(children: [ - const SizedBox( - width: 50, child: Text('行为:', style: TextStyle(fontSize: 16, fontWeight: FontWeight.w500))), SizedBox( - width: 110, + width: 58, + child: Text('${localizations.action}:', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))), + SizedBox( + width: 165, height: 50, child: DropdownButtonFormField( onSaved: (val) => rule.type = val!, @@ -511,9 +522,11 @@ class _RewriteRuleState extends State { value: ruleType, decoration: const InputDecoration( errorStyle: TextStyle(height: 0, fontSize: 0), - contentPadding: EdgeInsets.only(left: 7, right: 7), + contentPadding: EdgeInsets.only(), ), - items: RuleType.values.map((e) => DropdownMenuItem(value: e, child: Text(e.label))).toList(), + items: RuleType.values + .map((e) => DropdownMenuItem(value: e, child: Text(isCN ? e.label : e.name))) + .toList(), onChanged: (val) { ruleType = val!; items = ruleType == widget.rule?.type ? widget.items : []; @@ -521,7 +534,8 @@ class _RewriteRuleState extends State { )), const SizedBox(width: 10), TextButton( - onPressed: () => showEdit(rule), child: const Text("点击编辑", style: TextStyle(fontSize: 16))), + onPressed: () => showEdit(rule), + child: Text(localizations.clickEdit, style: const TextStyle(fontSize: 16))), ]), const SizedBox(height: 10), Padding(padding: const EdgeInsets.only(left: 60), child: getDescribe()), @@ -530,13 +544,15 @@ class _RewriteRuleState extends State { } Widget getDescribe() { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); if (items?.isNotEmpty == true && (ruleType == RuleType.requestReplace || ruleType == RuleType.responseReplace)) { - return Text("替换: ${items?.where((it) => it.enabled).map((e) => e.type.label).join(" ")}", + return Text( + "${localizations.replace}: ${items?.where((it) => it.enabled).map((e) => e.type.getDescribe(isCN)).join(" ")}", style: const TextStyle(color: Colors.grey)); } if (ruleType == RuleType.requestUpdate || ruleType == RuleType.responseUpdate) { - return Text("${items?.length}条修改", style: const TextStyle(color: Colors.grey)); + return Text(localizations.itemUpdate(items?.length ?? 0), style: const TextStyle(color: Colors.grey)); } return const SizedBox(); } @@ -560,7 +576,7 @@ class _RewriteRuleState extends State { Widget textField(String label, TextEditingController controller, String hint, {bool required = false, TextInputType? keyboardType, FormFieldSetter? onSaved}) { return Row(children: [ - SizedBox(width: 50, child: Text(label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))), + SizedBox(width: 58, child: Text(label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500))), Expanded( child: TextFormField( controller: controller, diff --git a/lib/ui/mobile/setting/rewrite/rewrite_replace.dart b/lib/ui/mobile/setting/rewrite/rewrite_replace.dart index 2fd2527..df86dd3 100644 --- a/lib/ui/mobile/setting/rewrite/rewrite_replace.dart +++ b/lib/ui/mobile/setting/rewrite/rewrite_replace.dart @@ -1,5 +1,7 @@ -import 'package:flutter/material.dart'; +import 'package:file_picker/file_picker.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/ui/component/state_component.dart'; import 'package:network_proxy/ui/component/widgets.dart'; @@ -22,6 +24,8 @@ class _RewriteReplaceState extends State { List items = []; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override initState() { super.initState(); @@ -53,11 +57,13 @@ class _RewriteReplaceState extends State { @override Widget build(BuildContext context) { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); + return Scaffold( resizeToAvoidBottomInset: false, appBar: AppBar( title: ListTile( - title: Text(widget.ruleType.label, + title: Text(isCN ? widget.ruleType.name : widget.ruleType.name, textAlign: TextAlign.center, style: const TextStyle(fontWeight: FontWeight.w500)), subtitle: Text(widget.subtitle, maxLines: 1, @@ -76,7 +82,7 @@ class _RewriteReplaceState extends State { } Navigator.of(context).pop(items); }, - child: const Text("完成", style: TextStyle(fontSize: 16))), + child: Text(localizations.done, style: const TextStyle(fontSize: 16))), ]), body: rewriteWidgets()); } @@ -89,7 +95,9 @@ class _RewriteReplaceState extends State { if (widget.ruleType == RuleType.responseReplace || widget.ruleType == RuleType.requestReplace) { bool requestEdited = widget.ruleType == RuleType.requestReplace; - List tabs = requestEdited ? ["请求行", "请求头", "请求体"] : ["状态码", "响应头", "响应体"]; + List tabs = requestEdited + ? [localizations.requestLine, localizations.requestHeader, localizations.requestBody] + : [localizations.statusCode, localizations.responseHeader, localizations.responseBody]; return DefaultTabController( length: tabs.length, @@ -114,12 +122,13 @@ class _RewriteReplaceState extends State { //tabBar TabBar tabBar(List tabs) { return TabBar( + labelPadding: const EdgeInsets.symmetric(horizontal: 0), tabs: tabs .map((label) => Tab( height: 38, child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ - Text(label, style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), - const SizedBox(width: 5), + Text(label, style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)), + const SizedBox(width: 3), Dot(color: items[tabs.indexOf(label)].enabled ? const Color(0xFF00FF00) : Colors.grey) ]))) .toList()); @@ -127,15 +136,34 @@ class _RewriteReplaceState extends State { //body Widget body() { + bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh'); var rewriteItem = items.firstWhere( (item) => item.type == RewriteType.replaceRequestBody || item.type == RewriteType.replaceResponseBody); return ListView(children: [ Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const SizedBox(width: 10), + const SizedBox(width: 5), + Text("${localizations.type}: "), + SizedBox( + width: 90, + child: DropdownButtonFormField( + value: rewriteItem.bodyType ?? ReplaceBodyType.text.name, + focusColor: Colors.transparent, + itemHeight: 48, + decoration: + const InputDecoration(contentPadding: EdgeInsets.all(10), isDense: true, border: InputBorder.none), + items: ReplaceBodyType.values + .map((e) => DropdownMenuItem( + value: e.name, + child: Text(isCN ? e.label : e.name.toUpperCase(), + style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)))) + .toList(), + onChanged: (val) => setState(() { + rewriteItem.bodyType = val!; + }))), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -145,13 +173,49 @@ class _RewriteReplaceState extends State { })) ])) ]), + const SizedBox(height: 10), + if (rewriteItem.bodyType == ReplaceBodyType.file.name) + fileBodyEdit(rewriteItem) + else + TextFormField( + initialValue: rewriteItem.body, + style: const TextStyle(fontSize: 14), + maxLines: 15, + decoration: decoration(localizations.replaceBodyWith, + hintText: '${localizations.example} {"code":"200","data":{}}'), + onChanged: (val) => rewriteItem.body = val) + ]); + } + + Widget fileBodyEdit(RewriteItem item) { + return Column(children: [ const SizedBox(height: 5), - TextFormField( - initialValue: rewriteItem.body, - style: const TextStyle(fontSize: 14), - maxLines: 15, - decoration: decoration('消息体替换为:', hintText: '示例: {"code":"200","data":{}}'), - onChanged: (val) => rewriteItem.body = val) + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + FilledButton( + onPressed: () async { + FilePickerResult? result = await FilePicker.platform.pickFiles(); + if (result == null) { + return; + } + item.bodyFile = result.files.single.path; + setState(() {}); + }, + child: Text(localizations.selectFile, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500))), + const SizedBox(width: 10), + FilledButton( + onPressed: () { + setState(() { + item.bodyFile = null; + }); + }, + child: Text(localizations.delete, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500))), + ]), + const SizedBox(height: 10), + if (item.bodyFile != null) + Container( + padding: const EdgeInsets.all(8), + foregroundDecoration: BoxDecoration(border: Border.all(color: Theme.of(context).primaryColor, width: 1)), + child: Text(item.bodyFile ?? '')) ]); } @@ -162,11 +226,11 @@ class _RewriteReplaceState extends State { return Column(children: [ Row(mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('Header列表'), + const Text('Header'), const SizedBox(width: 10), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -186,7 +250,7 @@ class _RewriteReplaceState extends State { return Column( children: [ Row(children: [ - const Text('请求方法'), + Text(localizations.requestMethod), const SizedBox(width: 10), SizedBox( width: 120, @@ -207,7 +271,7 @@ class _RewriteReplaceState extends State { })), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -220,9 +284,11 @@ class _RewriteReplaceState extends State { ])), ]), const SizedBox(height: 15), - textField("Path", rewriteItem.path, "示例: /api/v1/user", onChanged: (val) => rewriteItem.values['path'] = val), + textField("Path", rewriteItem.path, "${localizations.example} /api/v1/user", + onChanged: (val) => rewriteItem.values['path'] = val), const SizedBox(height: 15), - textField("URL参数", rewriteItem.queryParam, "示例: id=1&name=2", onChanged: (val) => rewriteItem.queryParam = val), + textField("URL${localizations.param}", rewriteItem.queryParam, "${localizations.example} id=1&name=2", + onChanged: (val) => rewriteItem.queryParam = val), ], ); } @@ -230,13 +296,13 @@ class _RewriteReplaceState extends State { //重定向 Widget redirectEdit(RewriteItem rewriteItem) { return TextFormField( - decoration: decoration('重定向到:', hintText: 'http://www.example.com/api'), + decoration: decoration(localizations.redirectTo, hintText: 'http://www.example.com/api'), maxLines: 5, initialValue: rewriteItem.redirectUrl, onChanged: (val) => rewriteItem.redirectUrl = val, validator: (val) { if (val == null || val.trim().isEmpty) { - return '重定向URL不能为空'; + return '${localizations.redirect} URL ${localizations.cannotBeEmpty}'; } return null; }); @@ -267,7 +333,7 @@ class _RewriteReplaceState extends State { padding: const EdgeInsets.all(10), child: Column(children: [ Row(crossAxisAlignment: CrossAxisAlignment.center, children: [ - const Text('状态码'), + Text(localizations.statusCode), const SizedBox(width: 10), SizedBox( width: 100, @@ -284,7 +350,7 @@ class _RewriteReplaceState extends State { )), Expanded( child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [ - const Text('启用'), + Text(localizations.enable), const SizedBox(width: 10), SwitchWidget( value: rewriteItem.enabled, @@ -331,6 +397,8 @@ class Headers extends StatefulWidget { class HeadersState extends State with AutomaticKeepAliveClientMixin { final Map _headers = {}; + AppLocalizations get localizations => AppLocalizations.of(context)!; + @override bool get wantKeepAlive => true; @@ -366,7 +434,7 @@ class HeadersState extends State with AutomaticKeepAliveClientMixin { ]; list.add(TextButton( - child: const Text("添加Header", textAlign: TextAlign.center), + child: Text("${localizations.add}Header", textAlign: TextAlign.center), onPressed: () { setState(() { _headers[TextEditingController()] = TextEditingController(); diff --git a/lib/ui/mobile/setting/rewrite/rewrite_update.dart b/lib/ui/mobile/setting/rewrite/rewrite_update.dart index 26ac8dd..cdaba23 100644 --- a/lib/ui/mobile/setting/rewrite/rewrite_update.dart +++ b/lib/ui/mobile/setting/rewrite/rewrite_update.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/components/request_rewrite_manager.dart'; import 'package:network_proxy/ui/component/widgets.dart'; @@ -17,6 +18,8 @@ class RewriteUpdateWidget extends StatefulWidget { class _RewriteUpdateState extends State { List items = []; + AppLocalizations get i18n => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -31,16 +34,18 @@ class _RewriteUpdateState extends State { @override Widget build(BuildContext context) { + bool isCN = Localizations.localeOf(context).languageCode == "zh"; return Scaffold( appBar: AppBar( centerTitle: true, - title: Text(widget.ruleType.label, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + title: Text(isCN ? widget.ruleType.label : widget.ruleType.name, + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), actions: [ TextButton( onPressed: () { Navigator.of(context).pop(items); }, - child: const Text("完成")), + child: Text(i18n.done)), const SizedBox(width: 10) ], ), @@ -92,6 +97,8 @@ class _RewriteUpdateAddState extends State { GlobalKey formKey = GlobalKey(); late RewriteItem rewriteItem; + AppLocalizations get i18n => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -108,31 +115,31 @@ class _RewriteUpdateAddState extends State { String keyTips = ""; String valueTips = ""; if (isDelete) { - keyTips = "匹配规则"; - valueTips = "为空表示匹配全部"; + keyTips = i18n.matchRule; + valueTips = i18n.emptyMatchAll; } else if (rewriteType == RewriteType.updateQueryParam || rewriteType == RewriteType.updateHeader) { keyTips = rewriteType == RewriteType.updateQueryParam ? "name=123" : "Content-Type: application/json"; valueTips = rewriteType == RewriteType.updateQueryParam ? "name=456" : "Content-Type: application/xml"; } var typeList = widget.ruleType == RuleType.requestUpdate ? RewriteType.updateRequest : RewriteType.updateResponse; - + bool isCN = Localizations.localeOf(context).languageCode == "zh"; return AlertDialog( - title: const Text("添加修改", - style: TextStyle(fontSize: 15, fontWeight: FontWeight.w500), textAlign: TextAlign.center), + title: Text(i18n.add, + style: const TextStyle(fontSize: 15, fontWeight: FontWeight.w500), textAlign: TextAlign.center), actions: [ - TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("取消")), + TextButton(onPressed: () => Navigator.of(context).pop(), child: Text(i18n.cancel)), TextButton( onPressed: () { if (!(formKey.currentState as FormState).validate()) { - FlutterToastr.show("缺少配置", context, position: FlutterToastr.center); + FlutterToastr.show(i18n.cannotBeEmpty, context, position: FlutterToastr.center); return; } (formKey.currentState as FormState).save(); rewriteItem.type = rewriteType; Navigator.of(context).pop(rewriteItem); }, - child: const Text("确定")), + child: Text(i18n.confirm)), ], content: SizedBox( width: 320, @@ -142,10 +149,10 @@ class _RewriteUpdateAddState extends State { child: ListView(children: [ Row( children: [ - const Text('类型'), + Text(i18n.type), const SizedBox(width: 15), SizedBox( - width: 120, + width: 140, child: DropdownButtonFormField( value: rewriteType, focusColor: Colors.transparent, @@ -155,7 +162,7 @@ class _RewriteUpdateAddState extends State { items: typeList .map((e) => DropdownMenuItem( value: e, - child: Text(e.label, + child: Text(e.getDescribe(isCN), style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500)))) .toList(), onChanged: (val) { @@ -166,17 +173,17 @@ class _RewriteUpdateAddState extends State { ], ), const SizedBox(height: 15), - textField(isUpdate ? "匹配" : "名称", rewriteItem.key, keyTips, + textField(isUpdate ? i18n.match : i18n.name, rewriteItem.key, keyTips, required: !isDelete, onSaved: (val) => rewriteItem.key = val), const SizedBox(height: 15), - textField(isUpdate ? "替换" : "值", rewriteItem.value, valueTips, + textField(isUpdate ? i18n.replace : i18n.value, rewriteItem.value, valueTips, onSaved: (val) => rewriteItem.value = val), ])))); } Widget textField(String label, String? val, String hint, {bool required = false, FormFieldSetter? onSaved}) { return Row(children: [ - SizedBox(width: 50, child: Text(label)), + SizedBox(width: 55, child: Text(label)), Expanded( child: TextFormField( initialValue: val, @@ -211,6 +218,8 @@ class UpdateList extends StatefulWidget { } class _UpdateListState extends State { + AppLocalizations get i18n => AppLocalizations.of(context)!; + @override void initState() { super.initState(); @@ -231,10 +240,10 @@ class _UpdateListState extends State { Row( mainAxisAlignment: MainAxisAlignment.start, children: [ - Container(width: 130, padding: const EdgeInsets.only(left: 10), child: const Text("类型")), - const SizedBox(width: 50, child: Text("启用", textAlign: TextAlign.center)), + Container(width: 130, padding: const EdgeInsets.only(left: 10), child: Text(i18n.type)), + SizedBox(width: 50, child: Text(i18n.enable, textAlign: TextAlign.center)), const VerticalDivider(), - const Expanded(child: Text("修改")), + Expanded(child: Text(i18n.modify)), ], ), const Divider(thickness: 0.5), @@ -269,7 +278,10 @@ class _UpdateListState extends State { padding: const EdgeInsets.all(5), child: Row( children: [ - SizedBox(width: 130, child: Text(list[index].type.label, style: const TextStyle(fontSize: 13))), + SizedBox( + width: 130, + child: Text(list[index].type.getDescribe(i18n.localeName == 'zh'), + style: const TextStyle(fontSize: 13))), SizedBox( width: 40, child: SwitchWidget( @@ -307,7 +319,7 @@ class _UpdateListState extends State { builder: (ctx) { return Wrap(alignment: WrapAlignment.center, children: [ BottomSheetItem( - text: "编辑", + text: i18n.modify, onPressed: () async { showDialog( context: context, @@ -321,14 +333,14 @@ class _UpdateListState extends State { }), const Divider(thickness: 0.5), BottomSheetItem( - text: widget.items[index].enabled ? "禁用" : "启用", + text: widget.items[index].enabled ? i18n.disabled : i18n.enable, onPressed: () => widget.items[index].enabled = !widget.items[index].enabled), const Divider(thickness: 0.5), BottomSheetItem( - text: "删除", + text: i18n.delete, onPressed: () async { widget.items.removeAt(index); - if (mounted) FlutterToastr.show('删除成功', context); + if (mounted) FlutterToastr.show(i18n.deleteSuccess, context); }), Container(color: Theme.of(context).hoverColor, height: 8), TextButton( @@ -336,7 +348,7 @@ class _UpdateListState extends State { height: 50, width: double.infinity, padding: const EdgeInsets.only(top: 10), - child: const Text("取消", textAlign: TextAlign.center)), + child: Text(i18n.cancel, textAlign: TextAlign.center)), onPressed: () { Navigator.of(context).pop(); }), diff --git a/pubspec.lock b/pubspec.lock index 40374dc..0b301de 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -162,6 +162,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "7.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6" + url: "https://pub.flutter-io.cn" + source: hosted + version: "6.1.1" file_selector: dependency: "direct main" description: @@ -474,10 +482,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_foundation: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 044dea9..1469668 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: network_proxy description: ProxyPin publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.6+6 +version: 1.0.6+7 environment: sdk: '>=3.0.2 <4.0.0' @@ -37,6 +37,8 @@ dependencies: file_selector: ^1.0.1 flutter_js: ^0.8.0 flutter_code_editor: + file_picker: ^6.1.1 + dev_dependencies: flutter_test: sdk: flutter