From 4154bdace58c86c180d76266dc341de19e02032f Mon Sep 17 00:00:00 2001 From: wanghongen Date: Sun, 27 Aug 2023 01:01:19 +0800 Subject: [PATCH] =?UTF-8?q?=E6=89=8B=E6=9C=BA=E7=AB=AF=E8=AF=B7=E6=B1=82?= =?UTF-8?q?=E6=94=B6=E8=97=8F&=E8=AF=B7=E6=B1=82=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=8F=91=E9=80=81=E7=9B=B4=E6=8E=A5=E6=9F=A5=E7=9C=8B=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E4=BD=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/network/bin/configuration.dart | 12 +- lib/network/http_client.dart | 1 - lib/ui/desktop/desktop.dart | 23 +- lib/ui/desktop/left/favorite.dart | 4 + lib/ui/desktop/left/request_editor.dart | 9 +- .../toolbar/setting/external_proxy.dart | 43 ++-- lib/ui/mobile/menu.dart | 20 +- lib/ui/mobile/mobile.dart | 44 ++-- lib/ui/mobile/request/favorite.dart | 175 +++++++++++++++ lib/ui/mobile/request/request.dart | 23 +- lib/ui/mobile/request/request_editor.dart | 208 +++++++++++++----- lib/ui/mobile/setting/request_rewrite.dart | 12 +- 12 files changed, 438 insertions(+), 136 deletions(-) create mode 100644 lib/ui/mobile/request/favorite.dart diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index 87249eb..266bcd3 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -33,11 +33,8 @@ class Configuration { //是否设置系统代理 bool enableSystemProxy = true; - //是否引导 - bool guide = false; - //是否显示更新内容公告 - bool upgradeNotice = true; + bool upgradeNoticeV2 = true; //请求重写 RequestRewrites requestRewrites = RequestRewrites.instance; @@ -112,7 +109,6 @@ class Configuration { var file = await configFile(); var exits = await file.exists(); if (!exits) { - guide = true; return; } @@ -121,8 +117,7 @@ class Configuration { port = config['port'] ?? port; enableSsl = config['enableSsl'] == true; enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true); - guide = config['guide'] ?? false; - upgradeNotice = config['upgradeNotice'] ?? true; + upgradeNoticeV2 = config['upgradeNoticeV2'] ?? true; if (config['externalProxy'] != null) { externalProxy = ProxyInfo.fromJson(config['externalProxy']); } @@ -163,8 +158,7 @@ class Configuration { Map toJson() { return { - 'guide': guide, - 'upgradeNotice': upgradeNotice, + 'upgradeNoticeV2': upgradeNoticeV2, 'port': port, 'enableSsl': enableSsl, 'enableSystemProxy': enableSystemProxy, diff --git a/lib/network/http_client.dart b/lib/network/http_client.dart index 288d736..54135f7 100644 --- a/lib/network/http_client.dart +++ b/lib/network/http_client.dart @@ -106,7 +106,6 @@ class HttpClients { var proxyTypes = request.uri.startsWith("https://") ? ProxyTypes.https : ProxyTypes.http; proxyInfo = await SystemProxy.getSystemProxy(proxyTypes); } - print(proxyInfo); var httpResponseHandler = HttpResponseHandler(); HostAndPort hostPort = HostAndPort.of(request.uri); diff --git a/lib/ui/desktop/desktop.dart b/lib/ui/desktop/desktop.dart index d465da4..3d8a5a4 100644 --- a/lib/ui/desktop/desktop.dart +++ b/lib/ui/desktop/desktop.dart @@ -57,9 +57,9 @@ class _DesktopHomePagePageState extends State implements EventL proxyServer = ProxyServer(widget.configuration, listener: this); panel = NetworkTabController(tabStyle: const TextStyle(fontSize: 18), proxyServer: proxyServer); - if (widget.configuration.guide) { + if (widget.configuration.upgradeNoticeV2) { WidgetsBinding.instance.addPostFrameCallback((_) { - guideDialog(); + showUpgradeNotice(); }); } } @@ -112,30 +112,33 @@ class _DesktopHomePagePageState extends State implements EventL }); } - //首次引导 - guideDialog() { + //更新引导 + showUpgradeNotice() { showDialog( context: context, barrierDismissible: false, builder: (_) { return AlertDialog( + scrollable: true, actions: [ TextButton( onPressed: () { - widget.configuration.guide = false; + widget.configuration.upgradeNoticeV2 = false; widget.configuration.flushConfig(); Navigator.pop(context); }, child: const Text('关闭')) ], - title: const Text('提示', style: TextStyle(fontSize: 18)), + title: const Text('更新内容V1.0.2', style: TextStyle(fontSize: 18)), content: const Text( - '默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n' + '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n' '点击的HTTPS抓包(加锁图标),选择安装根证书,按照提示操作即可。\n\n' '新增更新:\n' - '1. 增加高级搜索,点击搜索Icon触发。\n' - '2. 显示SSL握手异常、建立连接异常、未知异常等请求。\n' - '3.响应体大时异步加载json,请求重写增加域名,修复手机扫码连接未开启代理时不转发问题', + '1.请求编辑发送可直接查看响应体,发送请求无需开启代理;\n' + '2. 详情增加快速请求重写, 复制cURL格式可直接导入Postman;\n' + '3. 增加请求收藏功能;\n' + '4. 主题增加Material3切换;\n' + '5. 请求删除&大响应体直接转发;', style: TextStyle(fontSize: 14))); }); } diff --git a/lib/ui/desktop/left/favorite.dart b/lib/ui/desktop/left/favorite.dart index feeec7d..b068c1d 100644 --- a/lib/ui/desktop/left/favorite.dart +++ b/lib/ui/desktop/left/favorite.dart @@ -35,6 +35,10 @@ class _FavoritesState extends State { builder: (BuildContext context, AsyncSnapshot> snapshot) { if (snapshot.hasData) { var favorites = snapshot.data ?? Queue(); + if (favorites.isEmpty) { + return const Center(child: Text("暂无收藏")); + } + return ListView.separated( itemCount: favorites.length, itemBuilder: (_, index) { diff --git a/lib/ui/desktop/left/request_editor.dart b/lib/ui/desktop/left/request_editor.dart index 68f7c6f..14f6f59 100644 --- a/lib/ui/desktop/left/request_editor.dart +++ b/lib/ui/desktop/left/request_editor.dart @@ -45,6 +45,7 @@ class RequestEditorState extends State { @override void dispose() { RawKeyboard.instance.removeListener(onKeyEvent); + responseChange.dispose(); super.dispose(); } @@ -123,11 +124,6 @@ class _HttpState extends State<_HttpWidget> { final headerKey = GlobalKey(); String? body; - @override - void initState() { - super.initState(); - } - String? getBody() { return body; } @@ -175,6 +171,9 @@ class _HttpState extends State<_HttpWidget> { body = const JsonEncoder.withIndent(' ').convert(const JsonDecoder().convert(body!)); } catch (_) {} } + if (widget.readOnly) { + return SelectableText(body ?? ''); + } return TextField( autofocus: true, diff --git a/lib/ui/desktop/toolbar/setting/external_proxy.dart b/lib/ui/desktop/toolbar/setting/external_proxy.dart index 4ea86de..b607d51 100644 --- a/lib/ui/desktop/toolbar/setting/external_proxy.dart +++ b/lib/ui/desktop/toolbar/setting/external_proxy.dart @@ -98,26 +98,29 @@ class _ExternalProxyDialogState extends State { socket.destroy(); } on SocketException catch (_) { setting = false; - - await showDialog( - context: context, - builder: (_) => AlertDialog( - title: const Text("外部代理连接失败"), - content: const Text('网络不通所有接口将会访问失败,是否继续设置外部代理。', style: TextStyle(fontSize: 12)), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: const Text("取消")), - TextButton( - onPressed: () { - setting = true; - Navigator.of(context).pop(); - }, - child: const Text("确定")) - ], - )); + if (context.mounted) { + await showDialog( + context: context, + builder: (_) => + AlertDialog( + title: const Text("外部代理连接失败"), + content: const Text( + '网络不通所有接口将会访问失败,是否继续设置外部代理。', style: TextStyle(fontSize: 12)), + actions: [ + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text("取消")), + TextButton( + onPressed: () { + setting = true; + Navigator.of(context).pop(); + }, + child: const Text("确定")) + ], + )); + } } } diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index 1d5bc82..606a300 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -10,6 +10,7 @@ import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/setting.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/theme.dart'; import 'package:network_proxy/ui/mobile/connect_remote.dart'; +import 'package:network_proxy/ui/mobile/request/favorite.dart'; import 'package:network_proxy/ui/mobile/setting/app_whitelist.dart'; import 'package:network_proxy/ui/mobile/setting/filter.dart'; import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart'; @@ -33,19 +34,26 @@ class DrawerWidget extends StatelessWidget { children: [ DrawerHeader( decoration: BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer), - child: const Text('设置'), + child: const Text(''), ), + ListTile( + leading: const Icon(Icons.favorite), + title: const Text("收藏"), + trailing: const Icon(Icons.arrow_right), + onTap: () => navigator(context, MobileFavorites(proxyServer: proxyServer))), + const Divider(thickness: 0.3), PortWidget(proxyServer: proxyServer), ListTile( title: const Text("HTTPS抓包"), trailing: const Icon(Icons.arrow_right), onTap: () => navigator(context, MobileSslWidget(proxyServer: proxyServer))), const ThemeSetting(), - Platform.isIOS ? const SizedBox() : - ListTile( - title: const Text("应用白名单"), - trailing: const Icon(Icons.arrow_right), - onTap: () => navigator(context, AppWhitelist(proxyServer: proxyServer))), + Platform.isIOS + ? const SizedBox() + : ListTile( + title: const Text("应用白名单"), + trailing: const Icon(Icons.arrow_right), + onTap: () => navigator(context, AppWhitelist(proxyServer: proxyServer))), ListTile( title: const Text("域名白名单"), trailing: const Icon(Icons.arrow_right), diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index 4ab8ed6..6bd4cea 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -54,12 +54,11 @@ class MobileHomeState extends State implements EventListener { }); super.initState(); - - WidgetsBinding.instance.addPostFrameCallback((_) { - if (widget.configuration.upgradeNotice) { + if (widget.configuration.upgradeNoticeV2) { + WidgetsBinding.instance.addPostFrameCallback((_) { showUpgradeNotice(); - } - }); + }); + } } @override @@ -106,11 +105,16 @@ class MobileHomeState extends State implements EventListener { } showUpgradeNotice() { - String content = '1. 增加高级搜索,点击搜索icon触发。\n' - '2. 显示SSL握手异常、建立连接异常、未知异常等请求。\n' - '3.响应体大时异步加载json,请求重写增加域名,修复手机扫码连接未开启代理时不转发问题'; - showAlertDialog('更新内容', content, () { - widget.configuration.upgradeNotice = false; + String content = '提示:默认不会开启HTTPS抓包,请安装证书后再开启HTTPS抓包。\n\n' + '新增更新:\n' + '1.请求编辑发送可直接查看响应体,发送请求无需开启代理;\n' + '2. 详情增加快速请求重写, 复制cURL格式可直接导入Postman;\n' + '3. 增加请求收藏功能;\n' + '4. 主题增加Material3切换;\n' + '5. 请求删除&大响应体直接转发;\n' + '6. 安卓应用白名单,调整请求展示列表;'; + showAlertDialog('更新内容V1.0.2', content, () { + widget.configuration.upgradeNoticeV2 = false; widget.configuration.flushConfig(); }); } @@ -134,14 +138,18 @@ class MobileHomeState extends State implements EventListener { context: context, barrierDismissible: false, builder: (_) { - return AlertDialog(actions: [ - TextButton( - onPressed: () { - onClose.call(); - Navigator.pop(context); - }, - child: const Text('关闭')) - ], title: Text(title, style: const TextStyle(fontSize: 18)), content: Text(content)); + return AlertDialog( + scrollable: true, + actions: [ + TextButton( + onPressed: () { + onClose.call(); + Navigator.pop(context); + }, + child: const Text('关闭')) + ], + title: Text(title, style: const TextStyle(fontSize: 18)), + content: Text(content)); }); } diff --git a/lib/ui/mobile/request/favorite.dart b/lib/ui/mobile/request/favorite.dart new file mode 100644 index 0000000..c7aa29f --- /dev/null +++ b/lib/ui/mobile/request/favorite.dart @@ -0,0 +1,175 @@ +import 'dart:collection'; + +import 'package:date_format/date_format.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; +import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/host_port.dart'; +import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/network/http_client.dart'; +import 'package:network_proxy/storage/favorites.dart'; +import 'package:network_proxy/ui/component/utils.dart'; +import 'package:network_proxy/ui/content/panel.dart'; +import 'package:network_proxy/ui/mobile/request/request_editor.dart'; +import 'package:network_proxy/utils/curl.dart'; + +class MobileFavorites extends StatefulWidget { + final ProxyServer proxyServer; + + const MobileFavorites({Key? key, required this.proxyServer}) : super(key: key); + + @override + State createState() { + return _FavoritesState(); + } +} + +class _FavoritesState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text("收藏请求", style: TextStyle(fontSize: 16)), centerTitle: true), + body: FutureBuilder( + future: FavoriteStorage.favorites, + builder: (BuildContext context, AsyncSnapshot> snapshot) { + if (snapshot.hasData) { + var favorites = snapshot.data ?? Queue(); + if (favorites.isEmpty) { + return const Center(child: Text("暂无收藏")); + } + + return ListView.separated( + itemCount: favorites.length, + itemBuilder: (_, index) { + var request = favorites.elementAt(index); + return _FavoriteItem( + request, + onRemove: (HttpRequest request) { + FavoriteStorage.removeFavorite(request); + FlutterToastr.show('已删除收藏', context); + setState(() {}); + }, + proxyServer: widget.proxyServer, + ); + }, + separatorBuilder: (_, __) => const Divider(height: 1, thickness: 0.3), + ); + } else { + return const SizedBox(); + } + })); + } +} + +class _FavoriteItem extends StatefulWidget { + final ProxyServer proxyServer; + final HttpRequest request; + final Function(HttpRequest request)? onRemove; + + const _FavoriteItem(this.request, {Key? key, required this.onRemove, required this.proxyServer}) : super(key: key); + + @override + State<_FavoriteItem> createState() => _FavoriteItemState(); +} + +class _FavoriteItemState extends State<_FavoriteItem> { + @override + Widget build(BuildContext context) { + var request = widget.request; + var response = request.response; + var title = '${request.method.name} ${request.requestUrl}'; + var time = formatDate(request.requestTime, [mm, '-', d, ' ', HH, ':', nn, ':', ss]); + return ListTile( + onLongPress: menu, + minLeadingWidth: 25, + leading: getIcon(response), + title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 2), + subtitle: Text( + '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} ', + maxLines: 1), + dense: true, + onTap: onClick); + } + + ///右键菜单 + menu() { + showModalBottomSheet( + context: context, + enableDrag: true, + builder: (ctx) { + return Wrap(alignment: WrapAlignment.center, children: [ + menuItem("复制请求链接", () => widget.request.requestUrl), + const Divider(thickness: 0.5), + menuItem("复制请求和响应", () => copyRequest(widget.request, widget.request.response)), + const Divider(thickness: 0.5), + menuItem("复制 cURL 请求", () => curlRequest(widget.request)), + const Divider(thickness: 0.5), + TextButton( + child: const SizedBox(width: double.infinity, child: Text("请求重放", textAlign: TextAlign.center)), + onPressed: () { + var request = widget.request.copy(uri: widget.request.requestUrl); + HttpClients.proxyRequest( + proxyInfo: widget.proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", widget.proxyServer.port) : null, + request); + + FlutterToastr.show('已重新发送请求', context); + Navigator.of(context).pop(); + }), + const Divider(thickness: 0.5), + TextButton( + child: const SizedBox(width: double.infinity, child: Text("编辑请求重放", textAlign: TextAlign.center)), + onPressed: () { + Navigator.of(context).pop(); + Navigator.of(context).push(MaterialPageRoute( + builder: (context) => + MobileRequestEditor(request: widget.request, proxyServer: widget.proxyServer))); + }), + const Divider(thickness: 0.5), + TextButton( + child: const SizedBox(width: double.infinity, child: Text("删除收藏", textAlign: TextAlign.center)), + onPressed: () { + widget.onRemove?.call(widget.request); + Navigator.of(context).pop(); + }), + Container( + color: Theme.of(context).hoverColor, + height: 8, + ), + TextButton( + child: Container( + height: 60, + width: double.infinity, + padding: const EdgeInsets.only(top: 10), + child: const Text("取消", textAlign: TextAlign.center)), + onPressed: () { + Navigator.of(context).pop(); + }, + ), + ]); + }, + ); + } + + Widget menuItem(String title, String Function() callback) { + return TextButton( + child: SizedBox(width: double.infinity, child: Text(title, textAlign: TextAlign.center)), + onPressed: () { + Clipboard.setData(ClipboardData(text: callback.call())).then((value) { + FlutterToastr.show('已复制到剪切板', context); + Navigator.of(context).pop(); + }); + }); + } + + //点击事件 + void onClick() { + Navigator.push(context, MaterialPageRoute(builder: (context) { + return NetworkTabController( + proxyServer: widget.proxyServer, + httpRequest: widget.request, + httpResponse: widget.request.response, + title: const Text("抓包详情", style: TextStyle(fontSize: 16))); + })); + } +} diff --git a/lib/ui/mobile/request/request.dart b/lib/ui/mobile/request/request.dart index 1e536e0..660fae3 100644 --- a/lib/ui/mobile/request/request.dart +++ b/lib/ui/mobile/request/request.dart @@ -6,6 +6,7 @@ import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/network/http_client.dart'; +import 'package:network_proxy/storage/favorites.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/content/panel.dart'; import 'package:network_proxy/ui/mobile/request/request_editor.dart'; @@ -59,7 +60,7 @@ class RequestRowState extends State { trailing: const Icon(Icons.chevron_right), dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 8), - onLongPress: () => menu(menuPosition(context)), + onLongPress: menu, onTap: () { Navigator.push(context, MaterialPageRoute(builder: (context) { return NetworkTabController( @@ -72,7 +73,7 @@ class RequestRowState extends State { } ///菜单 - menu(RelativeRect position) { + menu() { showModalBottomSheet( context: context, enableDrag: true, @@ -80,20 +81,16 @@ class RequestRowState extends State { return Wrap(alignment: WrapAlignment.center, children: [ menuItem("复制请求链接", () => widget.request.requestUrl), const Divider(thickness: 0.5), - menuItem("复制请求和响应", () => copyRequest(widget.request, response)), - const Divider(thickness: 0.5), menuItem("复制 cURL 请求", () => curlRequest(widget.request)), const Divider(thickness: 0.5), TextButton( child: const SizedBox(width: double.infinity, child: Text("请求重放", textAlign: TextAlign.center)), onPressed: () { var request = widget.request.copy(uri: widget.request.requestUrl); - if (!widget.proxyServer.isRunning) { - FlutterToastr.show('代理服务未启动', context); - return; - } + HttpClients.proxyRequest( + proxyInfo: widget.proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", widget.proxyServer.port) : null, + request); - HttpClients.proxyRequest(proxyInfo: ProxyInfo.of("127.0.0.1", widget.proxyServer.port), request); FlutterToastr.show('已重新发送请求', context); Navigator.of(context).pop(); }), @@ -107,6 +104,14 @@ class RequestRowState extends State { MobileRequestEditor(request: widget.request, proxyServer: widget.proxyServer))); }), const Divider(thickness: 0.5), + TextButton( + child: const SizedBox(width: double.infinity, child: Text("收藏请求", textAlign: TextAlign.center)), + onPressed: () { + FavoriteStorage.addFavorite(widget.request); + FlutterToastr.show('收藏成功', context); + Navigator.of(context).pop(); + }), + const Divider(thickness: 0.5), TextButton( child: const SizedBox(width: double.infinity, child: Text("删除", textAlign: TextAlign.center)), onPressed: () { diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index 6601af4..e3743bf 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:flutter/material.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; @@ -18,66 +20,158 @@ class MobileRequestEditor extends StatefulWidget { } } -class RequestEditorState extends State { - final requestLineKey = GlobalKey<_RequestLineState>(); - final headerKey = GlobalKey(); +class RequestEditorState extends State with SingleTickerProviderStateMixin { + var tabs = const [ + Tab(text: "请求"), + Tab(text: "响应"), + ]; - String requestBody = ""; + final requestLineKey = GlobalKey<_RequestLineState>(); + final requestKey = GlobalKey<_HttpState>(); + + ValueNotifier responseChange = ValueNotifier(false); + + late TabController tabController; + + HttpResponse? response; + + @override + void dispose() { + tabController.dispose(); + responseChange.dispose(); + super.dispose(); + } @override void initState() { super.initState(); - requestBody = widget.request?.bodyAsString ?? ""; + tabController = TabController(length: tabs.length, vsync: this); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("编辑请求", style: TextStyle(fontSize: 16)), - centerTitle: true, - leading: TextButton( - onPressed: () => Navigator.pop(context), - child: Text("取消", style: Theme.of(context).textTheme.bodyMedium)), - actions: [TextButton.icon(icon: const Icon(Icons.send), label: const Text("发送"), onPressed: sendRequest)], - ), - body: SingleChildScrollView( - padding: const EdgeInsets.all(15), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - _RequestLine(request: widget.request, key: requestLineKey), // 请求行 - Headers(headers: widget.request?.headers, key: headerKey), // 请求头 - const SizedBox(height: 10), - const Text("Body", style: TextStyle(fontWeight: FontWeight.w500, color: Colors.blue)), - body() - ]))); + title: const Text("编辑请求", style: TextStyle(fontSize: 16)), + centerTitle: true, + leading: TextButton( + onPressed: () => Navigator.pop(context), + child: Text("取消", style: Theme.of(context).textTheme.bodyMedium)), + actions: [TextButton.icon(icon: const Icon(Icons.send), label: const Text("发送"), onPressed: sendRequest)], + bottom: TabBar(controller: tabController, tabs: tabs)), + body: TabBarView( + controller: tabController, + children: [ + _HttpWidget( + title: _RequestLine(request: widget.request, key: requestLineKey), + message: widget.request, + key: requestKey), + ValueListenableBuilder( + valueListenable: responseChange, + builder: (_, value, __) => _HttpWidget( + title: Row(children: [ + const Text('状态码:', style: TextStyle(fontWeight: FontWeight.w500)), + Text(response?.status.toString() ?? "", style: const TextStyle(color: Colors.blue)) + ]), + readOnly: true, + message: response)), + ], + )); } ///发送请求 - sendRequest() { - if (!widget.proxyServer.isRunning) { - FlutterToastr.show('代理服务未启动', context); - return; - } - + sendRequest() async { var currentState = requestLineKey.currentState!; + var headers = requestKey.currentState?.getHeaders(); + var requestBody = requestKey.currentState?.getBody(); + HttpRequest request = HttpRequest(HttpMethod.valueOf(currentState.requestMethod), currentState.requestUrl); - var headers = headerKey.currentState?.getHeaders(); request.headers.addAll(headers); - request.body = requestBody.codeUnits; - HttpClients.proxyRequest(proxyInfo: ProxyInfo.of("127.0.0.1", widget.proxyServer.port), request); - FlutterToastr.show('已重新发送请求', context); - Navigator.pop(context, request); + request.body = requestBody?.codeUnits; + + HttpClients.proxyRequest( + proxyInfo: widget.proxyServer.isRunning ? ProxyInfo.of("127.0.0.1", widget.proxyServer.port) : null, + request) + .then((response) { + this.response = response; + tabController.animateTo(1); + responseChange.value = !responseChange.value; + }); + + if (context.mounted) { + FlutterToastr.show('已发送请求', context); + } + } +} + +class _HttpWidget extends StatefulWidget { + final HttpMessage? message; + final bool readOnly; + final Widget title; + + const _HttpWidget({this.message, this.readOnly = false, super.key, required this.title}); + + @override + State createState() { + return _HttpState(); + } +} + +class _HttpState extends State<_HttpWidget> with AutomaticKeepAliveClientMixin { + final headerKey = GlobalKey(); + String? body; + + @override + bool get wantKeepAlive => true; + + String? getBody() { + return body; } - ///请求体 - Widget body() { + HttpHeaders? getHeaders() { + return headerKey.currentState?.getHeaders(); + } + + @override + Widget build(BuildContext context) { + super.build(context); + body = widget.message?.bodyAsString; + headerKey.currentState?.refreshHeader(widget.message?.headers); + + if (widget.message == null && widget.readOnly) { + return const Center(child: Text("无数据")); + } + + return SingleChildScrollView( + padding: const EdgeInsets.all(15), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + widget.title, + 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() + ])); + } + + 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 TextField( - controller: TextEditingController(text: requestBody), + controller: TextEditingController(text: body), + readOnly: widget.readOnly, onChanged: (value) { - requestBody = value; + body = value; }, minLines: 3, - maxLines: 10); + maxLines: 15); } } @@ -142,8 +236,9 @@ class _RequestLineState extends State<_RequestLine> { class Headers extends StatefulWidget { final HttpHeaders? headers; + final bool readOnly; //只读 - const Headers({super.key, this.headers}); + const Headers({super.key, this.headers, required this.readOnly}); @override State createState() { @@ -175,6 +270,14 @@ class HeadersState extends State { return headers; } + //刷新header + refreshHeader(HttpHeaders? headers) { + this.headers.clear(); + headers?.forEach((name, values) { + this.headers[name] = values; + }); + } + @override Widget build(BuildContext context) { return Container( @@ -185,13 +288,15 @@ class HeadersState extends State { child: Text("Headers", style: TextStyle(fontWeight: FontWeight.w500, color: Colors.blue))), const SizedBox(height: 10), ...buildHeaders(), - Container( - alignment: Alignment.center, - child: TextButton( - onPressed: () { - modifyHeader("", ""); - }, - child: const Text("添加Header", textAlign: TextAlign.center))) //添加按钮 + widget.readOnly + ? const SizedBox() + : Container( + alignment: Alignment.center, + child: TextButton( + onPressed: () { + modifyHeader("", ""); + }, + child: const Text("添加Header", textAlign: TextAlign.center))) //添加按钮 ])); } @@ -201,11 +306,14 @@ class HeadersState extends State { for (var val in values) { var header = row(Text(key, style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w500)), Text(val, style: const TextStyle(fontSize: 12), maxLines: 5, overflow: TextOverflow.ellipsis)); - var ink = InkWell( - onTap: () => modifyHeader(key, val), - onLongPress: () => deleteHeader(key), - child: Padding(padding: const EdgeInsets.only(top: 5, bottom: 5), child: header)); - list.add(ink); + + Widget headerWidget = Padding(padding: const EdgeInsets.only(top: 5, bottom: 5), child: header); + if (!widget.readOnly) { + headerWidget = + InkWell(onTap: () => modifyHeader(key, val), onLongPress: () => deleteHeader(key), child: headerWidget); + } + + list.add(headerWidget); list.add(const Divider(thickness: 0.2)); } }); diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index e168352..298e489 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -265,19 +265,15 @@ class _RequestRuleListState extends State { cell( Container( constraints: const BoxConstraints(maxWidth: 150), - child: SelectableText.rich( - maxLines: 3, - TextSpan(text: widget.requestRewrites.rules[index].requestBody), - style: const TextStyle(fontSize: 12))), + child: Text(widget.requestRewrites.rules[index].requestBody ?? '', + maxLines: 3, style: const TextStyle(fontSize: 12))), index), cell( Container( constraints: const BoxConstraints(maxWidth: 200), padding: const EdgeInsetsDirectional.all(10), - child: SelectableText.rich( - maxLines: 5, - TextSpan(text: widget.requestRewrites.rules[index].responseBody), - style: const TextStyle(fontSize: 12)), + child: Text(widget.requestRewrites.rules[index].responseBody ?? '', + maxLines: 5, style: const TextStyle(fontSize: 12)), ), index) ],