2024-04-30 23:38:49 +08:00
|
|
|
import 'dart:async';
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'dart:convert';
|
2024-07-26 08:05:22 +08:00
|
|
|
import 'dart:io';
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'dart:isolate';
|
2024-09-20 14:32:57 +08:00
|
|
|
import 'dart:typed_data';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'package:archive/archive.dart';
|
2024-12-03 21:47:12 +08:00
|
|
|
import 'package:fl_clash/clash/clash.dart';
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'package:fl_clash/common/archive.dart';
|
2024-06-07 17:22:55 +08:00
|
|
|
import 'package:fl_clash/enum/enum.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:fl_clash/providers/providers.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:fl_clash/state.dart';
|
2025-03-12 17:15:31 +08:00
|
|
|
import 'package:fl_clash/widgets/dialog.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
import 'package:flutter/material.dart';
|
2025-02-09 18:39:38 +08:00
|
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
2024-08-04 08:21:14 +08:00
|
|
|
import 'package:path/path.dart';
|
2024-05-31 09:59:18 +08:00
|
|
|
import 'package:url_launcher/url_launcher.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
|
|
|
|
import 'common/common.dart';
|
2025-03-12 17:15:31 +08:00
|
|
|
import 'fragments/profiles/override_profile.dart';
|
2024-11-09 20:17:57 +08:00
|
|
|
import 'models/models.dart';
|
2024-04-30 23:38:49 +08:00
|
|
|
|
|
|
|
|
class AppController {
|
2025-02-09 18:39:38 +08:00
|
|
|
bool lastTunEnable = false;
|
|
|
|
|
int? lastProfileModified;
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
final BuildContext context;
|
2025-02-09 18:39:38 +08:00
|
|
|
final WidgetRef _ref;
|
2024-04-30 23:38:49 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
AppController(this.context, WidgetRef ref) : _ref = ref;
|
2024-12-09 01:40:39 +08:00
|
|
|
|
|
|
|
|
updateClashConfigDebounce() {
|
2025-04-09 16:46:14 +08:00
|
|
|
debouncer.call(DebounceTag.updateClashConfig, () async {
|
|
|
|
|
final isPatch = globalState.appState.needApply ? false : true;
|
|
|
|
|
await updateClashConfig(isPatch);
|
2025-03-05 16:08:50 +08:00
|
|
|
});
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateGroupsDebounce() {
|
|
|
|
|
debouncer.call(DebounceTag.updateGroups, updateGroups);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addCheckIpNumDebounce() {
|
|
|
|
|
debouncer.call(DebounceTag.addCheckIpNum, () {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(checkIpNumProvider.notifier).add();
|
2024-10-27 16:59:23 +08:00
|
|
|
});
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:08:50 +08:00
|
|
|
applyProfileDebounce({
|
|
|
|
|
bool silence = false,
|
|
|
|
|
}) {
|
|
|
|
|
debouncer.call(DebounceTag.applyProfile, (silence) {
|
|
|
|
|
applyProfile(silence: silence);
|
|
|
|
|
}, args: [silence]);
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
savePreferencesDebounce() {
|
|
|
|
|
debouncer.call(DebounceTag.savePreferences, savePreferences);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
changeProxyDebounce(String groupName, String proxyName) {
|
|
|
|
|
debouncer.call(DebounceTag.changeProxy,
|
|
|
|
|
(String groupName, String proxyName) async {
|
2024-12-03 21:47:12 +08:00
|
|
|
await changeProxy(
|
|
|
|
|
groupName: groupName,
|
|
|
|
|
proxyName: proxyName,
|
|
|
|
|
);
|
|
|
|
|
await updateGroups();
|
2024-12-09 01:40:39 +08:00
|
|
|
}, args: [groupName, proxyName]);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
restartCore() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
await clashService?.reStart();
|
2025-04-09 16:46:14 +08:00
|
|
|
await _initCore();
|
2025-02-09 18:39:38 +08:00
|
|
|
|
|
|
|
|
if (_ref.read(runTimeProvider.notifier).isStart) {
|
|
|
|
|
await globalState.handleStart();
|
|
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
2024-08-15 16:18:00 +08:00
|
|
|
updateStatus(bool isStart) async {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (isStart) {
|
2025-01-13 19:08:17 +08:00
|
|
|
await globalState.handleStart([
|
2024-04-30 23:38:49 +08:00
|
|
|
updateRunTime,
|
|
|
|
|
updateTraffic,
|
2025-01-13 19:08:17 +08:00
|
|
|
]);
|
2024-12-03 21:47:12 +08:00
|
|
|
final currentLastModified =
|
2025-02-09 18:39:38 +08:00
|
|
|
await _ref.read(currentProfileProvider)?.profileLastModified;
|
|
|
|
|
if (currentLastModified == null || lastProfileModified == null) {
|
2024-12-03 21:47:12 +08:00
|
|
|
addCheckIpNumDebounce();
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
if (currentLastModified <= (lastProfileModified ?? 0)) {
|
2024-12-03 21:47:12 +08:00
|
|
|
addCheckIpNumDebounce();
|
|
|
|
|
return;
|
2024-09-20 14:32:57 +08:00
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
applyProfileDebounce();
|
2024-04-30 23:38:49 +08:00
|
|
|
} else {
|
2024-08-15 16:18:00 +08:00
|
|
|
await globalState.handleStop();
|
2024-12-03 21:47:12 +08:00
|
|
|
await clashCore.resetTraffic();
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(trafficsProvider.notifier).clear();
|
|
|
|
|
_ref.read(totalTrafficProvider.notifier).value = Traffic();
|
|
|
|
|
_ref.read(runTimeProvider.notifier).value = null;
|
2024-07-26 08:05:22 +08:00
|
|
|
addCheckIpNumDebounce();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateRunTime() {
|
2024-08-15 16:18:00 +08:00
|
|
|
final startTime = globalState.startTime;
|
|
|
|
|
if (startTime != null) {
|
|
|
|
|
final startTimeStamp = startTime.millisecondsSinceEpoch;
|
2024-05-06 10:32:39 +08:00
|
|
|
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(runTimeProvider.notifier).value = nowTimeStamp - startTimeStamp;
|
2024-04-30 23:38:49 +08:00
|
|
|
} else {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(runTimeProvider.notifier).value = null;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
updateTraffic() async {
|
|
|
|
|
final traffic = await clashCore.getTraffic();
|
|
|
|
|
_ref.read(trafficsProvider.notifier).addTraffic(traffic);
|
|
|
|
|
_ref.read(totalTrafficProvider.notifier).value =
|
|
|
|
|
await clashCore.getTotalTraffic();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-03 18:02:05 +08:00
|
|
|
addProfile(Profile profile) async {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(profilesProvider.notifier).setProfile(profile);
|
|
|
|
|
if (_ref.read(currentProfileIdProvider) != null) return;
|
|
|
|
|
_ref.read(currentProfileIdProvider.notifier).value = profile.id;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
deleteProfile(String id) async {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(profilesProvider.notifier).deleteProfileById(id);
|
2024-12-03 21:47:12 +08:00
|
|
|
clearEffect(id);
|
2025-02-09 18:39:38 +08:00
|
|
|
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;
|
2024-04-30 23:38:49 +08:00
|
|
|
} else {
|
2025-02-09 18:39:38 +08:00
|
|
|
currentProfileId.value = null;
|
2024-08-15 16:18:00 +08:00
|
|
|
updateStatus(false);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-09 01:40:39 +08:00
|
|
|
updateProviders() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(providersProvider.notifier).value =
|
|
|
|
|
await clashCore.getExternalProviders();
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateLocalIp() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(localIpProvider.notifier).value = null;
|
2024-12-09 01:40:39 +08:00
|
|
|
await Future.delayed(commonDuration);
|
2025-04-09 16:46:14 +08:00
|
|
|
_ref.read(localIpProvider.notifier).value = await utils.getLocalIpAddress();
|
2024-11-09 20:17:57 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-19 13:13:31 +08:00
|
|
|
Future<void> updateProfile(Profile profile) async {
|
2024-08-04 08:21:14 +08:00
|
|
|
final newProfile = await profile.update();
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref
|
|
|
|
|
.read(profilesProvider.notifier)
|
|
|
|
|
.setProfile(newProfile.copyWith(isUpdating: false));
|
|
|
|
|
if (profile.id == _ref.read(currentProfileIdProvider)) {
|
2025-03-05 16:08:50 +08:00
|
|
|
applyProfileDebounce(silence: true);
|
2024-12-09 01:40:39 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:08:50 +08:00
|
|
|
setProfile(Profile profile) {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(profilesProvider.notifier).setProfile(profile);
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:08:50 +08:00
|
|
|
setProfileAndAutoApply(Profile profile) {
|
|
|
|
|
_ref.read(profilesProvider.notifier).setProfile(profile);
|
2025-02-09 18:39:38 +08:00
|
|
|
if (profile.id == _ref.read(currentProfileIdProvider)) {
|
2025-03-05 16:08:50 +08:00
|
|
|
applyProfileDebounce(silence: true);
|
2025-01-13 19:08:17 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
setProfiles(List<Profile> profiles) {
|
|
|
|
|
_ref.read(profilesProvider.notifier).value = profiles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addLog(Log log) {
|
|
|
|
|
_ref.read(logsProvider).add(log);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addSortNum() {
|
|
|
|
|
return _ref.read(sortNumProvider.notifier).add();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getCurrentGroupName() {
|
|
|
|
|
final currentGroupName = _ref.read(currentProfileProvider.select(
|
|
|
|
|
(state) => state?.currentGroupName,
|
|
|
|
|
));
|
|
|
|
|
return currentGroupName;
|
|
|
|
|
}
|
|
|
|
|
|
2025-03-05 16:08:50 +08:00
|
|
|
ProxyCardState getProxyCardState(proxyName) {
|
|
|
|
|
return _ref.read(getProxyCardStateProvider(proxyName));
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getSelectedProxyName(groupName) {
|
|
|
|
|
return _ref.read(getSelectedProxyNameProvider(groupName));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCurrentGroupName(String groupName) {
|
|
|
|
|
final profile = _ref.read(currentProfileProvider);
|
|
|
|
|
if (profile == null || profile.currentGroupName == groupName) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2025-03-05 16:08:50 +08:00
|
|
|
setProfile(
|
2025-02-09 18:39:38 +08:00
|
|
|
profile.copyWith(currentGroupName: groupName),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<void> updateClashConfig([bool? isPatch]) async {
|
2025-03-12 17:15:31 +08:00
|
|
|
commonPrint.log("update clash patch: ${isPatch ?? false}");
|
2024-09-20 14:32:57 +08:00
|
|
|
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
|
|
|
|
if (commonScaffoldState?.mounted != true) return;
|
|
|
|
|
await commonScaffoldState?.loadingRun(() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
await _updateClashConfig(
|
|
|
|
|
isPatch,
|
2024-09-20 14:32:57 +08:00
|
|
|
);
|
|
|
|
|
});
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
Future<void> _updateClashConfig([bool? isPatch]) async {
|
|
|
|
|
final profile = _ref.watch(currentProfileProvider);
|
|
|
|
|
await _ref.read(currentProfileProvider)?.checkAndUpdate();
|
|
|
|
|
final patchConfig = _ref.read(patchClashConfigProvider);
|
|
|
|
|
final appSetting = _ref.read(appSettingProvider);
|
|
|
|
|
bool enableTun = patchConfig.tun.enable;
|
|
|
|
|
if (enableTun != lastTunEnable &&
|
|
|
|
|
lastTunEnable == false &&
|
|
|
|
|
!Platform.isAndroid) {
|
|
|
|
|
final code = await system.authorizeCore();
|
|
|
|
|
switch (code) {
|
|
|
|
|
case AuthorizeCode.none:
|
|
|
|
|
break;
|
|
|
|
|
case AuthorizeCode.success:
|
|
|
|
|
lastTunEnable = enableTun;
|
|
|
|
|
await restartCore();
|
|
|
|
|
return;
|
|
|
|
|
case AuthorizeCode.error:
|
|
|
|
|
enableTun = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (appSetting.openLogs) {
|
|
|
|
|
clashCore.startLog();
|
|
|
|
|
} else {
|
|
|
|
|
clashCore.stopLog();
|
|
|
|
|
}
|
|
|
|
|
final res = await clashCore.updateConfig(
|
|
|
|
|
globalState.getUpdateConfigParams(isPatch),
|
|
|
|
|
);
|
2025-04-09 16:46:14 +08:00
|
|
|
if (isPatch == false) {
|
|
|
|
|
_ref.read(needApplyProvider.notifier).value = false;
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
if (res.isNotEmpty) throw res;
|
|
|
|
|
lastTunEnable = enableTun;
|
|
|
|
|
lastProfileModified = await profile?.profileLastModified;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future _applyProfile() async {
|
|
|
|
|
await clashCore.requestGc();
|
|
|
|
|
await updateClashConfig();
|
|
|
|
|
await updateGroups();
|
|
|
|
|
await updateProviders();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future applyProfile({bool silence = false}) async {
|
|
|
|
|
if (silence) {
|
|
|
|
|
await _applyProfile();
|
2024-07-26 08:05:22 +08:00
|
|
|
} else {
|
|
|
|
|
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
|
|
|
|
if (commonScaffoldState?.mounted != true) return;
|
|
|
|
|
await commonScaffoldState?.loadingRun(() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
await _applyProfile();
|
2024-07-26 08:05:22 +08:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
addCheckIpNumDebounce();
|
2024-07-13 16:36:08 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
handleChangeProfile() {
|
|
|
|
|
_ref.read(delayDataSourceProvider.notifier).value = {};
|
|
|
|
|
applyProfile();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateBrightness(Brightness brightness) {
|
|
|
|
|
_ref.read(appBrightnessProvider.notifier).value = brightness;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
autoUpdateProfiles() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
for (final profile in _ref.read(profilesProvider)) {
|
2024-06-07 17:22:55 +08:00
|
|
|
if (!profile.autoUpdate) continue;
|
2024-04-30 23:38:49 +08:00
|
|
|
final isNotNeedUpdate = profile.lastUpdateDate
|
|
|
|
|
?.add(
|
2024-05-06 10:32:39 +08:00
|
|
|
profile.autoUpdateDuration,
|
|
|
|
|
)
|
2024-06-03 18:02:05 +08:00
|
|
|
.isBeforeNow;
|
2024-06-07 17:22:55 +08:00
|
|
|
if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-13 23:43:42 +08:00
|
|
|
try {
|
2025-02-09 18:39:38 +08:00
|
|
|
await updateProfile(profile);
|
2024-06-13 23:43:42 +08:00
|
|
|
} catch (e) {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(logsProvider.notifier).addLog(
|
|
|
|
|
Log(
|
|
|
|
|
logLevel: LogLevel.info,
|
|
|
|
|
payload: e.toString(),
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-06-13 23:43:42 +08:00
|
|
|
}
|
2024-06-07 17:22:55 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
Future<void> updateGroups() async {
|
|
|
|
|
_ref.read(groupsProvider.notifier).value = await retry(
|
|
|
|
|
task: () async {
|
|
|
|
|
return await clashCore.getProxiesGroups();
|
|
|
|
|
},
|
|
|
|
|
retryIf: (res) => res.isEmpty,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-06-07 17:22:55 +08:00
|
|
|
updateProfiles() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
for (final profile in _ref.read(profilesProvider)) {
|
2024-06-07 17:22:55 +08:00
|
|
|
if (profile.type == ProfileType.file) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2024-06-19 13:13:31 +08:00
|
|
|
await updateProfile(profile);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
updateSystemColorSchemes(ColorSchemes colorSchemes) {
|
|
|
|
|
_ref.read(appSchemesProvider.notifier).value = colorSchemes;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
savePreferences() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
commonPrint.log("save preferences");
|
|
|
|
|
await preferences.saveConfig(globalState.config);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-07-26 08:05:22 +08:00
|
|
|
changeProxy({
|
|
|
|
|
required String groupName,
|
|
|
|
|
required String proxyName,
|
2024-12-03 21:47:12 +08:00
|
|
|
}) async {
|
2025-02-09 18:39:38 +08:00
|
|
|
await clashCore.changeProxy(
|
|
|
|
|
ChangeProxyParams(
|
|
|
|
|
groupName: groupName,
|
|
|
|
|
proxyName: proxyName,
|
|
|
|
|
),
|
2024-07-26 08:05:22 +08:00
|
|
|
);
|
2025-02-09 18:39:38 +08:00
|
|
|
if (_ref.read(appSettingProvider).closeConnections) {
|
|
|
|
|
clashCore.closeConnections();
|
|
|
|
|
}
|
2024-07-26 08:05:22 +08:00
|
|
|
addCheckIpNumDebounce();
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
handleBackOrExit() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
if (_ref.read(appSettingProvider).minimizeOnExit) {
|
2024-04-30 23:38:49 +08:00
|
|
|
if (system.isDesktop) {
|
2024-10-27 16:59:23 +08:00
|
|
|
await savePreferencesDebounce();
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
await system.back();
|
|
|
|
|
} else {
|
|
|
|
|
await handleExit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
handleExit() async {
|
2024-12-03 21:47:12 +08:00
|
|
|
try {
|
|
|
|
|
await updateStatus(false);
|
|
|
|
|
await clashCore.shutdown();
|
|
|
|
|
await clashService?.destroy();
|
|
|
|
|
await proxy?.stopProxy();
|
|
|
|
|
await savePreferences();
|
2025-01-13 19:08:17 +08:00
|
|
|
} finally {
|
|
|
|
|
system.exit();
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2024-05-20 15:15:09 +08:00
|
|
|
autoCheckUpdate() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
|
2024-06-03 18:02:05 +08:00
|
|
|
final res = await request.checkForUpdate();
|
|
|
|
|
checkUpdateResultHandle(data: res);
|
2024-05-31 09:59:18 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
checkUpdateResultHandle({
|
2024-06-03 18:02:05 +08:00
|
|
|
Map<String, dynamic>? data,
|
|
|
|
|
bool handleError = false,
|
|
|
|
|
}) async {
|
2025-04-09 16:46:14 +08:00
|
|
|
if (globalState.isPre) {
|
2025-03-12 17:15:31 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2024-06-03 18:02:05 +08:00
|
|
|
if (data != null) {
|
|
|
|
|
final tagName = data['tag_name'];
|
|
|
|
|
final body = data['body'];
|
2025-04-09 16:46:14 +08:00
|
|
|
final submits = utils.parseReleaseBody(body);
|
2024-06-13 23:43:42 +08:00
|
|
|
final textTheme = context.textTheme;
|
2025-01-13 19:08:17 +08:00
|
|
|
final res = await globalState.showMessage(
|
2024-05-31 09:59:18 +08:00
|
|
|
title: appLocalizations.discoverNewVersion,
|
|
|
|
|
message: TextSpan(
|
|
|
|
|
text: "$tagName \n",
|
2024-06-13 23:43:42 +08:00
|
|
|
style: textTheme.headlineSmall,
|
2024-05-31 09:59:18 +08:00
|
|
|
children: [
|
|
|
|
|
TextSpan(
|
|
|
|
|
text: "\n",
|
2024-06-13 23:43:42 +08:00
|
|
|
style: textTheme.bodyMedium,
|
2024-05-31 09:59:18 +08:00
|
|
|
),
|
|
|
|
|
for (final submit in submits)
|
|
|
|
|
TextSpan(
|
|
|
|
|
text: "- $submit \n",
|
2024-06-13 23:43:42 +08:00
|
|
|
style: textTheme.bodyMedium,
|
2024-05-31 09:59:18 +08:00
|
|
|
),
|
|
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
confirmText: appLocalizations.goDownload,
|
|
|
|
|
);
|
2025-01-13 19:08:17 +08:00
|
|
|
if (res != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
launchUrl(
|
|
|
|
|
Uri.parse("https://github.com/$repository/releases/latest"),
|
|
|
|
|
);
|
2024-06-03 18:02:05 +08:00
|
|
|
} else if (handleError) {
|
2024-05-31 09:59:18 +08:00
|
|
|
globalState.showMessage(
|
|
|
|
|
title: appLocalizations.checkUpdate,
|
|
|
|
|
message: TextSpan(
|
|
|
|
|
text: appLocalizations.checkUpdateError,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
2024-05-20 15:15:09 +08:00
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
_handlePreference() async {
|
|
|
|
|
if (await preferences.isInit) {
|
|
|
|
|
return;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
2025-02-03 23:32:00 +08:00
|
|
|
final res = await globalState.showMessage(
|
|
|
|
|
title: appLocalizations.tip,
|
|
|
|
|
message: TextSpan(text: appLocalizations.cacheCorrupt),
|
|
|
|
|
);
|
2025-02-09 18:39:38 +08:00
|
|
|
if (res == true) {
|
2025-02-03 23:32:00 +08:00
|
|
|
final file = File(await appPath.sharedPreferencesPath);
|
|
|
|
|
final isExists = await file.exists();
|
|
|
|
|
if (isExists) {
|
|
|
|
|
await file.delete();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
await handleExit();
|
|
|
|
|
}
|
|
|
|
|
|
2025-04-09 16:46:14 +08:00
|
|
|
Future<void> _initCore() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
final isInit = await clashCore.isInit;
|
|
|
|
|
if (!isInit) {
|
|
|
|
|
await clashCore.setState(
|
|
|
|
|
globalState.getCoreState(),
|
|
|
|
|
);
|
|
|
|
|
await clashCore.init();
|
|
|
|
|
}
|
|
|
|
|
await applyProfile();
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
init() async {
|
|
|
|
|
await _handlePreference();
|
|
|
|
|
await _handlerDisclaimer();
|
2025-04-09 16:46:14 +08:00
|
|
|
await _initCore();
|
2024-12-03 21:47:12 +08:00
|
|
|
await _initStatus();
|
2025-02-09 18:39:38 +08:00
|
|
|
updateTray(true);
|
2024-12-09 01:40:39 +08:00
|
|
|
autoLaunch?.updateStatus(
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(appSettingProvider).autoLaunch,
|
2024-12-09 01:40:39 +08:00
|
|
|
);
|
2024-12-03 21:47:12 +08:00
|
|
|
autoUpdateProfiles();
|
|
|
|
|
autoCheckUpdate();
|
2025-02-09 18:39:38 +08:00
|
|
|
if (!_ref.read(appSettingProvider).silentLaunch) {
|
2025-01-13 19:08:17 +08:00
|
|
|
window?.show();
|
|
|
|
|
} else {
|
|
|
|
|
window?.hide();
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(initProvider.notifier).value = true;
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_initStatus() async {
|
2024-08-15 16:18:00 +08:00
|
|
|
if (Platform.isAndroid) {
|
2025-01-13 19:08:17 +08:00
|
|
|
await globalState.updateStartTime();
|
2024-08-15 16:18:00 +08:00
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
final status = globalState.isStart == true
|
|
|
|
|
? true
|
|
|
|
|
: _ref.read(appSettingProvider).autoRun;
|
2024-12-09 01:40:39 +08:00
|
|
|
|
|
|
|
|
await updateStatus(status);
|
|
|
|
|
if (!status) {
|
|
|
|
|
addCheckIpNumDebounce();
|
2024-05-06 10:32:39 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setDelay(Delay delay) {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(delayDataSourceProvider.notifier).setDelay(delay);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
toPage(PageLabel pageLabel) {
|
|
|
|
|
_ref.read(currentPageLabelProvider.notifier).value = pageLabel;
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
toProfiles() {
|
2025-03-12 17:15:31 +08:00
|
|
|
toPage(PageLabel.profiles);
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
initLink() {
|
|
|
|
|
linkManager.initAppLinksListen(
|
2025-01-13 19:08:17 +08:00
|
|
|
(url) async {
|
|
|
|
|
final res = await globalState.showMessage(
|
2024-04-30 23:38:49 +08:00
|
|
|
title: "${appLocalizations.add}${appLocalizations.profile}",
|
|
|
|
|
message: TextSpan(
|
|
|
|
|
children: [
|
|
|
|
|
TextSpan(text: appLocalizations.doYouWantToPass),
|
|
|
|
|
TextSpan(
|
|
|
|
|
text: " $url ",
|
|
|
|
|
style: TextStyle(
|
2024-05-06 10:32:39 +08:00
|
|
|
color: Theme.of(context).colorScheme.primary,
|
2024-04-30 23:38:49 +08:00
|
|
|
decoration: TextDecoration.underline,
|
2024-05-06 10:32:39 +08:00
|
|
|
decorationColor: Theme.of(context).colorScheme.primary,
|
2024-04-30 23:38:49 +08:00
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
TextSpan(
|
|
|
|
|
text:
|
2024-05-06 10:32:39 +08:00
|
|
|
"${appLocalizations.create}${appLocalizations.profile}"),
|
2024-04-30 23:38:49 +08:00
|
|
|
],
|
|
|
|
|
),
|
|
|
|
|
);
|
2025-01-13 19:08:17 +08:00
|
|
|
|
|
|
|
|
if (res != true) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
addProfileFormURL(url);
|
2024-04-30 23:38:49 +08:00
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
Future<bool> showDisclaimer() async {
|
|
|
|
|
return await globalState.showCommonDialog<bool>(
|
|
|
|
|
dismissible: false,
|
2025-03-12 17:15:31 +08:00
|
|
|
child: CommonDialog(
|
|
|
|
|
title: appLocalizations.disclaimer,
|
2024-09-08 21:21:21 +08:00
|
|
|
actions: [
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
|
|
|
|
Navigator.of(context).pop<bool>(false);
|
|
|
|
|
},
|
|
|
|
|
child: Text(appLocalizations.exit),
|
|
|
|
|
),
|
|
|
|
|
TextButton(
|
|
|
|
|
onPressed: () {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(appSettingProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(disclaimerAccepted: true),
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
Navigator.of(context).pop<bool>(true);
|
|
|
|
|
},
|
|
|
|
|
child: Text(appLocalizations.agree),
|
|
|
|
|
)
|
|
|
|
|
],
|
2025-03-12 17:15:31 +08:00
|
|
|
child: SelectableText(
|
|
|
|
|
appLocalizations.disclaimerDesc,
|
|
|
|
|
),
|
2024-09-08 21:21:21 +08:00
|
|
|
),
|
|
|
|
|
) ??
|
|
|
|
|
false;
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-03 23:32:00 +08:00
|
|
|
_handlerDisclaimer() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
if (_ref.read(appSettingProvider).disclaimerAccepted) {
|
2025-02-03 23:32:00 +08:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
final isDisclaimerAccepted = await showDisclaimer();
|
|
|
|
|
if (!isDisclaimerAccepted) {
|
|
|
|
|
await handleExit();
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
2025-02-03 23:32:00 +08:00
|
|
|
return;
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
addProfileFormURL(String url) async {
|
2024-06-27 17:23:59 +08:00
|
|
|
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
|
|
|
|
|
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
|
|
|
|
}
|
2024-05-11 17:02:34 +08:00
|
|
|
toProfiles();
|
|
|
|
|
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
|
|
|
|
if (commonScaffoldState?.mounted != true) return;
|
2024-06-03 18:02:05 +08:00
|
|
|
final profile = await commonScaffoldState?.loadingRun<Profile>(
|
2024-05-11 17:02:34 +08:00
|
|
|
() async {
|
2024-06-19 13:13:31 +08:00
|
|
|
return await Profile.normal(
|
2024-05-11 17:02:34 +08:00
|
|
|
url: url,
|
2024-06-19 13:13:31 +08:00
|
|
|
).update();
|
2024-05-11 17:02:34 +08:00
|
|
|
},
|
2024-06-03 18:02:05 +08:00
|
|
|
title: "${appLocalizations.add}${appLocalizations.profile}",
|
2024-05-11 17:02:34 +08:00
|
|
|
);
|
2024-06-03 18:02:05 +08:00
|
|
|
if (profile != null) {
|
|
|
|
|
await addProfile(profile);
|
|
|
|
|
}
|
2024-05-11 17:02:34 +08:00
|
|
|
}
|
|
|
|
|
|
2024-04-30 23:38:49 +08:00
|
|
|
addProfileFormFile() async {
|
2024-08-04 08:21:14 +08:00
|
|
|
final platformFile = await globalState.safeRun(picker.pickerFile);
|
2024-07-26 08:05:22 +08:00
|
|
|
final bytes = platformFile?.bytes;
|
|
|
|
|
if (bytes == null) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
if (!context.mounted) return;
|
|
|
|
|
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
|
|
|
|
|
toProfiles();
|
|
|
|
|
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
|
|
|
|
|
if (commonScaffoldState?.mounted != true) return;
|
2024-06-03 18:02:05 +08:00
|
|
|
final profile = await commonScaffoldState?.loadingRun<Profile?>(
|
2024-05-06 10:32:39 +08:00
|
|
|
() async {
|
2024-04-30 23:38:49 +08:00
|
|
|
await Future.delayed(const Duration(milliseconds: 300));
|
2024-06-19 13:13:31 +08:00
|
|
|
return await Profile.normal(label: platformFile?.name).saveFile(bytes);
|
2024-04-30 23:38:49 +08:00
|
|
|
},
|
2024-06-03 18:02:05 +08:00
|
|
|
title: "${appLocalizations.add}${appLocalizations.profile}",
|
2024-04-30 23:38:49 +08:00
|
|
|
);
|
2024-06-03 18:02:05 +08:00
|
|
|
if (profile != null) {
|
|
|
|
|
await addProfile(profile);
|
|
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|
2024-05-10 10:11:27 +08:00
|
|
|
|
2024-05-11 17:02:34 +08:00
|
|
|
addProfileFormQrCode() async {
|
2024-06-03 18:02:05 +08:00
|
|
|
final url = await globalState.safeRun(picker.pickerConfigQRCode);
|
|
|
|
|
if (url == null) return;
|
|
|
|
|
addProfileFormURL(url);
|
2024-05-11 17:02:34 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
updateViewSize(Size size) {
|
2024-06-03 18:02:05 +08:00
|
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
2025-03-12 17:15:31 +08:00
|
|
|
_ref.read(viewSizeProvider.notifier).value = size;
|
2024-06-03 18:02:05 +08:00
|
|
|
});
|
2024-05-20 15:15:09 +08:00
|
|
|
}
|
2024-06-23 00:26:24 +08:00
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
setProvider(ExternalProvider? provider) {
|
|
|
|
|
_ref.read(providersProvider.notifier).setProvider(provider);
|
2025-01-13 19:08:17 +08:00
|
|
|
}
|
|
|
|
|
|
2024-06-23 00:26:24 +08:00
|
|
|
List<Proxy> _sortOfName(List<Proxy> proxies) {
|
|
|
|
|
return List.of(proxies)
|
|
|
|
|
..sort(
|
2025-04-09 16:46:14 +08:00
|
|
|
(a, b) => utils.sortByChar(
|
|
|
|
|
utils.getPinyin(a.name),
|
|
|
|
|
utils.getPinyin(b.name),
|
2024-08-05 19:25:35 +08:00
|
|
|
),
|
2024-06-23 00:26:24 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-02-09 18:39:38 +08:00
|
|
|
List<Proxy> _sortOfDelay({
|
|
|
|
|
required List<Proxy> proxies,
|
|
|
|
|
String? testUrl,
|
|
|
|
|
}) {
|
2025-01-13 19:08:17 +08:00
|
|
|
return List.of(proxies)
|
2024-06-23 00:26:24 +08:00
|
|
|
..sort(
|
2024-06-27 17:23:59 +08:00
|
|
|
(a, b) {
|
2025-02-09 18:39:38 +08:00
|
|
|
final aDelay =
|
|
|
|
|
_ref.read(getDelayProvider(proxyName: a.name, testUrl: testUrl));
|
|
|
|
|
final bDelay =
|
|
|
|
|
_ref.read(getDelayProvider(proxyName: b.name, testUrl: testUrl));
|
2024-06-23 00:26:24 +08:00
|
|
|
if (aDelay == null && bDelay == null) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
if (aDelay == null || aDelay == -1) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (bDelay == null || bDelay == -1) {
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
return aDelay.compareTo(bDelay);
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2025-01-13 19:08:17 +08:00
|
|
|
List<Proxy> getSortProxies(List<Proxy> proxies, [String? url]) {
|
2025-02-09 18:39:38 +08:00
|
|
|
return switch (_ref.read(proxiesStyleSettingProvider).sortType) {
|
2024-06-23 00:26:24 +08:00
|
|
|
ProxiesSortType.none => proxies,
|
2025-02-09 18:39:38 +08:00
|
|
|
ProxiesSortType.delay => _sortOfDelay(
|
|
|
|
|
proxies: proxies,
|
|
|
|
|
testUrl: url,
|
|
|
|
|
),
|
2024-06-27 17:23:59 +08:00
|
|
|
ProxiesSortType.name => _sortOfName(proxies),
|
2024-06-23 00:26:24 +08:00
|
|
|
};
|
|
|
|
|
}
|
2024-07-24 01:27:49 +08:00
|
|
|
|
2024-12-03 21:47:12 +08:00
|
|
|
clearEffect(String profileId) async {
|
|
|
|
|
final profilePath = await appPath.getProfilePath(profileId);
|
|
|
|
|
final providersPath = await appPath.getProvidersPath(profileId);
|
|
|
|
|
return await Isolate.run(() async {
|
|
|
|
|
if (profilePath != null) {
|
2025-03-12 17:15:31 +08:00
|
|
|
final profileFile = File(profilePath);
|
|
|
|
|
final isExists = await profileFile.exists();
|
|
|
|
|
if (isExists) {
|
|
|
|
|
profileFile.delete(recursive: true);
|
|
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
if (providersPath != null) {
|
2025-03-12 17:15:31 +08:00
|
|
|
final providersFileDir = File(providersPath);
|
|
|
|
|
final isExists = await providersFileDir.exists();
|
|
|
|
|
if (isExists) {
|
|
|
|
|
providersFileDir.delete(recursive: true);
|
|
|
|
|
}
|
2024-12-03 21:47:12 +08:00
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
updateTun() {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith.tun(enable: !state.tun.enable),
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateSystemProxy() {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(networkSettingProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
2025-03-05 16:08:50 +08:00
|
|
|
systemProxy: !state.systemProxy,
|
2025-02-09 18:39:38 +08:00
|
|
|
),
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateStart() {
|
2025-03-05 16:08:50 +08:00
|
|
|
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
|
2025-02-09 18:39:38 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCurrentUnfoldSet(Set<String> value) {
|
|
|
|
|
final currentProfile = _ref.read(currentProfileProvider);
|
|
|
|
|
if (currentProfile == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
_ref.read(profilesProvider.notifier).setProfile(
|
|
|
|
|
currentProfile.copyWith(
|
|
|
|
|
unfoldSet: value,
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2024-12-06 22:35:28 +08:00
|
|
|
changeMode(Mode mode) {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(patchClashConfigProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(mode: mode),
|
|
|
|
|
);
|
2025-03-12 17:15:31 +08:00
|
|
|
if (mode == Mode.global) {
|
|
|
|
|
updateCurrentGroupName(GroupName.GLOBAL.name);
|
|
|
|
|
}
|
|
|
|
|
addCheckIpNumDebounce();
|
2024-12-06 22:35:28 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
updateAutoLaunch() {
|
2025-02-09 18:39:38 +08:00
|
|
|
_ref.read(appSettingProvider.notifier).updateState(
|
|
|
|
|
(state) => state.copyWith(
|
|
|
|
|
autoLaunch: !state.autoLaunch,
|
|
|
|
|
),
|
|
|
|
|
);
|
2024-09-26 14:29:04 +08:00
|
|
|
}
|
|
|
|
|
|
2024-09-08 21:21:21 +08:00
|
|
|
updateVisible() async {
|
2025-03-12 17:15:31 +08:00
|
|
|
final visible = await window?.isVisible;
|
2024-09-08 21:21:21 +08:00
|
|
|
if (visible != null && !visible) {
|
|
|
|
|
window?.show();
|
|
|
|
|
} else {
|
|
|
|
|
window?.hide();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateMode() {
|
2025-02-09 18:39:38 +08:00
|
|
|
_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],
|
|
|
|
|
);
|
|
|
|
|
},
|
|
|
|
|
);
|
2024-09-08 21:21:21 +08:00
|
|
|
}
|
|
|
|
|
|
2025-03-12 17:15:31 +08:00
|
|
|
handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
|
|
|
|
|
final res = await globalState.showCommonDialog<Rule>(
|
|
|
|
|
child: AddRuleDialog(
|
|
|
|
|
rule: rule,
|
|
|
|
|
snippet: ref.read(
|
|
|
|
|
profileOverrideStateProvider.select(
|
|
|
|
|
(state) => state.snippet!,
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
if (res == null) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
ref.read(profileOverrideStateProvider.notifier).updateState(
|
|
|
|
|
(state) {
|
|
|
|
|
final model = state.copyWith.overrideData!(
|
|
|
|
|
rule: state.overrideData!.rule.updateRules(
|
|
|
|
|
(rules) {
|
|
|
|
|
final index = rules.indexWhere((item) => item.id == res.id);
|
|
|
|
|
if (index == -1) {
|
|
|
|
|
return List.from([res, ...rules]);
|
|
|
|
|
}
|
|
|
|
|
return List.from(rules)..[index] = res;
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
);
|
|
|
|
|
return model;
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-20 14:32:57 +08:00
|
|
|
Future<bool> exportLogs() async {
|
2025-02-09 18:39:38 +08:00
|
|
|
final logsRaw = _ref.read(logsProvider).list.map(
|
|
|
|
|
(item) => item.toString(),
|
|
|
|
|
);
|
2024-09-20 14:32:57 +08:00
|
|
|
final data = await Isolate.run<List<int>>(() async {
|
|
|
|
|
final logsRawString = logsRaw.join("\n");
|
|
|
|
|
return utf8.encode(logsRawString);
|
|
|
|
|
});
|
|
|
|
|
return await picker.saveFile(
|
2025-04-09 16:46:14 +08:00
|
|
|
utils.logFile,
|
2024-09-20 14:32:57 +08:00
|
|
|
Uint8List.fromList(data),
|
|
|
|
|
) !=
|
|
|
|
|
null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Future<List<int>> backupData() async {
|
2025-02-03 23:32:00 +08:00
|
|
|
final homeDirPath = await appPath.homeDirPath;
|
|
|
|
|
final profilesPath = await appPath.profilesPath;
|
2025-02-09 18:39:38 +08:00
|
|
|
final configJson = globalState.config.toJson();
|
2024-09-20 14:32:57 +08:00
|
|
|
return Isolate.run<List<int>>(() async {
|
|
|
|
|
final archive = Archive();
|
|
|
|
|
archive.add("config.json", configJson);
|
|
|
|
|
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
|
|
|
|
|
final zipEncoder = ZipEncoder();
|
|
|
|
|
return zipEncoder.encode(archive) ?? [];
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
2024-10-14 10:03:23 +08:00
|
|
|
updateTray([bool focus = false]) async {
|
2024-12-03 21:47:12 +08:00
|
|
|
tray.update(
|
2025-02-09 18:39:38 +08:00
|
|
|
trayState: _ref.read(trayStateProvider),
|
2024-10-08 17:28:18 +08:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2024-08-04 08:21:14 +08:00
|
|
|
recoveryData(
|
|
|
|
|
List<int> data,
|
|
|
|
|
RecoveryOption recoveryOption,
|
|
|
|
|
) async {
|
|
|
|
|
final archive = await Isolate.run<Archive>(() {
|
|
|
|
|
final zipDecoder = ZipDecoder();
|
|
|
|
|
return zipDecoder.decodeBytes(data);
|
|
|
|
|
});
|
2025-02-03 23:32:00 +08:00
|
|
|
final homeDirPath = await appPath.homeDirPath;
|
2024-08-04 08:21:14 +08:00
|
|
|
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");
|
2025-02-09 18:39:38 +08:00
|
|
|
if (configIndex == -1) throw "invalid backup file";
|
2024-08-04 08:21:14 +08:00
|
|
|
final configFile = configs[configIndex];
|
2025-02-09 18:39:38 +08:00
|
|
|
var tempConfig = Config.compatibleFromJson(
|
2024-08-04 08:21:14 +08:00
|
|
|
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);
|
|
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
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,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_recovery(Config config, RecoveryOption recoveryOption) {
|
|
|
|
|
final profiles = config.profiles;
|
|
|
|
|
for (final profile in profiles) {
|
|
|
|
|
_ref.read(profilesProvider.notifier).setProfile(profile);
|
|
|
|
|
}
|
|
|
|
|
final onlyProfiles = recoveryOption == RecoveryOption.onlyProfiles;
|
|
|
|
|
if (onlyProfiles) {
|
|
|
|
|
final currentProfile = _ref.read(currentProfileProvider);
|
|
|
|
|
if (currentProfile != null) {
|
|
|
|
|
_ref.read(currentProfileIdProvider.notifier).value = profiles.first.id;
|
|
|
|
|
}
|
|
|
|
|
return;
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
2025-02-09 18:39:38 +08:00
|
|
|
_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;
|
2024-08-04 08:21:14 +08:00
|
|
|
}
|
2024-04-30 23:38:49 +08:00
|
|
|
}
|