Add android separates the core process

Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Update go version

Optimize more details
This commit is contained in:
chen08209
2025-07-31 17:09:18 +08:00
parent e956373ef4
commit ed7868282a
276 changed files with 85260 additions and 80090 deletions

View File

@@ -1,12 +1,14 @@
import 'dart:async';
import 'dart:convert';
import 'dart:ffi' show Pointer;
import 'dart:io';
import 'dart:isolate';
import 'package:animations/animations.dart';
import 'package:dio/dio.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/clash/clash.dart';
import 'package:fl_clash/common/theme.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/l10n/l10n.dart';
import 'package:fl_clash/plugins/service.dart';
@@ -16,6 +18,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_js/flutter_js.dart';
import 'package:material_color_utilities/palettes/core_palette.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:path/path.dart';
import 'package:url_launcher/url_launcher.dart';
import 'common/common.dart';
@@ -27,10 +30,6 @@ typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState {
static GlobalState? _instance;
Map<CacheTag, FixedMap<String, double>> computeHeightMapCache = {};
// Map<CacheTag, double> computeScrollPositionCache = {};
// final Map<String, double> scrollPositionCache = {};
bool isService = false;
Timer? timer;
Timer? groupsUpdateTimer;
late Config config;
@@ -47,9 +46,9 @@ class GlobalState {
UpdateTasks tasks = [];
final navigatorKey = GlobalKey<NavigatorState>();
AppController? _appController;
// GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
bool isInit = false;
bool isUserDisconnected = false;
bool isService = false;
bool get isStart => startTime != null && startTime!.isBeforeNow;
@@ -82,22 +81,54 @@ class GlobalState {
);
await _initDynamicColor();
await init();
await window?.init(version);
_shakingStore();
}
Future<void> _shakingStore() async {
final profileIds = config.profiles.map((item) => item.id);
final providersRootPath = await appPath.getProvidersRootPath();
final profilesRootPath = await appPath.profilesPath;
Isolate.run(() async {
final profilesDir = Directory(profilesRootPath);
final providersDir = Directory(providersRootPath);
final List<FileSystemEntity> entities = [];
if (await profilesDir.exists()) {
entities.addAll(
profilesDir.listSync().where(
(item) => !item.path.contains('providers'),
),
);
}
if (await providersDir.exists()) {
entities.addAll(providersDir.listSync());
}
final deleteFutures = entities.map((entity) async {
if (!profileIds.contains(basenameWithoutExtension(entity.path))) {
final res = await coreController.deleteFile(entity.path);
if (res.isNotEmpty) {
throw res;
}
}
return true;
});
await Future.wait(deleteFutures);
});
}
Future<void> _initDynamicColor() async {
try {
corePalette = await DynamicColorPlugin.getCorePalette();
accentColor = await DynamicColorPlugin.getAccentColor() ??
accentColor =
await DynamicColorPlugin.getAccentColor() ??
Color(defaultPrimaryColor);
} catch (_) {}
}
Future<void> init() async {
packageInfo = await PackageInfo.fromPlatform();
config = await preferences.getConfig() ??
Config(
themeProps: defaultThemeProps,
);
config =
await preferences.getConfig() ?? Config(themeProps: defaultThemeProps);
await globalState.migrateOldData(config);
await AppLocalizations.load(
utils.getLocaleForString(config.appSetting.locale) ??
@@ -112,6 +143,9 @@ class GlobalState {
if (tasks != null) {
this.tasks = tasks;
}
if (this.tasks.isEmpty) {
return;
}
await executorUpdateTask();
timer = Timer(const Duration(seconds: 1), () async {
startUpdateTasks();
@@ -133,29 +167,33 @@ class GlobalState {
Future<void> handleStart([UpdateTasks? tasks]) async {
startTime ??= DateTime.now();
await clashCore.startListener();
await service?.startVpn();
await coreController.startListener();
await service?.start();
startUpdateTasks(tasks);
}
Future updateStartTime() async {
startTime = await clashLib?.getRunTime();
startTime = await service?.getRunTime();
}
Future handleStop() async {
startTime = null;
await clashCore.stopListener();
await service?.stopVpn();
await coreController.stopListener();
await service?.stop();
stopUpdateTasks();
}
Future<bool?> showMessage({
String? title,
required InlineSpan message,
BuildContext? context,
String? title,
String? confirmText,
bool cancelable = true,
bool? dismissible,
}) async {
return await showCommonDialog<bool>(
context: context,
dismissible: dismissible,
child: Builder(
builder: (context) {
return CommonDialog(
@@ -173,7 +211,7 @@ class GlobalState {
Navigator.of(context).pop(true);
},
child: Text(confirmText ?? appLocalizations.confirm),
)
),
],
child: Container(
width: 300,
@@ -184,9 +222,7 @@ class GlobalState {
style: Theme.of(context).textTheme.labelLarge,
children: [message],
),
style: const TextStyle(
overflow: TextOverflow.visible,
),
style: const TextStyle(overflow: TextOverflow.visible),
),
),
),
@@ -196,36 +232,34 @@ class GlobalState {
);
}
// Future<Map<String, dynamic>> getProfileMap(String id) async {
// final profilePath = await appPath.getProfilePath(id);
// final res = await Isolate.run<Result<dynamic>>(() async {
// try {
// final file = File(profilePath);
// if (!await file.exists()) {
// return Result.error("");
// }
// final value = await file.readAsString();
// return Result.success(utils.convertYamlNode(loadYaml(value)));
// } catch (e) {
// return Result.error(e.toString());
// }
// });
// if (res.isSuccess) {
// return res.data as Map<String, dynamic>;
// } else {
// throw res.message;
// }
// }
VpnOptions getVpnOptions() {
final vpnProps = config.vpnProps;
final networkProps = config.networkProps;
final port = config.patchClashConfig.mixedPort;
return VpnOptions(
stack: config.patchClashConfig.tun.stack.name,
enable: vpnProps.enable,
systemProxy: networkProps.systemProxy,
port: port,
ipv6: vpnProps.ipv6,
dnsHijacking: vpnProps.dnsHijacking,
accessControl: vpnProps.accessControl,
allowBypass: vpnProps.allowBypass,
bypassDomain: networkProps.bypassDomain,
);
}
Future<T?> showCommonDialog<T>({
required Widget child,
bool dismissible = true,
BuildContext? context,
bool? dismissible,
}) async {
return await showModal<T>(
context: navigatorKey.currentState!.context,
useRootNavigator: false,
context: context ?? globalState.navigatorKey.currentContext!,
configuration: FadeScaleTransitionConfiguration(
barrierColor: Colors.black38,
barrierDismissible: dismissible,
barrierDismissible: dismissible ?? true,
),
builder: (_) => child,
filter: commonFilter,
@@ -254,38 +288,50 @@ class GlobalState {
Future<void> migrateOldData(Config config) async {
final clashConfig = await preferences.getClashConfig();
if (clashConfig != null) {
config = config.copyWith(
patchClashConfig: clashConfig,
);
config = config.copyWith(patchClashConfig: clashConfig);
preferences.clearClashConfig();
preferences.saveConfig(config);
}
}
CoreState getCoreState() {
final currentProfile = config.currentProfile;
return CoreState(
vpnProps: config.vpnProps,
onlyStatisticsProxy: config.appSetting.onlyStatisticsProxy,
currentProfileName: currentProfile?.label ?? currentProfile?.id ?? '',
bypassDomain: config.networkProps.bypassDomain,
);
}
Future<SetupParams> getSetupParams({
required ClashConfig pathConfig,
}) async {
final clashConfig = await patchRawConfig(
patchConfig: pathConfig,
);
Future<SetupParams> getSetupParams() async {
final params = SetupParams(
config: clashConfig,
selectedMap: config.currentProfile?.selectedMap ?? {},
testUrl: config.appSetting.testUrl,
);
return params;
}
Future<void> genConfigFile(ClashConfig pathConfig) async {
final configFilePath = await appPath.configFilePath;
final config = await patchRawConfig(patchConfig: pathConfig);
final res = await Isolate.run<String>(() async {
try {
final res = json.encode(config);
final file = File(configFilePath);
if (!await file.exists()) {
await file.create(recursive: true);
}
await file.writeAsString(res);
return '';
} catch (e) {
return e.toString();
}
});
if (res.isNotEmpty) {
throw res;
}
}
AndroidState getAndroidState() {
return AndroidState(
currentProfileName: config.currentProfile?.label ?? '',
onlyStatisticsProxy: config.appSetting.onlyStatisticsProxy,
stopText: appLocalizations.stop,
crashlytics: config.appSetting.crashlytics,
);
}
Future<Map<String, dynamic>> patchRawConfig({
required ClashConfig patchConfig,
}) async {
@@ -390,7 +436,8 @@ class GlobalState {
if (overrideDns || !isEnableDns) {
final dns = switch (!isEnableDns) {
true => realPatchConfig.dns.copyWith(
nameserver: [...realPatchConfig.dns.nameserver, 'system://']),
nameserver: [...realPatchConfig.dns.nameserver, 'system://'],
),
false => realPatchConfig.dns,
};
rawConfig['dns'] = dns.toJson();
@@ -400,7 +447,7 @@ class GlobalState {
entry.value.splitByMultipleSeparators;
}
}
var rules = [];
List rules = [];
if (rawConfig['rules'] != null) {
rules = rawConfig['rules'];
}
@@ -419,10 +466,7 @@ class GlobalState {
}
Future<Map<String, dynamic>> getProfileConfig(String profileId) async {
final configMap = await switch (clashLibHandler != null) {
true => clashLibHandler!.getConfig(profileId),
false => clashCore.getConfig(profileId),
};
final configMap = await coreController.getConfig(profileId);
configMap['rules'] = configMap['rule'];
configMap.remove('rule');
return configMap;
@@ -464,10 +508,7 @@ class DetectionState {
CancelToken? cancelToken;
final state = ValueNotifier<NetworkDetectionState>(
const NetworkDetectionState(
isLoading: true,
ipInfo: null,
),
const NetworkDetectionState(isLoading: true, ipInfo: null),
);
DetectionState._internal();
@@ -481,9 +522,7 @@ class DetectionState {
debouncer.call(
FunctionTag.checkIp,
_checkIp,
duration: Duration(
milliseconds: 1200,
),
duration: Duration(milliseconds: 1200),
);
}
@@ -504,10 +543,7 @@ class DetectionState {
return;
}
_clearSetTimeoutTimer();
state.value = state.value.copyWith(
isLoading: true,
ipInfo: null,
);
state.value = state.value.copyWith(isLoading: true, ipInfo: null);
_preIsStart = isStart;
if (cancelToken != null) {
cancelToken!.cancel();
@@ -516,26 +552,17 @@ class DetectionState {
cancelToken = CancelToken();
final res = await request.checkIp(cancelToken: cancelToken);
if (res.isError) {
state.value = state.value.copyWith(
isLoading: true,
ipInfo: null,
);
state.value = state.value.copyWith(isLoading: true, ipInfo: null);
return;
}
final ipInfo = res.data;
if (ipInfo != null) {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: ipInfo,
);
state.value = state.value.copyWith(isLoading: false, ipInfo: ipInfo);
return;
}
_clearSetTimeoutTimer();
_setTimeoutTimer = Timer(const Duration(milliseconds: 300), () {
state.value = state.value.copyWith(
isLoading: false,
ipInfo: null,
);
state.value = state.value.copyWith(isLoading: false, ipInfo: null);
});
}