2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:async';
|
2025-05-02 02:24:12 +08:00
|
|
|
|
2026-09-05 20:09:39 +08:00
|
|
|
import 'package:fl_clash/providers/app.dart';
|
|
|
|
|
import 'package:fl_clash/providers/config.dart';
|
2026-10-15 12:31:27 +08:00
|
|
|
import 'package:fl_clash/ui/ui.dart';
|
2026-08-05 10:55:33 +08:00
|
|
|
import 'package:flutter/foundation.dart';
|
2026-09-05 20:09:39 +08:00
|
|
|
import 'package:material_ui/material_ui.dart';
|
2026-02-02 10:14:13 +08:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2024-07-02 08:08:31 +08:00
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
2025-01-13 19:08:17 +08:00
|
|
|
|
2026-10-15 12:31:27 +08:00
|
|
|
import 'common/common.dart';
|
|
|
|
|
import 'enum/enum.dart';
|
|
|
|
|
import 'models/models.dart';
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
class GlobalState {
|
2025-02-09 18:39:38 +08:00
|
|
|
static GlobalState? _instance;
|
2026-09-05 20:09:39 +08:00
|
|
|
GlobalKey<NavigatorState> get navigatorKey => rootNavigatorKey;
|
2026-10-15 12:31:27 +08:00
|
|
|
late String appEnv;
|
|
|
|
|
late PackageInfo packageInfo;
|
2024-09-05 08:59:45 +08:00
|
|
|
late Measure measure;
|
2026-02-02 10:14:13 +08:00
|
|
|
late ProviderContainer container;
|
2025-12-16 11:23:09 +08:00
|
|
|
bool needInitStatus = true;
|
2026-08-05 10:55:33 +08:00
|
|
|
|
|
|
|
|
bool get isPre => appEnv != 'stable';
|
|
|
|
|
|
|
|
|
|
bool get canCrashCore => canCrashCoreFor(isDebug: kDebugMode, appEnv: appEnv);
|
|
|
|
|
|
|
|
|
|
@visibleForTesting
|
|
|
|
|
static bool canCrashCoreFor({required bool isDebug, required String appEnv}) {
|
|
|
|
|
return isDebug || appEnv == 'dev';
|
|
|
|
|
}
|
2026-02-02 10:14:13 +08:00
|
|
|
|
|
|
|
|
String? lastConfigMd5;
|
2026-10-15 12:31:27 +08:00
|
|
|
ConfigCounts? lastConfigCounts;
|
2026-02-02 10:14:13 +08:00
|
|
|
bool isAttach = false;
|
2024-08-15 16:18:00 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
GlobalState._internal();
|
|
|
|
|
|
|
|
|
|
factory GlobalState() {
|
|
|
|
|
_instance ??= GlobalState._internal();
|
|
|
|
|
return _instance!;
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 10:14:13 +08:00
|
|
|
String get ua => container
|
|
|
|
|
.read(patchClashConfigProvider.select((state) => state.globalUa))
|
|
|
|
|
.takeFirstValid([packageInfo.ua]);
|
|
|
|
|
|
|
|
|
|
Future<T?> loadingRun<T>(
|
|
|
|
|
FutureOr<T> Function() futureFunction, {
|
|
|
|
|
String? title,
|
|
|
|
|
required LoadingTag? tag,
|
|
|
|
|
bool silence = false,
|
|
|
|
|
}) async {
|
|
|
|
|
return globalState.safeRun(
|
|
|
|
|
futureFunction,
|
|
|
|
|
silence: silence,
|
|
|
|
|
title: title,
|
|
|
|
|
onStart: () {
|
|
|
|
|
if (tag != null) {
|
|
|
|
|
container.read(loadingProvider(tag).notifier).start();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
onEnd: () {
|
|
|
|
|
if (tag != null) {
|
|
|
|
|
container.read(loadingProvider(tag).notifier).stop();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2026-10-08 17:58:43 +08:00
|
|
|
/// Concurrent [loadingRun]s would let the first to finish clear [tag].
|
|
|
|
|
Future<void> batchRun<T>(
|
|
|
|
|
List<T> items,
|
|
|
|
|
Future<void> Function(T item) task, {
|
|
|
|
|
required String Function(T item) label,
|
|
|
|
|
required int concurrency,
|
|
|
|
|
required LoadingTag? tag,
|
|
|
|
|
}) async {
|
|
|
|
|
final pool = TaskPool(concurrency);
|
|
|
|
|
final failures = List<UpdatingMessage?>.filled(items.length, null);
|
|
|
|
|
await loadingRun(
|
|
|
|
|
() => Future.wait([
|
|
|
|
|
for (final (index, item) in items.indexed)
|
|
|
|
|
pool.run(() async {
|
|
|
|
|
try {
|
|
|
|
|
await task(item);
|
|
|
|
|
} catch (e, s) {
|
|
|
|
|
commonPrint.log(
|
|
|
|
|
'${label(item)} ===> ${compactError(e)}, $s',
|
|
|
|
|
logLevel: LogLevel.warning,
|
|
|
|
|
);
|
|
|
|
|
failures[index] = UpdatingMessage(
|
|
|
|
|
label: label(item),
|
|
|
|
|
message: userFacingErrorMessage(e, currentAppLocalizations),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
]),
|
|
|
|
|
tag: tag,
|
|
|
|
|
);
|
|
|
|
|
dialogs.showFailures(failures.nonNulls.toList());
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-02 10:14:13 +08:00
|
|
|
Future<T?> safeRun<T>(
|
|
|
|
|
FutureOr<T> Function() futureFunction, {
|
|
|
|
|
String? title,
|
|
|
|
|
VoidCallback? onStart,
|
|
|
|
|
VoidCallback? onEnd,
|
|
|
|
|
bool silence = true,
|
|
|
|
|
}) async {
|
|
|
|
|
try {
|
|
|
|
|
onStart?.call();
|
|
|
|
|
return await futureFunction();
|
|
|
|
|
} catch (e, s) {
|
2026-09-05 20:09:39 +08:00
|
|
|
commonPrint.log(
|
|
|
|
|
title == null
|
|
|
|
|
? '${compactError(e)}, $s'
|
|
|
|
|
: '$title ===> ${compactError(e)}, $s',
|
|
|
|
|
logLevel: LogLevel.warning,
|
|
|
|
|
);
|
|
|
|
|
final message = userFacingErrorMessage(e, currentAppLocalizations);
|
2026-02-02 10:14:13 +08:00
|
|
|
if (silence) {
|
2026-09-05 20:09:39 +08:00
|
|
|
dialogs.showNotifier(message, level: MessageLevel.error);
|
2026-02-02 10:14:13 +08:00
|
|
|
} else {
|
2026-09-05 20:09:39 +08:00
|
|
|
unawaited(
|
|
|
|
|
dialogs.showMessage(
|
|
|
|
|
title: title ?? currentAppLocalizations.tip,
|
|
|
|
|
message: TextSpan(text: message),
|
|
|
|
|
),
|
2026-02-02 10:14:13 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
return null;
|
|
|
|
|
} finally {
|
|
|
|
|
onEnd?.call();
|
2025-01-13 19:08:17 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
final globalState = GlobalState();
|