Files
MWClash/lib/controller.dart

420 lines
11 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;
AppController(this.context) {
appState = context.read<AppState>();
config = context.read<Config>();
clashConfig = context.read<ClashConfig>();
updateClashConfigDebounce = debounce<Function()>(() async {
await updateClashConfig();
});
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();
appState.traffics = [];
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(
config: config,
appState: appState,
);
}
changeProxy() {
globalState.changeProxy(
appState: appState,
config: config,
clashConfig: clashConfig,
2024-04-30 23:38:49 +08:00
);
}
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(String id) async {
2024-04-30 23:38:49 +08:00
final profile = config.getCurrentProfileForId(id);
if (profile != null) {
final tempProfile = profile.copyWith();
await tempProfile.update();
config.setProfile(tempProfile);
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-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 {
await updateProfile(profile.id);
} 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.id);
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 {
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 {
2024-05-06 10:32:39 +08:00
if (config.autoRun) {
await updateSystemProxy(true);
} else {
await proxyManager.updateStartTime();
await updateSystemProxy(proxyManager.isStart);
}
autoUpdateProfiles();
updateLogStatus();
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 {
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 {
final profile = Profile(
url: url,
);
await profile.update();
return profile;
},
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
}
final profile = Profile(label: platformFile?.name);
await profile.saveFile(bytes);
return profile;
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);
}
updateViewWidth(double width) {
WidgetsBinding.instance.addPostFrameCallback((_) {
appState.viewWidth = width;
});
}
2024-04-30 23:38:49 +08:00
}