diff --git a/lib/main.dart b/lib/main.dart index 35a8c44..de5ee98 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -37,13 +37,15 @@ class FluentApp extends StatelessWidget { @override Widget build(BuildContext context) { + ThemeData(brightness: Brightness.dark, useMaterial3: false); return ValueListenableBuilder( valueListenable: themeNotifier, builder: (_, ThemeMode currentMode, __) { return MaterialApp( title: 'ProxyPin', + debugShowCheckedModeBanner: false, theme: ThemeData.light(useMaterial3: true), - darkTheme: ThemeData(brightness: Brightness.dark, useMaterial3: false), + darkTheme: ThemeData.dark(useMaterial3: false), themeMode: currentMode, home: const NetworkHomePage(), ); @@ -59,37 +61,42 @@ class NetworkHomePage extends StatefulWidget { } class _NetworkHomePagePageState extends State implements EventListener { + final domainStateKey = GlobalKey(); final NetworkTabController panel = NetworkTabController(); - late DomainWidget domainWidget; + late ProxyServer proxyServer; @override void onRequest(Channel channel, HttpRequest request) { - domainWidget.add(channel, request); + domainStateKey.currentState!.add(channel, request); } @override void onResponse(Channel channel, HttpResponse response) { - domainWidget.addResponse(channel, response); + domainStateKey.currentState!.addResponse(channel, response); } @override void initState() { super.initState(); - domainWidget = DomainWidget(panel: panel); proxyServer = ProxyServer(listener: this); } @override Widget build(BuildContext context) { + + final domainWidget = DomainWidget(key: domainStateKey, panel: panel); + return Scaffold( appBar: Tab( - child: Toolbar(proxyServer, domainWidget), + child: Toolbar(proxyServer, domainStateKey), ), - body: Row(children: [ - SizedBox(width: 400, child: domainWidget), - const VerticalDivider(), - Expanded(flex: 100, child: domainWidget.panel), - ])); + body: Padding( + padding: const EdgeInsets.only(top: 5), + child: Row(crossAxisAlignment: CrossAxisAlignment.start, children: [ + SizedBox(width: 400, child: domainWidget), + const VerticalDivider(), + Expanded(flex: 100, child: panel), + ]))); } } diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index 70b65b5..4f4f90d 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -1,5 +1,7 @@ import 'dart:io'; +import 'package:network_proxy/utils/ip.dart'; + class SystemProxy { /// 设置系统代理 static void setSystemProxy(int port, bool enableSsl) async { @@ -18,12 +20,13 @@ class SystemProxy { } var host = match.namedGroup('host'); var port = match.namedGroup('port'); + var name = await hardwarePort(); var results = await Process.run('bash', [ '-c', _concatCommands([ - 'networksetup -setwebproxy wi-fi $host $port', - enableSsl == true ? 'networksetup -setsecurewebproxy wi-fi $host $port' : '', - 'networksetup -setproxybypassdomains wi-fi 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, *.googleapis.com', + 'networksetup -setwebproxy $name $host $port', + enableSsl == true ? 'networksetup -setsecurewebproxy $name $host $port' : '', + 'networksetup -setproxybypassdomains $name 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}'); @@ -32,25 +35,65 @@ class SystemProxy { static Future setProxyEnableMacOS(bool proxyEnable, bool enableSsl) async { var proxyMode = proxyEnable ? 'on' : 'off'; + var name = await hardwarePort(); var results = await Process.run('bash', [ '-c', _concatCommands([ - 'networksetup -setwebproxystate wi-fi $proxyMode', - enableSsl ? 'networksetup -setsecurewebproxystate wi-fi $proxyMode' : '', + 'networksetup -setwebproxystate $name $proxyMode', + enableSsl ? 'networksetup -setsecurewebproxystate $name $proxyMode' : '', ]) ]); return results.exitCode == 0; } static Future setSslProxyEnableMacOS(bool proxyEnable, port) async { + var name = await hardwarePort(); + + var results = await Process.run('bash', [ + '-c', + proxyEnable + ? 'networksetup -setsecurewebproxy $name 127.0.0.1 $port' + : 'networksetup -setsecurewebproxystate $name off', + ]); + return results.exitCode == 0; + } + + static Future hardwarePort() async { + var name = await networkName(); var results = await Process.run('bash', [ '-c', _concatCommands([ - proxyEnable - ? 'networksetup -setsecurewebproxy wi-fi 127.0.0.1 $port' - : 'networksetup -setsecurewebproxystate wi-fi off', + 'networksetup -listnetworkserviceorder |grep "Device: $name" -A 1 |grep "Hardware Port" |awk -F ": " \'{print \$2}\'', ]) ]); + + return results.stdout.toString().split(", ")[0]; + } + + static Future _setProxyServerWindows(String proxyServer) async { + var results = await Process.run('reg', [ + 'add', + 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', + '/v', + 'ProxyServer', + '/f', + '/d', + proxyServer, + ]); + + Process.run('reg', [ + 'add', + 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', + '/v', + 'ProxyOverride', + '/t', + 'REG_SZ', + '/d', + '192.168.0.*;10.0.0.*;172.16.0.*;127.0.0.1;localhost;*.local', + '/f', + ]); + + print('set proxyServer $proxyServer, exitCode: ${results.exitCode}, stdout: ${results.stderr}'); return results.exitCode == 0; } @@ -69,21 +112,12 @@ class SystemProxy { return results.exitCode == 0; } - static Future _setProxyServerWindows(String proxyServer) async { - var results = await Process.run('reg', [ - 'add', - 'HKCU\\Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings', - '/v', - 'ProxyServer', - '/f', - '/d', - proxyServer, - ]); - print('set proxyServer $proxyServer, exitCode: ${results.exitCode}, stdout: ${results.stderr}'); - return results.exitCode == 0; - } - static _concatCommands(List commands) { return commands.where((element) => element.isNotEmpty).join(' && '); } } + +void main() async { + var r = await SystemProxy.hardwarePort(); + print(r); +} diff --git a/lib/ui/components.dart b/lib/ui/components.dart index 8b13789..6236aaf 100644 --- a/lib/ui/components.dart +++ b/lib/ui/components.dart @@ -1 +1,65 @@ +import 'package:flutter/material.dart'; +class ColorTransition extends StatefulWidget { + final Color begin; + final Color end; + final Duration duration; + final Widget child; + + const ColorTransition({super.key, required this.begin, this.end = Colors.transparent, + this.duration = const Duration(milliseconds: 500), required this.child}); + + @override + State createState() { + return ColorTransitionState(); + } +} + +class ColorTransitionState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + print("AnimationController"); + + //创建动画控制器 + _animationController = AnimationController( + vsync: this, + duration: widget.duration, + ); + + //添加动画执行刷新监听 + _animationController.addListener(() { + setState(() {}); + }); + + //颜色动画变化 + _animation = ColorTween(begin: widget.begin, end: widget.end).animate(_animationController); + + //添加到事件队列 + Future.delayed(const Duration(milliseconds: 80), () { + _animationController.forward(); + }); + } + + show() { + _animationController.reset(); + _animationController.forward(); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Container( + color: _animation.value, + child: widget.child, + ); + } +} diff --git a/lib/ui/left/domain.dart b/lib/ui/left/domain.dart index b63ca5a..3acad55 100644 --- a/lib/ui/left/domain.dart +++ b/lib/ui/left/domain.dart @@ -2,6 +2,7 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import 'package:network_proxy/network/http/http.dart'; +import 'package:network_proxy/ui/components.dart'; import 'package:network_proxy/ui/left/path.dart'; import '../../network/channel.dart'; @@ -12,42 +13,25 @@ import '../panel.dart'; class DomainWidget extends StatefulWidget { final NetworkTabController panel; - DomainWidget({required this.panel}) : super(key: GlobalKey<_DomainWidgetState>()); - - void add(Channel channel, HttpRequest request) { - var state = key as GlobalKey<_DomainWidgetState>; - state.currentState?.add(channel, request); - } - - void addResponse(Channel channel, HttpResponse response) { - var state = key as GlobalKey<_DomainWidgetState>; - state.currentState?.addResponse(channel, response); - } - - void clean() { - var state = key as GlobalKey<_DomainWidgetState>; - panel.change(null, null); - state.currentState?.clean(); - } + const DomainWidget({super.key, required this.panel}); @override State createState() { - return _DomainWidgetState(); + return DomainWidgetState(); } } -class _DomainWidgetState extends State { +class DomainWidgetState extends State { LinkedHashMap containerMap = LinkedHashMap(); @override Widget build(BuildContext context) { var list = containerMap.values; - return ListView.builder( - itemBuilder: (BuildContext context, int index) => list.elementAt(index), itemCount: list.length); + return SingleChildScrollView(child: Column(children: list.toList())); } ///添加请求 - void add(Channel channel, HttpRequest request) { + add(Channel channel, HttpRequest request) { HostAndPort hostAndPort = channel.getAttribute(AttributeKeys.host); HeaderBody? headerBody = containerMap[hostAndPort]; var listURI = PathRow(request, widget.panel); @@ -64,14 +48,15 @@ class _DomainWidgetState extends State { } ///添加响应 - void addResponse(Channel channel, HttpResponse response) { + addResponse(Channel channel, HttpResponse response) { HostAndPort hostAndPort = channel.getAttribute(AttributeKeys.host); HeaderBody? headerBody = containerMap[hostAndPort]; headerBody?.getBody(channel.id)?.add(response); } ///清理 - void clean() { + clean() { + widget.panel.change(null, null); setState(() { containerMap.clear(); }); @@ -107,6 +92,8 @@ class HeaderBody extends StatefulWidget { } class _HeaderBodyState extends State { + final GlobalKey transitionState = GlobalKey(); + late bool selected; @override @@ -117,6 +104,7 @@ class _HeaderBodyState extends State { changeState() { setState(() {}); + transitionState.currentState?.show(); } @override @@ -128,16 +116,21 @@ class _HeaderBodyState extends State { } Widget _hostWidget(String title) { - return ListTile( - leading: Icon(selected ? Icons.arrow_drop_down : Icons.arrow_right, size: 16), - dense: true, - horizontalTitleGap: 0, - visualDensity: const VisualDensity(vertical: -3.6), - title: Text(title, textAlign: TextAlign.left), - onTap: () { - setState(() { - selected = !selected; - }); - }); + return ColorTransition( + key: transitionState, + duration: const Duration(milliseconds: 1500), + begin: Colors.white30, + child: ListTile( + minLeadingWidth: 25, + leading: Icon(selected ? Icons.arrow_drop_down : Icons.arrow_right, size: 16), + dense: true, + horizontalTitleGap: 0, + visualDensity: const VisualDensity(vertical: -3.6), + title: Text(title, textAlign: TextAlign.left), + onTap: () { + setState(() { + selected = !selected; + }); + })); } } diff --git a/lib/ui/left/path.dart b/lib/ui/left/path.dart index 405f3a2..8caff32 100644 --- a/lib/ui/left/path.dart +++ b/lib/ui/left/path.dart @@ -36,6 +36,7 @@ class _PathRowState extends State { var title = '${request.method.name} ${Uri.parse(request.uri).path}'; var time = formatDate(request.requestTime, [HH, ':', nn, ':', ss]); return ListTile( + minLeadingWidth: 25, leading: Icon(getIcon(), size: 16, color: widget.color), title: Text(title, overflow: TextOverflow.ellipsis, maxLines: 1), subtitle: Text( diff --git a/lib/ui/panel.dart b/lib/ui/panel.dart index 4771aca..e8baf23 100644 --- a/lib/ui/panel.dart +++ b/lib/ui/panel.dart @@ -16,22 +16,22 @@ class NetworkTabController extends StatefulWidget { final ValueWrap request = ValueWrap(); final ValueWrap response = ValueWrap(); - NetworkTabController() : super(key: GlobalKey<_NetworkTabState>()); + NetworkTabController() : super(key: GlobalKey()); void change(HttpRequest? request, HttpResponse? response) { this.request.set(request); this.response.set(response); - var state = key as GlobalKey<_NetworkTabState>; + var state = key as GlobalKey; state.currentState?.changeState(); } @override State createState() { - return _NetworkTabState(); + return NetworkTabState(); } } -class _NetworkTabState extends State { +class NetworkTabState extends State { void changeState() { setState(() {}); } diff --git a/lib/ui/toolbar/setting/filter.dart b/lib/ui/toolbar/setting/filter.dart index 064de56..abb6456 100644 --- a/lib/ui/toolbar/setting/filter.dart +++ b/lib/ui/toolbar/setting/filter.dart @@ -144,10 +144,10 @@ class _DomainFilterState extends State { @override void dispose() { - super.dispose(); if (changed) { widget.proxyServer.flushConfig(); } + super.dispose(); } void add() { diff --git a/lib/ui/toolbar/setting/setting.dart b/lib/ui/toolbar/setting/setting.dart index ae8cb1d..f6ef990 100644 --- a/lib/ui/toolbar/setting/setting.dart +++ b/lib/ui/toolbar/setting/setting.dart @@ -26,23 +26,32 @@ class _SettingState extends State { offset: const Offset(10, 30), itemBuilder: (context) { return [ - PopupMenuItem(child: PortWidget(proxyServer: widget.proxyServer)), + PopupMenuItem(padding: const EdgeInsets.all(0), child: PortWidget(proxyServer: widget.proxyServer)), const PopupMenuItem( + padding: EdgeInsets.all(0), child: ThemeSetting(), ), PopupMenuItem( + padding: const EdgeInsets.all(0), child: ListTile( title: const Text("域名过滤"), dense: true, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, trailing: const Icon(Icons.arrow_right), onTap: () => _filter())), PopupMenuItem( + padding: const EdgeInsets.all(0), child: ListTile( - title: const Text("请求重写"), - dense: true, - trailing: const Icon(Icons.arrow_right), - onTap: () => _reqeustRewrite())), + title: const Text("请求重写"), + dense: true, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + trailing: const Icon(Icons.arrow_right), + onTap: () => _reqeustRewrite(), + )), PopupMenuItem( + padding: const EdgeInsets.all(0), child: const ListTile(title: Text("Github"), dense: true, trailing: Icon(Icons.arrow_right)), onTap: () { launchUrl(Uri.parse("https://github.com/wanghongenpin/network-proxy-flutter")); diff --git a/lib/ui/toolbar/ssl/ssl.dart b/lib/ui/toolbar/ssl/ssl.dart index e562845..6bfe0f4 100644 --- a/lib/ui/toolbar/ssl/ssl.dart +++ b/lib/ui/toolbar/ssl/ssl.dart @@ -23,20 +23,26 @@ class _SslState extends State { offset: const Offset(10, 30), itemBuilder: (context) { return [ - PopupMenuItem(child: _Switch(proxyServer: widget.proxyServer)), + PopupMenuItem(padding: const EdgeInsets.all(0), child: _Switch(proxyServer: widget.proxyServer)), PopupMenuItem( + padding: const EdgeInsets.all(0), child: ListTile( - dense: true, - 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( + padding: const EdgeInsets.all(0), child: ListTile( title: const Text("安装根证书到手机"), dense: true, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, trailing: const Icon(Icons.arrow_right), onTap: () async { mobileCer(await localIp()); @@ -124,6 +130,7 @@ class _SwitchState extends State<_Switch> { @override Widget build(BuildContext context) { return SwitchListTile( + hoverColor: Colors.transparent, title: const Text("启用Https代理", style: TextStyle(fontSize: 12)), visualDensity: const VisualDensity(horizontal: -4), dense: true, diff --git a/lib/ui/toolbar/toolbar.dart b/lib/ui/toolbar/toolbar.dart index 8f12db8..099d95e 100644 --- a/lib/ui/toolbar/toolbar.dart +++ b/lib/ui/toolbar/toolbar.dart @@ -11,9 +11,9 @@ import 'launch/launch.dart'; class Toolbar extends StatefulWidget { final ProxyServer proxyServer; - final DomainWidget domainWidget; + final GlobalKey domainStateKey; - const Toolbar(this.proxyServer, this.domainWidget, {super.key}); + const Toolbar(this.proxyServer, this.domainStateKey, {super.key}); @override State createState() { @@ -33,7 +33,7 @@ class _ToolbarState extends State with WindowListener { tooltip: "清理", icon: const Icon(Icons.cleaning_services_outlined), onPressed: () { - widget.domainWidget.clean(); + widget.domainStateKey.currentState?.clean(); }), const Padding(padding: EdgeInsets.only(left: 30)), SslWidget(proxyServer: widget.proxyServer), diff --git a/lib/utils/ip.dart b/lib/utils/ip.dart index 8a03de5..76015c5 100644 --- a/lib/utils/ip.dart +++ b/lib/utils/ip.dart @@ -15,3 +15,7 @@ Future localIp() async { String ip = await NetworkInterface.list().then((interfaces) => interfaces.first.addresses.first.address); return ip; } + +Future networkName() { + return NetworkInterface.list().then((interfaces) => interfaces.first.name); +} diff --git a/pubspec.yaml b/pubspec.yaml index ba091fe..4ce45c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,6 +18,7 @@ dependencies: window_manager: ^0.3.4 path_provider: ^2.0.15 url_launcher: ^6.1.11 + dev_dependencies: flutter_test: sdk: flutter diff --git a/test/tests.dart b/test/tests.dart index d011adc..189cfff 100644 --- a/test/tests.dart +++ b/test/tests.dart @@ -1,31 +1,5 @@ import 'package:basic_utils/basic_utils.dart'; +import 'package:network_proxy/network/channel.dart'; void main() { - CryptoUtils.rsaPrivateKeyFromPemPkcs1("""-----BEGIN RSA PRIVATE KEY----- -MIIEpQIBAAKCAQEA747TPw1BeGac2V3aFJo7F02F+CXpxPmrG/H1m+5X4TESWEVh -kHcFzxac/XHrhQ/Pollr3rHaNew0uvJnhDqpGn+TwndLKPcdwsr71RGbIqG2EWnS -6BZxoHt6aXGy/rzvBymXSzOGKkgCPzYZZYVPTY2JFNO6Re9tpUy06x1+Er+GD1Rl -FnukHp23dS8qO+QieAfvYVm/pTGBrIXfJ0SUKubx6bEd7C/HKwDwlO0mspE9AKGk -BOs0d7JaKlE6wasTLwIQN9F3l0Wk3QglPeEwg8TMIactyC/MGmjG26sMPSdtfhri -k7r1KRFFhwjVFiKHM4MwvSlLt7mxWS+0Hd2wMwIDAQABAoIBAQC/7tLZxaka6jvo -/ATqVNfbR8V6mAXAR7kMXsPF9yR5zHNCPfR5ZqNE1H1bopZsucpF7Rc8CYsngOSO -lADUNJugG28rc6DIZZV2qGM0xzJggOf686qDQNgxGD1vliUva/im6G/LVLC914vV -UWbvq+tp22hlQ24O8lILCnY5mhuUsEnBjk/zyifR5w8hX3QC6+742ry3bzAfrSpV -8vzi5QevZ/GUo5L03IMlguk5fuFXhDrJd4P5rKr2QIYJMweea275p17pHraPNE9U -poOdqZ90C6boAJ+meq2l09XYY9Kr56NoRlsqkscaGSNKnfPVbxzQt7SNgWHooqOZ -A8xyt5zhAoGBAPqqq2UG+lC9XizJYtF6PMGu+HFxJo0b68BUu94L3DikdKq91wgp -r9Tb+gdej31ZKmaJqvU0TP2Kcqu9W2mUybR5HDyOMrx1Iqm41xrl8IpTWgNeiuQS -B5A1tygk5mn+yNVWzz77e8L9Smdh827bwofGxxssevsa9solPMIpWYzdAoGBAPSn -pgJU/9DznVcbMwlQaD3Vl4JfxSujXA40LB+eQVxhkgCrY1IxlabnqFBf9nG361JZ -7wf4pu3C5LQ/jgtImNYEZokuzzHK2iYjXQ39TyY/zvNWFApVyZdVsxCQdV0SdbAV -JnOsneN1HzQ/F8k92fhxpYiypMMJ/Ppz7/RIeJhPAoGBAJ3Zl/EYwczSU1m0v+vW -JCh7vKwDaxl1ObJ2KZAcu+NXfLiAr8bOKZHLrNmzLHztB4xmqSecddc7thZ8OfJ0 -5cgmdCeWDFEtGlTyt1KS9jTFZGu2v5P73pNroBlBk2+wKFxgPEYXK8qQoqgTwK5Q -6WGDRaLREejLBBor9OT4ERJpAoGBAMwvPvWjSv1w1Ffmc2nKmbVBlflRRJVu+V1B -XbHlulcHd3rnjzUB/QwXOvoZnOy7usI4HK5ib67vgUCN6laweB3l/k1vq6y6x+Yc -jYUna+9YQbgElyUpA94xwAiLB+hth5s1i6nu3rb/ANPx9EAYRtp6vtSfg7LjmybL -q/AMq9kxAoGAD0tJemtN6QgXYzJQ4AwgbacIJ42hOMJsR5SZqLPlRIohJbb51ffG -rjYP2FBEJu6js1ihMXgJVI6Fmh9iKAa/x1JaVP0J4owXC7zwk3c0kTeDevWfR/8M -e+dTRBKfvm1Pj79Nmu7T0ERIaQr3aFcGtmLliIMpwHBC21y+3c4632E= ------END RSA PRIVATE KEY-----"""); }