diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 9742efa..70dc21e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,7 +1,7 @@ --- name: Bug report about: Create reports to help us improve -title: "[系统]xxx" +title: "[Windows、Mac、Android]xxx" labels: bug assignees: '' @@ -10,6 +10,6 @@ assignees: '' **描述错误** **To Reproduce** -重现行为的步骤: +重现行为的步骤: 如具体应用抓包失败,请说明软件名称以及具体操作页面 **屏幕截图* diff --git a/lib/network/bin/configuration.dart b/lib/network/bin/configuration.dart index 632eb67..8f72a90 100644 --- a/lib/network/bin/configuration.dart +++ b/lib/network/bin/configuration.dart @@ -21,10 +21,12 @@ import 'package:network_proxy/network/host_port.dart'; import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/network/util/logger.dart'; import 'package:network_proxy/network/util/request_rewrite.dart'; +import 'package:network_proxy/network/util/system_proxy.dart'; import 'package:network_proxy/utils/platform.dart'; import 'package:path_provider/path_provider.dart'; class Configuration { + ///代理相关配置 int port = 9099; //是否启用https抓包 @@ -33,6 +35,9 @@ class Configuration { //是否设置系统代理 bool enableSystemProxy = true; + //代理忽略域名 + String proxyPassDomains = SystemProxy.proxyPassDomains; + //是否显示更新内容公告 bool upgradeNoticeV3 = true; @@ -69,6 +74,7 @@ class Configuration { } String? userHome; + Future homeDir() async { if (userHome != null) { return File("${userHome!}${Platform.pathSeparator}.proxypin"); @@ -117,6 +123,7 @@ class Configuration { port = config['port'] ?? port; enableSsl = config['enableSsl'] == true; enableSystemProxy = config['enableSystemProxy'] ?? (config['enableDesktop'] ?? true); + proxyPassDomains = config['proxyPassDomains'] ?? SystemProxy.proxyPassDomains; upgradeNoticeV3 = config['upgradeNoticeV3'] ?? true; if (config['externalProxy'] != null) { externalProxy = ProxyInfo.fromJson(config['externalProxy']); @@ -162,6 +169,7 @@ class Configuration { 'port': port, 'enableSsl': enableSsl, 'enableSystemProxy': enableSystemProxy, + 'proxyPassDomains': proxyPassDomains, 'externalProxy': externalProxy?.toJson(), 'appWhitelist': appWhitelist, 'whitelist': HostFilter.whitelist.toJson(), diff --git a/lib/network/bin/server.dart b/lib/network/bin/server.dart index c0ce0d4..b4fef89 100644 --- a/lib/network/bin/server.dart +++ b/lib/network/bin/server.dart @@ -15,7 +15,6 @@ */ import 'dart:async'; -import 'dart:io'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/channel.dart'; @@ -59,9 +58,7 @@ class ProxyServer { return; } - if (Platform.isMacOS) { - SystemProxy.setSslProxyEnableMacOS(enableSsl, port); - } + SystemProxy.setSslProxyEnable(enableSsl, port); } /// 启动代理服务 @@ -100,11 +97,11 @@ class ProxyServer { setSystemProxyEnable(bool enable) async { //关闭系统代理 恢复成外部代理地址 if (!enable && configuration.externalProxy?.enabled == true) { - await SystemProxy.setSystemProxy(configuration.externalProxy!.port!, enableSsl); + await SystemProxy.setSystemProxy(configuration.externalProxy!.port!, enableSsl, configuration.proxyPassDomains); return; } - await SystemProxy.setSystemProxyEnable(port, enable, enableSsl); + await SystemProxy.setSystemProxyEnable(port, enable, enableSsl, passDomains: configuration.proxyPassDomains); } /// 重启代理服务 diff --git a/lib/network/util/system_proxy.dart b/lib/network/util/system_proxy.dart index e56820a..0bfa8d9 100644 --- a/lib/network/util/system_proxy.dart +++ b/lib/network/util/system_proxy.dart @@ -1,3 +1,19 @@ +/* + * Copyright 2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + import 'dart:io'; import 'package:network_proxy/network/host_port.dart'; @@ -8,84 +24,103 @@ import 'package:proxy_manager/proxy_manager.dart'; /// @author wanghongen /// 2023/7/26 class SystemProxy { - static String? _hardwarePort; + static SystemProxy? _instance; + + ///单例 + static SystemProxy get instance { + if (_instance == null) { + if (Platform.isMacOS) { + _instance = MacSystemProxy(); + } else if (Platform.isWindows) { + _instance = WindowsSystemProxy(); + } else if (Platform.isLinux) { + _instance = LinuxSystemProxy(); + } else { + _instance = SystemProxy(); + } + } + return _instance!; + } + + ///获取代理忽略地址 + static String get proxyPassDomains { + if (Platform.isMacOS) { + return '192.168.0.0/16;10.0.0.0/8;172.16.0.0/12;127.0.0.1;localhost;*.local;timestamp.apple.com'; + } + if (Platform.isWindows) { + return '192.168.0.*;10.0.0.*;172.16.0.*;127.0.0.1;localhost;*.local;'; + } + return ''; + } ///获取系统代理 static Future getSystemProxy(ProxyTypes types) async { - if (Platform.isWindows) { - return await _getSystemProxyWindows(); - } else if (Platform.isMacOS) { - return await _getSystemProxyMacOS(types); - } else if (Platform.isLinux) { - return await _getLinuxProxyServer(types); - } else { - return null; - } + return instance._getSystemProxy(types); } - /// 设置系统代理 - static Future setSystemProxy(int port, bool sslSetting) async { - if (Platform.isMacOS) { - await _setProxyServerMacOS(port, sslSetting); - } else if (Platform.isWindows) { - await _setProxyServerWindows(port); - } else { - ProxyManager manager = ProxyManager(); - manager.setAsSystemProxy(ProxyTypes.http, "127.0.0.1", port); - if (sslSetting) { - await manager.setAsSystemProxy(ProxyTypes.https, "127.0.0.1", port); - } - } + ///设置系统代理 + static Future setSystemProxy(int port, bool sslSetting, String proxyPassDomains) async { + instance._setSystemProxy(port, sslSetting, proxyPassDomains); + } + + ///设置Https代理启用状态 + static void setSslProxyEnable(bool proxyEnable, port) { + instance._setSslProxyEnable(proxyEnable, port); } /// 设置系统代理 /// @param sslSetting 是否设置https代理只在mac中有效 - static Future setSystemProxyEnable(int port, bool enable, bool sslSetting) async { + static Future setSystemProxyEnable(int port, bool enable, bool sslSetting, + {required String passDomains}) async { //启用系统代理 if (enable) { - await setSystemProxy(port, sslSetting); + await setSystemProxy(port, sslSetting, passDomains); return; } - if (Platform.isMacOS) { - await setProxyEnableMacOS(enable, sslSetting); - } else if (Platform.isWindows) { - await setProxyEnableWindows(enable); - } else { - ProxyManager manager = ProxyManager(); - await manager.cleanSystemProxy(); - } + instance._setProxyEnable(enable, sslSetting); } - static Future _setProxyServerMacOS(int port, bool sslSetting) async { - _hardwarePort = await hardwarePort(); - var results = await Process.run('bash', [ - '-c', - _concatCommands([ - 'networksetup -setwebproxy $_hardwarePort 127.0.0.1 $port', - sslSetting == true ? 'networksetup -setsecurewebproxy $_hardwarePort 127.0.0.1 $port' : '', - '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', - ]) - ]); - print('set proxyServer, name: $_hardwarePort, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); - return results.exitCode == 0; + ///设置代理忽略地址 + static Future setProxyPassDomains(String proxyPassDomains) async { + instance._setProxyPassDomains(proxyPassDomains); } - static Future getProxyEnable() async { - _hardwarePort ??= await hardwarePort(); - try { - var results = await Process.run('bash', ['-c', 'networksetup -getwebproxy $_hardwarePort']); - var proxyEnableLine = - (results.stdout as String).split('\n').where((item) => item.contains('Enabled')).first.trim(); - return proxyEnableLine.endsWith('Yes'); - } catch (e) { - print(e); - return false; - } - } + //子类抽象方法 ///获取系统代理 - static Future _getSystemProxyMacOS(ProxyTypes proxyTypes) async { + Future _getSystemProxy(ProxyTypes types) async { + return null; + } + + ///设置系统代理 + Future _setSystemProxy(int port, bool sslSetting, String proxyPassDomains) async { + ProxyManager manager = ProxyManager(); + await manager.setAsSystemProxy(ProxyTypes.https, "127.0.0.1", port); + setProxyPassDomains(proxyPassDomains); + } + + ///设置代理是否启用 + Future _setProxyEnable(bool proxyEnable, bool sslSetting) async { + ProxyManager manager = ProxyManager(); + await manager.cleanSystemProxy(); + } + + ///设置Https代理启用状态 + Future _setSslProxyEnable(bool proxyEnable, int port) async { + return false; + } + + ///设置代理忽略地址 + Future _setProxyPassDomains(String proxyPassDomains) async {} +} + +class MacSystemProxy implements SystemProxy { + static String? _hardwarePort; + + ///获取系统代理 + @override + Future _getSystemProxy(ProxyTypes proxyTypes) async { _hardwarePort = await hardwarePort(); var result = await Process.run('bash', [ '-c', @@ -105,22 +140,25 @@ class SystemProxy { return null; } - static Future setProxyEnableMacOS(bool proxyEnable, bool sslSetting) async { - var proxyMode = proxyEnable ? 'on' : 'off'; - _hardwarePort ??= await hardwarePort(); - print('set proxyEnable: $proxyEnable, name: $_hardwarePort'); - + ///mac设置代理地址 + @override + Future _setSystemProxy(int port, bool sslSetting, String proxyPassDomains) async { + _hardwarePort = await hardwarePort(); var results = await Process.run('bash', [ '-c', _concatCommands([ - 'networksetup -setwebproxystate $_hardwarePort $proxyMode', - sslSetting ? 'networksetup -setsecurewebproxystate $_hardwarePort $proxyMode' : '' + 'networksetup -setwebproxy $_hardwarePort 127.0.0.1 $port', + sslSetting == true ? 'networksetup -setsecurewebproxy $_hardwarePort 127.0.0.1 $port' : '', + 'networksetup -setproxybypassdomains $_hardwarePort ${proxyPassDomains.replaceAll(";", " ")}', ]) ]); + print('set proxyServer, name: $_hardwarePort, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); return results.exitCode == 0; } - static Future setSslProxyEnableMacOS(bool proxyEnable, port) async { + ///设置Https代理 + @override + Future _setSslProxyEnable(bool proxyEnable, port) async { var name = await hardwarePort(); var results = await Process.run('bash', [ @@ -132,6 +170,7 @@ class SystemProxy { return results.exitCode == 0; } + ///mac获取当前网络名称 static Future hardwarePort() async { var name = await networkName(); var results = await Process.run('bash', [ @@ -141,27 +180,46 @@ class SystemProxy { return results.stdout.toString().split(", ")[0]; } - static Future _setProxyServerWindows(int proxyPort) async { - ProxyManager manager = ProxyManager(); - await manager.setAsSystemProxy(ProxyTypes.https, "127.0.0.1", proxyPort); - var results = await _internetSettings('add', [ - 'ProxyOverride', - '/t', - 'REG_SZ', - '/d', - '192.168.0.*;10.0.0.*;172.16.0.*;127.0.0.1;localhost;*.local;', - '/f' - ]); - - print('set proxyServer $proxyPort, result: $results'); + ///设置代理忽略地址 + @override + Future _setProxyPassDomains(String proxyPassDomains) async { + _hardwarePort ??= await hardwarePort(); + var results = await Process.run( + 'bash', ['-c', 'networksetup -setproxybypassdomains $_hardwarePort ${proxyPassDomains.replaceAll(";", " ")}']); + print('set proxyPassDomains, name: $_hardwarePort, exitCode: ${results.exitCode}, stdout: ${results.stdout}'); } - static Future setProxyEnableWindows(bool proxyEnable) async { + ///mac设置代理是否启用 + @override + Future _setProxyEnable(bool proxyEnable, bool sslSetting) async { + var proxyMode = proxyEnable ? 'on' : 'off'; + _hardwarePort ??= await hardwarePort(); + print('set proxyEnable: $proxyEnable, name: $_hardwarePort'); + + await Process.run('bash', [ + '-c', + _concatCommands([ + 'networksetup -setwebproxystate $_hardwarePort $proxyMode', + sslSetting ? 'networksetup -setsecurewebproxystate $_hardwarePort $proxyMode' : '' + ]) + ]); + } + + static _concatCommands(List commands) { + return commands.where((element) => element.isNotEmpty).join(' && '); + } +} + +class WindowsSystemProxy extends SystemProxy { + ///设置windows代理是否启用 + @override + Future _setProxyEnable(bool proxyEnable, bool sslSetting) async { await _internetSettings('add', ['ProxyEnable', '/t', 'REG_DWORD', '/f', '/d', proxyEnable ? '1' : '0']); } - /// 获取系统代理 - static Future _getSystemProxyWindows() async { + ///获取系统代理 + @override + Future _getSystemProxy(ProxyTypes types) async { var results = await _internetSettings('query', ['ProxyEnable']); var proxyEnableLine = results.split('\r\n').where((item) => item.contains('ProxyEnable')).first.trim(); @@ -192,6 +250,13 @@ class SystemProxy { }); } + ///设置代理忽略地址 + @override + Future _setProxyPassDomains(String proxyPassDomains) async { + var results = await _internetSettings('add', ['ProxyOverride', '/t', 'REG_SZ', '/d', proxyPassDomains, '/f']); + print('set proxyPassDomains, stdout: $results'); + } + static Future _internetSettings(String cmd, List args) async { return Process.run('reg', [ cmd, @@ -200,9 +265,12 @@ class SystemProxy { ...args, ]).then((results) => results.stdout.toString()); } +} - static Future _getLinuxProxyServer(ProxyTypes types) async { - ///linux 获取代理 +class LinuxSystemProxy extends SystemProxy { + ///linux 获取代理 + @override + Future _getSystemProxy(ProxyTypes types) async { var mode = await Process.run("gsettings", ["get", "org.gnome.system.proxy", "mode"]) .then((value) => value.stdout.toString().trim()); if (mode.contains("manual")) { @@ -223,10 +291,6 @@ class SystemProxy { } return null; } - - static _concatCommands(List commands) { - return commands.where((element) => element.isNotEmpty).join(' && '); - } } void main() async { diff --git a/lib/ui/desktop/toolbar/setting/filter.dart b/lib/ui/desktop/toolbar/setting/filter.dart index 4d415d0..6fc4af5 100644 --- a/lib/ui/desktop/toolbar/setting/filter.dart +++ b/lib/ui/desktop/toolbar/setting/filter.dart @@ -23,13 +23,12 @@ class _FilterDialogState extends State { @override Widget build(BuildContext context) { return AlertDialog( + titlePadding: const EdgeInsets.only(left: 20, top: 10, right: 15), + contentPadding: const EdgeInsets.only(left: 20, right: 20), scrollable: true, title: const Row(children: [ Text("域名过滤", style: TextStyle(fontSize: 18)), - Expanded( - child: Align( - alignment: Alignment.topRight, - child: CloseButton())) + Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton())) ]), content: SizedBox( width: 680, diff --git a/lib/ui/desktop/toolbar/setting/setting.dart b/lib/ui/desktop/toolbar/setting/setting.dart index 577acec..2e095f7 100644 --- a/lib/ui/desktop/toolbar/setting/setting.dart +++ b/lib/ui/desktop/toolbar/setting/setting.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:network_proxy/network/bin/configuration.dart'; import 'package:network_proxy/network/bin/server.dart'; +import 'package:network_proxy/network/util/system_proxy.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/external_proxy.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/request_rewrite.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/theme.dart'; @@ -20,63 +21,62 @@ class Setting extends StatefulWidget { } class _SettingState extends State { - late ValueNotifier enableDesktopListenable; late Configuration configuration; @override void initState() { configuration = widget.proxyServer.configuration; - enableDesktopListenable = ValueNotifier(configuration.enableSystemProxy); super.initState(); } - @override - void dispose() { - enableDesktopListenable.dispose(); - super.dispose(); + Widget item(String text, {VoidCallback? onPressed}) { + return MenuItemButton( + trailingIcon: const Icon(Icons.arrow_right), + onPressed: onPressed, + child: Padding( + padding: const EdgeInsets.only(left: 10, right: 5), + child: Text(text, style: const TextStyle(fontSize: 14)))); } @override Widget build(BuildContext context) { - return PopupMenuButton( - tooltip: "设置", - icon: const Icon(Icons.settings), - surfaceTintColor: Colors.white70, - offset: const Offset(10, 30), - itemBuilder: (context) { - return [ - PopupMenuItem( - child: PortWidget(proxyServer: widget.proxyServer, textStyle: const TextStyle(fontSize: 13))), - PopupMenuItem( - child: ValueListenableBuilder( - valueListenable: enableDesktopListenable, - builder: (_, val, __) => setSystemProxy(), - )), - const PopupMenuItem(child: ThemeSetting(dense: true)), - menuItem("域名过滤", onTap: hostFilter), - menuItem("请求重写", onTap: requestRewrite), - menuItem("外部代理设置", onTap: setExternalProxy), - menuItem( - "Github", - onTap: () { - launchUrl(Uri.parse("https://github.com/wanghongenpin/network_proxy_flutter")); - }, - ) - ]; + var surfaceTintColor = + Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; + return MenuAnchor( + style: MenuStyle(surfaceTintColor: MaterialStatePropertyAll(surfaceTintColor)), + builder: (context, controller, child) { + return IconButton( + icon: const Icon(Icons.settings), + tooltip: "设置", + onPressed: () { + if (controller.isOpen) { + controller.close(); + } else { + controller.open(); + } + }); }, + menuChildren: [ + _ProxyMenu(proxyServer: widget.proxyServer), + item("域名过滤", onPressed: hostFilter), + item("请求重写", onPressed: requestRewrite), + const ThemeSetting(), + item("外部代理设置", onPressed: setExternalProxy), + item("Github", onPressed: () => launchUrl(Uri.parse("https://github.com/wanghongenpin/network_proxy_flutter"))), + ], ); } PopupMenuItem menuItem(String title, {GestureTapCallback? onTap}) { return PopupMenuItem( child: ListTile( - title: Text(title), - dense: true, - hoverColor: Colors.transparent, - focusColor: Colors.transparent, - trailing: const Icon(Icons.arrow_right), - onTap: onTap, - )); + title: Text(title), + dense: true, + hoverColor: Colors.transparent, + focusColor: Colors.transparent, + trailing: const Icon(Icons.arrow_right), + onTap: onTap, + )); } ///设置外部代理地址 @@ -89,22 +89,6 @@ class _SettingState extends State { }); } - ///设置系统代理 - Widget setSystemProxy() { - return SwitchListTile( - hoverColor: Colors.transparent, - title: const Text("设置为系统代理"), - visualDensity: const VisualDensity(horizontal: -4), - dense: true, - value: configuration.enableSystemProxy, - onChanged: (val) { - widget.proxyServer.setSystemProxyEnable(val); - configuration.enableSystemProxy = val; - enableDesktopListenable.value = !enableDesktopListenable.value; - configuration.flushConfig(); - }); - } - ///请求重写Dialog void requestRewrite() { showDialog( @@ -112,13 +96,12 @@ class _SettingState extends State { context: context, builder: (context) { return AlertDialog( + titlePadding: const EdgeInsets.only(left: 24, top: 10, right: 15), + contentPadding: const EdgeInsets.only(left: 24, right: 20), scrollable: true, title: const Row(children: [ Text("请求重写", style: TextStyle(fontSize: 14, fontWeight: FontWeight.w500)), - Expanded( - child: Align( - alignment: Alignment.topRight, - child: CloseButton())) + Expanded(child: Align(alignment: Alignment.topRight, child: CloseButton())) ]), content: RequestRewrite(configuration: configuration), ); @@ -137,6 +120,116 @@ class _SettingState extends State { } } +///代理菜单 +class _ProxyMenu extends StatefulWidget { + final ProxyServer proxyServer; + + const _ProxyMenu({required this.proxyServer}); + + @override + State createState() => _ProxyMenuState(); +} + +class _ProxyMenuState extends State<_ProxyMenu> { + var textEditingController = TextEditingController(); + + late Configuration configuration; + bool changed = false; + + @override + void initState() { + configuration = widget.proxyServer.configuration; + textEditingController.text = configuration.proxyPassDomains; + super.initState(); + } + + @override + void dispose() { + if (configuration.proxyPassDomains != textEditingController.text) { + changed = true; + configuration.proxyPassDomains = textEditingController.text; + SystemProxy.setProxyPassDomains(configuration.proxyPassDomains); + } + + if (changed) { + configuration.flushConfig(); + } + textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + var surfaceTintColor = + Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; + + return SubmenuButton( + menuStyle: MenuStyle( + surfaceTintColor: MaterialStatePropertyAll(surfaceTintColor), + padding: const MaterialStatePropertyAll(EdgeInsets.only(top: 10, bottom: 10)), + ), + menuChildren: [ + PortWidget(proxyServer: widget.proxyServer, textStyle: const TextStyle(fontSize: 13)), + const Divider(thickness: 0.3, height: 8), + setSystemProxy(), + const Divider(thickness: 0.3, height: 8), + const SizedBox(height: 3), + Padding( + padding: const EdgeInsets.only(left: 15), + child: Row(children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text("代理忽略域名"), + const SizedBox(height: 3), + Text("多个使用;分割", style: TextStyle(fontSize: 11, color: Colors.grey.shade600)), + ], + ), + Padding( + padding: const EdgeInsets.only(left: 35), + child: TextButton( + child: const Text("重置"), + onPressed: () { + textEditingController.text = SystemProxy.proxyPassDomains; + }, + )) + ])), + const SizedBox(height: 5), + Padding( + padding: const EdgeInsets.only(left: 15, right: 5), + child: TextField( + textInputAction: TextInputAction.done, + style: const TextStyle(fontSize: 13), + controller: textEditingController, + decoration: const InputDecoration( + contentPadding: EdgeInsets.all(10), + border: OutlineInputBorder(), + constraints: BoxConstraints(minWidth: 190, maxWidth: 190)), + maxLines: 5, + minLines: 1)), + const SizedBox(height: 10), + ], + child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("代理")), + ); + } + + ///设置系统代理 + Widget setSystemProxy() { + return SwitchListTile( + hoverColor: Colors.transparent, + title: const Text("设置为系统代理", maxLines: 1), + dense: true, + value: configuration.enableSystemProxy, + onChanged: (val) { + widget.proxyServer.setSystemProxyEnable(val); + configuration.enableSystemProxy = val; + setState(() { + changed = true; + }); + }); + } +} + class PortWidget extends StatefulWidget { final ProxyServer proxyServer; final TextStyle? textStyle; @@ -179,6 +272,7 @@ class _PortState extends State { @override Widget build(BuildContext context) { return Row(children: [ + const Padding(padding: EdgeInsets.only(left: 15)), Text("端口号:", style: widget.textStyle), SizedBox( width: 80, diff --git a/lib/ui/desktop/toolbar/setting/theme.dart b/lib/ui/desktop/toolbar/setting/theme.dart index 4b94c3a..a41af28 100644 --- a/lib/ui/desktop/toolbar/setting/theme.dart +++ b/lib/ui/desktop/toolbar/setting/theme.dart @@ -2,52 +2,63 @@ import 'package:flutter/material.dart'; import 'package:network_proxy/main.dart'; class ThemeSetting extends StatelessWidget { - final bool dense; - - const ThemeSetting({Key? key, this.dense = false}) : super(key: key); + const ThemeSetting({Key? key}) : super(key: key); @override Widget build(BuildContext context) { - return PopupMenuButton( - tooltip: themeNotifier.value.mode.name, - surfaceTintColor: Theme.of(context).colorScheme.onPrimary, - offset: const Offset(150, 0), - itemBuilder: (BuildContext context) { - return [ - PopupMenuItem( - child: Tooltip( - preferBelow: false, - message: "Material 3是谷歌开源设计系统的最新版本", - child: SwitchListTile( - value: themeNotifier.value.useMaterial3, - onChanged: (bool value) { - themeNotifier.value = themeNotifier.value.copy(useMaterial3: value); - Navigator.of(context).pop(); - }, - dense: true, - title: const Text("Material3"), - ))), - PopupMenuItem( - child: const ListTile(trailing: Icon(Icons.cached), dense: true, title: Text("跟随系统")), - onTap: () { - themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.system); - }), - PopupMenuItem( - child: const ListTile(trailing: Icon(Icons.nightlight_outlined), dense: true, title: Text("深色")), - onTap: () { - themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.dark); - }), - PopupMenuItem( - child: const ListTile(trailing: Icon(Icons.sunny), dense: true, title: Text("浅色")), - onTap: () { - themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.light); - }), - ]; - }, - child: ListTile( - title: const Text("主题"), - trailing: const Icon(Icons.arrow_right), - dense: dense, - )); + var surfaceTintColor = + Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; + + return SubmenuButton( + menuStyle: MenuStyle( + surfaceTintColor: MaterialStatePropertyAll(surfaceTintColor), + padding: const MaterialStatePropertyAll(EdgeInsets.only(top: 10, bottom: 10)), + ), + menuChildren: [ + SizedBox( + width: 180, + height: 38, + child: Tooltip( + preferBelow: false, + message: "Material 3是谷歌开源设计系统的最新版本", + child: SwitchListTile( + contentPadding: const EdgeInsets.only(left: 32, right: 5), + value: themeNotifier.value.useMaterial3, + onChanged: (bool value) { + themeNotifier.value = themeNotifier.value.copy(useMaterial3: value); + }, + dense: true, + title: const Text("Material3"), + ))), + MenuItemButton( + leadingIcon: themeNotifier.value.mode == ThemeMode.system + ? const Icon(Icons.check, size: 15) + : const SizedBox(width: 18), + trailingIcon: const Icon(Icons.cached), + child: const Text("跟随系统"), + onPressed: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.system); + }), + MenuItemButton( + leadingIcon: themeNotifier.value.mode == ThemeMode.dark + ? const Icon(Icons.check, size: 15) + : const SizedBox(width: 15), + trailingIcon: const Icon(Icons.nightlight_outlined), + child: const Text("深色"), + onPressed: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.dark); + }), + MenuItemButton( + leadingIcon: themeNotifier.value.mode == ThemeMode.light + ? const Icon(Icons.check, size: 15) + : const SizedBox(width: 15), + trailingIcon: const Icon(Icons.sunny), + child: const Text("浅色"), + onPressed: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.light); + }), + ], + child: const Padding(padding: EdgeInsets.only(left: 10), child: Text("主题")), + ); } } diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 5ae2575..dc3b805 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -21,9 +21,12 @@ class SslWidget extends StatefulWidget { class _SslState extends State { @override Widget build(BuildContext context) { + var surfaceTintColor = + Brightness.dark == Theme.of(context).brightness ? null : Theme.of(context).colorScheme.background; + return PopupMenuButton( icon: Icon(Icons.https, color: widget.proxyServer.enableSsl ? null : Colors.red), - surfaceTintColor: Colors.white70, + surfaceTintColor: surfaceTintColor, tooltip: "HTTPS代理", offset: const Offset(10, 30), itemBuilder: (context) { diff --git a/lib/ui/desktop/toolbar/toolbar.dart b/lib/ui/desktop/toolbar/toolbar.dart index c12a197..b30677b 100644 --- a/lib/ui/desktop/toolbar/toolbar.dart +++ b/lib/ui/desktop/toolbar/toolbar.dart @@ -60,12 +60,7 @@ class _ToolbarState extends State { @override Widget build(BuildContext context) { - return Container( - // decoration: BoxDecoration( - // border: Border( - // bottom: BorderSide(color: Theme.of(context).dividerColor, width: 0.1), - // )), - child: Row( + return Row( children: [ Padding(padding: EdgeInsets.only(left: Platform.isMacOS ? 80 : 30)), SocketLaunch(proxyServer: widget.proxyServer), @@ -103,7 +98,7 @@ class _ToolbarState extends State { )), //右对齐 const Padding(padding: EdgeInsets.only(left: 30)), ], - )); + ); } phoneConnect(List hosts, int port) { diff --git a/lib/ui/mobile/menu.dart b/lib/ui/mobile/menu.dart index a8e76f7..6dfee8c 100644 --- a/lib/ui/mobile/menu.dart +++ b/lib/ui/mobile/menu.dart @@ -9,7 +9,6 @@ import 'package:network_proxy/network/http_client.dart'; import 'package:network_proxy/network/util/host_filter.dart'; import 'package:network_proxy/ui/component/toolbox.dart'; import 'package:network_proxy/ui/desktop/toolbar/setting/setting.dart'; -import 'package:network_proxy/ui/desktop/toolbar/setting/theme.dart'; import 'package:network_proxy/ui/mobile/connect_remote.dart'; import 'package:network_proxy/ui/mobile/request/favorite.dart'; import 'package:network_proxy/ui/mobile/request/history.dart'; @@ -18,6 +17,7 @@ import 'package:network_proxy/ui/mobile/setting/app_whitelist.dart'; import 'package:network_proxy/ui/mobile/setting/filter.dart'; import 'package:network_proxy/ui/mobile/setting/request_rewrite.dart'; import 'package:network_proxy/ui/mobile/setting/ssl.dart'; +import 'package:network_proxy/ui/mobile/setting/theme.dart'; import 'package:network_proxy/utils/ip.dart'; import 'package:qr_flutter/qr_flutter.dart'; import 'package:qrscan/qrscan.dart' as scanner; @@ -52,12 +52,12 @@ class DrawerWidget extends StatelessWidget { onTap: () => navigator(context, MobileHistory(proxyServer: proxyServer, requestStateKey: requestStateKey)), ), const Divider(thickness: 0.3), - Padding(padding: const EdgeInsets.only(left: 15), child: PortWidget(proxyServer: proxyServer)), + PortWidget(proxyServer: proxyServer), ListTile( title: const Text("HTTPS抓包"), trailing: const Icon(Icons.arrow_right), onTap: () => navigator(context, MobileSslWidget(proxyServer: proxyServer))), - const ThemeSetting(), + const MobileThemeSetting(), Platform.isIOS ? const SizedBox() : ListTile( diff --git a/lib/ui/mobile/setting/theme.dart b/lib/ui/mobile/setting/theme.dart new file mode 100644 index 0000000..9d6eb25 --- /dev/null +++ b/lib/ui/mobile/setting/theme.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:network_proxy/main.dart'; + +class MobileThemeSetting extends StatelessWidget { + const MobileThemeSetting({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return PopupMenuButton( + tooltip: themeNotifier.value.mode.name, + surfaceTintColor: Theme.of(context).colorScheme.onPrimary, + offset: const Offset(150, 0), + itemBuilder: (BuildContext context) { + return [ + PopupMenuItem( + child: Tooltip( + preferBelow: false, + message: "Material 3是谷歌开源设计系统的最新版本", + child: SwitchListTile( + value: themeNotifier.value.useMaterial3, + onChanged: (bool value) { + themeNotifier.value = themeNotifier.value.copy(useMaterial3: value); + Navigator.of(context).pop(); + }, + dense: true, + title: const Text("Material3"), + ))), + PopupMenuItem( + child: const ListTile(trailing: Icon(Icons.cached), dense: true, title: Text("跟随系统")), + onTap: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.system); + }), + PopupMenuItem( + child: const ListTile(trailing: Icon(Icons.nightlight_outlined), dense: true, title: Text("深色")), + onTap: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.dark); + }), + PopupMenuItem( + child: const ListTile(trailing: Icon(Icons.sunny), dense: true, title: Text("浅色")), + onTap: () { + themeNotifier.value = themeNotifier.value.copy(mode: ThemeMode.light); + }), + ]; + }, + child: const ListTile( + title: Text("主题"), + trailing: Icon(Icons.arrow_right), + )); + } +}