Fix android vpn close issues

Add requests page

Fix checkUpdate dark mode style error

Fix quickStart error open app

Add memory proxies tab index

Support hidden group

Optimize logs
This commit is contained in:
chen08209
2024-06-13 23:43:42 +08:00
parent 5ab4dd0cbd
commit f7abf6446c
56 changed files with 1437 additions and 496 deletions

View File

@@ -82,6 +82,10 @@ class ApplicationState extends State<Application> {
super.initState();
globalState.appController = AppController(context);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
final currentContext = globalState.navigatorKey.currentContext;
if (currentContext != null) {
globalState.appController = AppController(currentContext);
}
await globalState.appController.init();
globalState.appController.initLink();
_updateGroups();

View File

@@ -100,7 +100,7 @@ class ClashCore {
UsedProxy.GLOBAL.name,
...(proxies[UsedProxy.GLOBAL.name]["all"] as List).where((e) {
final proxy = proxies[e];
return GroupTypeExtension.valueList.contains(proxy['type']);
return GroupTypeExtension.valueList.contains(proxy['type']) && proxy['hidden'] != true;
})
];
final groupsRaw = groupNames.map((groupName) {
@@ -210,7 +210,7 @@ class ClashCore {
clashFFI.startTUN(fd);
}
requestGc(){
requestGc() {
clashFFI.forceGc();
}
@@ -218,6 +218,16 @@ class ClashCore {
clashFFI.stopTun();
}
void setProcessMap(ProcessMapItem processMapItem) {
clashFFI.setProcessMap(json.encode(processMapItem).toNativeUtf8().cast());
}
DateTime? getRunTime() {
final runTimeString = clashFFI.getRunTime().cast<Utf8>().toDartString();
if (runTimeString.isEmpty) return null;
return DateTime.fromMillisecondsSinceEpoch(int.parse(runTimeString));
}
List<Connection> getConnections() {
final connectionsDataRaw = clashFFI.getConnections();
final connectionsData =

View File

@@ -1130,6 +1130,20 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function()>>('stopLog');
late final _stopLog = _stopLogPtr.asFunction<void Function()>();
void setProcessMap(
ffi.Pointer<ffi.Char> s,
) {
return _setProcessMap(
s,
);
}
late final _setProcessMapPtr =
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<ffi.Char>)>>(
'setProcessMap');
late final _setProcessMap =
_setProcessMapPtr.asFunction<void Function(ffi.Pointer<ffi.Char>)>();
void startTUN(
int fd,
) {
@@ -1142,19 +1156,15 @@ class ClashFFI {
_lookup<ffi.NativeFunction<ffi.Void Function(ffi.Int)>>('startTUN');
late final _startTUN = _startTUNPtr.asFunction<void Function(int)>();
int updateMarkSocketPort(
int markSocketPort,
) {
return _updateMarkSocketPort(
markSocketPort,
);
ffi.Pointer<ffi.Char> getRunTime() {
return _getRunTime();
}
late final _updateMarkSocketPortPtr =
_lookup<ffi.NativeFunction<GoUint8 Function(ffi.LongLong)>>(
'updateMarkSocketPort');
late final _updateMarkSocketPort =
_updateMarkSocketPortPtr.asFunction<int Function(int)>();
late final _getRunTimePtr =
_lookup<ffi.NativeFunction<ffi.Pointer<ffi.Char> Function()>>(
'getRunTime');
late final _getRunTime =
_getRunTimePtr.asFunction<ffi.Pointer<ffi.Char> Function()>();
void stopTun() {
return _stopTun();

View File

@@ -14,7 +14,9 @@ abstract mixin class ClashMessageListener {
void onDelay(Delay delay) {}
void onProcess(Metadata metadata) {}
void onProcess(Process process) {}
void onRequest(Connection connection) {}
void onNow(Now now) {}
}
@@ -41,11 +43,14 @@ class ClashMessage {
listener.onDelay(Delay.fromJson(m.data));
break;
case MessageType.process:
listener.onProcess(Metadata.fromJson(m.data));
listener.onProcess(Process.fromJson(m.data));
break;
case MessageType.now:
listener.onNow(Now.fromJson(m.data));
break;
case MessageType.request:
listener.onRequest(Connection.fromJson(m.data));
break;
}
}
});

View File

@@ -7,6 +7,7 @@ class Android {
init() async {
app?.onExit = () {
clashCore.shutdown();
print("adsadda==>");
exit(0);
};
}

View File

@@ -29,6 +29,13 @@ class Navigation {
label: "profiles",
fragment: ProfilesFragment(),
),
const NavigationItem(
icon: Icon(Icons.ballot),
label: "requests",
fragment: RequestFragment(),
description: "requestsDesc",
modes: [NavigationItemMode.desktop, NavigationItemMode.more],
),
const NavigationItem(
icon: Icon(Icons.swap_vert_circle),
label: "resources",

View File

@@ -145,7 +145,16 @@ class AppController {
if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
continue;
}
await updateProfile(profile.id);
try {
await updateProfile(profile.id);
} catch (e) {
appState.addLog(
Log(
logLevel: LogLevel.info,
payload: e.toString(),
),
);
}
}
}
@@ -222,20 +231,21 @@ class AppController {
final tagName = data['tag_name'];
final body = data['body'];
final submits = other.parseReleaseBody(body);
final textTheme = context.textTheme;
globalState.showMessage(
title: appLocalizations.discoverNewVersion,
message: TextSpan(
text: "$tagName \n",
style: context.textTheme.headlineSmall,
style: textTheme.headlineSmall,
children: [
TextSpan(
text: "\n",
style: context.textTheme.bodyMedium,
style: textTheme.bodyMedium,
),
for (final submit in submits)
TextSpan(
text: "- $submit \n",
style: context.textTheme.bodyMedium,
style: textTheme.bodyMedium,
),
],
),
@@ -261,7 +271,7 @@ class AppController {
window?.show();
}
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if(commonScaffoldState?.mounted == true){
if (commonScaffoldState?.mounted == true) {
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
@@ -269,7 +279,7 @@ class AppController {
clashConfig: clashConfig,
);
});
}else{
} else {
await globalState.applyProfile(
appState: appState,
config: config,

View File

@@ -56,7 +56,9 @@ enum ProfileType { file, url }
enum ResultType { success, error }
enum MessageType { log, tun, delay, process, now }
enum MessageType { log, tun, delay, process, now, request }
enum FindProcessMode { always, off }
enum RecoveryOption {
all,

View File

@@ -39,13 +39,6 @@ class _ConfigFragmentState extends State<ConfigFragment> {
}
}
_updateLoglevel(LogLevel? logLevel) {
if (logLevel == null ||
logLevel == globalState.appController.clashConfig.logLevel) return;
globalState.appController.clashConfig.logLevel = logLevel;
globalState.appController.updateClashConfigDebounce();
}
_buildAppSection() {
final items = [
if (Platform.isAndroid)
@@ -121,34 +114,56 @@ class _ConfigFragmentState extends State<ConfigFragment> {
);
}
_showLogLevelDialog(LogLevel value) {
globalState.showCommonDialog(
child: AlertDialog(
title: Text(appLocalizations.logLevel),
contentPadding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 16,
),
content: SizedBox(
width: 250,
child: Wrap(
children: [
for (final logLevel in LogLevel.values)
ListItem.radio(
delegate: RadioDelegate<LogLevel>(
value: logLevel,
groupValue: value,
onChanged: (LogLevel? value) {
if (value == null) {
return;
}
final appController = globalState.appController;
appController.clashConfig.logLevel = value;
appController.updateClashConfigDebounce();
Navigator.of(context).pop();
},
),
title: Text(logLevel.name),
)
],
),
),
),
);
}
_buildGeneralSection() {
final items = [
Padding(
padding: kMaterialListPadding,
child: Selector<ClashConfig, LogLevel>(
selector: (_, clashConfig) => clashConfig.logLevel,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
trailing: SizedBox(
height: 48,
child: DropdownMenu<LogLevel>(
width: 124,
initialSelection: value,
dropdownMenuEntries: [
for (final logLevel in LogLevel.values)
DropdownMenuEntry<LogLevel>(
value: logLevel,
label: logLevel.name,
)
],
onSelected: _updateLoglevel,
),
),
);
},
),
Selector<ClashConfig, LogLevel>(
selector: (_, clashConfig) => clashConfig.logLevel,
builder: (_, value, __) {
return ListItem(
leading: const Icon(Icons.info_outline),
title: Text(appLocalizations.logLevel),
subtitle: Text(value.name),
onTab: () {
_showLogLevelDialog(value);
},
);
},
),
Selector<ClashConfig, int>(
selector: (_, clashConfig) => clashConfig.mixedPort,
@@ -225,6 +240,26 @@ class _ConfigFragmentState extends State<ConfigFragment> {
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) =>
clashConfig.findProcessMode == FindProcessMode.always,
builder: (_, findProcess, __) {
return ListItem.switchItem(
leading: const Icon(Icons.polymer_outlined),
title: Text(appLocalizations.findProcessMode),
subtitle: Text(appLocalizations.findProcessModeDesc),
delegate: SwitchDelegate(
value: findProcess,
onChanged: (bool value) async {
final appController = globalState.appController;
appController.clashConfig.findProcessMode =
value ? FindProcessMode.always : FindProcessMode.off;
appController.updateClashConfigDebounce();
},
),
);
},
),
Selector<ClashConfig, bool>(
selector: (_, clashConfig) => clashConfig.tcpConcurrent,
builder: (_, tcpConcurrent, __) {
@@ -258,7 +293,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
appController.clashConfig.geodataLoader = value
? geodataLoaderMemconservative
: geodataLoaderStandard;
appController.updateClashConfigDebounce;
appController.updateClashConfigDebounce();
},
),
);
@@ -277,7 +312,7 @@ class _ConfigFragmentState extends State<ConfigFragment> {
final appController = globalState.appController;
appController.clashConfig.externalController =
value ? defaultExternalController : '';
appController.updateClashConfigDebounce;
appController.updateClashConfigDebounce();
},
),
);

View File

@@ -1,140 +1,140 @@
import 'dart:async';
import 'package:fl_clash/clash/core.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
class ConnectionsFragment extends StatefulWidget {
const ConnectionsFragment({super.key});
@override
State<ConnectionsFragment> createState() => _ConnectionsFragmentState();
}
class _ConnectionsFragmentState extends State<ConnectionsFragment> {
final connectionsNotifier = ValueNotifier<List<Connection>>([]);
Map<String, String?> idPackageNameMap = {};
Timer? timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_getConnections();
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(seconds: 3), (timer) {
if (mounted) {
_getConnections();
}
});
});
}
_getConnections() {
connectionsNotifier.value = clashCore
.getConnections();
}
@override
void dispose() {
super.dispose();
timer?.cancel();
timer = null;
}
Future<ImageProvider?> _getPackageIconWithConnection(
Connection connection) async {
final uid = connection.metadata.uid;
// if(globalState.packageNameMap[uid] == null){
// globalState.packageNameMap[uid] = await app?.getPackageName(connection.metadata);
// }
final packageName = globalState.packageNameMap[uid];
if(packageName == null) return null;
return await app?.getPackageIcon(packageName);
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<Connection>>(
valueListenable: connectionsNotifier,
builder: (_, List<Connection> connections, __) {
if (connections.isEmpty) {
return const NullStatus(
label: "未开启代理,或者没有连接数据",
);
}
return ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (_, index) {
final connection = connections[index];
return ListTile(
titleAlignment: ListTileTitleAlignment.top,
leading: Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIconWithConnection(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
),
contentPadding:
const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(connection.metadata.host.isNotEmpty
? connection.metadata.host
: connection.metadata.destinationIP),
Padding(
padding: const EdgeInsets.only(
top: 12,
),
child: Wrap(
runSpacing: 8,
spacing: 8,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
),
],
),
),
],
),
trailing: IconButton(
icon: const Icon(Icons.block),
onPressed: () {},
),
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: connections.length,
);
},
);
}
}
// import 'dart:async';
//
// import 'package:fl_clash/clash/core.dart';
// import 'package:fl_clash/models/models.dart';
// import 'package:fl_clash/plugins/app.dart';
// import 'package:fl_clash/state.dart';
// import 'package:fl_clash/widgets/widgets.dart';
// import 'package:flutter/material.dart';
//
// class ConnectionsFragment extends StatefulWidget {
// const ConnectionsFragment({super.key});
//
// @override
// State<ConnectionsFragment> createState() => _ConnectionsFragmentState();
// }
//
// class _ConnectionsFragmentState extends State<ConnectionsFragment> {
// final connectionsNotifier = ValueNotifier<List<Connection>>([]);
// Map<String, String?> idPackageNameMap = {};
//
// Timer? timer;
//
// @override
// void initState() {
// super.initState();
// WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
// _getConnections();
// if (timer != null) {
// timer?.cancel();
// timer = null;
// }
// timer = Timer.periodic(const Duration(seconds: 3), (timer) {
// if (mounted) {
// _getConnections();
// }
// });
// });
// }
//
// _getConnections() {
// connectionsNotifier.value = clashCore
// .getConnections();
// }
//
// @override
// void dispose() {
// super.dispose();
// timer?.cancel();
// timer = null;
// }
//
// Future<ImageProvider?> _getPackageIconWithConnection(
// Connection connection) async {
// final uid = connection.metadata.uid;
// // if(globalState.packageNameMap[uid] == null){
// // globalState.packageNameMap[uid] = await app?.getPackageName(connection.metadata);
// // }
// final packageName = globalState.packageNameMap[uid];
// if(packageName == null) return null;
// return await app?.getPackageIcon(packageName);
// }
//
// @override
// Widget build(BuildContext context) {
// return ValueListenableBuilder<List<Connection>>(
// valueListenable: connectionsNotifier,
// builder: (_, List<Connection> connections, __) {
// if (connections.isEmpty) {
// return const NullStatus(
// label: "未开启代理,或者没有连接数据",
// );
// }
// return ListView.separated(
// physics: const AlwaysScrollableScrollPhysics(),
// itemBuilder: (_, index) {
// final connection = connections[index];
// return ListTile(
// titleAlignment: ListTileTitleAlignment.top,
// leading: Container(
// margin: const EdgeInsets.only(top: 4),
// width: 48,
// height: 48,
// child: FutureBuilder<ImageProvider?>(
// future: _getPackageIconWithConnection(connection),
// builder: (_, snapshot) {
// if (!snapshot.hasData && snapshot.data == null) {
// return Container();
// } else {
// return Image(
// image: snapshot.data!,
// gaplessPlayback: true,
// width: 48,
// height: 48,
// );
// }
// },
// ),
// ),
// contentPadding:
// const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
// title: Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(connection.metadata.host.isNotEmpty
// ? connection.metadata.host
// : connection.metadata.destinationIP),
// Padding(
// padding: const EdgeInsets.only(
// top: 12,
// ),
// child: Wrap(
// runSpacing: 8,
// spacing: 8,
// children: [
// for (final chain in connection.chains)
// CommonChip(
// label: chain,
// ),
// ],
// ),
// ),
// ],
// ),
// trailing: IconButton(
// icon: const Icon(Icons.block),
// onPressed: () {},
// ),
// );
// },
// separatorBuilder: (BuildContext context, int index) {
// return const Divider(
// height: 0,
// );
// },
// itemCount: connections.length,
// );
// },
// );
// }
// }

View File

@@ -9,4 +9,5 @@ export 'config.dart';
export 'application_setting.dart';
export 'about.dart';
export 'backup_and_recovery.dart';
export 'resources.dart';
export 'resources.dart';
export 'requests.dart';

View File

@@ -1,5 +1,6 @@
import 'dart:async';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/state.dart';
@@ -22,14 +23,21 @@ class _LogsFragmentState extends State<LogsFragment> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
logsNotifier.value = context.read<AppState>().logs;
WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = globalState.appController.appState;
logsNotifier.value = List<Log>.from(appState.logs);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
logsNotifier.value = globalState.appController.appState.logs;
final logs = List<Log>.from(appState.logs);
if (!const ListEquality<Log>().equals(
logsNotifier.value,
logs,
)) {
logsNotifier.value = logs;
}
});
});
}

View File

@@ -1,3 +1,4 @@
import 'package:collection/collection.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
@@ -59,6 +60,18 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
});
}
_handleTabControllerChange() {
final indexIsChanging = _tabController?.indexIsChanging ?? false;
if (indexIsChanging) return;
final index = _tabController?.index;
if(index == null) return;
final appController = globalState.appController;
final currentGroups = appController.appState.currentGroups;
if (currentGroups.length > index) {
appController.config.updateCurrentGroupName(currentGroups[index].name);
}
}
@override
Widget build(BuildContext context) {
return Selector<AppState, bool>(
@@ -69,26 +82,34 @@ class _ProxiesFragmentState extends State<ProxiesFragment>
}
return child!;
},
child: Selector3<AppState, Config, ClashConfig, ProxiesSelectorState>(
selector: (_, appState, config, clashConfig) {
child: Selector2<AppState, Config, ProxiesSelectorState>(
selector: (_, appState, config) {
final currentGroups = appState.currentGroups;
final groupNames = currentGroups.map((e) => e.name).toList();
return ProxiesSelectorState(
groupNames: groupNames,
currentGroupName: config.currentGroupName,
);
},
shouldRebuild: (prev, next) {
if (prev.groupNames.length != next.groupNames.length) {
if (!const ListEquality<String>()
.equals(prev.groupNames, next.groupNames)) {
_tabController?.removeListener(_handleTabControllerChange);
_tabController?.dispose();
_tabController = null;
return true;
}
return prev != next;
return false;
},
builder: (_, state, __) {
final index = state.groupNames.indexWhere(
(item) => item == state.currentGroupName,
);
_tabController ??= TabController(
length: state.groupNames.length,
initialIndex: index == -1 ? 0 : index,
vsync: this,
);
)..addListener(_handleTabControllerChange);
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
@@ -200,7 +221,8 @@ class ProxiesTabView extends StatelessWidget {
_delayTest(List<Proxy> proxies) async {
for (final proxy in proxies) {
final appController = globalState.appController;
final proxyName = appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
final proxyName =
appController.appState.getRealProxyName(proxy.name) ?? proxy.name;
globalState.appController.setDelay(
Delay(
name: proxyName,

178
lib/fragments/requests.dart Normal file
View File

@@ -0,0 +1,178 @@
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
class RequestFragment extends StatefulWidget {
const RequestFragment({super.key});
@override
State<RequestFragment> createState() => _RequestFragmentState();
}
class _RequestFragmentState extends State<RequestFragment> {
final requestsNotifier = ValueNotifier<List<Connection>>([]);
final ScrollController _scrollController = ScrollController(
keepScrollOffset: false,
);
Timer? timer;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
final appState = globalState.appController.appState;
requestsNotifier.value = List<Connection>.from(appState.requests);
if (timer != null) {
timer?.cancel();
timer = null;
}
timer = Timer.periodic(const Duration(milliseconds: 200), (timer) {
final requests = List<Connection>.from(appState.requests);
if (!const ListEquality<Connection>().equals(
requestsNotifier.value,
requests,
)) {
requestsNotifier.value = requests;
}
});
});
}
@override
void dispose() {
super.dispose();
timer?.cancel();
_scrollController.dispose();
timer = null;
}
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<List<Connection>>(
valueListenable: requestsNotifier,
builder: (_, List<Connection> connections, __) {
if (connections.isEmpty) {
return NullStatus(
label: appLocalizations.nullRequestsDesc,
);
}
connections = connections.reversed.toList();
return ListView.separated(
controller: _scrollController,
itemBuilder: (_, index) {
final connection = connections[index];
return RequestItem(
key: Key(connection.id),
connection: connection,
);
},
separatorBuilder: (BuildContext context, int index) {
return const Divider(
height: 0,
);
},
itemCount: connections.length,
);
},
);
}
}
class RequestItem extends StatelessWidget {
final Connection connection;
const RequestItem({
super.key,
required this.connection,
});
Future<ImageProvider?> _getPackageIcon(Connection connection) async {
return await app?.getPackageIcon(connection.metadata.process);
}
String _getRequestText(Metadata metadata) {
var text = "${metadata.network}:://";
final ips = [
metadata.host,
metadata.destinationIP,
].where((ip) => ip.isNotEmpty);
text += ips.join("/");
text += ":${metadata.destinationPort}";
return text;
}
String _getSourceText(Connection connection) {
final metadata = connection.metadata;
if (metadata.process.isEmpty) {
return connection.start.lastUpdateTimeDesc;
}
return "${metadata.process} · ${connection.start.lastUpdateTimeDesc}";
}
@override
Widget build(BuildContext context) {
return ListTile(
titleAlignment: ListTileTitleAlignment.top,
leading: Platform.isAndroid
? Container(
margin: const EdgeInsets.only(top: 4),
width: 48,
height: 48,
child: FutureBuilder<ImageProvider?>(
future: _getPackageIcon(connection),
builder: (_, snapshot) {
if (!snapshot.hasData && snapshot.data == null) {
return Container();
} else {
return Image(
image: snapshot.data!,
gaplessPlayback: true,
width: 48,
height: 48,
);
}
},
),
)
: null,
title: Text(
_getRequestText(connection.metadata),
),
subtitle: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 12,
),
Text(
_getSourceText(connection),
),
const SizedBox(
height: 12,
),
Wrap(
runSpacing: 8,
spacing: 8,
children: [
for (final chain in connection.chains)
CommonChip(
label: chain,
),
],
),
const SizedBox(
height: 12,
),
],
),
);
}
}

View File

@@ -176,5 +176,10 @@
"tcpConcurrent": "Tcp concurrent",
"tcpConcurrentDesc": "Enabling it will allow tcp concurrency",
"geodataLoader": "Geo Low Memory Mode",
"geodataLoaderDesc": "Enabling will use the Geo low memory loader"
"geodataLoaderDesc": "Enabling will use the Geo low memory loader",
"requests": "Requests",
"requestsDesc": "View recently requested data",
"nullRequestsDesc": "No proxy or no request",
"findProcessMode": "Find process",
"findProcessModeDesc": "There is a risk of flashback after opening"
}

View File

@@ -176,5 +176,10 @@
"tcpConcurrent": "TCP并发",
"tcpConcurrentDesc": "开启后允许tcp并发",
"geodataLoader": "Geo低内存模式",
"geodataLoaderDesc": "开启将使用Geo低内存加载器"
"geodataLoaderDesc": "开启将使用Geo低内存加载器",
"requests": "请求",
"requestsDesc": "查看最近请求数据",
"nullRequestsDesc": "未开启代理或者没有请求",
"findProcessMode": "查找进程",
"findProcessModeDesc": "开启后存在闪退风险"
}

View File

@@ -128,6 +128,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Directly upload profile"),
"filterSystemApp":
MessageLookupByLibrary.simpleMessage("Filter system app"),
"findProcessMode": MessageLookupByLibrary.simpleMessage("Find process"),
"findProcessModeDesc": MessageLookupByLibrary.simpleMessage(
"There is a risk of flashback after opening"),
"general": MessageLookupByLibrary.simpleMessage("General"),
"geoData": MessageLookupByLibrary.simpleMessage("GeoData"),
"geodataLoader":
@@ -176,6 +179,8 @@ class MessageLookup extends MessageLookupByLibrary {
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("No logs"),
"nullProfileDesc": MessageLookupByLibrary.simpleMessage(
"No profile, Please add a profile"),
"nullRequestsDesc":
MessageLookupByLibrary.simpleMessage("No proxy or no request"),
"other": MessageLookupByLibrary.simpleMessage("Other"),
"outboundMode": MessageLookupByLibrary.simpleMessage("Outbound mode"),
"override": MessageLookupByLibrary.simpleMessage("Override"),
@@ -225,6 +230,9 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Only recovery profiles"),
"recoverySuccess":
MessageLookupByLibrary.simpleMessage("Recovery success"),
"requests": MessageLookupByLibrary.simpleMessage("Requests"),
"requestsDesc": MessageLookupByLibrary.simpleMessage(
"View recently requested data"),
"resources": MessageLookupByLibrary.simpleMessage("Resources"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage(
"External resource related info"),

View File

@@ -104,6 +104,9 @@ class MessageLookup extends MessageLookupByLibrary {
"file": MessageLookupByLibrary.simpleMessage("文件"),
"fileDesc": MessageLookupByLibrary.simpleMessage("直接上传配置文件"),
"filterSystemApp": MessageLookupByLibrary.simpleMessage("过滤系统应用"),
"findProcessMode": MessageLookupByLibrary.simpleMessage("查找进程"),
"findProcessModeDesc":
MessageLookupByLibrary.simpleMessage("开启后存在闪退风险"),
"general": MessageLookupByLibrary.simpleMessage("基础"),
"geoData": MessageLookupByLibrary.simpleMessage("地理数据"),
"geodataLoader": MessageLookupByLibrary.simpleMessage("Geo低内存模式"),
@@ -143,6 +146,7 @@ class MessageLookup extends MessageLookupByLibrary {
"nullLogsDesc": MessageLookupByLibrary.simpleMessage("暂无日志"),
"nullProfileDesc":
MessageLookupByLibrary.simpleMessage("没有配置文件,请先添加配置文件"),
"nullRequestsDesc": MessageLookupByLibrary.simpleMessage("未开启代理或者没有请求"),
"other": MessageLookupByLibrary.simpleMessage("其他"),
"outboundMode": MessageLookupByLibrary.simpleMessage("出站模式"),
"override": MessageLookupByLibrary.simpleMessage("覆写"),
@@ -180,6 +184,8 @@ class MessageLookup extends MessageLookupByLibrary {
"recoveryDesc": MessageLookupByLibrary.simpleMessage("从WebDAV恢复数据"),
"recoveryProfiles": MessageLookupByLibrary.simpleMessage("仅恢复配置文件"),
"recoverySuccess": MessageLookupByLibrary.simpleMessage("恢复成功"),
"requests": MessageLookupByLibrary.simpleMessage("请求"),
"requestsDesc": MessageLookupByLibrary.simpleMessage("查看最近请求数据"),
"resources": MessageLookupByLibrary.simpleMessage("资源"),
"resourcesDesc": MessageLookupByLibrary.simpleMessage("外部资源相关信息"),
"rule": MessageLookupByLibrary.simpleMessage("规则"),

View File

@@ -1829,6 +1829,56 @@ class AppLocalizations {
args: [],
);
}
/// `Requests`
String get requests {
return Intl.message(
'Requests',
name: 'requests',
desc: '',
args: [],
);
}
/// `View recently requested data`
String get requestsDesc {
return Intl.message(
'View recently requested data',
name: 'requestsDesc',
desc: '',
args: [],
);
}
/// `No proxy or no request`
String get nullRequestsDesc {
return Intl.message(
'No proxy or no request',
name: 'nullRequestsDesc',
desc: '',
args: [],
);
}
/// `Find process`
String get findProcessMode {
return Intl.message(
'Find process',
name: 'findProcessMode',
desc: '',
args: [],
);
}
/// `There is a risk of flashback after opening`
String get findProcessModeDesc {
return Intl.message(
'There is a risk of flashback after opening',
name: 'findProcessModeDesc',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<AppLocalizations> {

View File

@@ -31,7 +31,6 @@ Future<void> main() async {
config: config,
clashConfig: clashConfig,
);
runAppWithPreferences(
const Application(),
appState: appState,

View File

@@ -1,7 +1,10 @@
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:flutter/material.dart';
import 'connection.dart';
import 'ffi.dart';
import 'log.dart';
import 'navigation.dart';
@@ -29,6 +32,7 @@ class AppState with ChangeNotifier {
bool _isCompatible;
List<Group> _groups;
double _viewWidth;
List<Connection> _requests;
AppState({
required Mode mode,
@@ -42,6 +46,7 @@ class AppState with ChangeNotifier {
_viewWidth = 0,
_selectedMap = selectedMap,
_sortNum = 0,
_requests = [],
_mode = mode,
_delayMap = {},
_groups = [],
@@ -157,6 +162,24 @@ class AppState with ChangeNotifier {
notifyListeners();
}
List<Connection> get requests => _requests;
set requests(List<Connection> value) {
if (_requests != value) {
_requests = value;
notifyListeners();
}
}
addRequest(Connection value) {
_requests.add(value);
final maxLength = Platform.isAndroid ? 1000 : 60;
if (_requests.length > maxLength) {
_requests = _requests.sublist(_requests.length - maxLength);
}
notifyListeners();
}
List<Log> get logs => _logs;
set logs(List<Log> value) {
@@ -168,8 +191,10 @@ class AppState with ChangeNotifier {
addLog(Log log) {
_logs.add(log);
if (_logs.length > 60) {
_logs = _logs.sublist(_logs.length - 60);
if (!Platform.isAndroid) {
if (_logs.length > 60) {
_logs = _logs.sublist(_logs.length - 60);
}
}
notifyListeners();
}

View File

@@ -113,39 +113,29 @@ class ClashConfig extends ChangeNotifier {
LogLevel _logLevel;
String _externalController;
Mode _mode;
FindProcessMode _findProcessMode;
bool _unifiedDelay;
bool _tcpConcurrent;
Tun _tun;
Dns _dns;
List<String> _rules;
ClashConfig({
int? mixedPort,
Mode? mode,
bool? allowLan,
bool? ipv6,
LogLevel? logLevel,
String? externalController,
String? geodataLoader,
bool? unifiedDelay,
Tun? tun,
Dns? dns,
bool? tcpConcurrent,
List<String>? rules,
}) : _mixedPort = mixedPort ?? 7890,
_mode = mode ?? Mode.rule,
_ipv6 = ipv6 ?? false,
_allowLan = allowLan ?? false,
_tcpConcurrent = tcpConcurrent ?? false,
_logLevel = logLevel ?? LogLevel.info,
_tun = tun ?? const Tun(),
_unifiedDelay = unifiedDelay ?? false,
_geodataLoader = geodataLoader ?? geodataLoaderMemconservative,
_externalController = externalController ?? '',
_dns = dns ?? Dns(),
_rules = rules ?? [];
ClashConfig()
: _mixedPort = 7890,
_mode = Mode.rule,
_ipv6 = false,
_findProcessMode = FindProcessMode.off,
_allowLan = false,
_tcpConcurrent = false,
_logLevel = LogLevel.info,
_tun = const Tun(),
_unifiedDelay = false,
_geodataLoader = geodataLoaderMemconservative,
_externalController = '',
_dns = Dns(),
_rules = [];
@JsonKey(name: "mixed-port")
@JsonKey(name: "mixed-port", defaultValue: 7890)
int get mixedPort => _mixedPort;
set mixedPort(int value) {
@@ -155,6 +145,7 @@ class ClashConfig extends ChangeNotifier {
}
}
@JsonKey(defaultValue: Mode.rule)
Mode get mode => _mode;
set mode(Mode value) {
@@ -164,6 +155,16 @@ class ClashConfig extends ChangeNotifier {
}
}
@JsonKey(name: "find-process-mode", defaultValue: FindProcessMode.off)
FindProcessMode get findProcessMode => _findProcessMode;
set findProcessMode(FindProcessMode value) {
if (_findProcessMode != value) {
_findProcessMode = value;
notifyListeners();
}
}
@JsonKey(name: "allow-lan")
bool get allowLan => _allowLan;
@@ -174,7 +175,7 @@ class ClashConfig extends ChangeNotifier {
}
}
@JsonKey(name: "log-level")
@JsonKey(name: "log-level", defaultValue: LogLevel.info)
LogLevel get logLevel => _logLevel;
set logLevel(LogLevel value) {
@@ -282,17 +283,6 @@ class ClashConfig extends ChangeNotifier {
return _$ClashConfigFromJson(json);
}
ClashConfig copyWith({Tun? tun}) {
return ClashConfig(
mixedPort: mixedPort,
mode: mode,
logLevel: logLevel,
tun: tun ?? this.tun,
dns: dns,
allowLan: allowLan,
);
}
@override
String toString() {
return 'ClashConfig{_mixedPort: $_mixedPort, _allowLan: $_allowLan, _mode: $_mode, _logLevel: $_logLevel, _tun: $_tun, _dns: $_dns, _rules: $_rules}';

View File

@@ -32,8 +32,7 @@ class Props with _$Props {
bool? systemProxy,
}) = _Props;
factory Props.fromJson(Map<String, Object?> json) =>
_$PropsFromJson(json);
factory Props.fromJson(Map<String, Object?> json) => _$PropsFromJson(json);
}
@JsonSerializable()
@@ -151,6 +150,15 @@ class Config extends ChangeNotifier {
}
}
String? get currentGroupName => currentProfile?.currentGroupName;
updateCurrentGroupName(String groupName) {
if (currentProfile?.currentGroupName != groupName) {
currentProfile?.currentGroupName = groupName;
notifyListeners();
}
}
SelectedMap get currentSelectedMap {
return currentProfile?.selectedMap ?? {};
}

View File

@@ -14,6 +14,7 @@ class Metadata with _$Metadata {
required String destinationIP,
required String destinationPort,
required String host,
required String process,
required String remoteDestination,
}) = _Metadata;

View File

@@ -2,6 +2,7 @@
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/clash_config.dart';
import 'package:fl_clash/models/connection.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
part 'generated/ffi.g.dart';
@@ -66,16 +67,25 @@ class Now with _$Now {
@freezed
class Process with _$Process {
const factory Process({
required int uid,
required String network,
required String source,
required String target,
required int id,
required Metadata metadata,
}) = _Process;
factory Process.fromJson(Map<String, Object?> json) =>
_$ProcessFromJson(json);
}
@freezed
class ProcessMapItem with _$ProcessMapItem {
const factory ProcessMapItem({
required int id,
String? value,
}) = _ProcessMapItem;
factory ProcessMapItem.fromJson(Map<String, Object?> json) =>
_$ProcessMapItemFromJson(json);
}
@freezed
class ExternalProvider with _$ExternalProvider {
const factory ExternalProvider({

View File

@@ -35,30 +35,29 @@ Map<String, dynamic> _$DnsToJson(Dns instance) => <String, dynamic>{
'fake-ip-filter': instance.fakeIpFilter,
};
ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig(
mixedPort: (json['mixed-port'] as num?)?.toInt(),
mode: $enumDecodeNullable(_$ModeEnumMap, json['mode']),
allowLan: json['allow-lan'] as bool?,
ipv6: json['ipv6'] as bool? ?? false,
logLevel: $enumDecodeNullable(_$LogLevelEnumMap, json['log-level']),
externalController: json['external-controller'] as String? ?? '',
geodataLoader: json['geodata-loader'] as String? ?? 'memconservative',
unifiedDelay: json['unified-delay'] as bool? ?? false,
tun: json['tun'] == null
? null
: Tun.fromJson(json['tun'] as Map<String, dynamic>),
dns: json['dns'] == null
? null
: Dns.fromJson(json['dns'] as Map<String, dynamic>),
tcpConcurrent: json['tcp-concurrent'] as bool? ?? false,
rules:
(json['rules'] as List<dynamic>?)?.map((e) => e as String).toList(),
);
ClashConfig _$ClashConfigFromJson(Map<String, dynamic> json) => ClashConfig()
..mixedPort = (json['mixed-port'] as num?)?.toInt() ?? 7890
..mode = $enumDecodeNullable(_$ModeEnumMap, json['mode']) ?? Mode.rule
..findProcessMode = $enumDecodeNullable(
_$FindProcessModeEnumMap, json['find-process-mode']) ??
FindProcessMode.off
..allowLan = json['allow-lan'] as bool
..logLevel =
$enumDecodeNullable(_$LogLevelEnumMap, json['log-level']) ?? LogLevel.info
..externalController = json['external-controller'] as String? ?? ''
..ipv6 = json['ipv6'] as bool? ?? false
..geodataLoader = json['geodata-loader'] as String? ?? 'memconservative'
..unifiedDelay = json['unified-delay'] as bool? ?? false
..tcpConcurrent = json['tcp-concurrent'] as bool? ?? false
..tun = Tun.fromJson(json['tun'] as Map<String, dynamic>)
..dns = Dns.fromJson(json['dns'] as Map<String, dynamic>)
..rules = (json['rules'] as List<dynamic>).map((e) => e as String).toList();
Map<String, dynamic> _$ClashConfigToJson(ClashConfig instance) =>
<String, dynamic>{
'mixed-port': instance.mixedPort,
'mode': _$ModeEnumMap[instance.mode]!,
'find-process-mode': _$FindProcessModeEnumMap[instance.findProcessMode]!,
'allow-lan': instance.allowLan,
'log-level': _$LogLevelEnumMap[instance.logLevel]!,
'external-controller': instance.externalController,
@@ -77,6 +76,11 @@ const _$ModeEnumMap = {
Mode.direct: 'direct',
};
const _$FindProcessModeEnumMap = {
FindProcessMode.always: 'always',
FindProcessMode.off: 'off',
};
const _$LogLevelEnumMap = {
LogLevel.debug: 'debug',
LogLevel.info: 'info',

View File

@@ -27,6 +27,7 @@ mixin _$Metadata {
String get destinationIP => throw _privateConstructorUsedError;
String get destinationPort => throw _privateConstructorUsedError;
String get host => throw _privateConstructorUsedError;
String get process => throw _privateConstructorUsedError;
String get remoteDestination => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@@ -48,6 +49,7 @@ abstract class $MetadataCopyWith<$Res> {
String destinationIP,
String destinationPort,
String host,
String process,
String remoteDestination});
}
@@ -71,6 +73,7 @@ class _$MetadataCopyWithImpl<$Res, $Val extends Metadata>
Object? destinationIP = null,
Object? destinationPort = null,
Object? host = null,
Object? process = null,
Object? remoteDestination = null,
}) {
return _then(_value.copyWith(
@@ -102,6 +105,10 @@ class _$MetadataCopyWithImpl<$Res, $Val extends Metadata>
? _value.host
: host // ignore: cast_nullable_to_non_nullable
as String,
process: null == process
? _value.process
: process // ignore: cast_nullable_to_non_nullable
as String,
remoteDestination: null == remoteDestination
? _value.remoteDestination
: remoteDestination // ignore: cast_nullable_to_non_nullable
@@ -126,6 +133,7 @@ abstract class _$$MetadataImplCopyWith<$Res>
String destinationIP,
String destinationPort,
String host,
String process,
String remoteDestination});
}
@@ -147,6 +155,7 @@ class __$$MetadataImplCopyWithImpl<$Res>
Object? destinationIP = null,
Object? destinationPort = null,
Object? host = null,
Object? process = null,
Object? remoteDestination = null,
}) {
return _then(_$MetadataImpl(
@@ -178,6 +187,10 @@ class __$$MetadataImplCopyWithImpl<$Res>
? _value.host
: host // ignore: cast_nullable_to_non_nullable
as String,
process: null == process
? _value.process
: process // ignore: cast_nullable_to_non_nullable
as String,
remoteDestination: null == remoteDestination
? _value.remoteDestination
: remoteDestination // ignore: cast_nullable_to_non_nullable
@@ -197,6 +210,7 @@ class _$MetadataImpl implements _Metadata {
required this.destinationIP,
required this.destinationPort,
required this.host,
required this.process,
required this.remoteDestination});
factory _$MetadataImpl.fromJson(Map<String, dynamic> json) =>
@@ -217,11 +231,13 @@ class _$MetadataImpl implements _Metadata {
@override
final String host;
@override
final String process;
@override
final String remoteDestination;
@override
String toString() {
return 'Metadata(uid: $uid, network: $network, sourceIP: $sourceIP, sourcePort: $sourcePort, destinationIP: $destinationIP, destinationPort: $destinationPort, host: $host, remoteDestination: $remoteDestination)';
return 'Metadata(uid: $uid, network: $network, sourceIP: $sourceIP, sourcePort: $sourcePort, destinationIP: $destinationIP, destinationPort: $destinationPort, host: $host, process: $process, remoteDestination: $remoteDestination)';
}
@override
@@ -240,14 +256,24 @@ class _$MetadataImpl implements _Metadata {
(identical(other.destinationPort, destinationPort) ||
other.destinationPort == destinationPort) &&
(identical(other.host, host) || other.host == host) &&
(identical(other.process, process) || other.process == process) &&
(identical(other.remoteDestination, remoteDestination) ||
other.remoteDestination == remoteDestination));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, uid, network, sourceIP,
sourcePort, destinationIP, destinationPort, host, remoteDestination);
int get hashCode => Object.hash(
runtimeType,
uid,
network,
sourceIP,
sourcePort,
destinationIP,
destinationPort,
host,
process,
remoteDestination);
@JsonKey(ignore: true)
@override
@@ -272,6 +298,7 @@ abstract class _Metadata implements Metadata {
required final String destinationIP,
required final String destinationPort,
required final String host,
required final String process,
required final String remoteDestination}) = _$MetadataImpl;
factory _Metadata.fromJson(Map<String, dynamic> json) =
@@ -292,6 +319,8 @@ abstract class _Metadata implements Metadata {
@override
String get host;
@override
String get process;
@override
String get remoteDestination;
@override
@JsonKey(ignore: true)

View File

@@ -15,6 +15,7 @@ _$MetadataImpl _$$MetadataImplFromJson(Map<String, dynamic> json) =>
destinationIP: json['destinationIP'] as String,
destinationPort: json['destinationPort'] as String,
host: json['host'] as String,
process: json['process'] as String,
remoteDestination: json['remoteDestination'] as String,
);
@@ -27,6 +28,7 @@ Map<String, dynamic> _$$MetadataImplToJson(_$MetadataImpl instance) =>
'destinationIP': instance.destinationIP,
'destinationPort': instance.destinationPort,
'host': instance.host,
'process': instance.process,
'remoteDestination': instance.remoteDestination,
};

View File

@@ -848,10 +848,8 @@ Process _$ProcessFromJson(Map<String, dynamic> json) {
/// @nodoc
mixin _$Process {
int get uid => throw _privateConstructorUsedError;
String get network => throw _privateConstructorUsedError;
String get source => throw _privateConstructorUsedError;
String get target => throw _privateConstructorUsedError;
int get id => throw _privateConstructorUsedError;
Metadata get metadata => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -863,7 +861,9 @@ abstract class $ProcessCopyWith<$Res> {
factory $ProcessCopyWith(Process value, $Res Function(Process) then) =
_$ProcessCopyWithImpl<$Res, Process>;
@useResult
$Res call({int uid, String network, String source, String target});
$Res call({int id, Metadata metadata});
$MetadataCopyWith<$Res> get metadata;
}
/// @nodoc
@@ -879,30 +879,28 @@ class _$ProcessCopyWithImpl<$Res, $Val extends Process>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uid = null,
Object? network = null,
Object? source = null,
Object? target = null,
Object? id = null,
Object? metadata = null,
}) {
return _then(_value.copyWith(
uid: null == uid
? _value.uid
: uid // ignore: cast_nullable_to_non_nullable
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
network: null == network
? _value.network
: network // ignore: cast_nullable_to_non_nullable
as String,
source: null == source
? _value.source
: source // ignore: cast_nullable_to_non_nullable
as String,
target: null == target
? _value.target
: target // ignore: cast_nullable_to_non_nullable
as String,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Metadata,
) as $Val);
}
@override
@pragma('vm:prefer-inline')
$MetadataCopyWith<$Res> get metadata {
return $MetadataCopyWith<$Res>(_value.metadata, (value) {
return _then(_value.copyWith(metadata: value) as $Val);
});
}
}
/// @nodoc
@@ -912,7 +910,10 @@ abstract class _$$ProcessImplCopyWith<$Res> implements $ProcessCopyWith<$Res> {
__$$ProcessImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int uid, String network, String source, String target});
$Res call({int id, Metadata metadata});
@override
$MetadataCopyWith<$Res> get metadata;
}
/// @nodoc
@@ -926,28 +927,18 @@ class __$$ProcessImplCopyWithImpl<$Res>
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uid = null,
Object? network = null,
Object? source = null,
Object? target = null,
Object? id = null,
Object? metadata = null,
}) {
return _then(_$ProcessImpl(
uid: null == uid
? _value.uid
: uid // ignore: cast_nullable_to_non_nullable
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
network: null == network
? _value.network
: network // ignore: cast_nullable_to_non_nullable
as String,
source: null == source
? _value.source
: source // ignore: cast_nullable_to_non_nullable
as String,
target: null == target
? _value.target
: target // ignore: cast_nullable_to_non_nullable
as String,
metadata: null == metadata
? _value.metadata
: metadata // ignore: cast_nullable_to_non_nullable
as Metadata,
));
}
}
@@ -955,27 +946,19 @@ class __$$ProcessImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$ProcessImpl implements _Process {
const _$ProcessImpl(
{required this.uid,
required this.network,
required this.source,
required this.target});
const _$ProcessImpl({required this.id, required this.metadata});
factory _$ProcessImpl.fromJson(Map<String, dynamic> json) =>
_$$ProcessImplFromJson(json);
@override
final int uid;
final int id;
@override
final String network;
@override
final String source;
@override
final String target;
final Metadata metadata;
@override
String toString() {
return 'Process(uid: $uid, network: $network, source: $source, target: $target)';
return 'Process(id: $id, metadata: $metadata)';
}
@override
@@ -983,15 +966,14 @@ class _$ProcessImpl implements _Process {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProcessImpl &&
(identical(other.uid, uid) || other.uid == uid) &&
(identical(other.network, network) || other.network == network) &&
(identical(other.source, source) || other.source == source) &&
(identical(other.target, target) || other.target == target));
(identical(other.id, id) || other.id == id) &&
(identical(other.metadata, metadata) ||
other.metadata == metadata));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, uid, network, source, target);
int get hashCode => Object.hash(runtimeType, id, metadata);
@JsonKey(ignore: true)
@override
@@ -1009,27 +991,175 @@ class _$ProcessImpl implements _Process {
abstract class _Process implements Process {
const factory _Process(
{required final int uid,
required final String network,
required final String source,
required final String target}) = _$ProcessImpl;
{required final int id,
required final Metadata metadata}) = _$ProcessImpl;
factory _Process.fromJson(Map<String, dynamic> json) = _$ProcessImpl.fromJson;
@override
int get uid;
int get id;
@override
String get network;
@override
String get source;
@override
String get target;
Metadata get metadata;
@override
@JsonKey(ignore: true)
_$$ProcessImplCopyWith<_$ProcessImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ProcessMapItem _$ProcessMapItemFromJson(Map<String, dynamic> json) {
return _ProcessMapItem.fromJson(json);
}
/// @nodoc
mixin _$ProcessMapItem {
int get id => throw _privateConstructorUsedError;
String? get value => throw _privateConstructorUsedError;
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProcessMapItemCopyWith<ProcessMapItem> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $ProcessMapItemCopyWith<$Res> {
factory $ProcessMapItemCopyWith(
ProcessMapItem value, $Res Function(ProcessMapItem) then) =
_$ProcessMapItemCopyWithImpl<$Res, ProcessMapItem>;
@useResult
$Res call({int id, String? value});
}
/// @nodoc
class _$ProcessMapItemCopyWithImpl<$Res, $Val extends ProcessMapItem>
implements $ProcessMapItemCopyWith<$Res> {
_$ProcessMapItemCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? value = freezed,
}) {
return _then(_value.copyWith(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: freezed == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$ProcessMapItemImplCopyWith<$Res>
implements $ProcessMapItemCopyWith<$Res> {
factory _$$ProcessMapItemImplCopyWith(_$ProcessMapItemImpl value,
$Res Function(_$ProcessMapItemImpl) then) =
__$$ProcessMapItemImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({int id, String? value});
}
/// @nodoc
class __$$ProcessMapItemImplCopyWithImpl<$Res>
extends _$ProcessMapItemCopyWithImpl<$Res, _$ProcessMapItemImpl>
implements _$$ProcessMapItemImplCopyWith<$Res> {
__$$ProcessMapItemImplCopyWithImpl(
_$ProcessMapItemImpl _value, $Res Function(_$ProcessMapItemImpl) _then)
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? id = null,
Object? value = freezed,
}) {
return _then(_$ProcessMapItemImpl(
id: null == id
? _value.id
: id // ignore: cast_nullable_to_non_nullable
as int,
value: freezed == value
? _value.value
: value // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$ProcessMapItemImpl implements _ProcessMapItem {
const _$ProcessMapItemImpl({required this.id, this.value});
factory _$ProcessMapItemImpl.fromJson(Map<String, dynamic> json) =>
_$$ProcessMapItemImplFromJson(json);
@override
final int id;
@override
final String? value;
@override
String toString() {
return 'ProcessMapItem(id: $id, value: $value)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$ProcessMapItemImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.value, value) || other.value == value));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(runtimeType, id, value);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ProcessMapItemImplCopyWith<_$ProcessMapItemImpl> get copyWith =>
__$$ProcessMapItemImplCopyWithImpl<_$ProcessMapItemImpl>(
this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$ProcessMapItemImplToJson(
this,
);
}
}
abstract class _ProcessMapItem implements ProcessMapItem {
const factory _ProcessMapItem({required final int id, final String? value}) =
_$ProcessMapItemImpl;
factory _ProcessMapItem.fromJson(Map<String, dynamic> json) =
_$ProcessMapItemImpl.fromJson;
@override
int get id;
@override
String? get value;
@override
@JsonKey(ignore: true)
_$$ProcessMapItemImplCopyWith<_$ProcessMapItemImpl> get copyWith =>
throw _privateConstructorUsedError;
}
ExternalProvider _$ExternalProviderFromJson(Map<String, dynamic> json) {
return _ExternalProvider.fromJson(json);
}

View File

@@ -56,6 +56,7 @@ const _$MessageTypeEnumMap = {
MessageType.delay: 'delay',
MessageType.process: 'process',
MessageType.now: 'now',
MessageType.request: 'request',
};
_$DelayImpl _$$DelayImplFromJson(Map<String, dynamic> json) => _$DelayImpl(
@@ -81,18 +82,27 @@ Map<String, dynamic> _$$NowImplToJson(_$NowImpl instance) => <String, dynamic>{
_$ProcessImpl _$$ProcessImplFromJson(Map<String, dynamic> json) =>
_$ProcessImpl(
uid: (json['uid'] as num).toInt(),
network: json['network'] as String,
source: json['source'] as String,
target: json['target'] as String,
id: (json['id'] as num).toInt(),
metadata: Metadata.fromJson(json['metadata'] as Map<String, dynamic>),
);
Map<String, dynamic> _$$ProcessImplToJson(_$ProcessImpl instance) =>
<String, dynamic>{
'uid': instance.uid,
'network': instance.network,
'source': instance.source,
'target': instance.target,
'id': instance.id,
'metadata': instance.metadata,
};
_$ProcessMapItemImpl _$$ProcessMapItemImplFromJson(Map<String, dynamic> json) =>
_$ProcessMapItemImpl(
id: (json['id'] as num).toInt(),
value: json['value'] as String?,
);
Map<String, dynamic> _$$ProcessMapItemImplToJson(
_$ProcessMapItemImpl instance) =>
<String, dynamic>{
'id': instance.id,
'value': instance.value,
};
_$ExternalProviderImpl _$$ExternalProviderImplFromJson(

View File

@@ -24,10 +24,10 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
id: json['id'] as String?,
label: json['label'] as String?,
url: json['url'] as String?,
currentGroupName: json['currentGroupName'] as String?,
userInfo: json['userInfo'] == null
? null
: UserInfo.fromJson(json['userInfo'] as Map<String, dynamic>),
proxyName: json['proxyName'] as String?,
lastUpdateDate: json['lastUpdateDate'] == null
? null
: DateTime.parse(json['lastUpdateDate'] as String),
@@ -43,7 +43,7 @@ Profile _$ProfileFromJson(Map<String, dynamic> json) => Profile(
Map<String, dynamic> _$ProfileToJson(Profile instance) => <String, dynamic>{
'id': instance.id,
'label': instance.label,
'proxyName': instance.proxyName,
'currentGroupName': instance.currentGroupName,
'url': instance.url,
'lastUpdateDate': instance.lastUpdateDate?.toIso8601String(),
'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds,

View File

@@ -1583,6 +1583,7 @@ abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState {
/// @nodoc
mixin _$ProxiesSelectorState {
List<String> get groupNames => throw _privateConstructorUsedError;
String? get currentGroupName => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
$ProxiesSelectorStateCopyWith<ProxiesSelectorState> get copyWith =>
@@ -1595,7 +1596,7 @@ abstract class $ProxiesSelectorStateCopyWith<$Res> {
$Res Function(ProxiesSelectorState) then) =
_$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>;
@useResult
$Res call({List<String> groupNames});
$Res call({List<String> groupNames, String? currentGroupName});
}
/// @nodoc
@@ -1613,12 +1614,17 @@ class _$ProxiesSelectorStateCopyWithImpl<$Res,
@override
$Res call({
Object? groupNames = null,
Object? currentGroupName = freezed,
}) {
return _then(_value.copyWith(
groupNames: null == groupNames
? _value.groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
@@ -1631,7 +1637,7 @@ abstract class _$$ProxiesSelectorStateImplCopyWith<$Res>
__$$ProxiesSelectorStateImplCopyWithImpl<$Res>;
@override
@useResult
$Res call({List<String> groupNames});
$Res call({List<String> groupNames, String? currentGroupName});
}
/// @nodoc
@@ -1646,12 +1652,17 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? groupNames = null,
Object? currentGroupName = freezed,
}) {
return _then(_$ProxiesSelectorStateImpl(
groupNames: null == groupNames
? _value._groupNames
: groupNames // ignore: cast_nullable_to_non_nullable
as List<String>,
currentGroupName: freezed == currentGroupName
? _value.currentGroupName
: currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
@@ -1659,7 +1670,8 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res>
/// @nodoc
class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
const _$ProxiesSelectorStateImpl({required final List<String> groupNames})
const _$ProxiesSelectorStateImpl(
{required final List<String> groupNames, required this.currentGroupName})
: _groupNames = groupNames;
final List<String> _groupNames;
@@ -1670,9 +1682,12 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
return EqualUnmodifiableListView(_groupNames);
}
@override
final String? currentGroupName;
@override
String toString() {
return 'ProxiesSelectorState(groupNames: $groupNames)';
return 'ProxiesSelectorState(groupNames: $groupNames, currentGroupName: $currentGroupName)';
}
@override
@@ -1681,12 +1696,14 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
(other.runtimeType == runtimeType &&
other is _$ProxiesSelectorStateImpl &&
const DeepCollectionEquality()
.equals(other._groupNames, _groupNames));
.equals(other._groupNames, _groupNames) &&
(identical(other.currentGroupName, currentGroupName) ||
other.currentGroupName == currentGroupName));
}
@override
int get hashCode => Object.hash(
runtimeType, const DeepCollectionEquality().hash(_groupNames));
int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_groupNames), currentGroupName);
@JsonKey(ignore: true)
@override
@@ -1699,11 +1716,14 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState {
abstract class _ProxiesSelectorState implements ProxiesSelectorState {
const factory _ProxiesSelectorState(
{required final List<String> groupNames}) = _$ProxiesSelectorStateImpl;
{required final List<String> groupNames,
required final String? currentGroupName}) = _$ProxiesSelectorStateImpl;
@override
List<String> get groupNames;
@override
String? get currentGroupName;
@override
@JsonKey(ignore: true)
_$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl>
get copyWith => throw _privateConstructorUsedError;

View File

@@ -62,7 +62,7 @@ class UserInfo {
class Profile {
String id;
String? label;
String? proxyName;
String? currentGroupName;
String? url;
DateTime? lastUpdateDate;
Duration autoUpdateDuration;
@@ -74,8 +74,8 @@ class Profile {
String? id,
this.label,
this.url,
this.currentGroupName,
this.userInfo,
this.proxyName,
this.lastUpdateDate,
SelectedMap? selectedMap,
Duration? autoUpdateDuration,
@@ -134,6 +134,7 @@ class Profile {
return _$ProfileFromJson(json);
}
@override
bool operator ==(Object other) =>
identical(this, other) ||
@@ -141,34 +142,31 @@ class Profile {
runtimeType == other.runtimeType &&
id == other.id &&
label == other.label &&
proxyName == other.proxyName &&
currentGroupName == other.currentGroupName &&
url == other.url &&
lastUpdateDate == other.lastUpdateDate &&
autoUpdateDuration == other.autoUpdateDuration &&
userInfo == other.userInfo &&
autoUpdate == other.autoUpdate;
autoUpdate == other.autoUpdate &&
selectedMap == other.selectedMap;
@override
int get hashCode =>
id.hashCode ^
label.hashCode ^
proxyName.hashCode ^
currentGroupName.hashCode ^
url.hashCode ^
lastUpdateDate.hashCode ^
autoUpdateDuration.hashCode ^
userInfo.hashCode ^
autoUpdate.hashCode;
@override
String toString() {
return 'Profile{id: $id, label: $label, proxyName: $proxyName, url: $url, lastUpdateDate: $lastUpdateDate, autoUpdateDuration: $autoUpdateDuration, userInfo: $userInfo, autoUpdate: $autoUpdate}';
}
autoUpdate.hashCode ^
selectedMap.hashCode;
Profile copyWith({
String? label,
String? url,
UserInfo? userInfo,
String? groupName,
String? currentGroupName,
String? proxyName,
DateTime? lastUpdateDate,
Duration? autoUpdateDuration,
@@ -179,7 +177,7 @@ class Profile {
id: id,
label: label ?? this.label,
url: url ?? this.url,
proxyName: proxyName ?? this.proxyName,
currentGroupName: currentGroupName ?? this.currentGroupName,
userInfo: userInfo ?? this.userInfo,
selectedMap: selectedMap ?? this.selectedMap,
lastUpdateDate: lastUpdateDate ?? this.lastUpdateDate,

View File

@@ -94,6 +94,7 @@ class ProxiesCardSelectorState with _$ProxiesCardSelectorState {
class ProxiesSelectorState with _$ProxiesSelectorState {
const factory ProxiesSelectorState({
required List<String> groupNames,
required String? currentGroupName,
}) = _ProxiesSelectorState;
}

View File

@@ -18,6 +18,11 @@ class App {
methodChannel = const MethodChannel("app");
methodChannel!.setMethodCallHandler((call) async {
switch (call.method) {
case "exit":
if (onExit != null) {
await onExit!();
}
break;
case "gc":
clashCore.requestGc();
break;
@@ -63,9 +68,9 @@ class App {
});
}
Future<String?> getPackageName(Metadata metadata) async {
return await methodChannel?.invokeMethod<String>("getPackageName", {
"data": json.encode(metadata),
Future<String?> resolverProcess(Process process) async {
return await methodChannel?.invokeMethod<String>("resolverProcess", {
"data": json.encode(process),
});
}
}

View File

@@ -56,10 +56,6 @@ class Proxy extends ProxyPlatform {
return await methodChannel.invokeMethod<bool?>("SetProtect", {'fd': fd});
}
Future<int?> getRunTimeStamp() async {
return await methodChannel.invokeMethod<int?>("GetRunTimeStamp");
}
Future<bool?> startForeground({
required String title,
required String content,
@@ -80,14 +76,7 @@ class Proxy extends ProxyPlatform {
}
updateStartTime() async {
startTime = await getRunTime();
}
Future<DateTime?> getRunTime() async {
final runTimeStamp = await getRunTimeStamp();
return runTimeStamp != null
? DateTime.fromMillisecondsSinceEpoch(runTimeStamp)
: null;
startTime = clashCore.getRunTime();
}
}

View File

@@ -17,7 +17,6 @@ class GlobalState {
Function? updateCurrentDelayDebounce;
PageController? pageController;
final navigatorKey = GlobalKey<NavigatorState>();
final Map<int, String?> packageNameMap = {};
late AppController appController;
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
List<Function> updateFunctionLists = [];
@@ -212,30 +211,30 @@ class GlobalState {
required String message,
SnackBarAction? action,
}) {
final width = context.width;
EdgeInsets margin;
if (width < 600) {
margin = const EdgeInsets.only(
bottom: 96,
right: 16,
left: 16,
);
} else {
margin = EdgeInsets.only(
bottom: 16,
left: 16,
right: width - 316,
);
}
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
action: action,
content: Text(message),
behavior: SnackBarBehavior.floating,
duration: const Duration(milliseconds: 1500),
margin: margin,
),
);
// final width = context.width;
// EdgeInsets margin;
// if (width < 600) {
// margin = const EdgeInsets.only(
// bottom: 96,
// right: 16,
// left: 16,
// );
// } else {
// margin = EdgeInsets.only(
// bottom: 16,
// left: 16,
// right: width - 316,
// );
// }
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// action: action,
// content: Text(message),
// behavior: SnackBarBehavior.floating,
// duration: const Duration(milliseconds: 1500),
// margin: margin,
// ),
// );
}
Future<T?> safeRun<T>(

View File

@@ -56,9 +56,20 @@ class _ClashMessageContainerState extends State<ClashMessageContainer>
}
@override
void onProcess(Metadata metadata) async {
var packageName = await app?.getPackageName(metadata);
globalState.packageNameMap[metadata.uid] = packageName;
super.onProcess(metadata);
void onProcess(Process process) async {
var packageName = await app?.resolverProcess(process);
clashCore.setProcessMap(
ProcessMapItem(
id: process.id,
value: packageName,
),
);
super.onProcess(process);
}
@override
void onRequest(Connection connection) async {
globalState.appController.appState.addRequest(connection);
super.onRequest(connection);
}
}

View File

@@ -122,6 +122,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
return floatingActionButton ?? Container();
},
),
resizeToAvoidBottomInset: true,
appBar: PreferredSize(
preferredSize: const Size.fromHeight(kToolbarHeight),
child: Stack(