diff --git a/lib/main.dart b/lib/main.dart index e4acdde7..43170cb9 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,12 +63,15 @@ Widget multiWindow(int windowId, Map argument) { return HttpBodyWidget( windowController: WindowController.fromWindowId(windowId), httpMessage: HttpMessage.fromJson(argument['httpMessage']), - inNewWindow: true); + inNewWindow: true, + hideRequestRewrite: true); } if (argument['name'] == 'EncoderWidget') { return EncoderWidget( - type: EncoderType.nameOf(argument['type']), windowController: WindowController.fromWindowId(windowId)); + type: EncoderType.nameOf(argument['type']), + text: argument['text'], + windowController: WindowController.fromWindowId(windowId)); } return const SizedBox(); diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 75259693..b7e7af82 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -103,11 +103,15 @@ class HttpRequest extends HttpMessage { String get requestUrl => uri.startsWith("/") ? '${remoteDomain()}$uri' : uri; - String? path() { - if (hostAndPort?.isSsl() == true && uri.startsWith("/")) { - return uri; + Uri? get requestUri { + try { + return Uri.parse(requestUrl); + } catch (e) { + return null; } + } + String? path() { try { var requestPath = Uri.parse(requestUrl).path; return requestPath.isEmpty ? "/" : requestPath; diff --git a/lib/network/http/http_parser.dart b/lib/network/http/http_parser.dart index ae8ce337..608dd89c 100644 --- a/lib/network/http/http_parser.dart +++ b/lib/network/http/http_parser.dart @@ -5,7 +5,7 @@ import 'package:network_proxy/network/http/http_headers.dart'; /// http解析器 class HttpParse { - static const int defaultMaxLength = 40960; + static const int defaultMaxLength = 102400; /// 解析请求行 List parseInitialLine(ByteBuf data, int size) { diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index 59121aee..88b751de 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/utils/ip.dart'; +import 'package:network_proxy/utils/lang.dart'; import 'package:proxy_manager/proxy_manager.dart'; /// @author wanghongen @@ -10,11 +11,13 @@ class SystemProxy { static String? _hardwarePort; ///获取系统代理 - static Future getSystemProxy(ProxyTypes proxyTypes) async { + static Future getSystemProxy(ProxyTypes types) async { if (Platform.isWindows) { return await _getSystemProxyWindows(); } else if (Platform.isMacOS) { - return await _getSystemProxyMacOS(proxyTypes); + return await _getSystemProxyMacOS(types); + } else if (Platform.isLinux) { + return await _getLinuxProxyServer(types); } else { return null; } @@ -198,6 +201,29 @@ class SystemProxy { ]).then((results) => results.stdout.toString()); } + static Future _getLinuxProxyServer(ProxyTypes types) async { + ///linux 获取代理 + var mode = await Process.run("gsettings", ["get", "org.gnome.system.proxy", "mode"]) + .then((value) => value.stdout.toString().trim()); + if (mode.contains("manual")) { + var hostFuture = Process.run("gsettings", ["get", "org.gnome.system.proxy.${types.name}", "host"]) + .then((value) => value.stdout.toString().trim()); + var portFuture = Process.run("gsettings", ["get", "org.gnome.system.proxy.${types.name}", "port"]) + .then((value) => value.stdout.toString().trim()); + + return Future.wait([hostFuture, portFuture]).then((value) { + print(value); + var host = Strings.trimWrap(value[0], "'"); + var port = Strings.trimWrap(value[1], "'"); + if (host.isNotEmpty && port.isNotEmpty) { + return ProxyInfo.of(host, int.parse(port)); + } + return null; + }); + } + return null; + } + static _concatCommands(List commands) { return commands.where((element) => element.isNotEmpty).join(' && '); } diff --git a/lib/ui/component/encoder.dart b/lib/ui/component/encoder.dart index 77b18412..d2850aa8 100644 --- a/lib/ui/component/encoder.dart +++ b/lib/ui/component/encoder.dart @@ -24,8 +24,9 @@ enum EncoderType { class EncoderWidget extends StatefulWidget { final EncoderType type; final WindowController? windowController; + final String? text; - const EncoderWidget({super.key, required this.type, this.windowController}); + const EncoderWidget({super.key, required this.type, this.windowController, this.text}); @override State createState() => _EncoderState(); @@ -47,6 +48,8 @@ class _EncoderState extends State with SingleTickerProviderStateM void initState() { super.initState(); type = widget.type; + inputText = widget.text ?? ''; + tabController = TabController(initialIndex: type.index, length: tabs.length, vsync: this); RawKeyboard.instance.addListener(onKeyEvent); } @@ -71,7 +74,7 @@ class _EncoderState extends State with SingleTickerProviderStateM @override Widget build(BuildContext context) { return Scaffold( - resizeToAvoidBottomInset: true, + resizeToAvoidBottomInset: true, appBar: AppBar( title: Text('${type.name.toUpperCase()}编码', style: const TextStyle(fontSize: 16)), centerTitle: true, @@ -91,7 +94,8 @@ class _EncoderState extends State with SingleTickerProviderStateM children: [ const Text('输入要转换的内容'), const SizedBox(height: 5), - TextField( + TextFormField( + initialValue: inputText, minLines: 5, maxLines: 10, onChanged: (text) => inputText = text, @@ -107,7 +111,15 @@ class _EncoderState extends State with SingleTickerProviderStateM const SizedBox(width: 50), type == EncoderType.md5 ? const SizedBox() - : OutlinedButton(onPressed: decode, child: Text('${type.name.toUpperCase()}解码')), + : FilledButton(onPressed: decode, child: Text('${type.name.toUpperCase()}解码')), + const SizedBox(width: 50), + OutlinedButton( + onPressed: () { + setState(() { + outputTextController.clear(); + }); + }, + child: const Text('清空结果')), ], ), const Text('转换结果'), diff --git a/lib/ui/component/multi_window.dart b/lib/ui/component/multi_window.dart new file mode 100644 index 00000000..175a75b7 --- /dev/null +++ b/lib/ui/component/multi_window.dart @@ -0,0 +1,29 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:desktop_multi_window/desktop_multi_window.dart'; +import 'package:flutter/material.dart'; +import 'package:network_proxy/ui/component/encoder.dart'; +import 'package:network_proxy/utils/platform.dart'; +import 'package:window_manager/window_manager.dart'; + +encodeWindow(EncoderType type, BuildContext context, [String? text]) async { + if (Platforms.isMobile()) { + Navigator.of(context).push(MaterialPageRoute(builder: (context) => EncoderWidget(type: type, text: text))); + return; + } + + var ratio = 1.0; + if (Platform.isWindows) { + ratio = WindowManager.instance.getDevicePixelRatio(); + } + + final window = await DesktopMultiWindow.createWindow(jsonEncode( + {'name': 'EncoderWidget', 'type': type.name, 'text': text}, + )); + window.setTitle('编码'); + window + ..setFrame(const Offset(80, 80) & Size(900 * ratio, 600 * ratio)) + ..center() + ..show(); +} diff --git a/lib/ui/component/state_component.dart b/lib/ui/component/state_component.dart index 5eacf472..40290ad2 100644 --- a/lib/ui/component/state_component.dart +++ b/lib/ui/component/state_component.dart @@ -1,26 +1,30 @@ -import 'package:flutter/material.dart'; +import 'package:flutter/material.dart'; -class StateComponent extends StatefulWidget { +class KeepAliveWrapper extends StatefulWidget { + const KeepAliveWrapper({Key? key, this.keepAlive = true, required this.child}) : super(key: key); + final bool keepAlive; final Widget child; - final Function? onChange; - - const StateComponent(this.child, {Key? key, this.onChange }) : super(key: key); @override - State createState() { - return _StateComponentState(); - } + State createState() => _KeepAliveWrapperState(); } -class _StateComponentState extends State { - void changeState() { - setState(() {}); - if (widget.onChange != null) { - widget.onChange!(); - } - } +class _KeepAliveWrapperState extends State with AutomaticKeepAliveClientMixin { @override Widget build(BuildContext context) { + super.build(context); return widget.child; } + + @override + void didUpdateWidget(covariant KeepAliveWrapper oldWidget) { + if (oldWidget.keepAlive != widget.keepAlive) { + // keepAlive 状态需要更新,实现在 AutomaticKeepAliveClientMixin 中 + updateKeepAlive(); + } + super.didUpdateWidget(oldWidget); + } + + @override + bool get wantKeepAlive => widget.keepAlive; } diff --git a/lib/ui/component/toolbox.dart b/lib/ui/component/toolbox.dart index f197dee6..cc1e59a1 100644 --- a/lib/ui/component/toolbox.dart +++ b/lib/ui/component/toolbox.dart @@ -5,6 +5,7 @@ import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/ui/component/encoder.dart'; +import 'package:network_proxy/ui/component/multi_window.dart'; import 'package:network_proxy/ui/mobile/request/request_editor.dart'; import 'package:network_proxy/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; @@ -34,28 +35,28 @@ class _ToolboxState extends State { onTap: httpRequest, child: Container( padding: const EdgeInsets.all(10), - child: const Column(children: [Icon(Icons.http), Text('发起请求')]), + child: const Column(children: [Icon(Icons.http), Text('发起请求', style: TextStyle(fontSize: 14))]), )), const Divider(thickness: 0.3), const Text('编码', style: TextStyle(fontSize: 14, fontWeight: FontWeight.bold)), Row( children: [ InkWell( - onTap: () => encode(EncoderType.url), + onTap: () => encodeWindow(EncoderType.url, context), child: Container( padding: const EdgeInsets.all(10), child: const Column(children: [Icon(Icons.link), Text(' URL')]), )), const SizedBox(width: 10), InkWell( - onTap: () => encode(EncoderType.base64), + onTap: () => encodeWindow(EncoderType.base64, context), child: Container( padding: const EdgeInsets.all(10), child: const Column(children: [Icon(Icons.currency_bitcoin), Text('Base64')]), )), const SizedBox(width: 15), InkWell( - onTap: () => encode(EncoderType.md5), + onTap: () => encodeWindow(EncoderType.md5, context), child: Container( padding: const EdgeInsets.all(10), child: const Column(children: [Icon(Icons.enhanced_encryption), Text('MD5')]), @@ -67,27 +68,6 @@ class _ToolboxState extends State { ); } - encode(EncoderType type) async { - if (Platforms.isMobile()) { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => EncoderWidget(type: type))); - return; - } - - var ratio = 1.0; - if (Platform.isWindows) { - ratio = WindowManager.instance.getDevicePixelRatio(); - } - - final window = await DesktopMultiWindow.createWindow(jsonEncode( - {'name': 'EncoderWidget', 'type': type.name}, - )); - window.setTitle('编码'); - window - ..setFrame(const Offset(80, 80) & Size(900 * ratio, 600 * ratio)) - ..center() - ..show(); - } - httpRequest() async { if (Platforms.isMobile()) { Navigator.of(context) diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index 77eaae92..eff659a6 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -8,8 +8,10 @@ import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; +import 'package:network_proxy/ui/component/encoder.dart'; import 'package:network_proxy/ui/component/json/json_viewer.dart'; import 'package:network_proxy/ui/component/json/theme.dart'; +import 'package:network_proxy/ui/component/multi_window.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart'; import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart'; @@ -24,9 +26,15 @@ class HttpBodyWidget extends StatefulWidget { final bool inNewWindow; //是否在新窗口打开 final WindowController? windowController; final ScrollController? scrollController; + final bool hideRequestRewrite; //是否隐藏请求重写 const HttpBodyWidget( - {super.key, required this.httpMessage, this.inNewWindow = false, this.windowController, this.scrollController}); + {super.key, + required this.httpMessage, + this.inNewWindow = false, + this.windowController, + this.scrollController, + this.hideRequestRewrite = false}); @override State createState() { @@ -73,13 +81,14 @@ class HttpBodyState extends State { List list = [ widget.inNewWindow ? const SizedBox() : titleWidget(), - TabBar( - labelPadding: const EdgeInsets.symmetric(horizontal: 2.0), - tabs: tabs.tabList(), - onTap: (index) { - tabIndex = index; - bodyKey.currentState?.changeState(widget.httpMessage, tabs.list[tabIndex]); - }), + SizedBox( + height: 36, + child: TabBar( + tabs: tabs.tabList(), + onTap: (index) { + tabIndex = index; + bodyKey.currentState?.changeState(widget.httpMessage, tabs.list[tabIndex]); + })), Padding( padding: const EdgeInsets.all(10), child: _Body( @@ -109,10 +118,10 @@ class HttpBodyState extends State { var type = widget.httpMessage is HttpRequest ? "Request" : "Response"; var list = [ - Text('$type Body', style: const TextStyle(fontSize: 16, fontWeight: FontWeight.w500)), + Text('$type Body', style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), const SizedBox(width: 15), IconButton( - icon: const Icon(Icons.copy), + icon: const Icon(Icons.copy, size: 20), tooltip: '复制', onPressed: () { var body = bodyKey.currentState?.body; @@ -123,10 +132,10 @@ class HttpBodyState extends State { }), ]; - if (!inNewWindow || Platforms.isMobile()) { + if (!widget.hideRequestRewrite) { list.add(const SizedBox(width: 5)); list.add(IconButton( - icon: const Icon(Icons.edit_document), + icon: const Icon(Icons.edit_document, size: 20), tooltip: '请求重写', onPressed: () { HttpRequest? request; @@ -137,7 +146,7 @@ class HttpBodyState extends State { } var body = bodyKey.currentState?.body; - var rule = RequestRewriteRule(true, request?.path() ?? '', request?.remoteDomain(), + var rule = RequestRewriteRule(true, request?.path() ?? '', request?.requestUri?.host, requestBody: widget.httpMessage is HttpRequest ? body : null, responseBody: widget.httpMessage is HttpResponse ? body : null); @@ -161,9 +170,16 @@ class HttpBodyState extends State { })); } + list.add(const SizedBox(width: 5)); + list.add(IconButton( + icon: const Icon(Icons.abc, size: 25), + tooltip: '编码', + onPressed: () { + encodeWindow(EncoderType.base64, context, bodyKey.currentState?.body); + })); if (!inNewWindow) { list.add(const SizedBox(width: 5)); - list.add(IconButton(icon: const Icon(Icons.open_in_new), tooltip: '新窗口打开', onPressed: () => openNew())); + list.add(IconButton(icon: const Icon(Icons.open_in_new, size: 20), tooltip: '新窗口打开', onPressed: () => openNew())); } return Row( @@ -315,7 +331,7 @@ class Tabs { } List tabList() { - return list.map((e) => Tab(child: Text(e.title))).toList(); + return list.map((e) => Tab(child: Text(e.title, style: const TextStyle(fontSize: 14)))).toList(); } } diff --git a/lib/ui/content/panel.dart b/lib/ui/content/panel.dart index 314066f3..3424d3e1 100644 --- a/lib/ui/content/panel.dart +++ b/lib/ui/content/panel.dart @@ -42,6 +42,7 @@ class NetworkTabController extends StatefulWidget { } class NetworkTabState extends State with SingleTickerProviderStateMixin { + final TextStyle textStyle = const TextStyle(fontSize: 14); late TabController _tabController; void changeState() { @@ -128,11 +129,13 @@ class NetworkTabState extends State with SingleTickerProvi if (widget.request.get() == null) { return const SizedBox(); } - var scrollController = ScrollController(); + + var scrollController = ScrollController(); //处理body也有滚动条问题 var path = widget.request.get()?.path() ?? ''; try { path = Uri.decodeFull(widget.request.get()?.path() ?? ''); } catch (_) {} + return ListView( controller: scrollController, children: [rowWidget("URI", path), ...message(widget.request.get(), "Request", scrollController)]); @@ -167,8 +170,9 @@ class NetworkTabState extends State with SingleTickerProvi for (var v in values) { headers.add(Row(children: [ SelectableText('$name: ', - style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.deepOrangeAccent)), - Expanded(child: SelectableText(v, contextMenuBuilder: contextMenu, maxLines: 8, minLines: 1)), + style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.deepOrangeAccent, fontSize: 14)), + Expanded( + child: SelectableText(v, style: textStyle, contextMenuBuilder: contextMenu, maxLines: 8, minLines: 1)), ])); headers.add(const Divider(thickness: 0.1)); } @@ -178,7 +182,7 @@ class NetworkTabState extends State with SingleTickerProvi Widget headerWidget = ExpansionTile( tilePadding: const EdgeInsets.only(left: 0), - title: Text("$type Headers", style: const TextStyle(fontWeight: FontWeight.w500)), + title: Text("$type Headers", style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), initiallyExpanded: true, shape: const Border(), children: headers); @@ -188,7 +192,7 @@ class NetworkTabState extends State with SingleTickerProvi Widget expansionTile(String title, List content) { return ExpansionTile( - title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500)), + title: Text(title, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), tilePadding: const EdgeInsets.only(left: 0), expandedAlignment: Alignment.topLeft, initiallyExpanded: true, @@ -206,10 +210,11 @@ class NetworkTabState extends State with SingleTickerProvi Widget rowWidget(final String name, String? value) { return Row(children: [ - Expanded(flex: 2, child: SelectableText(name, style: const TextStyle(fontWeight: FontWeight.w500))), + Expanded(flex: 2, child: SelectableText(name, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14))), Expanded( flex: 4, child: SelectableText( + style: textStyle, value ?? '', contextMenuBuilder: contextMenu, )) diff --git a/lib/ui/desktop/desktop.dart b/lib/ui/desktop/desktop.dart index 248cd70a..bb80a21a 100644 --- a/lib/ui/desktop/desktop.dart +++ b/lib/ui/desktop/desktop.dart @@ -50,7 +50,7 @@ class _DesktopHomePagePageState extends State implements EventL void initState() { super.initState(); proxyServer = ProxyServer(widget.configuration, listener: this); - panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 18), proxyServer: proxyServer); + panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 16), proxyServer: proxyServer); if (widget.configuration.upgradeNoticeV2) { WidgetsBinding.instance.addPostFrameCallback((_) { @@ -76,7 +76,7 @@ class _DesktopHomePagePageState extends State implements EventL return Container( decoration: BoxDecoration( border: Border( - right: BorderSide(color: Theme.of(context).dividerColor, width: 0.3), + right: BorderSide(color: Theme.of(context).dividerColor, width: 0.2), )), width: 55, child: leftNavigation(index)); diff --git a/lib/ui/desktop/left/request_editor.dart b/lib/ui/desktop/left/request_editor.dart index 653116a8..dc7d8664 100644 --- a/lib/ui/desktop/left/request_editor.dart +++ b/lib/ui/desktop/left/request_editor.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; @@ -9,6 +8,8 @@ import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; import 'package:network_proxy/network/http_client.dart'; import 'package:network_proxy/ui/component/split_view.dart'; +import 'package:network_proxy/ui/component/state_component.dart'; +import 'package:network_proxy/ui/content/body.dart'; import 'package:network_proxy/utils/curl.dart'; class RequestEditor extends StatefulWidget { @@ -216,13 +217,9 @@ class _HttpState extends State<_HttpWidget> { } Widget _body() { - if (body != null && widget.readOnly && widget.message?.contentType == ContentType.json) { - try { - body = const JsonEncoder.withIndent(' ').convert(const JsonDecoder().convert(body!)); - } catch (_) {} - } if (widget.readOnly) { - return SelectableText(body ?? ''); + return KeepAliveWrapper( + child: SingleChildScrollView(child: HttpBodyWidget(httpMessage: widget.message, hideRequestRewrite: true))); } return TextField( diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 5189b7df..e2971b8d 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -89,6 +89,40 @@ class _SslState extends State { } void pcCer() async { + List list = []; + if (Platform.isMacOS || Platform.isWindows) { + list = [ + Text(" 安装证书到本系统,${Platform.isMacOS ? "“安装完双击选择“始终信任此证书”。 如安装打开失败,请下载证书拖拽到系统证书里" : "选择“受信任的根证书颁发机构”"}"), + const SizedBox(height: 10), + FilledButton(onPressed: _installCert, child: const Text("安装证书")), + const SizedBox(height: 10), + Platform.isMacOS + ? Image.network("https://foruda.gitee.com/images/1689323260158189316/c2d881a4_1073801.png", + width: 800, height: 500) + : Row(children: [ + Image.network("https://foruda.gitee.com/images/1689335589122168223/c904a543_1073801.png", + width: 400, height: 400), + const SizedBox(width: 10), + Image.network("https://foruda.gitee.com/images/1689335334688878324/f6aa3a3a_1073801.png", + width: 400, height: 400) + ]) + ]; + } else { + list.add(const Text("安装证书到本系统,以Ubuntu为例 下载证书:\n" + "先把证书复制到 /usr/local/share/ca-certificates/,然后执行 update-ca-certificates 即可。\n" + "其他系统请网上搜索安装根证书")); + list.add(const SizedBox(height: 5)); + list.add(const Text("提示:FireFox有自己的信任证书库,所以要手动在设置中导入需要导入的证书。", style: TextStyle(fontSize: 12))); + list.add(const SizedBox(height: 10)); + list.add(const SelectableText.rich( + textAlign: TextAlign.justify, + TextSpan(style: TextStyle(color: Color(0xff6a8759)), children: [ + TextSpan(text: " sudo cp ProxyPinCA.crt /usr/local/share/ca-certificates/ \n"), + TextSpan(text: " sudo update-ca-certificates") + ]))); + list.add(const SizedBox(height: 10)); + } + showDialog( context: context, builder: (BuildContext context) { @@ -107,22 +141,7 @@ class _SslState extends State { }))) ]), alignment: Alignment.center, - children: [ - Text(" 安装证书到本系统,${Platform.isMacOS ? "“安装完双击选择“始终信任此证书”。 如安装打开失败,请下载证书拖拽到系统证书里" : "选择“受信任的根证书颁发机构”"}"), - const SizedBox(height: 10), - FilledButton(onPressed: _installCert, child: const Text("安装证书")), - const SizedBox(height: 10), - Platform.isMacOS - ? Image.network("https://foruda.gitee.com/images/1689323260158189316/c2d881a4_1073801.png", - width: 800, height: 500) - : Row(children: [ - Image.network("https://foruda.gitee.com/images/1689335589122168223/c904a543_1073801.png", - width: 400, height: 400), - const SizedBox(width: 10), - Image.network("https://foruda.gitee.com/images/1689335334688878324/f6aa3a3a_1073801.png", - width: 400, height: 400) - ]) - ]); + children: list); }); } @@ -269,4 +288,4 @@ class _SwitchState extends State<_Switch> { widget.proxyServer.configuration.flushConfig(); } } -} +} \ No newline at end of file diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index 195050ef..6d734603 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -8,6 +8,7 @@ import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http/http_headers.dart'; import 'package:network_proxy/network/http_client.dart'; +import 'package:network_proxy/ui/content/body.dart'; import 'package:network_proxy/utils/curl.dart'; class MobileRequestEditor extends StatefulWidget { @@ -130,6 +131,7 @@ class RequestEditorState extends State with SingleTickerPro HttpClients.proxyRequest(proxyInfo: proxyInfo, request).then((response) { FlutterToastr.show('请求成功', context); this.response = response; + this.response?.request = request; tabController.animateTo(1); responseChange.value = !responseChange.value; }).catchError((e) { @@ -183,19 +185,14 @@ class _HttpState extends State<_HttpWidget> with AutomaticKeepAliveClientMixin { Headers(headers: widget.message?.headers, key: headerKey, readOnly: widget.readOnly), // 请求头 const SizedBox(height: 10), const Text("Body", style: TextStyle(fontWeight: FontWeight.w500, color: Colors.blue)), - _body() + _body(), + const SizedBox(height: 10), ])); } Widget _body() { - if (body != null && widget.readOnly && widget.message?.contentType == ContentType.json) { - try { - body = const JsonEncoder.withIndent(' ').convert(const JsonDecoder().convert(body!)); - } catch (_) {} - } - if (widget.readOnly) { - return SelectableText(body ?? ''); + return SingleChildScrollView(child: HttpBodyWidget(httpMessage: widget.message)); } return TextField( diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index bc0064e8..00d2006a 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -98,7 +98,7 @@ class _MobileSslState extends State { builder: (context) { return AlertDialog( title: const Text("提示"), - content: const Text("请先启动代理服务"), + content: const Text("请先从首页点击开启图标启动代理服务"), actions: [ TextButton( onPressed: () { diff --git a/lib/utils/curl.dart b/lib/utils/curl.dart index 0f9d7cd7..1467b71c 100644 --- a/lib/utils/curl.dart +++ b/lib/utils/curl.dart @@ -39,14 +39,12 @@ HttpRequest parseCurl(String curl) { if (it.endsWith("\\")) { it = it.substring(0, it.length - 1); } + //header if (it.startsWith(_h) || it.startsWith(_header)) { int index = it.startsWith(_h) ? _h.length : _header.length; var line = it.substring(index).trim(); - if (line.startsWith("'") && line.endsWith("'")) { - line = line.substring(1, line.length - 1); - } - + line = Strings.trimWrap(line, "'"); var pair = _split(line, ":"); if (pair != null) { headers.add(pair.key, pair.value); @@ -60,18 +58,12 @@ HttpRequest parseCurl(String curl) { value = it.substring(_data.length).trim(); } value = value.startsWith('\$') ? value.substring(1) : value; - if (value.startsWith("'") && value.endsWith("'")) { - value = value.substring(1, value.length - 1); - } - body = value; + body = Strings.trimWrap(value, "'"); } else if (it.startsWith(_x) || it.startsWith(_request)) { //method int index = it.startsWith(_x) ? _x.length : _request.length; var value = it.substring(index).trim(); - if (value.startsWith("'") && value.endsWith("'")) { - value = value.substring(1, value.length - 1); - } - method = HttpMethod.valueOf(value); + method = HttpMethod.valueOf(Strings.trimWrap(value, "'")); } else if (it.trim().startsWith("'http") || it.startsWith('curl') && it.contains("'http")) { var index = it.indexOf("'"); var value = it.substring(index + 1).trim(); diff --git a/lib/utils/lang.dart b/lib/utils/lang.dart index 60a8c98c..c8c41ac6 100644 --- a/lib/utils/lang.dart +++ b/lib/utils/lang.dart @@ -17,6 +17,13 @@ class Strings { return null; } + + static String trimWrap(String str, String wrap) { + if (str.startsWith(wrap) && str.endsWith(wrap)) { + return str.substring(1, str.length - 1); + } + return str; + } } class Pair { diff --git a/linux/proxy-pin.desktop b/linux/proxy-pin.desktop new file mode 100644 index 00000000..c3c641a5 --- /dev/null +++ b/linux/proxy-pin.desktop @@ -0,0 +1,8 @@ +[Desktop Entry] +Name=ProxyPin +Exec=/opt/proxypin/ProxyPin +Icon=/opt/proxypin/data/flutter_assets/assets/icon.png +Terminal=false +Type=Application +Categories=Development +Keywords=proxypin; \ No newline at end of file diff --git a/pubspec.lock b/pubspec.lock index 014be58c..1f0c585e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -69,10 +69,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" + sha256: fd832b5384d0d6da4f6df60b854d33accaaeb63aa9e10e736a87381f08dee2cb url: "https://pub.flutter-io.cn" source: hosted - version: "0.3.3+4" + version: "0.3.3+5" crypto: dependency: "direct main" description: @@ -154,18 +154,18 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" + sha256: a25a15ebbdfc33ab1cd26c63a6ee519df92338a9c10f122adda92938253bef04 url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.2" + version: "2.0.3" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" + sha256: f185ac890306b5779ecbd611f52502d8d4d63d27703ef73161ca0407e815f02c url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.15" + version: "2.0.16" flutter_test: dependency: "direct dev" description: flutter @@ -292,66 +292,66 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" + sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" + sha256: "6b8b19bd80da4f11ce91b2d1fb931f3006911477cec227cce23d3253d80df3f1" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.2.0" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" + sha256: "19314d595120f82aca0ba62787d58dde2cc6b5df7d2f0daf72489e38d1b57f2d" url: "https://pub.flutter-io.cn" source: hosted - version: "2.3.0" + version: "2.3.1" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.2.1" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 + sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.0" + version: "2.1.1" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da + sha256: "8bc9f22eee8690981c22aa7fc602f5c85b497a6fb2ceb35ee5a5e5ed85ad8170" url: "https://pub.flutter-io.cn" source: hosted - version: "2.2.0" + version: "2.2.1" platform: dependency: transitive description: name: platform - sha256: "57c07bf82207aee366dfaa3867b3164e4f03a238a461a11b0e8a3a510d51203d" + sha256: ae68c7bfcd7383af3629daafb32fb4e8681c7154428da4febcff06200585f102 url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.1" + version: "3.1.2" plugin_platform_interface: dependency: transitive description: name: plugin_platform_interface - sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" + sha256: da3fdfeccc4d4ff2da8f8c556704c08f912542c5fb3cf2233ed75372384a034d url: "https://pub.flutter-io.cn" source: hosted - version: "2.1.5" + version: "2.1.6" pointycastle: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 + sha256: "2942294a500b4fa0b918685aff406773ba0a4cd34b7f42198742a94083020ce5" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.18" + version: "2.0.20" url_launcher_windows: dependency: transitive description: @@ -590,5 +590,5 @@ packages: source: hosted version: "1.0.2" sdks: - dart: ">=3.1.0-185.0.dev <4.0.0" - flutter: ">=3.10.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 0e2e8b1c..a85658be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: network_proxy description: network proxy publish_to: 'none' # Remove this line if you wish to publish to pub.dev -version: 1.0.2+2 +version: 1.0.2+3 environment: sdk: '>=3.0.2 <4.0.0' @@ -17,8 +17,8 @@ dependencies: date_format: ^2.0.7 window_manager: ^0.3.6 desktop_multi_window: ^0.2.0 - path_provider: ^2.1.0 - url_launcher: ^6.1.11 + path_provider: ^2.1.1 + url_launcher: ^6.1.12 proxy_manager: ^0.0.3 qr_flutter: ^4.1.0 easy_permission: ^1.0.0 diff --git a/windows/runner/flutter_window.cpp b/windows/runner/flutter_window.cpp index b25e363e..955ee303 100644 --- a/windows/runner/flutter_window.cpp +++ b/windows/runner/flutter_window.cpp @@ -31,6 +31,11 @@ bool FlutterWindow::OnCreate() { this->Show(); }); + // Flutter can complete the first frame before the "show window" callback is + // registered. The following call ensures a frame is pending to ensure the + // window is shown. It is a no-op if the first frame hasn't completed yet. + flutter_controller_->ForceRedraw(); + return true; }