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协议支持。
.
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