Support override script
Support proxies search Support svg display Optimize config persistence Add some scenes auto close connections Update core Optimize more details
This commit is contained in:
337
lib/state.dart
337
lib/state.dart
@@ -1,5 +1,9 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:ffi' show Pointer;
|
||||
|
||||
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';
|
||||
@@ -9,6 +13,7 @@ import 'package:fl_clash/plugins/service.dart';
|
||||
import 'package:fl_clash/widgets/dialog.dart';
|
||||
import 'package:fl_clash/widgets/scaffold.dart';
|
||||
import 'package:flutter/material.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:url_launcher/url_launcher.dart';
|
||||
@@ -39,11 +44,19 @@ class GlobalState {
|
||||
DateTime? startTime;
|
||||
UpdateTasks tasks = [];
|
||||
final navigatorKey = GlobalKey<NavigatorState>();
|
||||
late AppController appController;
|
||||
AppController? _appController;
|
||||
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
|
||||
bool isInit = false;
|
||||
|
||||
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
||||
|
||||
AppController get appController => _appController!;
|
||||
|
||||
set appController(AppController appController) {
|
||||
_appController = appController;
|
||||
isInit = true;
|
||||
}
|
||||
|
||||
GlobalState._internal();
|
||||
|
||||
factory GlobalState() {
|
||||
@@ -178,6 +191,27 @@ 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;
|
||||
// }
|
||||
// }
|
||||
|
||||
Future<T?> showCommonDialog<T>({
|
||||
required Widget child,
|
||||
bool dismissible = true,
|
||||
@@ -257,35 +291,286 @@ class GlobalState {
|
||||
);
|
||||
}
|
||||
|
||||
getUpdateConfigParams([bool? isPatch]) {
|
||||
final currentProfile = config.currentProfile;
|
||||
final clashConfig = config.patchClashConfig;
|
||||
Future<SetupParams> getSetupParams({
|
||||
required ClashConfig pathConfig,
|
||||
}) async {
|
||||
final clashConfig = await patchRawConfig(
|
||||
patchConfig: pathConfig,
|
||||
);
|
||||
final params = SetupParams(
|
||||
config: clashConfig,
|
||||
selectedMap: config.currentProfile?.selectedMap ?? {},
|
||||
testUrl: config.appSetting.testUrl,
|
||||
);
|
||||
return params;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> patchRawConfig({
|
||||
required ClashConfig patchConfig,
|
||||
}) async {
|
||||
final profile = config.currentProfile;
|
||||
if (profile == null) {
|
||||
return {};
|
||||
}
|
||||
final profileId = profile.id;
|
||||
final configMap = await getProfileConfig(profileId);
|
||||
final rawConfig = await handleEvaluate(configMap);
|
||||
final routeAddress =
|
||||
config.networkProps.routeMode == RouteMode.bypassPrivate
|
||||
? defaultBypassPrivateRouteAddress
|
||||
: clashConfig.tun.routeAddress;
|
||||
return UpdateConfigParams(
|
||||
profileId: config.currentProfileId ?? "",
|
||||
config: clashConfig.copyWith(
|
||||
globalUa: ua,
|
||||
tun: clashConfig.tun.copyWith(
|
||||
autoRoute: routeAddress.isEmpty ? true : false,
|
||||
routeAddress: routeAddress,
|
||||
),
|
||||
rule: currentProfile?.overrideData.runningRule ?? [],
|
||||
),
|
||||
params: ConfigExtendedParams(
|
||||
isPatch: isPatch ?? false,
|
||||
selectedMap: currentProfile?.selectedMap ?? {},
|
||||
overrideDns: config.overrideDns,
|
||||
testUrl: config.appSetting.testUrl,
|
||||
overrideRule:
|
||||
currentProfile?.overrideData.rule.type == OverrideRuleType.override
|
||||
? true
|
||||
: false,
|
||||
),
|
||||
);
|
||||
: patchConfig.tun.routeAddress;
|
||||
final realPatchConfig = !system.isDesktop
|
||||
? patchConfig.copyWith.tun(
|
||||
autoRoute: routeAddress.isEmpty ? true : false,
|
||||
routeAddress: routeAddress,
|
||||
)
|
||||
: patchConfig.copyWith.tun(
|
||||
autoRoute: true,
|
||||
routeAddress: [],
|
||||
);
|
||||
rawConfig["external-controller"] = realPatchConfig.externalController.value;
|
||||
rawConfig["external-ui"] = "";
|
||||
rawConfig["interface-name"] = "";
|
||||
rawConfig["external-ui-url"] = "";
|
||||
rawConfig["tcp-concurrent"] = realPatchConfig.tcpConcurrent;
|
||||
rawConfig["unified-delay"] = realPatchConfig.unifiedDelay;
|
||||
rawConfig["ipv6"] = realPatchConfig.ipv6;
|
||||
rawConfig["log-level"] = realPatchConfig.logLevel.name;
|
||||
rawConfig["port"] = 0;
|
||||
rawConfig["socks-port"] = 0;
|
||||
rawConfig["keep-alive-interval"] = realPatchConfig.keepAliveInterval;
|
||||
rawConfig["mixed-port"] = realPatchConfig.mixedPort;
|
||||
rawConfig["port"] = realPatchConfig.port;
|
||||
rawConfig["socks-port"] = realPatchConfig.socksPort;
|
||||
rawConfig["redir-port"] = realPatchConfig.redirPort;
|
||||
rawConfig["tproxy-port"] = realPatchConfig.tproxyPort;
|
||||
rawConfig["find-process-mode"] = realPatchConfig.findProcessMode.name;
|
||||
rawConfig["allow-lan"] = realPatchConfig.allowLan;
|
||||
rawConfig["mode"] = realPatchConfig.mode.name;
|
||||
if (rawConfig["tun"] == null) {
|
||||
rawConfig["tun"] = {};
|
||||
}
|
||||
rawConfig["tun"]["enable"] = realPatchConfig.tun.enable;
|
||||
rawConfig["tun"]["device"] = realPatchConfig.tun.device;
|
||||
rawConfig["tun"]["dns-hijack"] = realPatchConfig.tun.dnsHijack;
|
||||
rawConfig["tun"]["stack"] = realPatchConfig.tun.stack.name;
|
||||
rawConfig["tun"]["route-address"] = realPatchConfig.tun.routeAddress;
|
||||
rawConfig["tun"]["auto-route"] = realPatchConfig.tun.autoRoute;
|
||||
rawConfig["geodata-loader"] = realPatchConfig.geodataLoader.name;
|
||||
if (rawConfig["sniffer"]?["sniff"] != null) {
|
||||
for (final value in (rawConfig["sniffer"]?["sniff"] as Map).values) {
|
||||
if (value["ports"] != null && value["ports"] is List) {
|
||||
value["ports"] =
|
||||
value["ports"]?.map((item) => item.toString()).toList() ?? [];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rawConfig["profile"] == null) {
|
||||
rawConfig["profile"] = {};
|
||||
}
|
||||
if (rawConfig["proxy-providers"] != null) {
|
||||
final proxyProviders = rawConfig["proxy-providers"] as Map;
|
||||
for (final key in proxyProviders.keys) {
|
||||
final proxyProvider = proxyProviders[key];
|
||||
if (proxyProvider["type"] != "http") {
|
||||
continue;
|
||||
}
|
||||
if (proxyProvider["url"] != null) {
|
||||
proxyProvider["path"] = await appPath.getProvidersFilePath(
|
||||
profile.id,
|
||||
"proxies",
|
||||
proxyProvider["url"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (rawConfig["rule-providers"] != null) {
|
||||
final ruleProviders = rawConfig["rule-providers"] as Map;
|
||||
for (final key in ruleProviders.keys) {
|
||||
final ruleProvider = ruleProviders[key];
|
||||
if (ruleProvider["type"] != "http") {
|
||||
continue;
|
||||
}
|
||||
if (ruleProvider["url"] != null) {
|
||||
ruleProvider["path"] = await appPath.getProvidersFilePath(
|
||||
profile.id,
|
||||
"rules",
|
||||
ruleProvider["url"],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rawConfig["profile"]["store-selected"] = false;
|
||||
rawConfig["geox-url"] = realPatchConfig.geoXUrl.toJson();
|
||||
rawConfig["global-ua"] = realPatchConfig.globalUa;
|
||||
if (rawConfig["hosts"] == null) {
|
||||
rawConfig["hosts"] = {};
|
||||
}
|
||||
for (final host in realPatchConfig.hosts.entries) {
|
||||
rawConfig["hosts"][host.key] = host.value.splitByMultipleSeparators;
|
||||
}
|
||||
final overrideDns = globalState.config.overrideDns;
|
||||
if (overrideDns) {
|
||||
rawConfig["dns"] = realPatchConfig.dns.toJson();
|
||||
rawConfig["dns"]["nameserver-policy"] = {};
|
||||
for (final entry in realPatchConfig.dns.nameserverPolicy.entries) {
|
||||
rawConfig["dns"]["nameserver-policy"][entry.key] =
|
||||
entry.value.splitByMultipleSeparators;
|
||||
}
|
||||
} else {
|
||||
if (rawConfig["dns"] == null) {
|
||||
rawConfig["dns"] = {};
|
||||
}
|
||||
if (rawConfig["dns"]["enable"] != false) {
|
||||
rawConfig["dns"]["enable"] = true;
|
||||
}
|
||||
}
|
||||
var rules = [];
|
||||
if (rawConfig["rules"] != null) {
|
||||
rules = rawConfig["rules"];
|
||||
}
|
||||
rawConfig.remove("rules");
|
||||
|
||||
final overrideData = profile.overrideData;
|
||||
if (overrideData.enable && config.scriptProps.currentScript == null) {
|
||||
if (overrideData.rule.type == OverrideRuleType.override) {
|
||||
rules = overrideData.runningRule;
|
||||
} else {
|
||||
rules = [...overrideData.runningRule, ...rules];
|
||||
}
|
||||
}
|
||||
rawConfig["rule"] = rules;
|
||||
return rawConfig;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> getProfileConfig(String profileId) async {
|
||||
final configMap = await switch (clashLibHandler != null) {
|
||||
true => clashLibHandler!.getConfig(profileId),
|
||||
false => clashCore.getConfig(profileId),
|
||||
};
|
||||
configMap["rules"] = configMap["rule"];
|
||||
configMap.remove("rule");
|
||||
return configMap;
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> handleEvaluate(
|
||||
Map<String, dynamic> config,
|
||||
) async {
|
||||
final currentScript = globalState.config.scriptProps.currentScript;
|
||||
if (currentScript == null) {
|
||||
return config;
|
||||
}
|
||||
if (config["proxy-providers"] == null) {
|
||||
config["proxy-providers"] = {};
|
||||
}
|
||||
final configJs = json.encode(config);
|
||||
final runtime = getJavascriptRuntime();
|
||||
final res = await runtime.evaluateAsync("""
|
||||
${currentScript.content}
|
||||
main($configJs)
|
||||
""");
|
||||
if (res.isError) {
|
||||
throw res.stringResult;
|
||||
}
|
||||
final value = switch (res.rawResult is Pointer) {
|
||||
true => runtime.convertValue<Map<String, dynamic>>(res),
|
||||
false => Map<String, dynamic>.from(res.rawResult),
|
||||
};
|
||||
return value ?? config;
|
||||
}
|
||||
}
|
||||
|
||||
final globalState = GlobalState();
|
||||
|
||||
class DetectionState {
|
||||
static DetectionState? _instance;
|
||||
bool? _preIsStart;
|
||||
Timer? _setTimeoutTimer;
|
||||
CancelToken? cancelToken;
|
||||
|
||||
final state = ValueNotifier<NetworkDetectionState>(
|
||||
const NetworkDetectionState(
|
||||
isTesting: false,
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
),
|
||||
);
|
||||
|
||||
DetectionState._internal();
|
||||
|
||||
factory DetectionState() {
|
||||
_instance ??= DetectionState._internal();
|
||||
return _instance!;
|
||||
}
|
||||
|
||||
startCheck() {
|
||||
debouncer.call(
|
||||
FunctionTag.checkIp,
|
||||
_checkIp,
|
||||
);
|
||||
}
|
||||
|
||||
_checkIp() async {
|
||||
final appState = globalState.appState;
|
||||
final isInit = appState.isInit;
|
||||
if (!isInit) return;
|
||||
final isStart = appState.runTime != null;
|
||||
if (_preIsStart == false &&
|
||||
_preIsStart == isStart &&
|
||||
state.value.ipInfo != null) {
|
||||
return;
|
||||
}
|
||||
_clearSetTimeoutTimer();
|
||||
state.value = state.value.copyWith(
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
_preIsStart = isStart;
|
||||
if (cancelToken != null) {
|
||||
cancelToken!.cancel();
|
||||
cancelToken = null;
|
||||
}
|
||||
cancelToken = CancelToken();
|
||||
try {
|
||||
state.value = state.value.copyWith(
|
||||
isTesting: true,
|
||||
);
|
||||
final ipInfo = await request.checkIp(cancelToken: cancelToken);
|
||||
state.value = state.value.copyWith(
|
||||
isTesting: false,
|
||||
);
|
||||
if (ipInfo != null) {
|
||||
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,
|
||||
);
|
||||
});
|
||||
} catch (e) {
|
||||
if (e.toString() == "cancelled") {
|
||||
state.value = state.value.copyWith(
|
||||
isLoading: true,
|
||||
ipInfo: null,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_clearSetTimeoutTimer() {
|
||||
if (_setTimeoutTimer != null) {
|
||||
_setTimeoutTimer?.cancel();
|
||||
_setTimeoutTimer = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final detectionState = DetectionState();
|
||||
|
||||
Reference in New Issue
Block a user