Files
MWClash/lib/controller.dart
chen08209 f780530be2 Fix android tile service
Support append system DNS

Fix some issues
2025-10-07 19:29:13 +08:00

989 lines
30 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> tryStartCore() async {
if (coreController.isCompleted) {
return;
}
globalState.isUserDisconnected = true;
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.appController.tryStartCore();
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 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(), logLevel: LogLevel.warning);
}
}
}
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 (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}',
logLevel: LogLevel.warning,
);
};
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 result = await Future.wait([
coreController.preload(),
if (!globalState.isService) Future.delayed(Duration(milliseconds: 300)),
]);
final String message = result[0];
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('$title===> $e', logLevel: LogLevel.warning);
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;
}
}
}