diff --git a/README.md b/README.md index 92131cd..6007c32 100644 --- a/README.md +++ b/README.md @@ -9,9 +9,9 @@ ios下载地址(Safari浏览器打开): https://testflight.apple.com/join/gURG > ios个人开发者账号用到VPN没法上架AppStore, 后面可能会上架美版AppStore。 -- [ ] 接下来会持续完善功能和体验,请求重放编辑、模拟慢请求, UI优化。 +- [ ] 接下来会持续完善功能和体验,请求重写功能增、强模拟慢请求、请求debug, UI优化。 - [ ] 支持安卓微信小程序抓包,安卓分为系统证书和用户证书,下载的自签名根证书安装都是用户证书,微信不信任用户证书,不Root导致Https抓不了了, 目前市场上所有抓包软件抓不了微信的包,后面单独做个运行空间插件,动态反编译修改配置,信任用户证书来解决。 -- [ ] WebSocket协议支持。 +- [ ] WebSocket、HTTP2协议支持。 image. image diff --git a/lib/network/http_client.dart b/lib/network/http_client.dart index 7ce0ea8..7a07f07 100644 --- a/lib/network/http_client.dart +++ b/lib/network/http_client.dart @@ -89,7 +89,6 @@ class HttpResponseHandler extends ChannelHandler { @override void channelInactive(Channel channel) { - log.i("[${channel.id}] channelInactive"); - _completer.completeError("channelInactive"); + // log.i("[${channel.id}] channelInactive"); } } diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index b8afef4..43fe081 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -37,7 +37,7 @@ class SystemProxy { 'networksetup -setproxybypassdomains $_hardwarePort 192.168.0.0/16 10.0.0.0/8 172.16.0.0/12 127.0.0.1 localhost *.local timestamp.apple.com sequoia.apple.com seed-sequoia.siri.apple.com *.google.com', ]) ]); - print('set proxyServer, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); + print('set proxyServer, name: $_hardwarePort, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); return results.exitCode == 0; } diff --git a/lib/ui/component/json/json_text.dart b/lib/ui/component/json/json_text.dart new file mode 100644 index 0000000..00619f7 --- /dev/null +++ b/lib/ui/component/json/json_text.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; +import 'package:network_proxy/ui/component/json/theme.dart'; + +class JsonText extends StatelessWidget { + final ColorTheme colorTheme; + + final dynamic json; + final String indent; + + const JsonText({super.key, required this.json, this.indent = ' ', required this.colorTheme}); + + @override + Widget build(BuildContext context) { + TextSpan jsonText; + if (json is Map) { + jsonText = TextSpan(children: getMapText(json, prefix: indent)); + } else if (json is List) { + jsonText = TextSpan(children: getArrayText(json)); + } else { + jsonText = TextSpan(text: json.toString()); + } + + return SelectionArea(child: Text.rich(jsonText)); + } + + /// 获取Map json + List getMapText(Map map, + {String openPrefix = '', String prefix = '', String suffix = ''}) { + var result = []; + result.add(TextSpan(text: '$openPrefix{\n')); + + var entries = map.entries; + for (int i = 0; i < entries.length; i++) { + var entry = entries.elementAt(i); + String postfix = '${i == entries.length - 1 ? '' : ','} \n'; + + var textSpan = TextSpan(text: prefix, children: [ + TextSpan(text: '"${entry.key}"', style: TextStyle(color: colorTheme.propertyKey)), + const TextSpan(text: ': '), + getBasicValue(entry.value, postfix), + ]); + result.add(textSpan); + + if (entry.value is Map) { + result.add( + TextSpan(children: getMapText(entry.value, openPrefix: prefix, prefix: '$prefix$indent', suffix: postfix))); + } else if (entry.value is List) { + result.add(TextSpan( + children: getArrayText(entry.value, openPrefix: prefix, prefix: '$prefix$indent', suffix: postfix))); + } + } + + result.add(TextSpan(text: '$openPrefix}$suffix')); + return result; + } + + /// 获取数组json + List getArrayText(List list, {String openPrefix = '', String prefix = '', String suffix = ''}) { + var result = []; + result.add(TextSpan(text: '$openPrefix[\n')); + + for (int i = 0; i < list.length; i++) { + var value = list[i]; + String postfix = '${i == list.length - 1 ? '' : ','} \n'; + + result.add(getBasicValue(value, postfix)); + + if (value is Map) { + result + .add(TextSpan(children: getMapText(value, openPrefix: prefix, prefix: '$prefix$indent', suffix: postfix))); + } else if (value is List) { + result.add( + TextSpan(children: getArrayText(value, openPrefix: prefix, prefix: '$prefix$indent', suffix: postfix))); + } + } + + result.add(TextSpan(text: '$openPrefix]$suffix')); + return result; + } + + /// 获取基本类型值 复杂类型会忽略 + InlineSpan getBasicValue(dynamic value, String suffix) { + if (value == null) { + return TextSpan( + children: [TextSpan(text: 'null', style: TextStyle(color: colorTheme.keyword)), TextSpan(text: suffix)]); + } + + if (value is String) { + return TextSpan( + children: [TextSpan(text: '"$value"', style: TextStyle(color: colorTheme.string)), TextSpan(text: suffix)]); + } + + if (value is num) { + return TextSpan(children: [ + TextSpan(text: value.toString(), style: TextStyle(color: colorTheme.number)), + TextSpan(text: suffix) + ]); + } + + if (value is bool) { + return TextSpan(children: [ + TextSpan(text: value.toString(), style: TextStyle(color: colorTheme.keyword)), + TextSpan(text: suffix) + ]); + } + + return const TextSpan(text: ''); + } +} diff --git a/lib/ui/component/json/json_viewer.dart b/lib/ui/component/json/json_viewer.dart new file mode 100644 index 0000000..3e9fa0a --- /dev/null +++ b/lib/ui/component/json/json_viewer.dart @@ -0,0 +1,278 @@ +library flutter_json_widget; + +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:network_proxy/ui/component/json/theme.dart'; +import 'package:network_proxy/ui/component/json/toast.dart'; + +class JsonViewer extends StatelessWidget { + final dynamic jsonObj; + final ColorTheme colorTheme; + + const JsonViewer(this.jsonObj, {super.key, required this.colorTheme}); + + @override + Widget build(BuildContext context) { + return getContentWidget(jsonObj); + } + + Widget getContentWidget(dynamic content) { + if (content is List) { + return JsonArrayViewer(content, notRoot: false, colorTheme: colorTheme); + } else if (content is Map) { + return JsonObjectViewer(content, notRoot: false, colorTheme: colorTheme); + } else { + return Text(content?.toString() ?? ''); + } + } +} + +class JsonObjectViewer extends StatefulWidget { + final ColorTheme colorTheme; + + final Map jsonObj; + final bool notRoot; + + const JsonObjectViewer(this.jsonObj, {super.key, this.notRoot = false, required this.colorTheme}); + + @override + JsonObjectViewerState createState() => JsonObjectViewerState(); +} + +class JsonObjectViewerState extends State { + Map openFlag = {}; + + @override + void didUpdateWidget(covariant JsonObjectViewer oldWidget) { + super.didUpdateWidget(oldWidget); + openFlag = {}; + } + + @override + Widget build(BuildContext context) { + if (widget.notRoot) { + return Container( + padding: const EdgeInsets.only(left: 14.0), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: _getList()), + ); + } + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: _getList()); + } + + _getList() { + List list = []; + for (MapEntry entry in widget.jsonObj.entries) { + list.add(Row( + children: [ + getKeyWidget(entry), + Text(':', style: TextStyle(color: widget.colorTheme.colon)), + const SizedBox(width: 3), + _copyValue(context, _getValueWidget(entry.value, widget.colorTheme), entry.value), + ], + )); + list.add(const SizedBox(height: 4)); + if ((openFlag[entry.key] ?? false) && entry.value != null) { + list.add(getContentWidget(entry.value, widget.colorTheme)); + } + } + return list; + } + + // key + Widget getKeyWidget(MapEntry entry) { + //是否有子层级 + if (_isExtensible(entry.value)) { + return InkWell( + onTap: () { + setState(() { + openFlag[entry.key] = !(openFlag[entry.key] ?? false); + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (openFlag[entry.key] ?? false) + ? const Icon(Icons.arrow_drop_down, size: 14) + : const Icon(Icons.arrow_right, size: 14), + Text(entry.key, style: TextStyle(color: widget.colorTheme.propertyKey)), + ], + )); + } + + return Row(children: [ + const Icon(Icons.arrow_right, color: Color.fromARGB(0, 0, 0, 0), size: 14), + Text(entry.key, style: TextStyle(color: widget.colorTheme.propertyKey)), + ]); + } + + static getContentWidget(dynamic content, ColorTheme colorTheme) { + if (content is List) { + return JsonArrayViewer(content, notRoot: true, colorTheme: colorTheme); + } else { + return JsonObjectViewer(content, notRoot: true, colorTheme: colorTheme); + } + } +} + +class JsonArrayViewer extends StatefulWidget { + final ColorTheme colorTheme; + final List jsonArray; + final bool notRoot; + + const JsonArrayViewer(this.jsonArray, {super.key, this.notRoot = false, required this.colorTheme}); + + @override + State createState() => _JsonArrayViewerState(); +} + +class _JsonArrayViewerState extends State { + late List openFlag; + + @override + Widget build(BuildContext context) { + if (widget.notRoot) { + return Container( + padding: const EdgeInsets.only(left: 14.0), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: _getList())); + } + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: _getList()); + } + + @override + void initState() { + super.initState(); + openFlag = List.filled(widget.jsonArray.length, false); + } + + _getList() { + List list = []; + int i = 0; + for (dynamic content in widget.jsonArray) { + list.add(Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + getKeyWidget(content, i), + Text(':', style: TextStyle(color: widget.colorTheme.colon)), + const SizedBox(width: 3), + _copyValue(context, _getValueWidget(content, widget.colorTheme), content) + ], + )); + list.add(const SizedBox(height: 4)); + if (openFlag[i]) { + list.add(JsonObjectViewerState.getContentWidget(content, widget.colorTheme)); + } + i++; + } + return list; + } + + // key + Widget getKeyWidget(dynamic content, int index) { + //是否有子层级 + if (_isExtensible(content)) { + return InkWell( + onTap: () { + setState(() { + openFlag[index] = !(openFlag[index]); + }); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + openFlag[index] ? const Icon(Icons.arrow_drop_down, size: 14) : const Icon(Icons.arrow_right, size: 14), + Text('[$index]', style: TextStyle(color: widget.colorTheme.propertyKey)), + ], + )); + } + + return Row(children: [ + const Icon(Icons.arrow_right, color: Color.fromARGB(0, 0, 0, 0), size: 14), + Text('[$index]', style: TextStyle(color: widget.colorTheme.propertyKey)), + ]); + } +} + +Widget _getValueWidget(dynamic value, ColorTheme colorTheme) { + if (value == null) { + return Text('null', style: TextStyle(color: colorTheme.keyword)); + } else if (value is num) { + return Text(value.toString(), style: TextStyle(color: colorTheme.keyword)); + } else if (value is String) { + return Text('"$value"', style: TextStyle(color: colorTheme.string)); + } else if (value is bool) { + return Text(value.toString(), style: TextStyle(color: colorTheme.keyword)); + } else if (value is List) { + if (value.isEmpty) { + return const Text('Array[0]'); + } else { + return Text('Array<${_getTypeName(value[0])}>[${value.length}]'); + } + } + return const Text('Object', style: TextStyle(fontSize: 13)); +} + +///获取值的类型 +String _getTypeName(dynamic content) { + if (content is int) { + return 'int'; + } else if (content is String) { + return 'String'; + } else if (content is bool) { + return 'bool'; + } else if (content is double) { + return 'double'; + } else if (content is List) { + return 'List'; + } + return 'Object'; +} + +/// 复制值 +Widget _copyValue(BuildContext context, Widget child, Object? value) { + return Flexible( + child: InkWell( + child: child, + onSecondaryTapDown: (details) { + //显示复制菜单 + showMenu( + context: context, + position: RelativeRect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx, + details.globalPosition.dy, + ), + items: [ + PopupMenuItem( + height: 30, + child: const Text('复制'), + onTap: () { + Clipboard.setData(ClipboardData(text: jsonEncode(value))) + .then((value) => Toast.show('已复制到剪切板', context)); + }) + ]); + }, + onTap: () { + Clipboard.setData(ClipboardData(text: jsonEncode(value))).then((value) => Toast.show('已复制到剪切板', context)); + })); +} + +/// 是否可展开 +bool _isExtensible(dynamic content) { + if (content == null) { + return false; + } else if (content is int) { + return false; + } else if (content is String) { + return false; + } else if (content is bool) { + return false; + } else if (content is double) { + return false; + } + return true; +} diff --git a/lib/ui/component/json/theme.dart b/lib/ui/component/json/theme.dart new file mode 100644 index 0000000..f7a1c86 --- /dev/null +++ b/lib/ui/component/json/theme.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +enum ColorTheme { + light( + background: Color(0xffffffff), + propertyKey: Color(0xff871094), + colon: Colors.black, + string: Color(0xff067d17), + number: Color(0xff1750eb), + keyword: Color(0xff0033b3)), + dark( + background: Color(0xff2b2b2b), + propertyKey: Color(0xff9876aa), + colon: Color(0xffcc7832), + string: Color(0xff6a8759), + number: Color(0xff6897bb), + keyword: Color(0xffcc7832)); + + final Color background; + final Color propertyKey; + final Color colon; + final Color string; + final Color number; + final Color keyword; + + const ColorTheme( + {required this.background, + required this.propertyKey, + required this.colon, + required this.string, + required this.number, + required this.keyword}); + + static ColorTheme of(Brightness brightness) { + return brightness == Brightness.dark ? ColorTheme.dark : ColorTheme.light; + } +} diff --git a/lib/ui/component/json/toast.dart b/lib/ui/component/json/toast.dart new file mode 100644 index 0000000..25187dd --- /dev/null +++ b/lib/ui/component/json/toast.dart @@ -0,0 +1,8 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; + +class Toast { + static void show(String message, BuildContext context) { + FlutterToastr.show(message, context); + } +} diff --git a/lib/ui/content/body.dart b/lib/ui/content/body.dart index e1f147c..d9aca40 100644 --- a/lib/ui/content/body.dart +++ b/lib/ui/content/body.dart @@ -4,16 +4,20 @@ import 'dart:io'; import 'package:desktop_multi_window/desktop_multi_window.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_json_viewer_new/flutter_json_viewer.dart'; import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/http/http.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/utils.dart'; +import 'package:network_proxy/utils/num.dart'; import 'package:network_proxy/utils/platform.dart'; import 'package:window_manager/window_manager.dart'; +import '../component/json/json_text.dart'; + class HttpBodyWidget extends StatefulWidget { final HttpMessage? httpMessage; - final bool inNewWindow; //是否在新窗口 + final bool inNewWindow; //是否在新窗口打开 final WindowController? windowController; const HttpBodyWidget({super.key, required this.httpMessage, this.inNewWindow = false, this.windowController}); @@ -25,8 +29,8 @@ class HttpBodyWidget extends StatefulWidget { } class HttpBodyState extends State { - ValueNotifier tabIndex = ValueNotifier(0); - String? body; + var bodyKey = GlobalKey<_BodyState>(); + int tabIndex = 0; @override void initState() { @@ -34,6 +38,7 @@ class HttpBodyState extends State { RawKeyboard.instance.addListener(onKeyEvent); } + /// 按键事件 void onKeyEvent(RawKeyEvent event) { if (event.isKeyPressed(LogicalKeyboardKey.metaLeft) && event.isKeyPressed(LogicalKeyboardKey.keyW)) { RawKeyboard.instance.removeListener(onKeyEvent); @@ -44,7 +49,6 @@ class HttpBodyState extends State { @override void dispose() { - tabIndex.dispose(); RawKeyboard.instance.removeListener(onKeyEvent); super.dispose(); } @@ -56,24 +60,26 @@ class HttpBodyState extends State { } var tabs = Tabs.of(widget.httpMessage?.contentType); - body = widget.httpMessage?.bodyAsString; - if (tabIndex.value >= tabs.list.length) tabIndex.value = 0; + if (tabIndex >= tabs.list.length) tabIndex = tabs.list.length - 1; + bodyKey.currentState?.changeState(widget.httpMessage, tabs.list[tabIndex]); List list = [ widget.inNewWindow ? const SizedBox() : titleWidget(), - TabBar(tabs: tabs.tabList(), onTap: (index) => tabIndex.value = index), + TabBar( + labelPadding: const EdgeInsets.symmetric(horizontal: 2.0), + tabs: tabs.tabList(), + onTap: (index) { + tabIndex = index; + bodyKey.currentState?.changeState(widget.httpMessage, tabs.list[tabIndex]); + }), Padding( - padding: const EdgeInsets.all(10), - child: ValueListenableBuilder( - valueListenable: tabIndex, - builder: (_, value, __) { - return getBody(tabs.list[value]); - }), - ) //body + padding: const EdgeInsets.all(10), + child: _Body(key: bodyKey, message: widget.httpMessage, viewType: tabs.list[tabIndex])) //body ]; var tabController = DefaultTabController( + initialIndex: tabIndex, length: tabs.list.length, child: widget.inNewWindow ? ListView(children: list) @@ -99,10 +105,12 @@ class HttpBodyState extends State { icon: const Icon(Icons.copy), tooltip: '复制', onPressed: () { - if (body == null || body?.isEmpty == true) { + var body = bodyKey.currentState?.body; + if (body == null) { return; } - Clipboard.setData(ClipboardData(text: body!)).then((value) => FlutterToastr.show("已复制到剪切板", context)); + + Clipboard.setData(ClipboardData(text: body)).then((value) => FlutterToastr.show("已复制到剪切板", context)); }), const SizedBox(width: 5), inNewWindow @@ -133,35 +141,92 @@ class HttpBodyState extends State { Navigator.push( context, MaterialPageRoute(builder: (_) => HttpBodyWidget(httpMessage: widget.httpMessage, inNewWindow: true))); } +} - Widget getBody(ViewType type) { - var message = widget.httpMessage; - if (message == null || body == null) { +class _Body extends StatefulWidget { + final HttpMessage? message; + final ViewType viewType; + + const _Body({super.key, this.message, required this.viewType}); + + @override + State createState() { + return _BodyState(); + } +} + +class _BodyState extends State<_Body> { + late ViewType viewType; + HttpMessage? message; + + @override + void initState() { + super.initState(); + viewType = widget.viewType; + message = widget.message; + } + + changeState(HttpMessage? message, ViewType viewType) { + setState(() { + this.message = message; + this.viewType = viewType; + }); + } + + @override + Widget build(BuildContext context) { + return _getBody(viewType); + } + + String? get body { + if (message == null || message?.body == null) { + return null; + } + + if (viewType == ViewType.hex) { + return message!.body!.map(intToHex).join(" "); + } + if (viewType == ViewType.formUrl) { + return Uri.decodeFull(message!.bodyAsString); + } + if (viewType == ViewType.jsonText || viewType == ViewType.json) { + //json格式化 + var jsonObject = json.decode(message!.bodyAsString); + return const JsonEncoder.withIndent(" ").convert(jsonObject); + } + return message!.bodyAsString; + } + + Widget _getBody(ViewType type) { + if (message == null || message?.body == null) { return const SizedBox(); } try { - if (type == ViewType.json) { - return JsonViewer(json.decode(body!)); + if (type == ViewType.jsonText) { + var jsonObject = json.decode(message!.bodyAsString); + return JsonText(json: jsonObject, colorTheme: ColorTheme.of(Theme.of(context).brightness)); } - if (type == ViewType.jsonText) { - var jsonObject = json.decode(body!); - var prettyJsonString = const JsonEncoder.withIndent(' ').convert(jsonObject); - return SelectableText(prettyJsonString, contextMenuBuilder: contextMenu); + if (type == ViewType.json) { + return JsonViewer(json.decode(message!.bodyAsString), colorTheme: ColorTheme.of(Theme.of(context).brightness)); } if (type == ViewType.formUrl) { - return SelectableText(Uri.decodeFull(body!)); + return SelectableText(Uri.decodeFull(message!.bodyAsString), contextMenuBuilder: contextMenu); } if (type == ViewType.image) { - return Image.memory(Uint8List.fromList(message.body ?? []), fit: BoxFit.none); + return Image.memory(Uint8List.fromList(message?.body ?? []), fit: BoxFit.none); + } + if (type == ViewType.hex) { + return SelectableText(message!.body!.map(intToHex).join(" "), contextMenuBuilder: contextMenu); } } catch (e) { // ignore: avoid_print print(e); } - return SelectableText.rich(TextSpan(text: body), contextMenuBuilder: contextMenu); + + return SelectableText.rich(TextSpan(text: message?.bodyAsString), contextMenuBuilder: contextMenu); } } @@ -186,11 +251,13 @@ class Tabs { if (contentType == ContentType.formUrl || contentType == ContentType.json) { tabs.list.add(ViewType.text); } + + tabs.list.add(ViewType.hex); return tabs; } List tabList() { - return list.map((e) => Tab(text: e.title)).toList(); + return list.map((e) => Tab(child: Text(e.title))).toList(); } } @@ -203,6 +270,7 @@ enum ViewType { image("Image"), css("CSS"), js("JavaScript"), + hex("Hex"), ; final String title; diff --git a/lib/ui/content/panel.dart b/lib/ui/content/panel.dart index f572cbc..27ee1ca 100644 --- a/lib/ui/content/panel.dart +++ b/lib/ui/content/panel.dart @@ -162,7 +162,7 @@ class NetworkTabState extends State with SingleTickerProvi headers.add(Row(children: [ SelectableText('$name: ', style: const TextStyle(fontWeight: FontWeight.w500, color: Colors.deepOrangeAccent)), - Expanded(child: SelectableText(v, contextMenuBuilder: contextMenu, maxLines: 5, minLines: 1)), + Expanded(child: SelectableText(v, contextMenuBuilder: contextMenu, maxLines: 8, minLines: 1)), ])); headers.add(const Divider(thickness: 0.1)); } diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index 214aef4..013181a 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -147,7 +147,6 @@ class MoreEnum extends StatelessWidget { } else { scanRes = await FlutterBarcodeScanner.scanBarcode("#ff6666", "取消", true, ScanMode.QR); } - if (scanRes == "-1") return; if (scanRes.startsWith("http")) { launchUrl(Uri.parse(scanRes), mode: LaunchMode.externalApplication); diff --git a/lib/ui/mobile/mobile.dart b/lib/ui/mobile/mobile.dart index c96c839..48d2404 100644 --- a/lib/ui/mobile/mobile.dart +++ b/lib/ui/mobile/mobile.dart @@ -27,8 +27,6 @@ class MobileHomeState extends State implements EventListener { late ProxyServer proxyServer; ValueNotifier desktop = ValueNotifier(RemoteModel(connect: false)); - Timer? _connectCheckTimer; - @override void onRequest(Channel channel, HttpRequest request) { requestStateKey.currentState!.add(channel, request); @@ -48,7 +46,6 @@ class MobileHomeState extends State implements EventListener { checkConnectTask(context); } else { proxyServer.server?.remoteHost = null; - _connectCheckTimer?.cancel(); } }); super.initState(); @@ -121,7 +118,12 @@ class MobileHomeState extends State implements EventListener { /// 检查远程连接 checkConnectTask(BuildContext context) async { int retry = 0; - _connectCheckTimer = Timer.periodic(const Duration(milliseconds: 1000), (timer) async { + Timer.periodic(const Duration(milliseconds: 3000), (timer) async { + if (desktop.value.connect == false) { + timer.cancel(); + return; + } + try { var response = await HttpClients.get("http://${desktop.value.host}:${desktop.value.port}/ping") .timeout(const Duration(seconds: 1)); @@ -134,8 +136,7 @@ class MobileHomeState extends State implements EventListener { } if (retry > 3) { - _connectCheckTimer?.cancel(); - _connectCheckTimer = null; + timer.cancel(); desktop.value = RemoteModel(connect: false); if (context.mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("检查远程连接失败,已断开"))); diff --git a/lib/ui/mobile/request/request_editor.dart b/lib/ui/mobile/request/request_editor.dart index be3e496..6b8cfb4 100644 --- a/lib/ui/mobile/request/request_editor.dart +++ b/lib/ui/mobile/request/request_editor.dart @@ -269,7 +269,6 @@ class HeadersState extends State { //删除 deleteHeader(String key) { - HapticFeedback.heavyImpact(); showDialog( context: context, builder: (ctx) { diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index 5b48b08..956097d 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -30,13 +30,13 @@ class _MobileSslState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar( - title: const Text("Https代理"), + title: const Text("HTTPS代理"), centerTitle: true, ), body: ListView(children: [ SwitchListTile( hoverColor: Colors.transparent, - title: const Text("启用Https代理", style: TextStyle(fontSize: 16)), + title: const Text("启用HTTPS代理", style: TextStyle(fontSize: 16)), value: widget.proxyServer.enableSsl, onChanged: (val) { widget.proxyServer.enableSsl = val; @@ -95,7 +95,17 @@ class _MobileSslState extends State { showDialog( context: context, builder: (context) { - return const Text("请先启动抓包"); + return AlertDialog( + title: const Text("提示"), + content: const Text("请先启动代理服务"), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text("确定")) + ], + ); }); return; } diff --git a/lib/utils/ip.dart b/lib/utils/ip.dart index dd3672d..f4cfc38 100644 --- a/lib/utils/ip.dart +++ b/lib/utils/ip.dart @@ -1,7 +1,7 @@ import 'dart:io'; void main() { - NetworkInterface.list().then((interfaces) => interfaces.forEach((interface) { + NetworkInterface.list(type: InternetAddressType.IPv4).then((interfaces) => interfaces.forEach((interface) { print(interface.name); for (var address in interface.addresses) { print(" ${address.address}"); @@ -20,11 +20,8 @@ Future localIp() async { } Future localAddress() async { - return await NetworkInterface.list().then((interfaces) { - return interfaces - .firstWhere(primary, orElse: () => interfaces.first) - .addresses - .first; + return await NetworkInterface.list(type: InternetAddressType.IPv4).then((interfaces) { + return interfaces.firstWhere(primary, orElse: () => interfaces.first).addresses.first; }); } @@ -36,23 +33,29 @@ Future> localIps() async { return ipList!; } - var list = await NetworkInterface.list(); + var list = await NetworkInterface.list(type: InternetAddressType.IPv4); list.sort((a, b) { if (primary(a)) { return -1; } return 1; }); - ipList = list.map((it) => it.addresses.first.address).toList(); + + ipList = []; + for (var element in list) { + if (!ipList!.contains(element.addresses.first.address)) { + ipList?.add(element.addresses.first.address); + } + } return ipList!; } Future networkName() { - return NetworkInterface.list() + return NetworkInterface.list(type: InternetAddressType.IPv4) .then((interfaces) => interfaces.firstWhere(primary, orElse: () => interfaces.first).name); } // en0(macos系统) or WLAN(widows设备名)优先 bool primary(NetworkInterface it) { - return it.name == 'en0' || it.name == 'WLAN'; + return it.name == 'en0' || it.name.startsWith('WLAN') || it.name.startsWith("wlan"); } diff --git a/lib/utils/num.dart b/lib/utils/num.dart index 43a4a0f..bd4e668 100644 --- a/lib/utils/num.dart +++ b/lib/utils/num.dart @@ -18,3 +18,8 @@ int hexToInt(String hex) { } return val; } + +//int ---> hex +String intToHex(int i) { + return i.toRadixString(16).toUpperCase(); +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7339a3c..66dc7b4 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,7 +10,6 @@ import path_provider_foundation import proxy_manager import screen_retriever import share_plus -import sqflite import url_launcher_macos import window_manager @@ -20,7 +19,6 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ProxyManagerPlugin.register(with: registry.registrar(forPlugin: "ProxyManagerPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin")) - SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) WindowManagerPlugin.register(with: registry.registrar(forPlugin: "WindowManagerPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index b2204ea..a567e3c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -25,30 +25,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - cached_network_image: - dependency: transitive - description: - name: cached_network_image - sha256: fd3d0dc1d451f9a252b32d95d3f0c3c487bc41a75eba2e6097cb0b9c71491b15 - url: "https://pub.dev" - source: hosted - version: "3.2.3" - cached_network_image_platform_interface: - dependency: transitive - description: - name: cached_network_image_platform_interface - sha256: bb2b8403b4ccdc60ef5f25c70dead1f3d32d24b9d6117cfc087f496b178594a7 - url: "https://pub.dev" - source: hosted - version: "2.0.0" - cached_network_image_web: - dependency: transitive - description: - name: cached_network_image_web - sha256: b8eb814ebfcb4dea049680f8c1ffb2df399e4d03bf7a352c775e26fa06e02fa0 - url: "https://pub.dev" - source: hosted - version: "1.0.2" characters: dependency: transitive description: @@ -174,46 +150,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" - flutter_baseui_kit: - dependency: transitive - description: - name: flutter_baseui_kit - sha256: "4cc027d264f78e8cbbe4e0d2f3333cfb65d4e203146972bbcb20b49ac42d2dc8" - url: "https://pub.dev" - source: hosted - version: "0.0.1" - flutter_blurhash: - dependency: transitive - description: - name: flutter_blurhash - sha256: "05001537bd3fac7644fa6558b09ec8c0a3f2eba78c0765f88912882b1331a5c6" - url: "https://pub.dev" - source: hosted - version: "0.7.0" - flutter_cache_manager: - dependency: transitive - description: - name: flutter_cache_manager - sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" - url: "https://pub.dev" - source: hosted - version: "3.3.1" - flutter_foundation_base: - dependency: transitive - description: - name: flutter_foundation_base - sha256: "5c06851065c8cee4ef9783546401d0efd49ac3a2f5c1111a8d22ae92235c6d3d" - url: "https://pub.dev" - source: hosted - version: "0.0.2" - flutter_json_viewer_new: - dependency: "direct main" - description: - name: flutter_json_viewer_new - sha256: "5c862bc4b55bd808f3cf40824718a668425dcd8895e0a0de83e6db79ef6ce354" - url: "https://pub.dev" - source: hosted - version: "0.0.1" flutter_lints: dependency: "direct dev" description: @@ -222,14 +158,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" - flutter_overlay_kit: - dependency: transitive - description: - name: flutter_overlay_kit - sha256: a1493b2cab9c6dd29a636e7b4133fdc9b74374577e575969091f7e3d7214d778 - url: "https://pub.dev" - source: hosted - version: "0.0.1" flutter_plugin_android_lifecycle: dependency: transitive description: @@ -256,14 +184,6 @@ packages: description: flutter source: sdk version: "0.0.0" - fluttertoast: - dependency: transitive - description: - name: fluttertoast - sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" - url: "https://pub.dev" - source: hosted - version: "8.2.2" http: dependency: transitive description: @@ -352,14 +272,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" - octo_image: - dependency: transitive - description: - name: octo_image - sha256: "107f3ed1330006a3bea63615e81cf637433f5135a52466c7caa0e7152bca9143" - url: "https://pub.dev" - source: hosted - version: "1.0.2" path: dependency: transitive description: @@ -472,14 +384,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.3.3" - rxdart: - dependency: transitive - description: - name: rxdart - sha256: "0c7c0cedd93788d996e33041ffecda924cc54389199cde4e6a34b440f50044cb" - url: "https://pub.dev" - source: hosted - version: "0.27.7" screen_retriever: dependency: transitive description: @@ -504,14 +408,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.1" - shimmer: - dependency: transitive - description: - name: shimmer - sha256: "1f1009b5845a1f88f1c5630212279540486f97409e9fc3f63883e71070d107bf" - url: "https://pub.dev" - source: hosted - version: "2.0.0" sky_engine: dependency: transitive description: flutter @@ -525,22 +421,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - sqflite: - dependency: transitive - description: - name: sqflite - sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9 - url: "https://pub.dev" - source: hosted - version: "2.2.8+4" - sqflite_common: - dependency: transitive - description: - name: sqflite_common - sha256: "8f7603f3f8f126740bc55c4ca2d1027aab4b74a1267a3e31ce51fe40e3b65b8f" - url: "https://pub.dev" - source: hosted - version: "2.4.5+1" stack_trace: dependency: transitive description: @@ -565,14 +445,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" - synchronized: - dependency: transitive - description: - name: synchronized - sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" - url: "https://pub.dev" - source: hosted - version: "3.1.0" term_glyph: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index db9fd3f..54ff056 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,7 +24,6 @@ dependencies: easy_permission: ^1.0.0 qrscan: ^0.3.3 flutter_barcode_scanner: ^2.0.0 - flutter_json_viewer_new: ^0.0.1 flutter_toastr: ^1.0.3 share_plus: ^7.0.2