2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:async';
|
2025-05-02 02:24:12 +08:00
|
|
|
import 'dart:convert';
|
2025-10-14 15:13:52 +08:00
|
|
|
import 'dart:ffi' as ffi;
|
2025-05-02 02:24:12 +08:00
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:animations/animations.dart';
|
2025-04-18 17:50:46 +08:00
|
|
|
import 'package:dynamic_color/dynamic_color.dart';
|
2025-03-12 17:15:31 +08:00
|
|
|
import 'package:fl_clash/common/theme.dart';
|
2025-07-31 17:09:18 +08:00
|
|
|
import 'package:fl_clash/core/core.dart';
|
2024-08-15 16:18:00 +08:00
|
|
|
import 'package:fl_clash/plugins/service.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'package:fl_clash/providers/app.dart';
|
|
|
|
|
import 'package:fl_clash/providers/config.dart';
|
|
|
|
|
import 'package:fl_clash/providers/database.dart';
|
2025-03-12 17:15:31 +08:00
|
|
|
import 'package:fl_clash/widgets/dialog.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'package:fl_clash/widgets/list.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2025-06-07 01:48:34 +08:00
|
|
|
import 'package:flutter/services.dart';
|
2025-05-02 02:24:12 +08:00
|
|
|
import 'package:flutter_js/flutter_js.dart';
|
2025-04-18 17:50:46 +08:00
|
|
|
import 'package:material_color_utilities/palettes/core_palette.dart';
|
2024-07-02 08:08:31 +08:00
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
2024-07-21 21:51:56 +08:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-11-09 20:17:57 +08:00
|
|
|
import 'common/common.dart';
|
2025-12-16 11:23:09 +08:00
|
|
|
import 'database/database.dart';
|
|
|
|
|
import 'l10n/l10n.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'models/models.dart';
|
|
|
|
|
|
2025-01-13 19:08:17 +08:00
|
|
|
typedef UpdateTasks = List<FutureOr Function()>;
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
class GlobalState {
|
2025-02-09 18:39:38 +08:00
|
|
|
static GlobalState? _instance;
|
2025-12-16 11:23:09 +08:00
|
|
|
final navigatorKey = GlobalKey<NavigatorState>();
|
2024-04-30 23:38:49 +08:00
|
|
|
Timer? timer;
|
2025-03-12 17:15:31 +08:00
|
|
|
bool isPre = true;
|
2025-12-16 11:23:09 +08:00
|
|
|
late final String coreSHA256;
|
|
|
|
|
late final PackageInfo packageInfo;
|
2024-05-10 10:11:27 +08:00
|
|
|
Function? updateCurrentDelayDebounce;
|
2024-09-05 08:59:45 +08:00
|
|
|
late Measure measure;
|
2025-03-12 17:15:31 +08:00
|
|
|
late CommonTheme theme;
|
2025-04-18 17:50:46 +08:00
|
|
|
late Color accentColor;
|
2025-12-16 11:23:09 +08:00
|
|
|
bool needInitStatus = true;
|
2025-04-18 17:50:46 +08:00
|
|
|
CorePalette? corePalette;
|
2024-08-15 16:18:00 +08:00
|
|
|
DateTime? startTime;
|
2025-01-13 19:08:17 +08:00
|
|
|
UpdateTasks tasks = [];
|
2025-10-14 15:13:52 +08:00
|
|
|
SetupState? lastSetupState;
|
|
|
|
|
VpnState? lastVpnState;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-08-15 16:18:00 +08:00
|
|
|
bool get isStart => startTime != null && startTime!.isBeforeNow;
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
GlobalState._internal();
|
|
|
|
|
|
|
|
|
|
factory GlobalState() {
|
|
|
|
|
_instance ??= GlobalState._internal();
|
|
|
|
|
return _instance!;
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<ProviderContainer> init(int version) async {
|
2025-06-07 01:48:34 +08:00
|
|
|
coreSHA256 = const String.fromEnvironment('CORE_SHA256');
|
|
|
|
|
isPre = const String.fromEnvironment('APP_ENV') != 'stable';
|
2025-04-18 17:50:46 +08:00
|
|
|
await _initDynamicColor();
|
2025-12-16 11:23:09 +08:00
|
|
|
return await _initData(version);
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
Future<void> _initDynamicColor() async {
|
2025-04-18 17:50:46 +08:00
|
|
|
try {
|
|
|
|
|
corePalette = await DynamicColorPlugin.getCorePalette();
|
2025-07-31 17:09:18 +08:00
|
|
|
accentColor =
|
|
|
|
|
await DynamicColorPlugin.getAccentColor() ??
|
2025-04-18 17:50:46 +08:00
|
|
|
Color(defaultPrimaryColor);
|
|
|
|
|
} catch (_) {}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<ProviderContainer> _initData(int version) async {
|
|
|
|
|
final appState = AppState(
|
|
|
|
|
brightness: WidgetsBinding.instance.platformDispatcher.platformBrightness,
|
|
|
|
|
version: version,
|
|
|
|
|
viewSize: Size.zero,
|
|
|
|
|
requests: FixedList(maxLength),
|
|
|
|
|
logs: FixedList(maxLength),
|
|
|
|
|
traffics: FixedList(30),
|
|
|
|
|
totalTraffic: Traffic(),
|
|
|
|
|
systemUiOverlayStyle: const SystemUiOverlayStyle(),
|
|
|
|
|
);
|
|
|
|
|
final appStateOverrides = buildAppStateOverrides(appState);
|
2025-02-09 18:39:38 +08:00
|
|
|
packageInfo = await PackageInfo.fromPlatform();
|
2025-12-16 11:23:09 +08:00
|
|
|
final configMap = await preferences.getConfigMap();
|
|
|
|
|
final config = await migration.migrationIfNeeded(
|
|
|
|
|
configMap,
|
|
|
|
|
sync: (data) async {
|
|
|
|
|
final newConfigMap = data.configMap;
|
|
|
|
|
final config = Config.realFromJson(newConfigMap);
|
|
|
|
|
await Future.wait([
|
|
|
|
|
database.restore(data.profiles, data.scripts, data.rules, data.links),
|
|
|
|
|
preferences.saveConfig(config),
|
|
|
|
|
]);
|
|
|
|
|
return config;
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
final configOverrides = buildConfigOverrides(config);
|
|
|
|
|
final container = ProviderContainer(
|
|
|
|
|
overrides: [...appStateOverrides, ...configOverrides],
|
|
|
|
|
);
|
|
|
|
|
final profiles = await database.profilesDao.all().get();
|
|
|
|
|
container.read(profilesProvider.notifier).setAndReorder(profiles);
|
2025-02-09 18:39:38 +08:00
|
|
|
await AppLocalizations.load(
|
2025-12-16 11:23:09 +08:00
|
|
|
utils.getLocaleForString(config.appSettingProps.locale) ??
|
2025-02-09 18:39:38 +08:00
|
|
|
WidgetsBinding.instance.platformDispatcher.locale,
|
|
|
|
|
);
|
2025-12-16 11:23:09 +08:00
|
|
|
await window?.init(version, config.windowProps);
|
|
|
|
|
return container;
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
Future<void> startUpdateTasks([UpdateTasks? tasks]) async {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (timer != null && timer!.isActive == true) return;
|
2025-01-13 19:08:17 +08:00
|
|
|
if (tasks != null) {
|
|
|
|
|
this.tasks = tasks;
|
|
|
|
|
}
|
2025-07-31 17:09:18 +08:00
|
|
|
if (this.tasks.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-01-13 19:08:17 +08:00
|
|
|
await executorUpdateTask();
|
|
|
|
|
timer = Timer(const Duration(seconds: 1), () async {
|
|
|
|
|
startUpdateTasks();
|
2024-04-30 23:38:49 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
Future<void> executorUpdateTask() async {
|
2025-01-13 19:08:17 +08:00
|
|
|
for (final task in tasks) {
|
|
|
|
|
await task();
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
timer = null;
|
2025-01-13 19:08:17 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
void stopUpdateTasks() {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (timer == null || timer?.isActive == false) return;
|
|
|
|
|
timer?.cancel();
|
2025-01-13 19:08:17 +08:00
|
|
|
timer = null;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
Future<void> handleStart([UpdateTasks? tasks]) async {
|
2025-02-09 18:39:38 +08:00
|
|
|
startTime ??= DateTime.now();
|
2025-07-31 17:09:18 +08:00
|
|
|
await coreController.startListener();
|
|
|
|
|
await service?.start();
|
2025-01-13 19:08:17 +08:00
|
|
|
startUpdateTasks(tasks);
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
2025-01-13 19:08:17 +08:00
|
|
|
Future updateStartTime() async {
|
2025-07-31 17:09:18 +08:00
|
|
|
startTime = await service?.getRunTime();
|
2024-08-15 16:18:00 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-20 14:32:57 +08:00
|
|
|
Future handleStop() async {
|
2024-08-15 16:18:00 +08:00
|
|
|
startTime = null;
|
2025-07-31 17:09:18 +08:00
|
|
|
await coreController.stopListener();
|
|
|
|
|
await service?.stop();
|
2025-01-13 19:08:17 +08:00
|
|
|
stopUpdateTasks();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
Future<bool?> showMessage({
|
2024-04-30 23:38:49 +08:00
|
|
|
required InlineSpan message,
|
2025-07-31 17:09:18 +08:00
|
|
|
BuildContext? context,
|
|
|
|
|
String? title,
|
2024-05-31 09:59:18 +08:00
|
|
|
String? confirmText,
|
2025-10-14 15:13:52 +08:00
|
|
|
String? cancelText,
|
2025-02-09 18:39:38 +08:00
|
|
|
bool cancelable = true,
|
2025-07-31 17:09:18 +08:00
|
|
|
bool? dismissible,
|
2025-01-13 19:08:17 +08:00
|
|
|
}) async {
|
|
|
|
|
return await showCommonDialog<bool>(
|
2025-07-31 17:09:18 +08:00
|
|
|
context: context,
|
|
|
|
|
dismissible: dismissible,
|
2024-04-30 23:38:49 +08:00
|
|
|
child: Builder(
|
|
|
|
|
builder: (context) {
|
2025-03-12 17:15:31 +08:00
|
|
|
return CommonDialog(
|
|
|
|
|
title: title ?? appLocalizations.tip,
|
2024-04-30 23:38:49 +08:00
|
|
|
actions: [
|
2025-02-09 18:39:38 +08:00
|
|
|
if (cancelable)
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).pop(false);
|
|
|
|
|
},
|
2025-10-14 15:13:52 +08:00
|
|
|
child: Text(cancelText ?? appLocalizations.cancel),
|
2025-02-09 18:39:38 +08:00
|
|
|
),
|
2025-01-13 19:08:17 +08:00
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).pop(true);
|
|
|
|
|
},
|
2024-05-31 09:59:18 +08:00
|
|
|
child: Text(confirmText ?? appLocalizations.confirm),
|
2025-07-31 17:09:18 +08:00
|
|
|
),
|
2024-04-30 23:38:49 +08:00
|
|
|
],
|
2025-03-12 17:15:31 +08:00
|
|
|
child: Container(
|
|
|
|
|
width: 300,
|
|
|
|
|
constraints: const BoxConstraints(maxHeight: 200),
|
|
|
|
|
child: SingleChildScrollView(
|
|
|
|
|
child: SelectableText.rich(
|
|
|
|
|
TextSpan(
|
|
|
|
|
style: Theme.of(context).textTheme.labelLarge,
|
|
|
|
|
children: [message],
|
|
|
|
|
),
|
2025-07-31 17:09:18 +08:00
|
|
|
style: const TextStyle(overflow: TextOverflow.visible),
|
2025-03-12 17:15:31 +08:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
2024-04-30 23:38:49 +08:00
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-16 11:23:09 +08:00
|
|
|
Future<bool?> showAllUpdatingMessagesDialog(
|
|
|
|
|
List<UpdatingMessage> messages,
|
|
|
|
|
) async {
|
|
|
|
|
return await showCommonDialog<bool>(
|
|
|
|
|
child: Builder(
|
|
|
|
|
builder: (context) {
|
|
|
|
|
return CommonDialog(
|
|
|
|
|
padding: EdgeInsets.zero,
|
|
|
|
|
title: appLocalizations.tip,
|
|
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).pop(true);
|
|
|
|
|
},
|
|
|
|
|
child: Text(appLocalizations.confirm),
|
|
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
child: Container(
|
|
|
|
|
padding: EdgeInsets.symmetric(vertical: 4),
|
|
|
|
|
constraints: const BoxConstraints(maxHeight: 200),
|
|
|
|
|
child: ListView.separated(
|
|
|
|
|
itemBuilder: (_, index) {
|
|
|
|
|
final message = messages[index];
|
|
|
|
|
return ListItem(
|
|
|
|
|
padding: EdgeInsets.symmetric(horizontal: 24),
|
|
|
|
|
title: Text(message.label),
|
|
|
|
|
subtitle: Text(message.message),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
itemCount: messages.length,
|
|
|
|
|
separatorBuilder: (_, _) => Divider(height: 0),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
),
|
2025-07-31 17:09:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
2025-05-02 02:24:12 +08:00
|
|
|
|
2024-05-20 15:15:09 +08:00
|
|
|
Future<T?> showCommonDialog<T>({
|
2024-04-30 23:38:49 +08:00
|
|
|
required Widget child,
|
2025-07-31 17:09:18 +08:00
|
|
|
BuildContext? context,
|
|
|
|
|
bool? dismissible,
|
2025-10-14 15:13:52 +08:00
|
|
|
bool filter = true,
|
2024-04-30 23:38:49 +08:00
|
|
|
}) async {
|
|
|
|
|
return await showModal<T>(
|
2025-07-31 17:09:18 +08:00
|
|
|
useRootNavigator: false,
|
|
|
|
|
context: context ?? globalState.navigatorKey.currentContext!,
|
2024-09-08 21:21:21 +08:00
|
|
|
configuration: FadeScaleTransitionConfiguration(
|
2024-04-30 23:38:49 +08:00
|
|
|
barrierColor: Colors.black38,
|
2025-07-31 17:09:18 +08:00
|
|
|
barrierDismissible: dismissible ?? true,
|
2024-04-30 23:38:49 +08:00
|
|
|
),
|
|
|
|
|
builder: (_) => child,
|
2025-10-14 15:13:52 +08:00
|
|
|
filter: filter ? commonFilter : null,
|
2024-04-30 23:38:49 +08:00
|
|
|
);
|
|
|
|
|
}
|
2024-05-31 09:59:18 +08:00
|
|
|
|
2025-10-14 15:13:52 +08:00
|
|
|
void showNotifier(String text, {MessageActionState? actionState}) {
|
2025-01-13 19:08:17 +08:00
|
|
|
if (text.isEmpty) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-10-14 15:13:52 +08:00
|
|
|
navigatorKey.currentContext?.showNotifier(text, actionState: actionState);
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
2025-06-07 01:48:34 +08:00
|
|
|
Future<void> openUrl(String url) async {
|
2025-01-13 19:08:17 +08:00
|
|
|
final res = await showMessage(
|
2024-07-21 21:51:56 +08:00
|
|
|
message: TextSpan(text: url),
|
|
|
|
|
title: appLocalizations.externalLink,
|
|
|
|
|
confirmText: appLocalizations.go,
|
|
|
|
|
);
|
2025-01-13 19:08:17 +08:00
|
|
|
if (res != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
launchUrl(Uri.parse(url));
|
2024-07-21 21:51:56 +08:00
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
|
2025-05-02 02:24:12 +08:00
|
|
|
Future<Map<String, dynamic>> handleEvaluate(
|
2025-10-14 15:13:52 +08:00
|
|
|
String scriptContent,
|
2025-05-02 02:24:12 +08:00
|
|
|
Map<String, dynamic> config,
|
|
|
|
|
) async {
|
2025-06-07 01:48:34 +08:00
|
|
|
if (config['proxy-providers'] == null) {
|
|
|
|
|
config['proxy-providers'] = {};
|
2025-05-02 02:24:12 +08:00
|
|
|
}
|
|
|
|
|
final configJs = json.encode(config);
|
|
|
|
|
final runtime = getJavascriptRuntime();
|
2025-06-07 01:48:34 +08:00
|
|
|
final res = await runtime.evaluateAsync('''
|
2025-10-14 15:13:52 +08:00
|
|
|
$scriptContent
|
2025-05-02 02:24:12 +08:00
|
|
|
main($configJs)
|
2025-06-07 01:48:34 +08:00
|
|
|
''');
|
2025-05-02 02:24:12 +08:00
|
|
|
if (res.isError) {
|
|
|
|
|
throw res.stringResult;
|
|
|
|
|
}
|
2025-10-14 15:13:52 +08:00
|
|
|
final value = switch (res.rawResult is ffi.Pointer) {
|
2025-05-02 02:24:12 +08:00
|
|
|
true => runtime.convertValue<Map<String, dynamic>>(res),
|
|
|
|
|
false => Map<String, dynamic>.from(res.rawResult),
|
|
|
|
|
};
|
|
|
|
|
return value ?? config;
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final globalState = GlobalState();
|