Files
MWClash/lib/controller.dart
chen08209 ddc07e9f29 Add android separates the core process
Support core status check and force restart

Optimize proxies page and access page

Update flutter and pub dependencies

Optimize more details
2025-09-17 17:12:11 +08:00

974 lines
29 KiB
Dart

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'package:archive/archive.dart';
import 'package:fl_clash/common/archive.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/dialog.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:path/path.dart';
import 'package:url_launcher/url_launcher.dart';
import 'common/common.dart';
import 'models/models.dart';
class AppController {
int? lastProfileModified;
final BuildContext context;
final WidgetRef _ref;
AppController(this.context, WidgetRef ref) : _ref = ref;
void setupClashConfigDebounce() {
debouncer.call(FunctionTag.setupClashConfig, () async {
await setupClashConfig();
});
}
Future<void> updateClashConfigDebounce() async {
debouncer.call(FunctionTag.updateClashConfig, () async {
await updateClashConfig();
});
}
void updateGroupsDebounce([Duration? duration]) {
debouncer.call(FunctionTag.updateGroups, updateGroups, duration: duration);
}
void addCheckIpNumDebounce() {
debouncer.call(FunctionTag.addCheckIpNum, () {
_ref.read(checkIpNumProvider.notifier).add();
});
}
void applyProfileDebounce({bool silence = false}) {
debouncer.call(FunctionTag.applyProfile, (silence) {
applyProfile(silence: silence);
}, args: [silence]);
}
void savePreferencesDebounce() {
debouncer.call(FunctionTag.savePreferences, savePreferences);
}
void changeProxyDebounce(String groupName, String proxyName) {
debouncer.call(FunctionTag.changeProxy, (
String groupName,
String proxyName,
) async {
await changeProxy(groupName: groupName, proxyName: proxyName);
await updateGroups();
}, args: [groupName, proxyName]);
}
Future<void> restartCore() async {
globalState.isUserDisconnected = true;
await coreController.shutdown();
await _connectCore();
await _initCore();
_ref.read(initProvider.notifier).value = true;
if (_ref.read(isStartProvider)) {
await globalState.handleStart();
}
}
Future<void> updateStatus(bool isStart) async {
if (isStart) {
await globalState.handleStart([updateRunTime, updateTraffic]);
final currentLastModified = await _ref
.read(currentProfileProvider)
?.profileLastModified;
if (currentLastModified == null || lastProfileModified == null) {
addCheckIpNumDebounce();
return;
}
if (currentLastModified <= (lastProfileModified ?? 0)) {
addCheckIpNumDebounce();
return;
}
applyProfileDebounce();
} else {
await globalState.handleStop();
coreController.resetTraffic();
_ref.read(trafficsProvider.notifier).clear();
_ref.read(totalTrafficProvider.notifier).value = Traffic();
_ref.read(runTimeProvider.notifier).value = null;
addCheckIpNumDebounce();
}
}
void updateRunTime() {
final startTime = globalState.startTime;
if (startTime != null) {
final startTimeStamp = startTime.millisecondsSinceEpoch;
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
_ref.read(runTimeProvider.notifier).value = nowTimeStamp - startTimeStamp;
} else {
_ref.read(runTimeProvider.notifier).value = null;
}
}
Future<void> updateTraffic() async {
final onlyStatisticsProxy = _ref.read(
appSettingProvider.select((state) => state.onlyStatisticsProxy),
);
final traffic = await coreController.getTraffic(onlyStatisticsProxy);
_ref.read(trafficsProvider.notifier).addTraffic(traffic);
_ref.read(totalTrafficProvider.notifier).value = await coreController
.getTotalTraffic(onlyStatisticsProxy);
}
Future<void> addProfile(Profile profile) async {
_ref.read(profilesProvider.notifier).setProfile(profile);
if (_ref.read(currentProfileIdProvider) != null) return;
_ref.read(currentProfileIdProvider.notifier).value = profile.id;
}
Future<void> deleteProfile(String id) async {
_ref.read(profilesProvider.notifier).deleteProfileById(id);
clearEffect(id);
if (globalState.config.currentProfileId == id) {
final profiles = globalState.config.profiles;
final currentProfileId = _ref.read(currentProfileIdProvider.notifier);
if (profiles.isNotEmpty) {
final updateId = profiles.first.id;
currentProfileId.value = updateId;
} else {
currentProfileId.value = null;
updateStatus(false);
}
}
}
Future<void> updateProviders() async {
_ref.read(providersProvider.notifier).value = await coreController
.getExternalProviders();
}
Future<void> updateLocalIp() async {
_ref.read(localIpProvider.notifier).value = null;
await Future.delayed(commonDuration);
_ref.read(localIpProvider.notifier).value = await utils.getLocalIpAddress();
}
Future<void> updateProfile(Profile profile) async {
final newProfile = await profile.update();
_ref
.read(profilesProvider.notifier)
.setProfile(newProfile.copyWith(isUpdating: false));
if (profile.id == _ref.read(currentProfileIdProvider)) {
applyProfileDebounce(silence: true);
}
}
void setProfile(Profile profile) {
_ref.read(profilesProvider.notifier).setProfile(profile);
}
void setProfileAndAutoApply(Profile profile) {
_ref.read(profilesProvider.notifier).setProfile(profile);
if (profile.id == _ref.read(currentProfileIdProvider)) {
applyProfileDebounce(silence: true);
}
}
void setProfiles(List<Profile> profiles) {
_ref.read(profilesProvider.notifier).value = profiles;
}
void addLog(Log log) {
_ref.read(logsProvider).add(log);
}
void updateOrAddHotKeyAction(HotKeyAction hotKeyAction) {
final hotKeyActions = _ref.read(hotKeyActionsProvider);
final index = hotKeyActions.indexWhere(
(item) => item.action == hotKeyAction.action,
);
if (index == -1) {
_ref.read(hotKeyActionsProvider.notifier).value = List.from(hotKeyActions)
..add(hotKeyAction);
} else {
_ref.read(hotKeyActionsProvider.notifier).value = List.from(hotKeyActions)
..[index] = hotKeyAction;
}
_ref.read(hotKeyActionsProvider.notifier).value = index == -1
? (List.from(hotKeyActions)..add(hotKeyAction))
: (List.from(hotKeyActions)..[index] = hotKeyAction);
}
List<Group> getCurrentGroups() {
return _ref.read(currentGroupsStateProvider.select((state) => state.value));
}
String getRealTestUrl(String? url) {
return _ref.read(getRealTestUrlProvider(url));
}
int getProxiesColumns() {
return _ref.read(getProxiesColumnsProvider);
}
dynamic addSortNum() {
return _ref.read(sortNumProvider.notifier).add();
}
String? getCurrentGroupName() {
final currentGroupName = _ref.read(
currentProfileProvider.select((state) => state?.currentGroupName),
);
return currentGroupName;
}
String? getSelectedProxyName(String groupName) {
return _ref.read(getSelectedProxyNameProvider(groupName));
}
void updateCurrentGroupName(String groupName) {
final profile = _ref.read(currentProfileProvider);
if (profile == null || profile.currentGroupName == groupName) {
return;
}
setProfile(profile.copyWith(currentGroupName: groupName));
}
Future<void> updateClashConfig() async {
await safeRun(() async {
await _updateClashConfig();
}, needLoading: true);
}
Future<void> _updateClashConfig() async {
final updateParams = _ref.read(updateParamsProvider);
final res = await _requestAdmin(updateParams.tun.enable);
if (res.isError) {
return;
}
final realTunEnable = _ref.read(realTunEnableProvider);
final message = await coreController.updateConfig(
updateParams.copyWith.tun(enable: realTunEnable),
);
if (message.isNotEmpty) throw message;
}
Future<Result<bool>> _requestAdmin(bool enableTun) async {
if (system.isWindows && kDebugMode) {
return Result.success(false);
}
final realTunEnable = _ref.read(realTunEnableProvider);
if (enableTun != realTunEnable && realTunEnable == false) {
final code = await system.authorizeCore();
switch (code) {
case AuthorizeCode.success:
await restartCore();
return Result.error('');
case AuthorizeCode.none:
break;
case AuthorizeCode.error:
enableTun = false;
break;
}
}
_ref.read(realTunEnableProvider.notifier).value = enableTun;
return Result.success(enableTun);
}
Future<void> setupClashConfig() async {
await safeRun(() async {
await _setupClashConfig();
}, needLoading: true);
}
Future<void> _setupClashConfig() async {
await _ref.read(currentProfileProvider)?.checkAndUpdate();
final patchConfig = _ref.read(patchClashConfigProvider);
final res = await _requestAdmin(patchConfig.tun.enable);
if (res.isError) {
return;
}
final realTunEnable = _ref.read(realTunEnableProvider);
final realPatchConfig = patchConfig.copyWith.tun(enable: realTunEnable);
final message = await coreController.setupConfig(realPatchConfig);
lastProfileModified = await _ref.read(
currentProfileProvider.select((state) => state?.profileLastModified),
);
if (message.isNotEmpty) {
throw message;
}
}
Future _applyProfile() async {
await coreController.requestGc();
await setupClashConfig();
await updateGroups();
await updateProviders();
}
Future applyProfile({bool silence = false}) async {
if (silence) {
await _applyProfile();
} else {
await safeRun(() async {
await _applyProfile();
}, needLoading: true);
}
addCheckIpNumDebounce();
}
void handleChangeProfile() {
_ref.read(delayDataSourceProvider.notifier).value = {};
applyProfile();
_ref.read(logsProvider.notifier).value = FixedList(500);
_ref.read(requestsProvider.notifier).value = FixedList(500);
globalState.computeHeightMapCache = {};
}
void updateBrightness() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ref.read(systemBrightnessProvider.notifier).value =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
});
}
Future<void> autoUpdateProfiles() async {
for (final profile in _ref.read(profilesProvider)) {
if (!profile.autoUpdate) continue;
final isNotNeedUpdate = profile.lastUpdateDate
?.add(profile.autoUpdateDuration)
.isBeforeNow;
if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
continue;
}
try {
await updateProfile(profile);
} catch (e) {
commonPrint.log(e.toString());
}
}
}
Future<void> updateGroups() async {
try {
_ref.read(groupsProvider.notifier).value = await retry(
task: () async {
final sortType = _ref.read(
proxiesStyleSettingProvider.select((state) => state.sortType),
);
final delayMap = _ref.read(delayDataSourceProvider);
final testUrl = _ref.read(
appSettingProvider.select((state) => state.testUrl),
);
final selectedMap = _ref.read(
currentProfileProvider.select((state) => state?.selectedMap ?? {}),
);
return await coreController.getProxiesGroups(
selectedMap: selectedMap,
sortType: sortType,
delayMap: delayMap,
defaultTestUrl: testUrl,
);
},
retryIf: (res) => res.isEmpty,
);
} catch (_) {
_ref.read(groupsProvider.notifier).value = [];
}
}
Future<void> updateProfiles() async {
for (final profile in _ref.read(profilesProvider)) {
if (profile.type == ProfileType.file) {
continue;
}
await updateProfile(profile);
}
}
Future<void> savePreferences() async {
commonPrint.log('save preferences');
await preferences.saveConfig(globalState.config);
}
Future<void> changeProxy({
required String groupName,
required String proxyName,
}) async {
await coreController.changeProxy(
ChangeProxyParams(groupName: groupName, proxyName: proxyName),
);
if (_ref.read(appSettingProvider).closeConnections) {
coreController.closeConnections();
}
addCheckIpNumDebounce();
}
Future<void> handleBackOrExit() async {
if (_ref.read(backBlockProvider)) {
return;
}
if (_ref.read(appSettingProvider).minimizeOnExit) {
if (system.isDesktop) {
await savePreferences();
}
await system.back();
} else {
await handleExit();
}
}
void backBlock() {
_ref.read(backBlockProvider.notifier).value = true;
}
void unBackBlock() {
_ref.read(backBlockProvider.notifier).value = false;
}
Future<void> handleExit() async {
Future.delayed(commonDuration, () {
system.exit();
});
try {
await savePreferences();
await macOS?.updateDns(true);
await proxy?.stopProxy();
await coreController.shutdown();
await coreController.destroy();
} finally {
system.exit();
}
}
Future handleClear() async {
await preferences.clearPreferences();
commonPrint.log('clear preferences');
globalState.config = Config(themeProps: defaultThemeProps);
}
Future<void> autoCheckUpdate() async {
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
final res = await request.checkForUpdate();
checkUpdateResultHandle(data: res);
}
Future<void> checkUpdateResultHandle({
Map<String, dynamic>? data,
bool handleError = false,
}) async {
if (globalState.isPre) {
return;
}
if (data != null) {
final tagName = data['tag_name'];
final body = data['body'];
final submits = utils.parseReleaseBody(body);
final textTheme = context.textTheme;
final res = await globalState.showMessage(
title: appLocalizations.discoverNewVersion,
message: TextSpan(
text: '$tagName \n',
style: textTheme.headlineSmall,
children: [
TextSpan(text: '\n', style: textTheme.bodyMedium),
for (final submit in submits)
TextSpan(text: '- $submit \n', style: textTheme.bodyMedium),
],
),
confirmText: appLocalizations.goDownload,
);
if (res != true) {
return;
}
launchUrl(Uri.parse('https://github.com/$repository/releases/latest'));
} else if (handleError) {
globalState.showMessage(
title: appLocalizations.checkUpdate,
message: TextSpan(text: appLocalizations.checkUpdateError),
);
}
}
Future<void> _handlePreference() async {
if (await preferences.isInit) {
return;
}
final res = await globalState.showMessage(
title: appLocalizations.tip,
message: TextSpan(text: appLocalizations.cacheCorrupt),
);
if (res == true) {
final file = File(await appPath.sharedPreferencesPath);
final isExists = await file.exists();
if (isExists) {
await file.delete();
}
}
await handleExit();
}
Future<void> _initCore() async {
final isInit = await coreController.isInit;
if (!isInit) {
await coreController.init(globalState.appState.version);
}
await applyProfile();
}
Future<void> init() async {
FlutterError.onError = (details) {
commonPrint.log(
'exception: ${details.exception} stack: ${details.stack}',
);
};
updateTray(true);
autoUpdateProfiles();
autoCheckUpdate();
autoLaunch?.updateStatus(_ref.read(appSettingProvider).autoLaunch);
if (!_ref.read(appSettingProvider).silentLaunch) {
window?.show();
} else {
window?.hide();
}
await _handlePreference();
await _handlerDisclaimer();
await _showCrashlyticsTip();
await _connectCore();
await _initCore();
await _initStatus();
_ref.read(initProvider.notifier).value = true;
}
Future<void> _connectCore() async {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connecting;
final message = await coreController.preload();
if (message.isNotEmpty) {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
if (context.mounted) {
context.showNotifier(message);
}
return;
}
_ref.read(coreStatusProvider.notifier).value = CoreStatus.connected;
}
Future<void> _initStatus() async {
if (system.isAndroid) {
await globalState.updateStartTime();
}
final status = globalState.isStart == true
? true
: _ref.read(appSettingProvider).autoRun;
await updateStatus(status);
if (!status) {
addCheckIpNumDebounce();
}
}
void setDelay(Delay delay) {
_ref.read(delayDataSourceProvider.notifier).setDelay(delay);
}
void toPage(PageLabel pageLabel) {
_ref.read(currentPageLabelProvider.notifier).value = pageLabel;
}
void toProfiles() {
toPage(PageLabel.profiles);
}
void initLink() {
linkManager.initAppLinksListen((url) async {
final res = await globalState.showMessage(
title: '${appLocalizations.add}${appLocalizations.profile}',
message: TextSpan(
children: [
TextSpan(text: appLocalizations.doYouWantToPass),
TextSpan(
text: ' $url ',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
decorationColor: Theme.of(context).colorScheme.primary,
),
),
TextSpan(
text: '${appLocalizations.create}${appLocalizations.profile}',
),
],
),
);
if (res != true) {
return;
}
addProfileFormURL(url);
});
}
Future<bool> showDisclaimer() async {
return await globalState.showCommonDialog<bool>(
dismissible: false,
child: CommonDialog(
title: appLocalizations.disclaimer,
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop<bool>(false);
},
child: Text(appLocalizations.exit),
),
TextButton(
onPressed: () {
Navigator.of(context).pop<bool>(true);
},
child: Text(appLocalizations.agree),
),
],
child: Text(appLocalizations.disclaimerDesc),
),
) ??
false;
}
Future<void> _showCrashlyticsTip() async {
if (!system.isAndroid) {
return;
}
if (_ref.read(appSettingProvider.select((state) => state.crashlyticsTip))) {
return;
}
await globalState.showMessage(
title: appLocalizations.dataCollectionTip,
cancelable: false,
message: TextSpan(text: appLocalizations.dataCollectionContent),
);
_ref
.read(appSettingProvider.notifier)
.updateState((state) => state.copyWith(crashlyticsTip: true));
}
Future<void> _handlerDisclaimer() async {
if (_ref.read(
appSettingProvider.select((state) => state.disclaimerAccepted),
)) {
return;
}
final isDisclaimerAccepted = await showDisclaimer();
if (!isDisclaimerAccepted) {
await handleExit();
}
_ref
.read(appSettingProvider.notifier)
.updateState((state) => state.copyWith(disclaimerAccepted: true));
return;
}
Future<void> addProfileFormURL(String url) async {
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
}
toProfiles();
final profile = await safeRun(
() async {
return await Profile.normal(url: url).update();
},
needLoading: true,
title: '${appLocalizations.add}${appLocalizations.profile}',
);
if (profile != null) {
await addProfile(profile);
}
}
Future<void> addProfileFormFile() async {
final platformFile = await safeRun(picker.pickerFile);
final bytes = platformFile?.bytes;
if (bytes == null) {
return;
}
if (!context.mounted) return;
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
toProfiles();
final profile = await safeRun(
() async {
await Future.delayed(const Duration(milliseconds: 300));
return await Profile.normal(label: platformFile?.name).saveFile(bytes);
},
needLoading: true,
title: '${appLocalizations.add}${appLocalizations.profile}',
);
if (profile != null) {
await addProfile(profile);
}
}
Future<void> addProfileFormQrCode() async {
final url = await safeRun(picker.pickerConfigQRCode);
if (url == null) return;
addProfileFormURL(url);
}
void updateViewSize(Size size) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ref.read(viewSizeProvider.notifier).value = size;
});
}
void setProvider(ExternalProvider? provider) {
_ref.read(providersProvider.notifier).setProvider(provider);
}
Future<void> clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersDirPath = await appPath.getProvidersDirPath(profileId);
return await Isolate.run(() async {
final profileFile = File(profilePath);
final isExists = await profileFile.exists();
if (isExists) {
profileFile.delete(recursive: true);
}
await coreController.deleteFile(providersDirPath);
});
}
void updateTun() {
_ref
.read(patchClashConfigProvider.notifier)
.updateState((state) => state.copyWith.tun(enable: !state.tun.enable));
}
void updateSystemProxy() {
_ref
.read(networkSettingProvider.notifier)
.updateState(
(state) => state.copyWith(systemProxy: !state.systemProxy),
);
}
void handleCoreDisconnected() {
_ref.read(coreStatusProvider.notifier).value = CoreStatus.disconnected;
}
Future<List<Package>> getPackages() async {
if (_ref.read(isMobileViewProvider)) {
await Future.delayed(commonDuration);
}
if (_ref.read(packagesProvider).isEmpty) {
_ref.read(packagesProvider.notifier).value =
await app?.getPackages() ?? [];
}
return _ref.read(packagesProvider);
}
void updateStart() {
updateStatus(!_ref.read(isStartProvider));
}
void updateCurrentSelectedMap(String groupName, String proxyName) {
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile != null &&
currentProfile.selectedMap[groupName] != proxyName) {
final SelectedMap selectedMap = Map.from(currentProfile.selectedMap)
..[groupName] = proxyName;
_ref
.read(profilesProvider.notifier)
.setProfile(currentProfile.copyWith(selectedMap: selectedMap));
}
}
void updateCurrentUnfoldSet(Set<String> value) {
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {
return;
}
_ref
.read(profilesProvider.notifier)
.setProfile(currentProfile.copyWith(unfoldSet: value));
}
void changeMode(Mode mode) {
_ref
.read(patchClashConfigProvider.notifier)
.updateState((state) => state.copyWith(mode: mode));
if (mode == Mode.global) {
updateCurrentGroupName(GroupName.GLOBAL.name);
}
addCheckIpNumDebounce();
}
void updateAutoLaunch() {
_ref
.read(appSettingProvider.notifier)
.updateState((state) => state.copyWith(autoLaunch: !state.autoLaunch));
}
Future<void> updateVisible() async {
final visible = await window?.isVisible;
if (visible != null && !visible) {
window?.show();
} else {
window?.hide();
}
}
void updateMode() {
_ref.read(patchClashConfigProvider.notifier).updateState((state) {
final index = Mode.values.indexWhere((item) => item == state.mode);
if (index == -1) {
return null;
}
final nextIndex = index + 1 > Mode.values.length - 1 ? 0 : index + 1;
return state.copyWith(mode: Mode.values[nextIndex]);
});
}
Future<bool> exportLogs() async {
final logsRaw = _ref.read(logsProvider).list.map((item) => item.toString());
final data = await Isolate.run<List<int>>(() async {
final logsRawString = logsRaw.join('\n');
return utf8.encode(logsRawString);
});
return await picker.saveFile(utils.logFile, Uint8List.fromList(data)) !=
null;
}
Future<List<int>> backupData() async {
final homeDirPath = await appPath.homeDirPath;
final profilesPath = await appPath.profilesPath;
final configJson = globalState.config.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.addTextFile('config.json', configJson);
archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive);
});
}
Future<void> updateTray([bool focus = false]) async {
tray?.update(trayState: _ref.read(trayStateProvider));
}
Future<void> recoveryData(
List<int> data,
RecoveryOption recoveryOption,
) async {
final archive = await Isolate.run<Archive>(() {
final zipDecoder = ZipDecoder();
return zipDecoder.decodeBytes(data);
});
final homeDirPath = await appPath.homeDirPath;
final configs = archive.files
.where((item) => item.name.endsWith('.json'))
.toList();
final profiles = archive.files.where(
(item) => !item.name.endsWith('.json'),
);
final configIndex = configs.indexWhere(
(config) => config.name == 'config.json',
);
if (configIndex == -1) throw 'invalid backup file';
final configFile = configs[configIndex];
var tempConfig = Config.compatibleFromJson(
json.decode(utf8.decode(configFile.content)),
);
for (final profile in profiles) {
final filePath = join(homeDirPath, profile.name);
final file = File(filePath);
await file.create(recursive: true);
await file.writeAsBytes(profile.content);
}
final clashConfigIndex = configs.indexWhere(
(config) => config.name == 'clashConfig.json',
);
if (clashConfigIndex != -1) {
final clashConfigFile = configs[clashConfigIndex];
tempConfig = tempConfig.copyWith(
patchClashConfig: ClashConfig.fromJson(
json.decode(utf8.decode(clashConfigFile.content)),
),
);
}
_recovery(tempConfig, recoveryOption);
}
void _recovery(Config config, RecoveryOption recoveryOption) {
final recoveryStrategy = _ref.read(
appSettingProvider.select((state) => state.recoveryStrategy),
);
final profiles = config.profiles;
if (recoveryStrategy == RecoveryStrategy.override) {
_ref.read(profilesProvider.notifier).value = profiles;
} else {
for (final profile in profiles) {
_ref.read(profilesProvider.notifier).setProfile(profile);
}
}
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
if (!onlyProfiles) {
_ref.read(patchClashConfigProvider.notifier).value =
config.patchClashConfig;
_ref.read(appSettingProvider.notifier).value = config.appSetting;
_ref.read(currentProfileIdProvider.notifier).value =
config.currentProfileId;
_ref.read(appDAVSettingProvider.notifier).value = config.dav;
_ref.read(themeSettingProvider.notifier).value = config.themeProps;
_ref.read(windowSettingProvider.notifier).value = config.windowProps;
_ref.read(vpnSettingProvider.notifier).value = config.vpnProps;
_ref.read(proxiesStyleSettingProvider.notifier).value =
config.proxiesStyle;
_ref.read(overrideDnsProvider.notifier).value = config.overrideDns;
_ref.read(networkSettingProvider.notifier).value = config.networkProps;
_ref.read(hotKeyActionsProvider.notifier).value = config.hotKeyActions;
_ref.read(scriptStateProvider.notifier).value = config.scriptProps;
}
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
}
}
Future<T?> safeRun<T>(
FutureOr<T> Function() futureFunction, {
String? title,
bool needLoading = false,
bool silence = true,
}) async {
final realSilence = needLoading == true ? true : silence;
try {
if (needLoading) {
_ref.read(loadingProvider.notifier).value = true;
}
final res = await futureFunction();
return res;
} catch (e) {
commonPrint.log('$futureFunction ===> $e');
if (realSilence) {
globalState.showNotifier(e.toString());
} else {
globalState.showMessage(
title: title ?? appLocalizations.tip,
message: TextSpan(text: e.toString()),
);
}
return null;
} finally {
_ref.read(loadingProvider.notifier).value = false;
}
}
}