mirror of
https://github.com/wanghongenpin/proxypin.git
synced 2026-03-15 04:23:17 +08:00
401 lines
19 KiB
Dart
401 lines
19 KiB
Dart
/*
|
|
* Copyright 2023 Hongen Wang All rights reserved.
|
|
*
|
|
* 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:flutter/material.dart';
|
|
import 'package:proxypin/l10n/app_localizations.dart';
|
|
import 'package:proxypin/network/bin/server.dart';
|
|
import 'package:proxypin/network/components/host_filter.dart';
|
|
import 'package:proxypin/network/components/manager/hosts_manager.dart';
|
|
import 'package:proxypin/network/components/manager/request_block_manager.dart';
|
|
import 'package:proxypin/network/components/manager/request_breakpoint_manager.dart';
|
|
import 'package:proxypin/network/components/manager/request_rewrite_manager.dart';
|
|
import 'package:proxypin/network/http/http.dart';
|
|
import 'package:proxypin/network/util/system_proxy.dart';
|
|
import 'package:proxypin/storage/histories.dart';
|
|
import 'package:proxypin/ui/mobile/setting/hosts.dart';
|
|
import 'package:proxypin/ui/mobile/setting/request_breakpoint.dart';
|
|
import 'package:proxypin/ui/mobile/setting/request_map.dart';
|
|
import 'package:proxypin/ui/toolbox/toolbox.dart';
|
|
import 'package:proxypin/ui/component/utils.dart';
|
|
import 'package:proxypin/ui/configuration.dart';
|
|
import 'package:proxypin/ui/mobile/setting/preference.dart';
|
|
import 'package:proxypin/ui/mobile/request/favorite.dart';
|
|
import 'package:proxypin/ui/mobile/request/history.dart';
|
|
import 'package:proxypin/ui/mobile/setting/app_filter.dart';
|
|
import 'package:proxypin/ui/mobile/setting/filter.dart';
|
|
import 'package:proxypin/ui/mobile/setting/request_block.dart';
|
|
import 'package:proxypin/ui/mobile/setting/request_rewrite.dart';
|
|
import 'package:proxypin/ui/mobile/setting/request_crypto.dart';
|
|
import 'package:proxypin/ui/mobile/setting/script.dart';
|
|
import 'package:proxypin/ui/mobile/setting/ssl.dart';
|
|
import 'package:proxypin/ui/mobile/widgets/about.dart';
|
|
import 'package:proxypin/utils/listenable_list.dart';
|
|
|
|
import '../../component/proxy_port_setting.dart';
|
|
import '../../component/widgets.dart';
|
|
import '../../desktop/setting/external_proxy.dart';
|
|
|
|
///左侧抽屉
|
|
class DrawerWidget extends StatelessWidget {
|
|
final ProxyServer proxyServer;
|
|
final ListenableList<HttpRequest> container;
|
|
final HistoryTask historyTask;
|
|
|
|
DrawerWidget({super.key, required this.proxyServer, required this.container})
|
|
: historyTask = HistoryTask.ensureInstance(proxyServer.configuration, container);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
AppLocalizations localizations = AppLocalizations.of(context)!;
|
|
bool isCN = Localizations.localeOf(context) == const Locale.fromSubtags(languageCode: 'zh');
|
|
|
|
return Drawer(
|
|
backgroundColor: Theme.of(context).cardColor,
|
|
child: ListView(
|
|
padding: EdgeInsets.zero,
|
|
children: [
|
|
DrawerHeader(
|
|
decoration: BoxDecoration(color: Theme.of(context).colorScheme.primaryContainer),
|
|
child: Row(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
|
CircleAvatar(
|
|
radius: 24,
|
|
backgroundColor: Colors.white,
|
|
child: Center(
|
|
child: Image.asset(
|
|
'assets/icon_foreground.png',
|
|
width: 52,
|
|
))),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Text('ProxyPin', style: Theme.of(context).textTheme.titleLarge),
|
|
const SizedBox(height: 4),
|
|
Text(isCN ? "全平台开源免费抓包软件" : "Full platform open source free capture HTTP(S) traffic software",
|
|
maxLines: 2,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: Theme.of(context).textTheme.bodySmall)
|
|
]),
|
|
)
|
|
])),
|
|
// Favorites & History
|
|
ListTile(
|
|
leading: const Icon(Icons.favorite),
|
|
title: Text(localizations.favorites),
|
|
onTap: () => navigator(context, MobileFavorites(proxyServer: proxyServer))),
|
|
ListTile(
|
|
leading: const Icon(Icons.history),
|
|
title: Text(localizations.history),
|
|
onTap: () => navigator(
|
|
context, MobileHistory(proxyServer: proxyServer, container: container, historyTask: historyTask)),
|
|
),
|
|
const Divider(thickness: 0.3, height: 0),
|
|
ListTile(
|
|
leading: const Icon(Icons.construction),
|
|
title: Text(localizations.toolbox),
|
|
onTap: () => Navigator.of(context).push(
|
|
MaterialPageRoute(builder: (BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(localizations.toolbox), centerTitle: true),
|
|
body: Toolbox(proxyServer: proxyServer));
|
|
}),
|
|
)),
|
|
ListTile(
|
|
title: Text(localizations.httpsProxy),
|
|
leading: proxyServer.enableSsl ? Icon(Icons.lock_open) : Icon(Icons.https),
|
|
onTap: () => navigator(context, MobileSslWidget(proxyServer: proxyServer))),
|
|
const Divider(thickness: 0.3, height: 0),
|
|
ListTile(
|
|
title: Text(localizations.filter),
|
|
leading: const Icon(Icons.filter_alt_outlined),
|
|
onTap: () => navigator(context, FilterMenu(proxyServer: proxyServer))),
|
|
ListTile(
|
|
title: Text(localizations.hosts),
|
|
leading: Icon(Icons.domain),
|
|
onTap: () async {
|
|
var hostsManager = await HostsManager.instance;
|
|
if (context.mounted) {
|
|
navigator(context, HostsPage(hostsManager: hostsManager));
|
|
}
|
|
}),
|
|
ListTile(
|
|
title: Text(localizations.requestBlock),
|
|
leading: const Icon(Icons.block_flipped),
|
|
onTap: () async {
|
|
var requestBlockManager = await RequestBlockManager.instance;
|
|
if (context.mounted) {
|
|
navigator(context, MobileRequestBlock(requestBlockManager: requestBlockManager));
|
|
}
|
|
}),
|
|
ListTile(
|
|
title: Text(localizations.requestRewrite),
|
|
leading: const Icon(Icons.edit_outlined),
|
|
onTap: () async {
|
|
var requestRewrites = await RequestRewriteManager.instance;
|
|
if (context.mounted) {
|
|
navigator(context, MobileRequestRewrite(requestRewrites: requestRewrites));
|
|
}
|
|
}),
|
|
ListTile(
|
|
title: Text(localizations.requestMap),
|
|
leading: Icon(Icons.swap_horiz_outlined),
|
|
onTap: () => navigator(context, MobileRequestMapPage())),
|
|
ListTile(
|
|
title: Text(localizations.requestCrypto),
|
|
leading: const Icon(Icons.lock_outline),
|
|
onTap: () => navigator(context, const MobileRequestCryptoPage())),
|
|
ListTile(
|
|
title: Text(localizations.script),
|
|
leading: const Icon(Icons.code),
|
|
onTap: () => navigator(context, const MobileScript())),
|
|
ListTile(
|
|
title: Text(localizations.breakpoint),
|
|
leading: const Icon(Icons.bug_report_outlined),
|
|
onTap: () async {
|
|
var manager = await RequestBreakpointManager.instance;
|
|
if (context.mounted) {
|
|
navigator(context, MobileRequestBreakpointPage(manager: manager));
|
|
}
|
|
}),
|
|
ListTile(
|
|
title: Text(localizations.setting),
|
|
leading: const Icon(Icons.settings),
|
|
onTap: () => navigator(
|
|
context,
|
|
futureWidget(
|
|
AppConfiguration.instance,
|
|
(appConfiguration) =>
|
|
_SettingPage(proxyServer: proxyServer, appConfiguration: appConfiguration)))),
|
|
ListTile(
|
|
title: Text(localizations.about),
|
|
leading: const Icon(Icons.info_outline),
|
|
onTap: () => navigator(context, const About())),
|
|
const SizedBox(height: 20)
|
|
],
|
|
));
|
|
}
|
|
}
|
|
|
|
///跳转页面
|
|
void navigator(BuildContext context, Widget widget) {
|
|
Navigator.of(context).push(
|
|
MaterialPageRoute(builder: (BuildContext context) {
|
|
return widget;
|
|
}),
|
|
);
|
|
}
|
|
|
|
class _SettingPage extends StatelessWidget {
|
|
final ProxyServer proxyServer;
|
|
final AppConfiguration appConfiguration;
|
|
|
|
const _SettingPage({required this.proxyServer, required this.appConfiguration});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final configuration = proxyServer.configuration;
|
|
var textEditingController = TextEditingController(text: configuration.proxyPassDomains);
|
|
|
|
AppLocalizations localizations = AppLocalizations.of(context)!;
|
|
bool isEn = appConfiguration.language?.languageCode == 'en';
|
|
|
|
Widget section(List<Widget> tiles) => Card(
|
|
color: Colors.transparent,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(color: Theme.of(context).dividerColor.withValues(alpha: 0.13)),
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Column(children: tiles),
|
|
);
|
|
|
|
return Scaffold(
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(42),
|
|
child: AppBar(
|
|
title: Text(localizations.setting, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.w400)),
|
|
centerTitle: true,
|
|
)),
|
|
body: ListView(padding: const EdgeInsets.all(12), children: [
|
|
// Port and switches
|
|
Card(
|
|
color: Colors.transparent,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(color: Theme.of(context).dividerColor.withValues(alpha: 0.13)),
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Column(children: [
|
|
PortWidget(
|
|
proxyServer: proxyServer,
|
|
title: '${localizations.proxy}${isEn ? ' ' : ''}${localizations.port}',
|
|
textStyle: const TextStyle(fontSize: 16)),
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
if (Platform.isAndroid)
|
|
ListTile(
|
|
title: Text(localizations.systemProxy),
|
|
trailing: SwitchWidget(
|
|
value: configuration.enableSystemProxy,
|
|
scale: 0.8,
|
|
onChanged: (value) {
|
|
configuration.enableSystemProxy = value;
|
|
proxyServer.configuration.flushConfig();
|
|
})),
|
|
if (Platform.isAndroid)
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: const Text("SOCKS5"),
|
|
trailing: SwitchWidget(
|
|
value: configuration.enableSocks5,
|
|
scale: 0.8,
|
|
onChanged: (value) {
|
|
configuration.enableSocks5 = value;
|
|
proxyServer.configuration.flushConfig();
|
|
})),
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.enabledHTTP2),
|
|
trailing: SwitchWidget(
|
|
value: configuration.enabledHttp2,
|
|
scale: 0.8,
|
|
onChanged: (value) {
|
|
configuration.enabledHttp2 = value;
|
|
proxyServer.configuration.flushConfig();
|
|
})),
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.externalProxy),
|
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
|
onTap: () {
|
|
showDialog(
|
|
context: context,
|
|
builder: (_) => ExternalProxyDialog(configuration: proxyServer.configuration));
|
|
}),
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 15),
|
|
child: Row(children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(localizations.proxyIgnoreDomain, style: const TextStyle(fontSize: 14)),
|
|
const SizedBox(height: 3),
|
|
Text(isEn ? "Use ';' to separate multiple entries" : "多个使用;分割",
|
|
style: TextStyle(fontSize: 11, color: Colors.grey.shade600)),
|
|
],
|
|
)),
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 35),
|
|
child: TextButton(
|
|
child: Text(localizations.reset),
|
|
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,
|
|
onSubmitted: (_) {
|
|
configuration.proxyPassDomains = textEditingController.text;
|
|
proxyServer.configuration.flushConfig();
|
|
},
|
|
decoration:
|
|
const InputDecoration(contentPadding: EdgeInsets.all(10), border: OutlineInputBorder()),
|
|
maxLines: 5,
|
|
minLines: 1)),
|
|
const SizedBox(height: 10),
|
|
])),
|
|
const SizedBox(height: 12),
|
|
section([
|
|
ListTile(
|
|
title: Text(localizations.preference),
|
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
|
onTap: () =>
|
|
navigator(context, Preference(proxyServer: proxyServer, appConfiguration: appConfiguration))),
|
|
Divider(height: 0, thickness: 0.3, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.about),
|
|
trailing: const Icon(Icons.keyboard_arrow_right),
|
|
onTap: () => navigator(context, const About())),
|
|
]),
|
|
const SizedBox(height: 8),
|
|
]));
|
|
}
|
|
}
|
|
|
|
///抓包过滤菜单
|
|
class FilterMenu extends StatelessWidget {
|
|
final ProxyServer proxyServer;
|
|
|
|
const FilterMenu({super.key, required this.proxyServer});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
AppLocalizations localizations = AppLocalizations.of(context)!;
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(title: Text(localizations.filter, style: const TextStyle(fontSize: 16)), centerTitle: true),
|
|
body: Padding(
|
|
padding: const EdgeInsets.all(12),
|
|
child: Card(
|
|
color: Colors.transparent,
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(
|
|
side: BorderSide(color: Theme.of(context).dividerColor.withValues(alpha: 0.13)),
|
|
borderRadius: BorderRadius.circular(10)),
|
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
ListTile(
|
|
title: Text(localizations.domainWhitelist),
|
|
trailing: const Icon(Icons.arrow_right),
|
|
onTap: () => navigator(
|
|
context,
|
|
MobileFilterWidget(
|
|
configuration: proxyServer.configuration, hostList: HostFilter.whitelist))),
|
|
Divider(height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.domainBlacklist),
|
|
trailing: const Icon(Icons.arrow_right),
|
|
onTap: () => navigator(
|
|
context,
|
|
MobileFilterWidget(
|
|
configuration: proxyServer.configuration, hostList: HostFilter.blacklist))),
|
|
Platform.isIOS
|
|
? const SizedBox()
|
|
: Column(mainAxisSize: MainAxisSize.min, children: [
|
|
Divider(
|
|
height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.appWhitelist),
|
|
trailing: const Icon(Icons.arrow_right),
|
|
onTap: () => navigator(context, AppWhitelist(proxyServer: proxyServer))),
|
|
Divider(
|
|
height: 0, thickness: 0.4, color: Theme.of(context).dividerColor.withValues(alpha: 0.22)),
|
|
ListTile(
|
|
title: Text(localizations.appBlacklist),
|
|
trailing: const Icon(Icons.arrow_right),
|
|
onTap: () => navigator(context, AppBlacklist(proxyServer: proxyServer)))
|
|
])
|
|
]))));
|
|
}
|
|
}
|