diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 787bcc2..ce22f01 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -174,7 +174,9 @@ "androidRoot": "System Certificate (ROOT Device)", "androidRootMagisk": "Magisk module: \nAndroid ROOT devices can be used Magisk ProxyPinCA System Certificate Module, After installing and restarting the phone Check the system certificate to see if there is a ProxyPinCA certificate. If there is, it indicates that the certificate has been successfully installed。", "androidRootRename": "If the module does not take effect, you can install the system root certificate according to the online tutorial, and name the root certificate 243f0bfb.0", - "androidUserCA": "User certificate (many apps will not trust user certificates)", + "androidRootCADownload": "Download System Certificate", + "androidUserCA": "User Certificate", + "androidUserCATips": "Tips: Android7+ many apps will not trust user certificates", "androidUserCAInstall": "Open settings -> Security -> Encryption and credentials -> Install certificate -> CA certificate", "androidUserXposed": "It is recommended to use the Xposed module for packet capture (no need for ROOT), click to view wiki", "configWifiProxy": "Configure mobile Wi-Fi proxy", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 13bfd2b..4ccc04b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -175,7 +175,9 @@ "androidRoot": "系统证书 (ROOT设备)", "androidRootMagisk": "Magisk模块: \n安卓ROOT设备可以使用Magisk ProxyPinCA系统证书模块, 安装完重启手机后 在系统证书查看是否有ProxyPinCA证书,如果有说明证书安装成功。", "androidRootRename": "模块不生效可以根据网上教程安装系统根证书, 根证书命名成 243f0bfb.0", - "androidUserCA": "用户证书 (很多软件不会信任用户证书)", + "androidUserCA": "用户证书", + "androidUserCATips": "提示:Android7+ 很多软件不会信任用户证书", + "androidRootCADownload": "下载系统根证书", "androidUserCAInstall": "打开设置 -> 安全 -> 加密和凭据 -> 安装证书 -> CA 证书", "androidUserXposed": "推荐使用Xposed模块抓包(无需ROOT), 点击查看wiki", "configWifiProxy": "配置手机Wi-Fi代理", diff --git a/lib/network/http/http.dart b/lib/network/http/http.dart index 1fb8215..896d28c 100644 --- a/lib/network/http/http.dart +++ b/lib/network/http/http.dart @@ -285,7 +285,7 @@ enum HttpMethod { } static List methods() { - return values.where((method) => method != HttpMethod.propfind && method != HttpMethod.report).toList(); + return values.where((method) => method != HttpMethod.propfind && method != HttpMethod.report).toList(); } } diff --git a/lib/ui/desktop/toolbar/ssl/ssl.dart b/lib/ui/desktop/toolbar/ssl/ssl.dart index 28d481e..b8d602c 100644 --- a/lib/ui/desktop/toolbar/ssl/ssl.dart +++ b/lib/ui/desktop/toolbar/ssl/ssl.dart @@ -237,6 +237,8 @@ class _SslState extends State { ]), ListView( children: [ + Text(localizations.androidUserCATips, style: const TextStyle(fontWeight: FontWeight.w500)), + const SizedBox(height: 10), SelectableText.rich(TextSpan( text: "1. ${localizations.configWifiProxy} Host:$host Port:${widget.proxyServer.port}")), diff --git a/lib/ui/mobile/setting/app_whitelist.dart b/lib/ui/mobile/setting/app_whitelist.dart index 25e5221..e04db69 100644 --- a/lib/ui/mobile/setting/app_whitelist.dart +++ b/lib/ui/mobile/setting/app_whitelist.dart @@ -83,7 +83,6 @@ class _AppWhitelistState extends State { body: FutureBuilder( future: Future.wait(appWhitelist), builder: (BuildContext context, AsyncSnapshot> snapshot) { - print(snapshot.data); if (snapshot.hasData) { if (snapshot.data!.isEmpty) { return Center( diff --git a/lib/ui/mobile/setting/ssl.dart b/lib/ui/mobile/setting/ssl.dart index 9ff74da..f7b99ee 100644 --- a/lib/ui/mobile/setting/ssl.dart +++ b/lib/ui/mobile/setting/ssl.dart @@ -1,9 +1,13 @@ import 'dart:io'; +import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:flutter_toastr/flutter_toastr.dart'; import 'package:network_proxy/network/bin/server.dart'; import 'package:network_proxy/network/util/crts.dart'; +import 'package:network_proxy/network/util/file_read.dart'; +import 'package:network_proxy/utils/lang.dart'; import 'package:url_launcher/url_launcher.dart'; class MobileSslWidget extends StatefulWidget { @@ -36,7 +40,7 @@ class _MobileSslState extends State { title: Text(localizations.httpsProxy, style: const TextStyle(fontSize: 16)), centerTitle: true, ), - body: ListView(children: [ + body: Column(children: [ SwitchListTile( hoverColor: Colors.transparent, title: Text(localizations.enabledHttps), @@ -48,13 +52,13 @@ class _MobileSslState extends State { CertificateManager.cleanCache(); setState(() {}); }), - Platform.isIOS ? ios() : Padding(padding: const EdgeInsets.only(left: 15), child: android()), - const SizedBox(height: 20) + Expanded(child: Platform.isIOS ? ios() : const AndroidCaInstall()), + // const SizedBox(height: 20) ])); } Widget ios() { - return Column(children: [ + return ListView(children: [ // if (localizations.localeName != 'zh') // ExpansionTile( // title: Text(localizations.useGuide), @@ -89,64 +93,127 @@ class _MobileSslState extends State { ]); } - Widget android() { - bool isCN = localizations.localeName == 'zh'; - - return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(localizations.installRootCa), - ExpansionTile( - title: Text(localizations.androidRoot, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), - tilePadding: const EdgeInsets.only(left: 0), - expandedAlignment: Alignment.topLeft, - initiallyExpanded: true, - shape: const Border(), - children: [ - Text(localizations.androidRootMagisk), - TextButton( - child: Text("https://${isCN ? 'gitee' : 'github'}.com/wanghongenpin/Magisk-ProxyPinCA/releases"), - onPressed: () { - launchUrl( - Uri.parse("https://${isCN ? 'gitee' : 'github'}.com/wanghongenpin/Magisk-ProxyPinCA/releases")); - }), - SelectableText(localizations.androidRootRename), - ]), - const SizedBox(height: 20), - ExpansionTile( - title: Text(localizations.androidUserCA, style: const TextStyle(fontWeight: FontWeight.w500, fontSize: 14)), - tilePadding: const EdgeInsets.only(left: 0), - expandedAlignment: Alignment.topLeft, - expandedCrossAxisAlignment: CrossAxisAlignment.start, - initiallyExpanded: true, - shape: const Border(), - children: [ - TextButton( - onPressed: () => _downloadCert(), - child: Text.rich(TextSpan(children: [ - TextSpan(text: "1. ${localizations.downloadRootCa} "), - WidgetSpan(child: SelectableText("http://127.0.0.1:${widget.proxyServer.port}/ssl")) - ]))), - TextButton(onPressed: () {}, child: Text("2. ${localizations.androidUserCAInstall}")), - TextButton( - onPressed: () { - launchUrl(Uri.parse(isCN - ? "https://gitee.com/wanghongenpin/network-proxy-flutter/wikis/%E5%AE%89%E5%8D%93%E6%97%A0ROOT%E4%BD%BF%E7%94%A8Xposed%E6%A8%A1%E5%9D%97%E6%8A%93%E5%8C%85" - : "https://github.com/wanghongenpin/network_proxy_flutter/wiki/Android-without-ROOT-uses-Xposed-module-to-capture-packets")); - }, - child: Text(localizations.androidUserXposed)), - ClipRRect( - child: Align( - alignment: Alignment.topCenter, - heightFactor: .7, - child: Image.network( - "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", - height: 680, - ))) - ]) - ]); - } - void _downloadCert() async { CertificateManager.cleanCache(); launchUrl(Uri.parse("http://127.0.0.1:${widget.proxyServer.port}/ssl"), mode: LaunchMode.externalApplication); } } + +class AndroidCaInstall extends StatefulWidget { + const AndroidCaInstall({super.key}); + + @override + State createState() => _AndroidCaInstallState(); +} + +class _AndroidCaInstallState extends State with SingleTickerProviderStateMixin { + late TabController _tabController; + + AppLocalizations get localizations => AppLocalizations.of(context)!; + + @override + void initState() { + super.initState(); + _tabController = TabController(length: 2, vsync: this); + } + + @override + void dispose() { + _tabController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + endDrawerEnableOpenDragGesture: false, + appBar: AppBar( + automaticallyImplyLeading: false, + title: Text(localizations.installRootCa, style: const TextStyle(fontSize: 16)), + bottom: TabBar( + controller: _tabController, + labelPadding: const EdgeInsets.symmetric(horizontal: 5), + tabs: [ + Tab(text: localizations.androidRoot), + Tab(text: localizations.androidUserCA), + ])), + body: TabBarView(controller: _tabController, children: [rootCA(), userCA()])); + } + + rootCA() { + bool isCN = localizations.localeName == 'zh'; + return ListView(padding: const EdgeInsets.all(10), children: [ + Text(localizations.androidRootMagisk), + TextButton( + child: Text("https://${isCN ? 'gitee' : 'github'}.com/wanghongenpin/Magisk-ProxyPinCA/releases"), + onPressed: () { + launchUrl(Uri.parse("https://${isCN ? 'gitee' : 'github'}.com/wanghongenpin/Magisk-ProxyPinCA/releases")); + }), + const SizedBox(height: 15), + SelectableText(localizations.androidRootRename, style: const TextStyle(fontWeight: FontWeight.w500)), + const SizedBox(height: 10), + FilledButton(onPressed: () => _downloadCert('243f0bfb.0'), child: Text(localizations.androidRootCADownload)), + const SizedBox(height: 10), + Text( + "Android 13: ${isCN ? "将证书挂载到" : "Mount the certificate to"} '/system/etc/security/cacerts' ${isCN ? "目录" : "Directory"}" + .fixAutoLines()), + const SizedBox(height: 5), + Text( + "Android 14: ${isCN ? "将证书挂载到" : "Mount the certificate to"} '/apex/com.android.conscrypt/cacerts' ${isCN ? "目录" : "Directory"}" + .fixAutoLines()), + const SizedBox(height: 5), + ClipRRect( + child: Align( + alignment: Alignment.topCenter, + child: Image.network( + scale: 0.5, + "https://foruda.gitee.com/images/1710181660282752846/cb520c0b_1073801.png", + height: 460, + ))) + ]); + } + + userCA() { + bool isCN = localizations.localeName == 'zh'; + + return ListView(padding: const EdgeInsets.all(10), children: [ + Text(localizations.androidUserCATips, style: const TextStyle(fontWeight: FontWeight.w500)), + const SizedBox(height: 5), + TextButton( + style: const ButtonStyle(alignment: Alignment.centerLeft), + onPressed: () {}, + child: Text("1. ${localizations.downloadRootCa} ", textAlign: TextAlign.left), + ), + FilledButton(onPressed: () => _downloadCert('243f0bfb.0'), child: Text(localizations.downloadRootCa)), + const SizedBox(height: 5), + TextButton(onPressed: () {}, child: Text("2. ${localizations.androidUserCAInstall}")), + TextButton( + onPressed: () { + launchUrl(Uri.parse(isCN + ? "https://gitee.com/wanghongenpin/network-proxy-flutter/wikis/%E5%AE%89%E5%8D%93%E6%97%A0ROOT%E4%BD%BF%E7%94%A8Xposed%E6%A8%A1%E5%9D%97%E6%8A%93%E5%8C%85" + : "https://github.com/wanghongenpin/network_proxy_flutter/wiki/Android-without-ROOT-uses-Xposed-module-to-capture-packets")); + }, + child: Text(localizations.androidUserXposed)), + ClipRRect( + child: Align( + alignment: Alignment.topCenter, + heightFactor: .7, + child: Image.network( + "https://foruda.gitee.com/images/1689352695624941051/74e3bed6_1073801.png", + height: 680, + ))) + ]); + } + + void _downloadCert(String name) async { + String? outputFile = await FilePicker.platform.saveFile( + dialogTitle: 'Please select the path to save:', + fileName: name, + bytes: await FileRead.read('assets/certs/ca.crt')); + + if (outputFile != null && mounted) { + AppLocalizations localizations = AppLocalizations.of(context)!; + FlutterToastr.show(localizations.success, context); + } + } +}