Optimize desktop view

Optimize logs, requests, connection pages

Optimize windows tray auto hide

Optimize some details

Update core
This commit is contained in:
chen08209
2025-06-07 01:48:34 +08:00
parent adb890d763
commit 1154e7b245
169 changed files with 6484 additions and 5230 deletions
+170 -122
View File
@@ -2,7 +2,6 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:isolate';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'package:fl_clash/clash/clash.dart';
@@ -12,7 +11,9 @@ 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';
@@ -29,29 +30,29 @@ class AppController {
AppController(this.context, WidgetRef ref) : _ref = ref;
setupClashConfigDebounce() {
void setupClashConfigDebounce() {
debouncer.call(FunctionTag.setupClashConfig, () async {
await setupClashConfig();
});
}
updateClashConfigDebounce() {
Future<void> updateClashConfigDebounce() async {
debouncer.call(FunctionTag.updateClashConfig, () async {
await updateClashConfig();
});
}
updateGroupsDebounce() {
void updateGroupsDebounce() {
debouncer.call(FunctionTag.updateGroups, updateGroups);
}
addCheckIpNumDebounce() {
void addCheckIpNumDebounce() {
debouncer.call(FunctionTag.addCheckIpNum, () {
_ref.read(checkIpNumProvider.notifier).add();
});
}
applyProfileDebounce({
void applyProfileDebounce({
bool silence = false,
}) {
debouncer.call(FunctionTag.applyProfile, (silence) {
@@ -59,11 +60,11 @@ class AppController {
}, args: [silence]);
}
savePreferencesDebounce() {
void savePreferencesDebounce() {
debouncer.call(FunctionTag.savePreferences, savePreferences);
}
changeProxyDebounce(String groupName, String proxyName) {
void changeProxyDebounce(String groupName, String proxyName) {
debouncer.call(FunctionTag.changeProxy,
(String groupName, String proxyName) async {
await changeProxy(
@@ -74,8 +75,8 @@ class AppController {
}, args: [groupName, proxyName]);
}
restartCore() async {
commonPrint.log("restart core");
Future<void> restartCore() async {
commonPrint.log('restart core');
await clashService?.reStart();
await _initCore();
if (_ref.read(runTimeProvider.notifier).isStart) {
@@ -83,7 +84,7 @@ class AppController {
}
}
updateStatus(bool isStart) async {
Future<void> updateStatus(bool isStart) async {
if (isStart) {
await globalState.handleStart([
updateRunTime,
@@ -102,7 +103,7 @@ class AppController {
applyProfileDebounce();
} else {
await globalState.handleStop();
await clashCore.resetTraffic();
clashCore.resetTraffic();
_ref.read(trafficsProvider.notifier).clear();
_ref.read(totalTrafficProvider.notifier).value = Traffic();
_ref.read(runTimeProvider.notifier).value = null;
@@ -110,7 +111,7 @@ class AppController {
}
}
updateRunTime() {
void updateRunTime() {
final startTime = globalState.startTime;
if (startTime != null) {
final startTimeStamp = startTime.millisecondsSinceEpoch;
@@ -121,20 +122,20 @@ class AppController {
}
}
updateTraffic() async {
Future<void> updateTraffic() async {
final traffic = await clashCore.getTraffic();
_ref.read(trafficsProvider.notifier).addTraffic(traffic);
_ref.read(totalTrafficProvider.notifier).value =
await clashCore.getTotalTraffic();
}
addProfile(Profile profile) async {
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;
}
deleteProfile(String id) async {
Future<void> deleteProfile(String id) async {
_ref.read(profilesProvider.notifier).deleteProfileById(id);
clearEffect(id);
if (globalState.config.currentProfileId == id) {
@@ -150,12 +151,12 @@ class AppController {
}
}
updateProviders() async {
Future<void> updateProviders() async {
_ref.read(providersProvider.notifier).value =
await clashCore.getExternalProviders();
}
updateLocalIp() async {
Future<void> updateLocalIp() async {
_ref.read(localIpProvider.notifier).value = null;
await Future.delayed(commonDuration);
_ref.read(localIpProvider.notifier).value = await utils.getLocalIpAddress();
@@ -171,26 +172,26 @@ class AppController {
}
}
setProfile(Profile profile) {
void setProfile(Profile profile) {
_ref.read(profilesProvider.notifier).setProfile(profile);
}
setProfileAndAutoApply(Profile profile) {
void setProfileAndAutoApply(Profile profile) {
_ref.read(profilesProvider.notifier).setProfile(profile);
if (profile.id == _ref.read(currentProfileIdProvider)) {
applyProfileDebounce(silence: true);
}
}
setProfiles(List<Profile> profiles) {
void setProfiles(List<Profile> profiles) {
_ref.read(profilesProvider.notifier).value = profiles;
}
addLog(Log log) {
void addLog(Log log) {
_ref.read(logsProvider).add(log);
}
updateOrAddHotKeyAction(HotKeyAction hotKeyAction) {
void updateOrAddHotKeyAction(HotKeyAction hotKeyAction) {
final hotKeyActions = _ref.read(hotKeyActionsProvider);
final index =
hotKeyActions.indexWhere((item) => item.action == hotKeyAction.action);
@@ -219,26 +220,26 @@ class AppController {
return _ref.read(getProxiesColumnsProvider);
}
addSortNum() {
dynamic addSortNum() {
return _ref.read(sortNumProvider.notifier).add();
}
getCurrentGroupName() {
String? getCurrentGroupName() {
final currentGroupName = _ref.read(currentProfileProvider.select(
(state) => state?.currentGroupName,
));
return currentGroupName;
}
ProxyCardState getProxyCardState(proxyName) {
ProxyCardState getProxyCardState(String proxyName) {
return _ref.read(getProxyCardStateProvider(proxyName));
}
getSelectedProxyName(groupName) {
String? getSelectedProxyName(String groupName) {
return _ref.read(getSelectedProxyNameProvider(groupName));
}
updateCurrentGroupName(String groupName) {
void updateCurrentGroupName(String groupName) {
final profile = _ref.read(currentProfileProvider);
if (profile == null || profile.currentGroupName == groupName) {
return;
@@ -249,11 +250,12 @@ class AppController {
}
Future<void> updateClashConfig() async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await _updateClashConfig();
});
await safeRun(
() async {
await _updateClashConfig();
},
needLoading: true,
);
}
Future<void> _updateClashConfig() async {
@@ -272,13 +274,16 @@ class AppController {
}
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("");
return Result.error('');
case AuthorizeCode.none:
break;
case AuthorizeCode.error:
@@ -291,14 +296,15 @@ class AppController {
}
Future<void> setupClashConfig() async {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await _setupClashConfig();
});
await safeRun(
() async {
await _setupClashConfig();
},
needLoading: true,
);
}
_setupClashConfig() async {
Future<void> _setupClashConfig() async {
await _ref.read(currentProfileProvider)?.checkAndUpdate();
final patchConfig = _ref.read(patchClashConfigProvider);
final res = await _requestAdmin(patchConfig.tun.enable);
@@ -332,29 +338,32 @@ class AppController {
if (silence) {
await _applyProfile();
} else {
final commonScaffoldState = globalState.homeScaffoldKey.currentState;
if (commonScaffoldState?.mounted != true) return;
await commonScaffoldState?.loadingRun(() async {
await _applyProfile();
});
await safeRun(
() async {
await _applyProfile();
},
needLoading: true,
);
}
addCheckIpNumDebounce();
}
handleChangeProfile() {
void handleChangeProfile() {
_ref.read(delayDataSourceProvider.notifier).value = {};
applyProfile();
_ref.read(logsProvider.notifier).value = FixedList(500);
_ref.read(requestsProvider.notifier).value = FixedList(500);
globalState.cacheHeightMap = {};
globalState.cacheScrollPosition = {};
globalState.computeHeightMapCache = {};
}
updateBrightness(Brightness brightness) {
_ref.read(appBrightnessProvider.notifier).value = brightness;
void updateBrightness() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ref.read(systemBrightnessProvider.notifier).value =
WidgetsBinding.instance.platformDispatcher.platformBrightness;
});
}
autoUpdateProfiles() async {
Future<void> autoUpdateProfiles() async {
for (final profile in _ref.read(profilesProvider)) {
if (!profile.autoUpdate) continue;
final isNotNeedUpdate = profile.lastUpdateDate
@@ -386,7 +395,7 @@ class AppController {
}
}
updateProfiles() async {
Future<void> updateProfiles() async {
for (final profile in _ref.read(profilesProvider)) {
if (profile.type == ProfileType.file) {
continue;
@@ -395,12 +404,12 @@ class AppController {
}
}
savePreferences() async {
commonPrint.log("save preferences");
Future<void> savePreferences() async {
commonPrint.log('save preferences');
await preferences.saveConfig(globalState.config);
}
changeProxy({
Future<void> changeProxy({
required String groupName,
required String proxyName,
}) async {
@@ -416,13 +425,13 @@ class AppController {
addCheckIpNumDebounce();
}
handleBackOrExit() async {
Future<void> handleBackOrExit() async {
if (_ref.read(backBlockProvider)) {
return;
}
if (_ref.read(appSettingProvider).minimizeOnExit) {
if (system.isDesktop) {
await savePreferencesDebounce();
await savePreferences();
}
await system.back();
} else {
@@ -430,21 +439,21 @@ class AppController {
}
}
backBlock() {
void backBlock() {
_ref.read(backBlockProvider.notifier).value = true;
}
unBackBlock() {
void unBackBlock() {
_ref.read(backBlockProvider.notifier).value = false;
}
handleExit() async {
Future<void> handleExit() async {
Future.delayed(commonDuration, () {
system.exit();
});
try {
await savePreferences();
await system.setMacOSDns(true);
await macOS?.updateDns(true);
await proxy?.stopProxy();
await clashCore.shutdown();
await clashService?.destroy();
@@ -455,19 +464,19 @@ class AppController {
Future handleClear() async {
await preferences.clearPreferences();
commonPrint.log("clear preferences");
commonPrint.log('clear preferences');
globalState.config = Config(
themeProps: defaultThemeProps,
);
}
autoCheckUpdate() async {
Future<void> autoCheckUpdate() async {
if (!_ref.read(appSettingProvider).autoCheckUpdate) return;
final res = await request.checkForUpdate();
checkUpdateResultHandle(data: res);
}
checkUpdateResultHandle({
Future<void> checkUpdateResultHandle({
Map<String, dynamic>? data,
bool handleError = false,
}) async {
@@ -482,16 +491,16 @@ class AppController {
final res = await globalState.showMessage(
title: appLocalizations.discoverNewVersion,
message: TextSpan(
text: "$tagName \n",
text: '$tagName \n',
style: textTheme.headlineSmall,
children: [
TextSpan(
text: "\n",
text: '\n',
style: textTheme.bodyMedium,
),
for (final submit in submits)
TextSpan(
text: "- $submit \n",
text: '- $submit \n',
style: textTheme.bodyMedium,
),
],
@@ -502,7 +511,7 @@ class AppController {
return;
}
launchUrl(
Uri.parse("https://github.com/$repository/releases/latest"),
Uri.parse('https://github.com/$repository/releases/latest'),
);
} else if (handleError) {
globalState.showMessage(
@@ -514,7 +523,7 @@ class AppController {
}
}
_handlePreference() async {
Future<void> _handlePreference() async {
if (await preferences.isInit) {
return;
}
@@ -543,9 +552,11 @@ class AppController {
await applyProfile();
}
init() async {
Future<void> init() async {
FlutterError.onError = (details) {
commonPrint.log(details.stack.toString());
if (kDebugMode) {
commonPrint.log(details.stack.toString());
}
};
updateTray(true);
await _initCore();
@@ -565,8 +576,8 @@ class AppController {
_ref.read(initProvider.notifier).value = true;
}
_initStatus() async {
if (Platform.isAndroid) {
Future<void> _initStatus() async {
if (system.isAndroid) {
await globalState.updateStartTime();
}
final status = globalState.isStart == true
@@ -579,28 +590,28 @@ class AppController {
}
}
setDelay(Delay delay) {
void setDelay(Delay delay) {
_ref.read(delayDataSourceProvider.notifier).setDelay(delay);
}
toPage(PageLabel pageLabel) {
void toPage(PageLabel pageLabel) {
_ref.read(currentPageLabelProvider.notifier).value = pageLabel;
}
toProfiles() {
void toProfiles() {
toPage(PageLabel.profiles);
}
initLink() {
void initLink() {
linkManager.initAppLinksListen(
(url) async {
final res = await globalState.showMessage(
title: "${appLocalizations.add}${appLocalizations.profile}",
title: '${appLocalizations.add}${appLocalizations.profile}',
message: TextSpan(
children: [
TextSpan(text: appLocalizations.doYouWantToPass),
TextSpan(
text: " $url ",
text: ' $url ',
style: TextStyle(
color: Theme.of(context).colorScheme.primary,
decoration: TextDecoration.underline,
@@ -609,7 +620,7 @@ class AppController {
),
TextSpan(
text:
"${appLocalizations.create}${appLocalizations.profile}"),
'${appLocalizations.create}${appLocalizations.profile}'),
],
),
);
@@ -652,7 +663,7 @@ class AppController {
false;
}
_handlerDisclaimer() async {
Future<void> _handlerDisclaimer() async {
if (_ref.read(appSettingProvider).disclaimerAccepted) {
return;
}
@@ -663,62 +674,64 @@ class AppController {
return;
}
addProfileFormURL(String url) async {
Future<void> 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>(
final profile = await safeRun(
() async {
return await Profile.normal(
url: url,
).update();
},
title: "${appLocalizations.add}${appLocalizations.profile}",
needLoading: true,
title: '${appLocalizations.add}${appLocalizations.profile}',
);
if (profile != null) {
await addProfile(profile);
}
}
addProfileFormFile() async {
final platformFile = await globalState.safeRun(picker.pickerFile);
Future<void> addProfileFormFile() async {
final platformFile = await safeRun(picker.pickerFile);
final bytes = platformFile?.bytes;
if (bytes == null) {
return null;
return;
}
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?>(
final profile = await safeRun(
() async {
await Future.delayed(const Duration(milliseconds: 300));
return await Profile.normal(label: platformFile?.name).saveFile(bytes);
},
title: "${appLocalizations.add}${appLocalizations.profile}",
needLoading: true,
title: '${appLocalizations.add}${appLocalizations.profile}',
);
if (profile != null) {
await addProfile(profile);
}
}
addProfileFormQrCode() async {
final url = await globalState.safeRun(picker.pickerConfigQRCode);
Future<void> addProfileFormQrCode() async {
final url = await safeRun(
picker.pickerConfigQRCode,
);
if (url == null) return;
addProfileFormURL(url);
}
updateViewSize(Size size) {
void updateViewSize(Size size) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ref.read(viewSizeProvider.notifier).value = size;
});
}
setProvider(ExternalProvider? provider) {
void setProvider(ExternalProvider? provider) {
_ref.read(providersProvider.notifier).setProvider(provider);
}
@@ -763,18 +776,22 @@ class AppController {
);
}
List<Proxy> getSortProxies(List<Proxy> proxies, [String? url]) {
return switch (_ref.read(proxiesStyleSettingProvider).sortType) {
List<Proxy> getSortProxies({
required List<Proxy> proxies,
required ProxiesSortType sortType,
String? testUrl,
}) {
return switch (sortType) {
ProxiesSortType.none => proxies,
ProxiesSortType.delay => _sortOfDelay(
proxies: proxies,
testUrl: url,
testUrl: testUrl,
),
ProxiesSortType.name => _sortOfName(proxies),
};
}
clearEffect(String profileId) async {
Future<Null> clearEffect(String profileId) async {
final profilePath = await appPath.getProfilePath(profileId);
final providersDirPath = await appPath.getProvidersDirPath(profileId);
return await Isolate.run(() async {
@@ -791,13 +808,13 @@ class AppController {
});
}
updateTun() {
void updateTun() {
_ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith.tun(enable: !state.tun.enable),
);
}
updateSystemProxy() {
void updateSystemProxy() {
_ref.read(networkSettingProvider.notifier).updateState(
(state) => state.copyWith(
systemProxy: !state.systemProxy,
@@ -816,11 +833,11 @@ class AppController {
return _ref.read(packagesProvider);
}
updateStart() {
void updateStart() {
updateStatus(!_ref.read(runTimeProvider.notifier).isStart);
}
updateCurrentSelectedMap(String groupName, String proxyName) {
void updateCurrentSelectedMap(String groupName, String proxyName) {
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile != null &&
currentProfile.selectedMap[groupName] != proxyName) {
@@ -835,7 +852,7 @@ class AppController {
}
}
updateCurrentUnfoldSet(Set<String> value) {
void updateCurrentUnfoldSet(Set<String> value) {
final currentProfile = _ref.read(currentProfileProvider);
if (currentProfile == null) {
return;
@@ -847,7 +864,7 @@ class AppController {
);
}
changeMode(Mode mode) {
void changeMode(Mode mode) {
_ref.read(patchClashConfigProvider.notifier).updateState(
(state) => state.copyWith(mode: mode),
);
@@ -857,7 +874,7 @@ class AppController {
addCheckIpNumDebounce();
}
updateAutoLaunch() {
void updateAutoLaunch() {
_ref.read(appSettingProvider.notifier).updateState(
(state) => state.copyWith(
autoLaunch: !state.autoLaunch,
@@ -865,7 +882,7 @@ class AppController {
);
}
updateVisible() async {
Future<void> updateVisible() async {
final visible = await window?.isVisible;
if (visible != null && !visible) {
window?.show();
@@ -874,7 +891,7 @@ class AppController {
}
}
updateMode() {
void updateMode() {
_ref.read(patchClashConfigProvider.notifier).updateState(
(state) {
final index = Mode.values.indexWhere((item) => item == state.mode);
@@ -889,7 +906,7 @@ class AppController {
);
}
handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
Future<void> handleAddOrUpdate(WidgetRef ref, [Rule? rule]) async {
final res = await globalState.showCommonDialog<Rule>(
child: AddRuleDialog(
rule: rule,
@@ -926,7 +943,7 @@ class AppController {
(item) => item.toString(),
);
final data = await Isolate.run<List<int>>(() async {
final logsRawString = logsRaw.join("\n");
final logsRawString = logsRaw.join('\n');
return utf8.encode(logsRawString);
});
return await picker.saveFile(
@@ -942,20 +959,20 @@ class AppController {
final configJson = globalState.config.toJson();
return Isolate.run<List<int>>(() async {
final archive = Archive();
archive.add("config.json", configJson);
await archive.addDirectoryToArchive(profilesPath, homeDirPath);
archive.add('config.json', configJson);
archive.addDirectoryToArchive(profilesPath, homeDirPath);
final zipEncoder = ZipEncoder();
return zipEncoder.encode(archive) ?? [];
});
}
updateTray([bool focus = false]) async {
Future<void> updateTray([bool focus = false]) async {
tray.update(
trayState: _ref.read(trayStateProvider),
);
}
recoveryData(
Future<void> recoveryData(
List<int> data,
RecoveryOption recoveryOption,
) async {
@@ -965,12 +982,12 @@ class AppController {
});
final homeDirPath = await appPath.homeDirPath;
final configs =
archive.files.where((item) => item.name.endsWith(".json")).toList();
archive.files.where((item) => item.name.endsWith('.json')).toList();
final profiles =
archive.files.where((item) => !item.name.endsWith(".json"));
archive.files.where((item) => !item.name.endsWith('.json'));
final configIndex =
configs.indexWhere((config) => config.name == "config.json");
if (configIndex == -1) throw "invalid backup file";
configs.indexWhere((config) => config.name == 'config.json');
if (configIndex == -1) throw 'invalid backup file';
final configFile = configs[configIndex];
var tempConfig = Config.compatibleFromJson(
json.decode(
@@ -984,7 +1001,7 @@ class AppController {
await file.writeAsBytes(profile.content);
}
final clashConfigIndex =
configs.indexWhere((config) => config.name == "clashConfig.json");
configs.indexWhere((config) => config.name == 'clashConfig.json');
if (clashConfigIndex != -1) {
final clashConfigFile = configs[clashConfigIndex];
tempConfig = tempConfig.copyWith(
@@ -1003,7 +1020,7 @@ class AppController {
);
}
_recovery(Config config, RecoveryOption recoveryOption) {
void _recovery(Config config, RecoveryOption recoveryOption) {
final recoveryStrategy = _ref.read(appSettingProvider.select(
(state) => state.recoveryStrategy,
));
@@ -1040,4 +1057,35 @@ class AppController {
_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('$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;
}
}
}