import 'dart:async'; import 'dart:io'; import 'package:animations/animations.dart'; import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/plugins/proxy.dart'; import 'package:fl_clash/widgets/scaffold.dart'; import 'package:flutter/material.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:url_launcher/url_launcher.dart'; import 'controller.dart'; import 'models/models.dart'; import 'common/common.dart'; class GlobalState { Timer? timer; Timer? groupsUpdateTimer; var isVpnService = false; late PackageInfo packageInfo; Function? updateCurrentDelayDebounce; PageController? pageController; final navigatorKey = GlobalKey(); late AppController appController; GlobalKey homeScaffoldKey = GlobalKey(); List updateFunctionLists = []; startListenUpdate() { if (timer != null && timer!.isActive == true) return; timer = Timer.periodic(const Duration(seconds: 1), (Timer t) { for (final function in updateFunctionLists) { function(); } }); } stopListenUpdate() { if (timer == null || timer?.isActive == false) return; timer?.cancel(); } Future updateClashConfig({ required ClashConfig clashConfig, required Config config, bool isPatch = true, }) async { await config.currentProfile?.checkAndUpdate(); final res = await clashCore.updateConfig( UpdateConfigParams( profileId: config.currentProfileId ?? "", config: clashConfig, params: ConfigExtendedParams( isPatch: isPatch, isCompatible: true, selectedMap: config.currentSelectedMap, testUrl: config.testUrl, ), ), ); if (res.isNotEmpty) throw res; } updateCoreVersionInfo(AppState appState) { appState.versionInfo = clashCore.getVersionInfo(); } Future startSystemProxy({ required AppState appState, required Config config, required ClashConfig clashConfig, }) async { if (!globalState.isVpnService && Platform.isAndroid) { await proxy?.initService(); } else { await proxyManager.startProxy( port: clashConfig.mixedPort, ); } startListenUpdate(); } Future stopSystemProxy() async { await proxyManager.stopProxy(); stopListenUpdate(); } Future applyProfile({ required AppState appState, required Config config, required ClashConfig clashConfig, }) async { await updateClashConfig( clashConfig: clashConfig, config: config, isPatch: false, ); await updateGroups(appState); await updateProviders(appState); } updateProviders(AppState appState) async { appState.providers = await clashCore.getExternalProviders(); } init({ required AppState appState, required Config config, required ClashConfig clashConfig, }) async { appState.isInit = clashCore.isInit; if (!appState.isInit) { appState.isInit = await clashService.init( config: config, clashConfig: clashConfig, ); clashCore.setState( CoreState( accessControl: config.isAccessControl ? config.accessControl : null, allowBypass: config.allowBypass, systemProxy: config.systemProxy, mixedPort: clashConfig.mixedPort, onlyProxy: config.onlyProxy, currentProfileName: config.currentProfile?.label ?? config.currentProfileId ?? "", ), ); } updateCoreVersionInfo(appState); } Future updateGroups(AppState appState) async { appState.groups = await clashCore.getProxiesGroups(); } showMessage({ required String title, required InlineSpan message, Function()? onTab, String? confirmText, }) { showCommonDialog( child: Builder( builder: (context) { return AlertDialog( title: Text(title), content: 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, ), ), ), ), actions: [ TextButton( onPressed: onTab ?? () { Navigator.of(context).pop(); }, child: Text(confirmText ?? appLocalizations.confirm), ) ], ); }, ), ); } changeProxy({ required Config config, required String groupName, required String proxyName, }) { clashCore.changeProxy( ChangeProxyParams( groupName: groupName, proxyName: proxyName, ), ); if (config.isCloseConnections) { clashCore.closeConnections(); } } Future showCommonDialog({ required Widget child, }) async { return await showModal( context: navigatorKey.currentState!.context, configuration: const FadeScaleTransitionConfiguration( barrierColor: Colors.black38, ), builder: (_) => child, filter: filter, ); } updateTraffic({ AppState? appState, }) { final traffic = clashCore.getTraffic(); if (Platform.isAndroid && isVpnService == true) { proxy?.startForeground( title: clashCore.getState().currentProfileName, content: "$traffic", ); } else { if (appState != null) { appState.addTraffic(traffic); appState.totalTraffic = clashCore.getTotalTraffic(); } } } showSnackBar( BuildContext context, { required String message, SnackBarAction? action, }) { final width = context.width; EdgeInsets margin; if (width < 600) { margin = const EdgeInsets.only( bottom: 16, right: 16, left: 16, ); } else { margin = EdgeInsets.only( bottom: 16, left: 16, right: width - 316, ); } ScaffoldMessenger.of(context).showSnackBar( SnackBar( action: action, content: Text(message), behavior: SnackBarBehavior.floating, duration: const Duration(milliseconds: 1500), margin: margin, ), ); } Future safeRun( FutureOr Function() futureFunction, { String? title, }) async { try { final res = await futureFunction(); return res; } catch (e) { showMessage( title: title ?? appLocalizations.tip, message: TextSpan( text: e.toString(), ), ); return null; } } openUrl(String url) { showMessage( message: TextSpan(text: url), title: appLocalizations.externalLink, confirmText: appLocalizations.go, onTab: () { launchUrl(Uri.parse(url)); }, ); } } final globalState = GlobalState();