Files
MWClash/lib/controller.dart

464 lines
12 KiB
Dart
Raw Normal View History

2024-04-30 23:38:49 +08:00
import 'dart:async';
import 'package:fl_clash/enum/enum.dart';
2024-04-30 23:38:49 +08:00
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.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 'clash/core.dart';
import 'models/models.dart';
import 'common/common.dart';
class AppController {
final BuildContext context;
late AppState appState;
late Config config;
late ClashConfig clashConfig;
late Measure measure;
late Function updateClashConfigDebounce;
late Function addCheckIpNumDebounce;
2024-04-30 23:38:49 +08:00
AppController(this.context) {
appState = context.read<AppState>();
config = context.read<Config>();
clashConfig = context.read<ClashConfig>();
updateClashConfigDebounce = debounce<Function()>(() async {
await updateClashConfig();
});
2024-07-13 16:36:08 +08:00
addCheckIpNumDebounce = debounce(() {
appState.checkIpNum++;
});
2024-04-30 23:38:49 +08:00
measure = Measure.of(context);
}
Future<void> updateSystemProxy(bool isStart) async {
if (isStart) {
await globalState.startSystemProxy(
appState: appState,
2024-04-30 23:38:49 +08:00
config: config,
clashConfig: clashConfig,
);
updateRunTime();
updateTraffic();
globalState.updateFunctionLists = [
updateRunTime,
updateTraffic,
];
} else {
await globalState.stopSystemProxy();
clashCore.resetTraffic();
2024-04-30 23:38:49 +08:00
appState.traffics = [];
appState.totalTraffic = Traffic();
2024-04-30 23:38:49 +08:00
appState.runTime = null;
}
}
updateCoreVersionInfo() {
globalState.updateCoreVersionInfo(appState);
}
updateRunTime() {
if (proxyManager.startTime != null) {
final startTimeStamp = proxyManager.startTime!.millisecondsSinceEpoch;
2024-05-06 10:32:39 +08:00
final nowTimeStamp = DateTime.now().millisecondsSinceEpoch;
2024-04-30 23:38:49 +08:00
appState.runTime = nowTimeStamp - startTimeStamp;
} else {
appState.runTime = null;
}
}
updateTraffic() {
globalState.updateTraffic(
appState: appState,
);
}
addProfile(Profile profile) async {
2024-04-30 23:38:49 +08:00
config.setProfile(profile);
if (config.currentProfileId != null) return;
await changeProfile(profile.id);
2024-04-30 23:38:49 +08:00
}
deleteProfile(String id) async {
config.deleteProfileById(id);
final profilePath = await appPath.getProfilePath(id);
if (profilePath == null) return;
clashCore.clearEffect(profilePath);
2024-04-30 23:38:49 +08:00
if (config.currentProfileId == id) {
if (config.profiles.isNotEmpty) {
final updateId = config.profiles.first.id;
changeProfile(updateId);
} else {
changeProfile(null);
}
}
}
Future<void> updateProfile(Profile profile) async {
await profile.update();
config.setProfile(await profile.update());
2024-04-30 23:38:49 +08:00
}
Future<void> updateClashConfig({bool isPatch = true}) async {
await globalState.updateClashConfig(
2024-04-30 23:38:49 +08:00
clashConfig: clashConfig,
config: config,
isPatch: isPatch,
);
}
Future applyProfile() async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
2024-05-04 21:51:40 +08:00
});
}
2024-07-13 16:36:08 +08:00
Future rawApplyProfile() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
2024-04-30 23:38:49 +08:00
changeProfile(String? value) async {
if (value == config.currentProfileId) return;
config.currentProfileId = value;
2024-05-04 16:39:21 +08:00
await applyProfile();
2024-04-30 23:38:49 +08:00
appState.delayMap = {};
saveConfigPreferences();
}
autoUpdateProfiles() async {
for (final profile in config.profiles) {
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,
)
.isBeforeNow;
if (isNotNeedUpdate == false || profile.type == ProfileType.file) {
continue;
}
try {
updateProfile(profile);
} catch (e) {
appState.addLog(
Log(
logLevel: LogLevel.info,
payload: e.toString(),
),
);
}
}
}
updateProfiles() async {
for (final profile in config.profiles) {
if (profile.type == ProfileType.file) {
continue;
}
await updateProfile(profile);
2024-04-30 23:38:49 +08:00
}
}
2024-05-03 14:31:10 +08:00
Future<void> updateGroups() async {
await globalState.updateGroups(appState);
2024-04-30 23:38:49 +08:00
}
updateSystemColorSchemes(SystemColorSchemes systemColorSchemes) {
appState.systemColorSchemes = systemColorSchemes;
}
savePreferences() async {
await saveConfigPreferences();
await saveClashConfigPreferences();
}
saveConfigPreferences() async {
debugPrint("saveConfigPreferences");
await preferences.saveConfig(config);
}
saveClashConfigPreferences() async {
debugPrint("saveClashConfigPreferences");
await preferences.saveClashConfig(clashConfig);
}
handleBackOrExit() async {
if (config.isMinimizeOnExit) {
if (system.isDesktop) {
await savePreferences();
}
await system.back();
} else {
await handleExit();
}
}
handleExit() async {
await updateSystemProxy(false);
await savePreferences();
clashCore.shutdown();
system.exit();
}
updateLogStatus() {
if (config.openLogs) {
clashCore.startLog();
} else {
clashCore.stopLog();
appState.logs = [];
}
}
autoCheckUpdate() async {
2024-05-31 09:59:18 +08:00
if (!config.autoCheckUpdate) return;
final res = await request.checkForUpdate();
checkUpdateResultHandle(data: res);
2024-05-31 09:59:18 +08:00
}
checkUpdateResultHandle({
Map<String, dynamic>? data,
bool handleError = false,
}) async {
if (data != null) {
final tagName = data['tag_name'];
final body = data['body'];
2024-05-31 09:59:18 +08:00
final submits = other.parseReleaseBody(body);
final textTheme = context.textTheme;
2024-05-31 09:59:18 +08:00
globalState.showMessage(
title: appLocalizations.discoverNewVersion,
message: TextSpan(
text: "$tagName \n",
style: textTheme.headlineSmall,
2024-05-31 09:59:18 +08:00
children: [
TextSpan(
text: "\n",
style: textTheme.bodyMedium,
2024-05-31 09:59:18 +08:00
),
for (final submit in submits)
TextSpan(
text: "- $submit \n",
style: textTheme.bodyMedium,
2024-05-31 09:59:18 +08:00
),
],
),
onTab: () {
launchUrl(
Uri.parse("https://github.com/$repository/releases/latest"),
);
},
confirmText: appLocalizations.goDownload,
);
} else if (handleError) {
2024-05-31 09:59:18 +08:00
globalState.showMessage(
title: appLocalizations.checkUpdate,
message: TextSpan(
text: appLocalizations.checkUpdateError,
),
);
}
}
init() async {
updateLogStatus();
if (!config.silentLaunch) {
window?.show();
}
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted == true) {
await commonScaffoldState?.loadingRun(() async {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}, title: appLocalizations.init);
} else {
await globalState.applyProfile(
appState: appState,
config: config,
clashConfig: clashConfig,
);
}
await afterInit();
}
2024-04-30 23:38:49 +08:00
afterInit() async {
await proxyManager.updateStartTime();
if (proxyManager.isStart) {
2024-05-06 10:32:39 +08:00
await updateSystemProxy(true);
} else {
await updateSystemProxy(config.autoRun);
2024-05-06 10:32:39 +08:00
}
autoUpdateProfiles();
autoCheckUpdate();
2024-04-30 23:38:49 +08:00
}
setDelay(Delay delay) {
appState.setDelay(delay);
}
toPage(int index, {bool hasAnimate = false}) {
if (index > appState.currentNavigationItems.length - 1) {
return;
}
appState.currentLabel = appState.currentNavigationItems[index].label;
2024-04-30 23:38:49 +08:00
if ((config.isAnimateToPage || hasAnimate)) {
globalState.pageController?.animateToPage(
index,
duration: kTabScrollDuration,
curve: Curves.easeOut,
);
} else {
globalState.pageController?.jumpToPage(index);
}
}
toProfiles() {
final index = appState.currentNavigationItems.indexWhere(
2024-05-06 10:32:39 +08:00
(element) => element.label == "profiles",
2024-04-30 23:38:49 +08:00
);
if (index != -1) {
toPage(index);
}
}
initLink() {
linkManager.initAppLinksListen(
2024-05-06 10:32:39 +08:00
(url) {
2024-04-30 23:38:49 +08:00
globalState.showMessage(
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
],
),
onTab: () {
addProfileFormURL(url);
},
);
},
);
}
addProfileFormURL(String url) async {
if (globalState.navigatorKey.currentState?.canPop() ?? false) {
globalState.navigatorKey.currentState?.popUntil((route) => route.isFirst);
}
toProfiles();
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
final profile = await commonScaffoldState?.loadingRun<Profile>(
() async {
return await Profile.normal(
url: url,
).update();
},
title: "${appLocalizations.add}${appLocalizations.profile}",
);
if (profile != null) {
await addProfile(profile);
}
}
2024-04-30 23:38:49 +08:00
addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerConfigFile);
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;
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));
final bytes = platformFile?.bytes;
2024-04-30 23:38:49 +08:00
if (bytes == null) {
return null;
2024-04-30 23:38:49 +08:00
}
return await Profile.normal(label: platformFile?.name).saveFile(bytes);
2024-04-30 23:38:49 +08:00
},
title: "${appLocalizations.add}${appLocalizations.profile}",
2024-04-30 23:38:49 +08:00
);
if (profile != null) {
await addProfile(profile);
}
2024-04-30 23:38:49 +08:00
}
addProfileFormQrCode() async {
final url = await globalState.safeRun(picker.pickerConfigQRCode);
if (url == null) return;
addProfileFormURL(url);
}
int get columns =>
globalState.getColumns(appState.viewMode, config.proxiesColumns);
changeColumns() {
config.proxiesColumns = globalState.getColumns(
appState.viewMode,
columns - 1,
);
}
updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width;
});
}
List<Proxy> _sortOfName(List<Proxy> proxies) {
return List.of(proxies)
..sort(
(a, b) => other.sortByChar(a.name, b.name),
);
}
List<Proxy> _sortOfDelay(List<Proxy> proxies) {
return proxies = List.of(proxies)
..sort(
(a, b) {
final aDelay = appState.getDelay(a.name);
final bDelay = appState.getDelay(b.name);
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);
},
);
}
List<Proxy> getSortProxies(List<Proxy> proxies) {
return switch (config.proxiesSortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(proxies),
ProxiesSortType.name => _sortOfName(proxies),
};
}
2024-04-30 23:38:49 +08:00
}