diff --git a/lib/ui/component/utils.dart b/lib/ui/component/utils.dart index f1a379f..d0dd1f3 100644 --- a/lib/ui/component/utils.dart +++ b/lib/ui/component/utils.dart @@ -88,12 +88,12 @@ Widget contextMenu(BuildContext context, EditableTextState editableTextState) { ) ]; if (Platform.isIOS) { - // list.add(ContextMenuButtonItem( - // onPressed: () async { - // editableTextState.shareSelection(SelectionChangedCause.toolbar); - // }, - // type: ContextMenuButtonType.share, - // )); + list.add(ContextMenuButtonItem( + onPressed: () async { + editableTextState.shareSelection(SelectionChangedCause.toolbar); + }, + type: ContextMenuButtonType.share, + )); } return AdaptiveTextSelectionToolbar.buttonItems( diff --git a/lib/ui/desktop/left/favorite.dart b/lib/ui/desktop/left/favorite.dart index 2976014..3012d7a 100644 --- a/lib/ui/desktop/left/favorite.dart +++ b/lib/ui/desktop/left/favorite.dart @@ -46,6 +46,7 @@ class _FavoritesState extends State { var request = favorites.elementAt(index); return _FavoriteItem( request, + index: index, panel: widget.panel, onRemove: (HttpRequest request) { FavoriteStorage.removeFavorite(request); @@ -64,11 +65,12 @@ class _FavoritesState extends State { } class _FavoriteItem extends StatefulWidget { + final int index; final HttpRequest request; final NetworkTabController panel; final Function(HttpRequest request)? onRemove; - const _FavoriteItem(this.request, {Key? key, required this.panel, required this.onRemove}) : super(key: key); + const _FavoriteItem(this.request, {Key? key, required this.panel, required this.onRemove, required this.index}) : super(key: key); @override State<_FavoriteItem> createState() => _FavoriteItemState(); @@ -92,9 +94,15 @@ class _FavoriteItemState extends State<_FavoriteItem> { 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), + subtitle: Text.rich( + style: const TextStyle(fontSize: 12), + maxLines: 1, + TextSpan(children: [ + TextSpan(text: '#${widget.index} ', style: const TextStyle( color: Colors.teal)), + TextSpan( + text: + '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} '), + ])), selected: selected, dense: true, onTap: onClick)); diff --git a/lib/ui/desktop/left/history.dart b/lib/ui/desktop/left/history.dart index c91b2b8..8425c74 100644 --- a/lib/ui/desktop/left/history.dart +++ b/lib/ui/desktop/left/history.dart @@ -174,10 +174,10 @@ class _HistoryState extends State<_HistoryWidget> { } //构建历史记录 - Widget buildItem(BuildContext context, int index, HistoryItem item) { + Widget buildItem(BuildContext rootContext, int index, HistoryItem item) { return GestureDetector( onSecondaryTapDown: (details) => { - showContextMenu(context, details.globalPosition, items: [ + showContextMenu(rootContext, details.globalPosition, items: [ CustomPopupMenuItem( height: 35, child: const Text('导出', style: TextStyle(fontSize: 13)), onTap: () => export(item)), CustomPopupMenuItem( diff --git a/lib/ui/desktop/toolbar/setting/filter.dart b/lib/ui/desktop/toolbar/setting/filter.dart index c067043..4d415d0 100644 --- a/lib/ui/desktop/toolbar/setting/filter.dart +++ b/lib/ui/desktop/toolbar/setting/filter.dart @@ -24,17 +24,12 @@ class _FilterDialogState extends State { Widget build(BuildContext context) { return AlertDialog( scrollable: true, - title: Row(children: [ - const Text("域名过滤", style: TextStyle(fontSize: 18)), + title: const Row(children: [ + Text("域名过滤", style: TextStyle(fontSize: 18)), Expanded( child: Align( alignment: Alignment.topRight, - child: ElevatedButton.icon( - icon: const Icon(Icons.close, size: 15), - label: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); - }))) + child: CloseButton())) ]), content: SizedBox( width: 680, diff --git a/lib/ui/desktop/toolbar/setting/request_rewrite.dart b/lib/ui/desktop/toolbar/setting/request_rewrite.dart index 2e59f30..8f350d9 100644 --- a/lib/ui/desktop/toolbar/setting/request_rewrite.dart +++ b/lib/ui/desktop/toolbar/setting/request_rewrite.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; @@ -73,16 +74,32 @@ class _RequestRewriteState extends State { icon: const Icon(Icons.remove, size: 18), label: const Text("删除", style: TextStyle(fontSize: 12)), onPressed: () { - var removeSelected = requestRuleList.removeSelected(); - if (removeSelected.isEmpty) { + var selected = requestRuleList.currentSelectedIndex(); + if (selected < 0) { return; } - changed = true; - setState(() { - widget.configuration.requestRewrites.removeIndex(removeSelected); - requestRuleList.changeState(); - }); + showDialog( + context: context, + builder: (ctx) { + return AlertDialog( + title: const Text("是否删除该请求重写?", style: TextStyle(fontSize: 18)), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text("取消")), + TextButton( + onPressed: () { + changed = true; + setState(() { + widget.configuration.requestRewrites.removeIndex(requestRuleList.removeSelected()); + requestRuleList.changeState(); + }); + FlutterToastr.show('删除成功', context); + Navigator.pop(context); + }, + child: const Text("删除")), + ], + ); + }); }) ]), const SizedBox(height: 10), @@ -217,7 +234,6 @@ class _RuleAddDialogState extends State { RequestRewrites.instance.addRule(rule); } - enableNotifier.dispose(); Navigator.of(context).pop(rule); } }), diff --git a/lib/ui/desktop/toolbar/setting/setting.dart b/lib/ui/desktop/toolbar/setting/setting.dart index c844507..577acec 100644 --- a/lib/ui/desktop/toolbar/setting/setting.dart +++ b/lib/ui/desktop/toolbar/setting/setting.dart @@ -113,15 +113,12 @@ class _SettingState extends State { builder: (context) { return AlertDialog( scrollable: true, - title: Row(children: [ - const Text("请求重写", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), + title: const Row(children: [ + Text("请求重写", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), Expanded( child: Align( alignment: Alignment.topRight, - child: ElevatedButton.icon( - icon: const Icon(Icons.close, size: 15), - label: const Text("关闭", style: TextStyle(fontSize: 14)), - onPressed: () => Navigator.of(context).pop()))) + child: CloseButton())) ]), content: RequestRewrite(configuration: configuration), ); diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 725d451..5ae2575 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -28,19 +28,18 @@ class _SslState extends State { offset: const Offset(10, 30), itemBuilder: (context) { return [ - PopupMenuItem( - child: _Switch(proxyServer: widget.proxyServer, onEnableChange: (val) => setState(() {}))), + PopupMenuItem(child: _Switch(proxyServer: widget.proxyServer, onEnableChange: (val) => setState(() {}))), PopupMenuItem( child: ListTile( - dense: true, - hoverColor: Colors.transparent, - focusColor: Colors.transparent, - title: const Text("安装根证书到本机"), - trailing: const Icon(Icons.arrow_right), - onTap: () { - pcCer(); - }, - )), + dense: true, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + title: const Text("安装根证书到本机"), + trailing: const Icon(Icons.arrow_right), + onTap: () { + pcCer(); + }, + )), PopupMenuItem( child: ListTile( title: const Text("安装根证书到 iOS"), @@ -93,14 +92,14 @@ class _SslState extends State { const SizedBox(height: 10), Platform.isMacOS ? Image.network("https://foruda.gitee.com/images/1689323260158189316/c2d881a4_1073801.png", - width: 800, height: 500) + 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) - ]) + 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" @@ -123,17 +122,9 @@ class _SslState extends State { builder: (BuildContext context) { return SimpleDialog( contentPadding: const EdgeInsets.all(16), - title: Row(children: [ - const Text("电脑HTTPS抓包配置", style: TextStyle(fontSize: 18)), - Expanded( - child: Align( - alignment: Alignment.topRight, - child: ElevatedButton.icon( - icon: const Icon(Icons.close, size: 15), - label: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); - }))) + title: const Row(children: [ + Text("电脑HTTPS抓包配置", style: TextStyle(fontSize: 16)), + Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton())) ]), alignment: Alignment.center, children: list); @@ -146,17 +137,9 @@ class _SslState extends State { builder: (BuildContext context) { return SimpleDialog( contentPadding: const EdgeInsets.all(16), - title: Row(children: [ - const Text("iOS根证书安装指南", style: TextStyle(fontSize: 18)), - Expanded( - child: Align( - alignment: Alignment.topRight, - child: ElevatedButton.icon( - icon: const Icon(Icons.close, size: 15), - label: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); - }))) + title: const Row(children: [ + Text("iOS根证书安装指南", style: TextStyle(fontSize: 16)), + Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton())) ]), alignment: Alignment.center, children: [ @@ -197,40 +180,58 @@ class _SslState extends State { builder: (BuildContext context) { return SimpleDialog( contentPadding: const EdgeInsets.all(16), - title: Row(children: [ - const Text("Android根证书安装指南", style: TextStyle(fontSize: 18)), - Expanded( - child: Align( - alignment: Alignment.topRight, - child: ElevatedButton.icon( - icon: const Icon(Icons.close, size: 15), - label: const Text("关闭"), - onPressed: () { - Navigator.of(context).pop(); - }))) + title: const Row(children: [ + Text("Android根证书安装指南", style: TextStyle(fontSize: 16)), + Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton())) ]), alignment: Alignment.center, children: [ - SelectableText.rich(TextSpan(text: "1. 配置手机Wi-Fi代理 Host:$host Port:${widget.proxyServer.port}")), + ExpansionTile( + title: const Text("Root用户:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), + tilePadding: const EdgeInsets.only(left: 0), + expandedAlignment: Alignment.topLeft, + initiallyExpanded: true, + shape: const Border(), + children: [ + const Text("针对安卓Root用户做了个Magisk模块ProxyPinCA系统证书,安装完重启手机即可。"), + TextButton( + child: const Text("https://gitee.com/wanghongenpin/Magisk-ProxyPinCA/releases/tag/1.0.0"), + onPressed: () { + launchUrl( + Uri.parse("https://gitee.com/wanghongenpin/Magisk-ProxyPinCA/releases/tag/1.0.0")); + }) + ]), const SizedBox(height: 10), - const Row( + ExpansionTile( + title: const Text("非Root用户:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), + tilePadding: const EdgeInsets.only(left: 0), + expandedAlignment: Alignment.topLeft, + expandedCrossAxisAlignment: CrossAxisAlignment.start, + initiallyExpanded: true, + shape: const Border(), children: [ - Text("2. 在 Android 设备上打开浏览器访问:\t"), - SelectableText.rich( - TextSpan(text: "http://proxy.pin/ssl", style: TextStyle(decoration: TextDecoration.underline))) + SelectableText.rich(TextSpan(text: "1. 配置手机Wi-Fi代理 Host:$host Port:${widget.proxyServer.port}")), + const SizedBox(height: 10), + const Row( + children: [ + Text("2. 在 Android 设备上打开浏览器访问:\t"), + SelectableText.rich(TextSpan( + text: "http://proxy.pin/ssl", style: TextStyle(decoration: TextDecoration.underline))) + ], + ), + const SizedBox(height: 10), + const Text("2. 打开设置 -> 安全 -> 加密和凭据 -> 安装证书 -> CA 证书"), + const SizedBox(height: 10), + ClipRRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: .7, + child: Image.network( + "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", + height: 550, + ))) ], ), - const SizedBox(height: 10), - const Text("2. 打开设置 -> 安全 -> 加密和凭据 -> 安装证书 -> CA 证书"), - const SizedBox(height: 10), - ClipRRect( - child: Align( - alignment: Alignment.topCenter, - heightFactor: .7, - child: Image.network( - "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", - height: 550, - ))) ]); }); } diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index 0d09f62..a8e76f7 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -49,11 +49,7 @@ class DrawerWidget extends StatelessWidget { leading: const Icon(Icons.history), title: const Text("历史"), trailing: const Icon(Icons.arrow_right), - onTap: () => navigator( - context, - Scaffold( - appBar: AppBar(title: const Text("历史记录", style: TextStyle(fontSize: 16)), centerTitle: true), - body: MobileHistory(proxyServer: proxyServer, requestStateKey: requestStateKey))), + onTap: () => navigator(context, MobileHistory(proxyServer: proxyServer, requestStateKey: requestStateKey)), ), const Divider(thickness: 0.3), Padding(padding: const EdgeInsets.only(left: 15), child: PortWidget(proxyServer: proxyServer)), @@ -139,25 +135,25 @@ class MoreEnum extends StatelessWidget { })), PopupMenuItem( child: ListTile( - dense: true, - leading: const Icon(Icons.qr_code_scanner_outlined), - title: const Text("连接终端"), - onTap: () { - connectRemote(context); - }, - )), + dense: true, + leading: const Icon(Icons.qr_code_scanner_outlined), + title: const Text("连接终端"), + onTap: () { + connectRemote(context); + }, + )), PopupMenuItem( child: ListTile( - dense: true, - leading: const Icon(Icons.phone_iphone), - title: const Text("我的二维码"), - onTap: () async { - var ip = await localIp(); - if (context.mounted) { - connectQrCode(context, ip, proxyServer.port); - } - }, - )), + dense: true, + leading: const Icon(Icons.phone_iphone), + title: const Text("我的二维码"), + onTap: () async { + var ip = await localIp(); + if (context.mounted) { + connectQrCode(context, ip, proxyServer.port); + } + }, + )), PopupMenuItem( child: ListTile( dense: true, diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index 5a0059a..a492ce9 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -114,7 +114,8 @@ class MobileHomeState extends State implements EventListener { '2. 请求重写增加名称&URL参数重写;\n' '3. 请求重写增加重定向;\n' '4. 建立连接异常显示请求体;\n' - '5. 请求编辑重发响应体查看增加多种格式,详情Body体增加快速解码入口;'; + '5. 请求编辑重发响应体查看增加多种格式,详情Body体增加快速解码入口\n' + '6. 请求列表增加编号;'; showAlertDialog('更新内容V1.0.3', content, () { widget.configuration.upgradeNoticeV3 = false; widget.configuration.flushConfig(); diff --git a/lib/ui/mobile/request/favorite.dart b/lib/ui/mobile/request/favorite.dart index c7aa29f..43134cc 100644 --- a/lib/ui/mobile/request/favorite.dart +++ b/lib/ui/mobile/request/favorite.dart @@ -45,6 +45,7 @@ class _FavoritesState extends State { var request = favorites.elementAt(index); return _FavoriteItem( request, + index: index, onRemove: (HttpRequest request) { FavoriteStorage.removeFavorite(request); FlutterToastr.show('已删除收藏', context); @@ -63,11 +64,13 @@ class _FavoritesState extends State { } class _FavoriteItem extends StatefulWidget { + final int index; 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); + const _FavoriteItem(this.request, {Key? key, required this.onRemove, required this.proxyServer, required this.index}) + : super(key: key); @override State<_FavoriteItem> createState() => _FavoriteItemState(); @@ -80,14 +83,19 @@ class _FavoriteItemState extends State<_FavoriteItem> { var response = request.response; var title = '${request.method.name} ${request.requestUrl}'; var time = formatDate(request.requestTime, [mm, '-', d, ' ', HH, ':', nn, ':', ss]); + String subtitle = + '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''} '; 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), + subtitle: Text.rich( + maxLines: 1, + TextSpan(children: [ + TextSpan(text: '#${widget.index} ', style: const TextStyle(fontSize: 12, color: Colors.teal)), + TextSpan(text: subtitle, style: const TextStyle(fontSize: 12)), + ])), dense: true, onTap: onClick); } @@ -130,6 +138,7 @@ class _FavoriteItemState extends State<_FavoriteItem> { child: const SizedBox(width: double.infinity, child: Text("删除收藏", textAlign: TextAlign.center)), onPressed: () { widget.onRemove?.call(widget.request); + FlutterToastr.show('删除成功', context); Navigator.of(context).pop(); }), Container( diff --git a/lib/ui/mobile/request/history.dart b/lib/ui/mobile/request/history.dart index a243610..7b8a8be 100644 --- a/lib/ui/mobile/request/history.dart +++ b/lib/ui/mobile/request/history.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:date_format/date_format.dart'; +import 'package:file_selector/file_selector.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; @@ -14,6 +15,7 @@ import 'package:network_proxy/network/http/http.dart'; import 'package:network_proxy/storage/histories.dart'; import 'package:network_proxy/ui/component/utils.dart'; import 'package:network_proxy/ui/mobile/request/list.dart'; +import 'package:share_plus/share_plus.dart'; import '../../../utils/har.dart'; @@ -51,14 +53,19 @@ class _MobileHistoryState extends State { children.add(buildItem(data, i, entry)); } - if (children.isEmpty) { - return const Center(child: Text("暂无历史记录")); - } - return ListView.separated( - itemCount: children.length, - itemBuilder: (context, index) => children[index], - separatorBuilder: (_, index) => const Divider(thickness: 0.3, height: 0), - ); + return Scaffold( + appBar: AppBar( + title: const Text("历史记录", style: TextStyle(fontSize: 16)), + centerTitle: true, + actions: [TextButton(onPressed: () => import(data), child: const Text("导入"))], + ), + body: children.isEmpty + ? const Center(child: Text("暂无历史记录")) + : ListView.separated( + itemCount: children.length, + itemBuilder: (context, index) => children[index], + separatorBuilder: (_, index) => const Divider(thickness: 0.3, height: 0), + )); }); } @@ -83,9 +90,35 @@ class _MobileHistoryState extends State { onTap: () {}); } + //导入har + import(HistoryStorage storage) async { + const XTypeGroup typeGroup = XTypeGroup( + label: 'Har', + ); + final XFile? file = await openFile(acceptedTypeGroups: [typeGroup]); + if (file == null) { + return; + } + + print(file); + try { + var historyItem = await storage.addHarFile(file); + setState(() { + Navigator.pushNamed(context, '/domain', arguments: {'item': historyItem}); + FlutterToastr.show("导入成功", context); + }); + } catch (e, t) { + print(e); + print(t); + if (context.mounted) { + FlutterToastr.show("导入失败 $e", context); + } + } + } + //写入文件 _writeHarFile(HistoryStorage storage, List container, String name) async { - var file = await HistoryStorage.openFile("${DateTime.now().millisecondsSinceEpoch}.txt"); + var file = await HistoryStorage.openFile("${DateTime.now().millisecondsSinceEpoch.toRadixString(36)}.txt"); print(file); RandomAccessFile open = await file.open(mode: FileMode.append); HistoryItem history = await storage.addHistory(name, file, 0); @@ -107,6 +140,7 @@ class _MobileHistoryState extends State { HapticFeedback.heavyImpact(); showContextMenu(context, detail.globalPosition.translate(-50, index == 0 ? -100 : 100), items: [ PopupMenuItem(child: const Text("重命名"), onTap: () => renameHistory(storage, item)), + PopupMenuItem(child: const Text("分享"), onTap: () => export(storage, item)), const PopupMenuDivider(height: 0.3), PopupMenuItem(child: const Text("删除"), onTap: () => deleteHistory(storage, index)) ]); @@ -130,6 +164,19 @@ class _MobileHistoryState extends State { )); } + //导出har + export(HistoryStorage storage, HistoryItem item) async { + //文件名称 + String fileName = + '${item.name.contains("ProxyPin") ? '' : 'ProxyPin'}${item.name}.har'.replaceAll(" ", "_").replaceAll(":", "_"); + //获取请求 + List requests = await storage.getRequests(item); + var json = await Har.writeJson(requests, title: item.name); + var file = XFile.fromData(Uint8List.fromList(json.codeUnits), name: fileName, mimeType: "har"); + Share.shareXFiles([file], subject: fileName); + Future.delayed(const Duration(seconds: 30), () => item.requests = null); + } + //重命名 renameHistory(HistoryStorage storage, HistoryItem item) { String name = ""; diff --git a/lib/ui/mobile/request/list.dart b/lib/ui/mobile/request/list.dart index 20e8220..ee36657 100644 --- a/lib/ui/mobile/request/list.dart +++ b/lib/ui/mobile/request/list.dart @@ -215,6 +215,7 @@ class RequestSequenceState extends State with AutomaticKeepAliv GlobalKey key = GlobalKey(); indexes[view.elementAt(index)] = key; return RequestRow( + index: index, key: key, request: view.elementAt(index), proxyServer: widget.proxyServer, diff --git a/lib/ui/mobile/request/request.dart b/lib/ui/mobile/request/request.dart index 660fae3..6894989 100644 --- a/lib/ui/mobile/request/request.dart +++ b/lib/ui/mobile/request/request.dart @@ -14,13 +14,14 @@ import 'package:network_proxy/utils/curl.dart'; ///请求行 class RequestRow extends StatefulWidget { + final int index; final HttpRequest request; final ProxyServer proxyServer; final bool displayDomain; final Function(HttpRequest)? onRemove; const RequestRow( - {super.key, required this.request, required this.proxyServer, this.displayDomain = true, this.onRemove}); + {super.key, required this.request, required this.proxyServer, this.displayDomain = true, this.onRemove, required this.index}); @override State createState() { @@ -51,12 +52,16 @@ class RequestRowState extends State { var time = formatDate(request.requestTime, [HH, ':', nn, ':', ss]); var subTitle = '$time - [${response?.status.code ?? ''}] ${response?.contentType.name.toUpperCase() ?? ''} ${response?.costTime() ?? ''}'; - return ListTile( visualDensity: const VisualDensity(vertical: -4), leading: getIcon(response), title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 2, style: const TextStyle(fontSize: 14)), - subtitle: Text(subTitle, maxLines: 1, style: const TextStyle(fontSize: 12)), + subtitle: Text.rich( + maxLines: 1, + TextSpan(children: [ + TextSpan(text: '#${widget.index} ', style: const TextStyle(fontSize: 12, color: Colors.teal)), + TextSpan(text: subTitle, style: const TextStyle(fontSize: 12)), + ])), trailing: const Icon(Icons.chevron_right), dense: true, contentPadding: const EdgeInsets.symmetric(horizontal: 8), diff --git a/lib/ui/mobile/setting/request_rewrite.dart b/lib/ui/mobile/setting/request_rewrite.dart index 8732c7b..b8c5dde 100644 --- a/lib/ui/mobile/setting/request_rewrite.dart +++ b/lib/ui/mobile/setting/request_rewrite.dart @@ -76,16 +76,32 @@ class _MobileRequestRewriteState extends State { icon: const Icon(Icons.remove, size: 18), label: const Text("删除", style: TextStyle(fontSize: 14)), onPressed: () { - var removeSelected = requestRuleList.removeSelected(); - if (removeSelected.isEmpty) { + var selected = requestRuleList.currentSelectedIndex(); + if (selected < 0) { return; } - changed = true; - setState(() { - widget.configuration.requestRewrites.removeIndex(removeSelected); - requestRuleList.changeState(); - }); + showDialog( + context: context, + builder: (ctx) { + return AlertDialog( + title: const Text("是否删除该请求重写?", style: TextStyle(fontSize: 18)), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: const Text("取消")), + TextButton( + onPressed: () { + changed = true; + setState(() { + widget.configuration.requestRewrites.removeIndex(requestRuleList.removeSelected()); + requestRuleList.changeState(); + }); + FlutterToastr.show('删除成功', context); + Navigator.pop(context); + }, + child: const Text("删除")), + ], + ); + }); }) ]), const SizedBox(height: 10), @@ -161,7 +177,7 @@ class _RewriteRuleState extends State { RequestRewrites.instance.addRule(rule); } - FlutterToastr.show("添加请求重写规则成功", context); + FlutterToastr.show("保存请求重写规则成功", context); Navigator.of(context).pop(rule); } }) diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index 8ef3d91..8a5bcdf 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -52,16 +52,13 @@ class _MobileSslState extends State { expandedAlignment: Alignment.topLeft, expandedCrossAxisAlignment: CrossAxisAlignment.start, shape: const Border(), - children: [ - TextButton(onPressed: () => _downloadCert(), child: const Text("1. 点击下载根证书")), - ...(Platform.isIOS ? ios() : android()), - const SizedBox(height: 20) - ]) + children: [...(Platform.isIOS ? ios() : android()), const SizedBox(height: 20)]) ])); } List ios() { return [ + TextButton(onPressed: () => _downloadCert(), child: const Text("1. 点击下载根证书")), TextButton(onPressed: () {}, child: const Text("2. 安装根证书 -> 信任证书")), TextButton(onPressed: () {}, child: const Text("2.1 安装根证书 设置 > 已下载描述文件 > 安装")), Padding( @@ -78,16 +75,40 @@ class _MobileSslState extends State { List android() { return [ - TextButton(onPressed: () => _downloadCert(), child: const Text("1.1系统根证书将根证书命名成 243f0bfb.0")), - TextButton(onPressed: () {}, child: const Text("2. 打开设置 -> 安全 -> 加密和凭据 -> 安装证书 -> CA 证书")), - ClipRRect( - child: Align( - alignment: Alignment.topCenter, - heightFactor: .7, - child: Image.network( - "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", - height: 680, - ))) + ExpansionTile( + title: const Text("Root用户:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), + tilePadding: const EdgeInsets.only(left: 0), + expandedAlignment: Alignment.topLeft, + initiallyExpanded: true, + shape: const Border(), + children: [ + const Text("针对安卓Root用户做了个Magisk模块ProxyPinCA系统证书,安装完重启手机即可。"), + TextButton( + child: const Text("https://gitee.com/wanghongenpin/Magisk-ProxyPinCA/releases/tag/1.0.0"), + onPressed: () { + launchUrl(Uri.parse("https://gitee.com/wanghongenpin/Magisk-ProxyPinCA/releases/tag/1.0.0")); + }) + ]), + const SizedBox(height: 10), + ExpansionTile( + title: const Text("非Root用户:", style: TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), + tilePadding: const EdgeInsets.only(left: 0), + expandedAlignment: Alignment.topLeft, + expandedCrossAxisAlignment: CrossAxisAlignment.start, + initiallyExpanded: true, + shape: const Border(), + children: [ + TextButton(onPressed: () => _downloadCert(), child: const Text("1. 点击下载根证书")), + TextButton(onPressed: () {}, child: const Text("2. 打开设置 -> 安全 -> 加密和凭据 -> 安装证书 -> CA 证书")), + ClipRRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: .7, + child: Image.network( + "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", + height: 680, + ))) + ]) ]; } diff --git a/lib/utils/har.dart b/lib/utils/har.dart index d49cbc9..96b087f 100644 --- a/lib/utils/har.dart +++ b/lib/utils/har.dart @@ -60,7 +60,7 @@ class Har { return har; } - static Future writeFile(List list, File file, {String title = ''}) async { + static Future writeJson(List list, {String title = ''}) async { var entries = _entries(list); Map har = {}; title = title.contains("ProxyPin") ? title : "[ProxyPin]$title"; @@ -77,7 +77,11 @@ class Har { ], "entries": entries, }; - var json = jsonEncode(har); + return jsonEncode(har); + } + + static Future writeFile(List list, File file, {String title = ''}) async { + var json = await writeJson(list, title: title); return file.writeAsString(json); }