Files
MWClash/lib/state.dart
2025-05-01 20:45:23 +08:00

292 lines
8.0 KiB
Dart

import 'dart:async';
import 'package:animations/animations.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/enum/enum.dart';
import 'package:fl_clash/l10n/l10n.dart';
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:material_color_utilities/palettes/core_palette.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:url_launcher/url_launcher.dart';
import 'common/common.dart';
import 'controller.dart';
import 'models/models.dart';
typedef UpdateTasks = List<FutureOr Function()>;
class GlobalState {
static GlobalState? _instance;
Map<CacheTag, double> cacheScrollPosition = {};
Map<CacheTag, FixedMap<String, double>> cacheHeightMap = {};
bool isService = false;
Timer? timer;
Timer? groupsUpdateTimer;
late Config config;
late AppState appState;
bool isPre = true;
String? coreSHA256;
late PackageInfo packageInfo;
Function? updateCurrentDelayDebounce;
late Measure measure;
late CommonTheme theme;
late Color accentColor;
CorePalette? corePalette;
DateTime? startTime;
UpdateTasks tasks = [];
final navigatorKey = GlobalKey<NavigatorState>();
late AppController appController;
GlobalKey<CommonScaffoldState> homeScaffoldKey = GlobalKey();
bool get isStart => startTime != null && startTime!.isBeforeNow;
GlobalState._internal();
factory GlobalState() {
_instance ??= GlobalState._internal();
return _instance!;
}
initApp(int version) async {
coreSHA256 = const String.fromEnvironment("CORE_SHA256");
isPre = const String.fromEnvironment("APP_ENV") != 'stable';
appState = AppState(
version: version,
viewSize: Size.zero,
requests: FixedList(maxLength),
logs: FixedList(maxLength),
traffics: FixedList(30),
totalTraffic: Traffic(),
);
await _initDynamicColor();
await init();
}
_initDynamicColor() async {
try {
corePalette = await DynamicColorPlugin.getCorePalette();
accentColor = await DynamicColorPlugin.getAccentColor() ??
Color(defaultPrimaryColor);
} catch (_) {}
}
init() async {
packageInfo = await PackageInfo.fromPlatform();
config = await preferences.getConfig() ??
Config(
themeProps: defaultThemeProps,
);
await globalState.migrateOldData(config);
await AppLocalizations.load(
utils.getLocaleForString(config.appSetting.locale) ??
WidgetsBinding.instance.platformDispatcher.locale,
);
}
String get ua => config.patchClashConfig.globalUa ?? packageInfo.ua;
startUpdateTasks([UpdateTasks? tasks]) async {
if (timer != null && timer!.isActive == true) return;
if (tasks != null) {
this.tasks = tasks;
}
await executorUpdateTask();
timer = Timer(const Duration(seconds: 1), () async {
startUpdateTasks();
});
}
executorUpdateTask() async {
for (final task in tasks) {
await task();
}
timer = null;
}
stopUpdateTasks() {
if (timer == null || timer?.isActive == false) return;
timer?.cancel();
timer = null;
}
handleStart([UpdateTasks? tasks]) async {
startTime ??= DateTime.now();
await clashCore.startListener();
await service?.startVpn();
startUpdateTasks(tasks);
}
Future updateStartTime() async {
startTime = await clashLib?.getRunTime();
}
Future handleStop() async {
startTime = null;
await clashCore.stopListener();
await service?.stopVpn();
stopUpdateTasks();
}
Future<bool?> showMessage({
String? title,
required InlineSpan message,
String? confirmText,
bool cancelable = true,
}) async {
return await showCommonDialog<bool>(
child: Builder(
builder: (context) {
return CommonDialog(
title: title ?? appLocalizations.tip,
actions: [
if (cancelable)
TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(appLocalizations.cancel),
),
TextButton(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(confirmText ?? appLocalizations.confirm),
)
],
child: Container(
width: 300,
constraints: const BoxConstraints(maxHeight: 200),
child: SingleChildScrollView(
child: SelectableText.rich(
TextSpan(
style: Theme.of(context).textTheme.labelLarge,
children: [message],
),
style: const TextStyle(
overflow: TextOverflow.visible,
),
),
),
),
);
},
),
);
}
Future<T?> showCommonDialog<T>({
required Widget child,
bool dismissible = true,
}) async {
return await showModal<T>(
context: navigatorKey.currentState!.context,
configuration: FadeScaleTransitionConfiguration(
barrierColor: Colors.black38,
barrierDismissible: dismissible,
),
builder: (_) => child,
filter: commonFilter,
);
}
Future<T?> safeRun<T>(
FutureOr<T> Function() futureFunction, {
String? title,
bool silence = true,
}) async {
try {
final res = await futureFunction();
return res;
} catch (e) {
commonPrint.log("$e");
if (silence) {
showNotifier(e.toString());
} else {
showMessage(
title: title ?? appLocalizations.tip,
message: TextSpan(
text: e.toString(),
),
);
}
return null;
}
}
showNotifier(String text) {
if (text.isEmpty) {
return;
}
navigatorKey.currentContext?.showNotifier(text);
}
openUrl(String url) async {
final res = await showMessage(
message: TextSpan(text: url),
title: appLocalizations.externalLink,
confirmText: appLocalizations.go,
);
if (res != true) {
return;
}
launchUrl(Uri.parse(url));
}
Future<void> migrateOldData(Config config) async {
final clashConfig = await preferences.getClashConfig();
if (clashConfig != null) {
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,
);
}
getUpdateConfigParams([bool? isPatch]) {
final currentProfile = config.currentProfile;
final clashConfig = config.patchClashConfig;
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,
),
);
}
}
final globalState = GlobalState();