Files
MWClash/lib/providers/state.dart
chen08209 31de2e51bc Fix windows some issues
Optimize overwrite handle

Optimize access control page

Optimize some details
2025-12-09 16:28:10 +08:00

716 lines
20 KiB
Dart

import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/models/models.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'app.dart';
import 'config.dart';
part 'generated/state.g.dart';
@riverpod
Config configState(Ref ref) {
final themeProps = ref.watch(themeSettingProvider);
final patchClashConfig = ref.watch(patchClashConfigProvider);
final appSetting = ref.watch(appSettingProvider);
final profiles = ref.watch(profilesProvider);
final currentProfileId = ref.watch(currentProfileIdProvider);
final overrideDns = ref.watch(overrideDnsProvider);
final networkProps = ref.watch(networkSettingProvider);
final vpnProps = ref.watch(vpnSettingProvider);
final proxiesStyle = ref.watch(proxiesStyleSettingProvider);
final scripts = ref.watch(scriptsProvider);
final hotKeyActions = ref.watch(hotKeyActionsProvider);
final dav = ref.watch(appDAVSettingProvider);
final windowProps = ref.watch(windowSettingProvider);
final rules = ref.watch(rulesProvider);
return Config(
dav: dav,
windowProps: windowProps,
hotKeyActions: hotKeyActions,
proxiesStyle: proxiesStyle,
vpnProps: vpnProps,
networkProps: networkProps,
overrideDns: overrideDns,
currentProfileId: currentProfileId,
profiles: profiles,
appSetting: appSetting,
themeProps: themeProps,
patchClashConfig: patchClashConfig,
scripts: scripts,
rules: rules,
);
}
@riverpod
GroupsState currentGroupsState(Ref ref) {
final mode = ref.watch(
patchClashConfigProvider.select((state) => state.mode),
);
final groups = ref.watch(groupsProvider);
return GroupsState(
value: switch (mode) {
Mode.direct => [],
Mode.global => groups.toList(),
Mode.rule =>
groups
.where((item) => item.hidden == false)
.where((element) => element.name != GroupName.GLOBAL.name)
.toList(),
},
);
}
@riverpod
NavigationItemsState navigationItemsState(Ref ref) {
final openLogs = ref.watch(appSettingProvider).openLogs;
final hasProfiles = ref.watch(
profilesProvider.select((state) => state.isNotEmpty),
);
final hasProxies = ref.watch(
currentGroupsStateProvider.select((state) => state.value.isNotEmpty),
);
final isInit = ref.watch(initProvider);
return NavigationItemsState(
value: navigation.getItems(
openLogs: openLogs,
hasProxies: !isInit ? hasProfiles : hasProxies,
),
);
}
@riverpod
NavigationItemsState currentNavigationItemsState(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final navigationItemsState = ref.watch(navigationItemsStateProvider);
final navigationItemMode = switch (viewWidth <= maxMobileWidth) {
true => NavigationItemMode.mobile,
false => NavigationItemMode.desktop,
};
return NavigationItemsState(
value: navigationItemsState.value
.where((element) => element.modes.contains(navigationItemMode))
.toList(),
);
}
@riverpod
UpdateParams updateParams(Ref ref) {
final routeMode = ref.watch(
networkSettingProvider.select((state) => state.routeMode),
);
return ref.watch(
patchClashConfigProvider.select(
(state) => UpdateParams(
tun: state.tun.getRealTun(routeMode),
allowLan: state.allowLan,
findProcessMode: state.findProcessMode,
mode: state.mode,
logLevel: state.logLevel,
ipv6: state.ipv6,
tcpConcurrent: state.tcpConcurrent,
externalController: state.externalController,
unifiedDelay: state.unifiedDelay,
mixedPort: state.mixedPort,
),
),
);
}
@riverpod
ProxyState proxyState(Ref ref) {
final isStart = ref.watch(runTimeProvider.select((state) => state != null));
final vm2 = ref.watch(
networkSettingProvider.select(
(state) => VM2(a: state.systemProxy, b: state.bypassDomain),
),
);
final mixedPort = ref.watch(
patchClashConfigProvider.select((state) => state.mixedPort),
);
return ProxyState(
isStart: isStart,
systemProxy: vm2.a,
bassDomain: vm2.b,
port: mixedPort,
);
}
@riverpod
TrayState trayState(Ref ref) {
final isStart = ref.watch(runTimeProvider.select((state) => state != null));
final systemProxy = ref.watch(
networkSettingProvider.select((state) => state.systemProxy),
);
final clashConfigVm3 = ref.watch(
patchClashConfigProvider.select(
(state) => VM3(a: state.mode, b: state.mixedPort, c: state.tun.enable),
),
);
final appSettingVm3 = ref.watch(
appSettingProvider.select(
(state) =>
VM3(a: state.autoLaunch, b: state.locale, c: state.showTrayTitle),
),
);
final groups = ref.watch(currentGroupsStateProvider).value;
final brightness = ref.watch(systemBrightnessProvider);
final selectedMap = ref.watch(selectedMapProvider);
return TrayState(
mode: clashConfigVm3.a,
port: clashConfigVm3.b,
autoLaunch: appSettingVm3.a,
systemProxy: systemProxy,
tunEnable: clashConfigVm3.c,
isStart: isStart,
locale: appSettingVm3.b,
brightness: brightness,
groups: groups,
selectedMap: selectedMap,
showTrayTitle: appSettingVm3.c,
);
}
@riverpod
TrayTitleState trayTitleState(Ref ref) {
final showTrayTitle = ref.watch(
appSettingProvider.select((state) => state.showTrayTitle),
);
final traffic = ref.watch(
trafficsProvider.select((state) => state.list.safeLast(Traffic())),
);
return TrayTitleState(showTrayTitle: showTrayTitle, traffic: traffic);
}
@riverpod
VpnState vpnState(Ref ref) {
final vpnProps = ref.watch(vpnSettingProvider);
final stack = ref.watch(
patchClashConfigProvider.select((state) => state.tun.stack),
);
return VpnState(stack: stack, vpnProps: vpnProps);
}
@riverpod
NavigationState navigationState(Ref ref) {
final pageLabel = ref.watch(currentPageLabelProvider);
final navigationItems = ref.watch(currentNavigationItemsStateProvider).value;
final viewMode = ref.watch(viewModeProvider);
final locale = ref.watch(appSettingProvider).locale;
final index = navigationItems.lastIndexWhere(
(element) => element.label == pageLabel,
);
final currentIndex = index == -1 ? 0 : index;
return NavigationState(
pageLabel: pageLabel,
navigationItems: navigationItems,
viewMode: viewMode,
locale: locale,
currentIndex: currentIndex,
);
}
@riverpod
double contentWidth(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final sideWidth = ref.watch(sideWidthProvider);
return viewWidth - sideWidth;
}
@riverpod
DashboardState dashboardState(Ref ref) {
final dashboardWidgets = ref.watch(
appSettingProvider.select((state) => state.dashboardWidgets),
);
final contentWidth = ref.watch(contentWidthProvider);
return DashboardState(
dashboardWidgets: dashboardWidgets,
contentWidth: contentWidth,
);
}
@riverpod
ProxiesActionsState proxiesActionsState(Ref ref) {
final pageLabel = ref.watch(currentPageLabelProvider);
final hasProviders = ref.watch(
providersProvider.select((state) => state.isNotEmpty),
);
final type = ref.watch(
proxiesStyleSettingProvider.select((state) => state.type),
);
return ProxiesActionsState(
pageLabel: pageLabel,
hasProviders: hasProviders,
type: type,
);
}
@riverpod
StartButtonSelectorState startButtonSelectorState(Ref ref) {
final isInit = ref.watch(initProvider);
final hasProfile = ref.watch(
profilesProvider.select((state) => state.isNotEmpty),
);
return StartButtonSelectorState(isInit: isInit, hasProfile: hasProfile);
}
@riverpod
ProfilesSelectorState profilesSelectorState(Ref ref) {
final currentProfileId = ref.watch(currentProfileIdProvider);
final profiles = ref.watch(profilesProvider);
final columns = ref.watch(
contentWidthProvider.select((state) => utils.getProfilesColumns(state)),
);
return ProfilesSelectorState(
profiles: profiles,
currentProfileId: currentProfileId,
columns: columns,
);
}
@riverpod
GroupsState filterGroupsState(Ref ref, String query) {
final currentGroups = ref.watch(currentGroupsStateProvider);
if (query.isEmpty) {
return currentGroups;
}
final lowQuery = query.toLowerCase();
final groups = currentGroups.value
.map((group) {
return group.copyWith(
all: group.all
.where((proxy) => proxy.name.toLowerCase().contains(lowQuery))
.toList(),
);
})
.where((group) => group.all.isNotEmpty)
.toList();
return GroupsState(value: groups);
}
@riverpod
ProxiesListState proxiesListState(Ref ref) {
final query = ref.watch(queryProvider(QueryTag.proxies));
final currentGroups = ref.watch(filterGroupsStateProvider(query));
final currentUnfoldSet = ref.watch(unfoldSetProvider);
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final columns = ref.watch(getProxiesColumnsProvider);
return ProxiesListState(
groups: currentGroups.value,
currentUnfoldSet: currentUnfoldSet,
proxyCardType: cardType,
columns: columns,
);
}
@riverpod
ProxiesTabState proxiesTabState(Ref ref) {
final query = ref.watch(queryProvider(QueryTag.proxies));
final currentGroups = ref.watch(filterGroupsStateProvider(query));
final currentGroupName = ref.watch(
currentProfileProvider.select((state) => state?.currentGroupName),
);
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final columns = ref.watch(getProxiesColumnsProvider);
return ProxiesTabState(
groups: currentGroups.value,
currentGroupName: currentGroupName,
proxyCardType: cardType,
columns: columns,
);
}
@riverpod
bool isStart(Ref ref) {
return ref.watch(runTimeProvider.select((state) => state != null));
}
@riverpod
VM2<List<String>, String?> proxiesTabControllerState(Ref ref) {
return ref.watch(
proxiesTabStateProvider.select(
(state) => VM2(
a: state.groups.map((group) => group.name).toList(),
b: state.currentGroupName,
),
),
);
}
@riverpod
ProxyGroupSelectorState proxyGroupSelectorState(
Ref ref,
String groupName,
String query,
) {
final proxiesStyle = ref.watch(proxiesStyleSettingProvider);
final group = ref.watch(
currentGroupsStateProvider.select(
(state) => state.value.getGroup(groupName),
),
);
final sortNum = ref.watch(sortNumProvider);
final columns = ref.watch(getProxiesColumnsProvider);
final lowQuery = query.toLowerCase();
final proxies =
group?.all.where((item) {
return item.name.toLowerCase().contains(lowQuery);
}).toList() ??
[];
return ProxyGroupSelectorState(
testUrl: group?.testUrl,
proxiesSortType: proxiesStyle.sortType,
proxyCardType: proxiesStyle.cardType,
sortNum: sortNum,
groupType: group?.type ?? GroupType.Selector,
proxies: proxies,
columns: columns,
);
}
@riverpod
PackageListSelectorState packageListSelectorState(Ref ref) {
final packages = ref.watch(packagesProvider);
final accessControl = ref.watch(
vpnSettingProvider.select((state) => state.accessControl),
);
return PackageListSelectorState(
packages: packages,
accessControl: accessControl,
);
}
@riverpod
MoreToolsSelectorState moreToolsSelectorState(Ref ref) {
final viewMode = ref.watch(viewModeProvider);
final navigationItems = ref.watch(
navigationItemsStateProvider.select((state) {
return state.value.where((element) {
final isMore = element.modes.contains(NavigationItemMode.more);
final isDesktop = element.modes.contains(NavigationItemMode.desktop);
if (isMore && !isDesktop) return true;
if (viewMode != ViewMode.mobile || !isMore) {
return false;
}
return true;
}).toList();
}),
);
return MoreToolsSelectorState(navigationItems: navigationItems);
}
@riverpod
bool isCurrentPage(
Ref ref,
PageLabel pageLabel, {
bool Function(PageLabel pageLabel, ViewMode viewMode)? handler,
}) {
final currentPageLabel = ref.watch(currentPageLabelProvider);
if (pageLabel == currentPageLabel) {
return true;
}
if (handler != null) {
final viewMode = ref.watch(viewModeProvider);
return handler(currentPageLabel, viewMode);
}
return false;
}
@riverpod
String getRealTestUrl(Ref ref, [String? testUrl]) {
final currentTestUrl = ref.watch(appSettingProvider).testUrl;
return testUrl.getSafeValue(currentTestUrl);
}
@riverpod
int? getDelay(Ref ref, {required String proxyName, String? testUrl}) {
final currentTestUrl = ref.watch(getRealTestUrlProvider(testUrl));
final proxyState = ref.watch(realSelectedProxyStateProvider(proxyName));
final delay = ref.watch(
delayDataSourceProvider.select((state) {
final delayMap = state[proxyState.testUrl.getSafeValue(currentTestUrl)];
return delayMap?[proxyState.proxyName];
}),
);
return delay;
}
@riverpod
Map<String, String> selectedMap(Ref ref) {
final selectedMap = ref.watch(
currentProfileProvider.select((state) => state?.selectedMap ?? {}),
);
return selectedMap;
}
@riverpod
Set<String> unfoldSet(Ref ref) {
final unfoldSet = ref.watch(
currentProfileProvider.select((state) => state?.unfoldSet ?? {}),
);
return unfoldSet;
}
@riverpod
HotKeyAction getHotKeyAction(Ref ref, HotAction hotAction) {
return ref.watch(
hotKeyActionsProvider.select((state) {
final index = state.indexWhere((item) => item.action == hotAction);
return index != -1 ? state[index] : HotKeyAction(action: hotAction);
}),
);
}
@riverpod
Profile? currentProfile(Ref ref) {
final profileId = ref.watch(currentProfileIdProvider);
return ref.watch(
profilesProvider.select((state) => state.getProfile(profileId)),
);
}
@riverpod
int getProxiesColumns(Ref ref) {
final contentWidth = ref.watch(contentWidthProvider);
final proxiesLayout = ref.watch(
proxiesStyleSettingProvider.select((state) => state.layout),
);
return utils.getProxiesColumns(contentWidth, proxiesLayout);
}
@riverpod
SelectedProxyState realSelectedProxyState(Ref ref, String proxyName) {
final groups = ref.watch(groupsProvider);
final selectedMap = ref.watch(selectedMapProvider);
return computeRealSelectedProxyState(
proxyName,
groups: groups,
selectedMap: selectedMap,
);
}
@riverpod
String? getProxyName(Ref ref, String groupName) {
final proxyName = ref.watch(
selectedMapProvider.select((state) => state[groupName]),
);
return proxyName;
}
@riverpod
String? getSelectedProxyName(Ref ref, String groupName) {
final proxyName = ref.watch(getProxyNameProvider(groupName));
final group = ref.watch(
groupsProvider.select((state) => state.getGroup(groupName)),
);
return group?.getCurrentSelectedName(proxyName ?? '');
}
@riverpod
String getProxyDesc(Ref ref, Proxy proxy) {
final groupTypeNamesList = GroupType.values.map((e) => e.name).toList();
if (!groupTypeNamesList.contains(proxy.type)) {
return proxy.type;
} else {
final groups = ref.watch(groupsProvider);
final index = groups.indexWhere((element) => element.name == proxy.name);
if (index == -1) return proxy.type;
final state = ref.watch(realSelectedProxyStateProvider(proxy.name));
return "${proxy.type}(${state.proxyName.isNotEmpty ? state.proxyName : '*'})";
}
}
@riverpod
OverrideData? getProfileOverrideData(Ref ref, String profileId) {
return ref.watch(
profilesProvider.select(
(state) => state.getProfile(profileId)?.overrideData,
),
);
}
@riverpod
VM2? layoutChange(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final textScale = ref.watch(
themeSettingProvider.select((state) => state.textScale),
);
return VM2(a: viewWidth, b: textScale);
}
@riverpod
VM2<int, bool> checkIp(Ref ref) {
final checkIpNum = ref.watch(checkIpNumProvider);
final containsDetection = ref.watch(
dashboardStateProvider.select(
(state) =>
state.dashboardWidgets.contains(DashboardWidget.networkDetection),
),
);
return VM2(a: checkIpNum, b: containsDetection);
}
@riverpod
ColorScheme genColorScheme(
Ref ref,
Brightness brightness, {
Color? color,
bool ignoreConfig = false,
}) {
final vm2 = ref.watch(
themeSettingProvider.select(
(state) => VM2(a: state.primaryColor, b: state.schemeVariant),
),
);
if (color == null && (ignoreConfig == true || vm2.a == null)) {
// if (globalState.corePalette != null) {
// return globalState.corePalette!.toColorScheme(brightness: brightness);
// }
return ColorScheme.fromSeed(
seedColor:
globalState.corePalette
?.toColorScheme(brightness: brightness)
.primary ??
globalState.accentColor,
brightness: brightness,
dynamicSchemeVariant: vm2.b,
);
}
return ColorScheme.fromSeed(
seedColor: color ?? Color(vm2.a!),
brightness: brightness,
dynamicSchemeVariant: vm2.b,
);
}
@riverpod
VM4<String?, String?, Dns?, bool> needSetup(Ref ref) {
final profileId = ref.watch(currentProfileIdProvider);
// final content = ref.watch(
// scriptsProvider.select((state) => state.currentScript?.content),
// );
final overrideDns = ref.watch(overrideDnsProvider);
final dns = overrideDns == true
? ref.watch(patchClashConfigProvider.select((state) => state.dns))
: null;
final appendSystemDns = ref.watch(
networkSettingProvider.select((state) => state.appendSystemDns),
);
return VM4(profileId, '', dns, appendSystemDns);
}
@riverpod
Brightness currentBrightness(Ref ref) {
final themeMode = ref.watch(
themeSettingProvider.select((state) => state.themeMode),
);
final systemBrightness = ref.watch(systemBrightnessProvider);
return switch (themeMode) {
ThemeMode.system => systemBrightness,
ThemeMode.light => Brightness.light,
ThemeMode.dark => Brightness.dark,
};
}
@riverpod
VM2<bool, bool> autoSetSystemDnsState(Ref ref) {
final isStart = ref.watch(runTimeProvider.select((state) => state != null));
final realTunEnable = ref.watch(realTunEnableProvider);
final autoSetSystemDns = ref.watch(
networkSettingProvider.select((state) => state.autoSetSystemDns),
);
return VM2(a: isStart ? realTunEnable : false, b: autoSetSystemDns);
}
@riverpod
VM3<bool, int, ProxiesSortType> needUpdateGroups(Ref ref) {
final isProxies = ref.watch(
currentPageLabelProvider.select((state) => state == PageLabel.proxies),
);
final sortNum = ref.watch(sortNumProvider);
final sortType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.sortType),
);
return VM3(a: isProxies, b: sortNum, c: sortType);
}
@riverpod
AndroidState androidState(Ref ref) {
final currentProfileName = ref.watch(
currentProfileProvider.select((state) => state?.label ?? ''),
);
final onlyStatisticsProxy = ref.watch(
appSettingProvider.select((state) => state.onlyStatisticsProxy),
);
ref.watch((appSettingProvider).select((state) => state.locale));
final crashlytics = ref.watch(
(appSettingProvider).select((state) => state.crashlytics),
);
return AndroidState(
currentProfileName: currentProfileName,
onlyStatisticsProxy: onlyStatisticsProxy,
stopText: appLocalizations.stop,
crashlytics: crashlytics,
);
}
@riverpod
double overlayTopOffset(Ref ref) {
final isMobileView = ref.watch(isMobileViewProvider);
final version = ref.watch(versionProvider);
ref.watch(viewSizeProvider);
double top = kHeaderHeight;
if ((version <= 10 || !isMobileView) && system.isMacOS || !system.isDesktop) {
top = 0;
}
return kToolbarHeight + top;
}
@riverpod
Profile? profile(Ref ref, String profileId) {
return ref.watch(
profilesProvider.select((state) => state.getProfile(profileId)),
);
}
@riverpod
Overwrite? profileOverwrite(Ref ref, String profileId) {
return ref.watch(
profileProvider(profileId).select((state) => state?.overwrite),
);
}
@riverpod
class AccessControlState extends _$AccessControlState
with AutoDisposeNotifierMixin {
@override
AccessControl build() => AccessControl();
}
@riverpod
SetupState setupState(Ref ref, String profileId) {
ref.watch(
profileProvider(profileId).select(
(state) =>
VM3(a: state?.id, b: state?.lastUpdateDate, c: state?.overwrite),
),
);
ref.watch(patchClashConfigProvider.select((state) => state.dns));
ref.watch(overrideDnsProvider);
ref.watch(scriptsProvider);
ref.watch(rulesProvider);
return globalState.getSetupState(profileId);
}