Add linux deb dependencies Add backup recovery strategy select Support custom text scaling Optimize the display of different text scale Optimize windows setup experience Optimize startTun performance Optimize android tv experience Optimize default option Optimize computed text size Optimize hyperOS freeform window Add developer mode Update core Optimize more details
558 lines
15 KiB
Dart
558 lines
15 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
|
|
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 navigationsState(Ref ref) {
|
|
final openLogs = ref.watch(appSettingProvider).openLogs;
|
|
final hasProxies = ref.watch(
|
|
currentGroupsStateProvider.select((state) => state.value.isNotEmpty));
|
|
return NavigationItemsState(
|
|
value: navigation.getItems(
|
|
openLogs: openLogs,
|
|
hasProxies: hasProxies,
|
|
),
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
NavigationItemsState currentNavigationsState(Ref ref) {
|
|
final viewWidth = ref.watch(viewWidthProvider);
|
|
final navigationItemsState = ref.watch(navigationsStateProvider);
|
|
final navigationItemMode = switch (viewWidth <= maxMobileWidth) {
|
|
true => NavigationItemMode.mobile,
|
|
false => NavigationItemMode.desktop,
|
|
};
|
|
return NavigationItemsState(
|
|
value: navigationItemsState.value
|
|
.where(
|
|
(element) => element.modes.contains(navigationItemMode),
|
|
)
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
CoreState coreState(Ref ref) {
|
|
final vpnProps = ref.watch(vpnSettingProvider);
|
|
final currentProfile = ref.watch(currentProfileProvider);
|
|
final onlyStatisticsProxy = ref.watch(appSettingProvider).onlyStatisticsProxy;
|
|
return CoreState(
|
|
vpnProps: vpnProps,
|
|
onlyStatisticsProxy: onlyStatisticsProxy,
|
|
currentProfileName: currentProfile?.label ?? currentProfile?.id ?? "",
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
ClashConfigState clashConfigState(Ref ref) {
|
|
final clashConfig = ref.watch(patchClashConfigProvider);
|
|
final overrideDns = ref.watch(overrideDnsProvider);
|
|
final overrideData =
|
|
ref.watch(currentProfileProvider.select((state) => state?.overrideData));
|
|
final routeMode =
|
|
ref.watch(networkSettingProvider.select((state) => state.routeMode));
|
|
return ClashConfigState(
|
|
overrideDns: overrideDns,
|
|
clashConfig: clashConfig,
|
|
overrideData: overrideData ?? OverrideData(),
|
|
routeMode: routeMode,
|
|
);
|
|
}
|
|
|
|
@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 networkProps = ref.watch(networkSettingProvider);
|
|
final clashConfig = ref.watch(
|
|
patchClashConfigProvider,
|
|
);
|
|
final appSetting = ref.watch(
|
|
appSettingProvider,
|
|
);
|
|
final groups = ref.watch(
|
|
groupsProvider,
|
|
);
|
|
final brightness = ref.watch(
|
|
appBrightnessProvider,
|
|
);
|
|
|
|
final selectedMap = ref.watch(selectedMapProvider);
|
|
|
|
return TrayState(
|
|
mode: clashConfig.mode,
|
|
port: clashConfig.mixedPort,
|
|
autoLaunch: appSetting.autoLaunch,
|
|
systemProxy: networkProps.systemProxy,
|
|
tunEnable: clashConfig.tun.enable,
|
|
isStart: isStart,
|
|
locale: appSetting.locale,
|
|
brightness: brightness,
|
|
groups: groups,
|
|
selectedMap: selectedMap,
|
|
);
|
|
}
|
|
|
|
@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
|
|
HomeState homeState(Ref ref) {
|
|
final pageLabel = ref.watch(currentPageLabelProvider);
|
|
final navigationItems = ref.watch(currentNavigationsStateProvider).value;
|
|
final viewMode = ref.watch(viewModeProvider);
|
|
final locale = ref.watch(appSettingProvider).locale;
|
|
return HomeState(
|
|
pageLabel: pageLabel,
|
|
navigationItems: navigationItems,
|
|
viewMode: viewMode,
|
|
locale: locale,
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
DashboardState dashboardState(Ref ref) {
|
|
final dashboardWidgets =
|
|
ref.watch(appSettingProvider.select((state) => state.dashboardWidgets));
|
|
final viewWidth = ref.watch(viewWidthProvider);
|
|
return DashboardState(
|
|
dashboardWidgets: dashboardWidgets,
|
|
viewWidth: viewWidth,
|
|
);
|
|
}
|
|
|
|
@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(
|
|
viewWidthProvider.select((state) => utils.getProfilesColumns(state)));
|
|
return ProfilesSelectorState(
|
|
profiles: profiles,
|
|
currentProfileId: currentProfileId,
|
|
columns: columns,
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
ProxiesListSelectorState proxiesListSelectorState(Ref ref) {
|
|
final groupNames = ref.watch(currentGroupsStateProvider.select((state) {
|
|
return state.value.map((e) => e.name).toList();
|
|
}));
|
|
final currentUnfoldSet = ref.watch(unfoldSetProvider);
|
|
final proxiesStyle = ref.watch(proxiesStyleSettingProvider);
|
|
final sortNum = ref.watch(sortNumProvider);
|
|
final columns = ref.watch(getProxiesColumnsProvider);
|
|
return ProxiesListSelectorState(
|
|
groupNames: groupNames,
|
|
currentUnfoldSet: currentUnfoldSet,
|
|
proxiesSortType: proxiesStyle.sortType,
|
|
proxyCardType: proxiesStyle.cardType,
|
|
sortNum: sortNum,
|
|
columns: columns,
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
ProxiesSelectorState proxiesSelectorState(Ref ref) {
|
|
final groupNames = ref.watch(
|
|
currentGroupsStateProvider.select(
|
|
(state) {
|
|
return state.value.map((e) => e.name).toList();
|
|
},
|
|
),
|
|
);
|
|
final currentGroupName = ref.watch(currentProfileProvider.select(
|
|
(state) => state?.currentGroupName,
|
|
));
|
|
return ProxiesSelectorState(
|
|
groupNames: groupNames,
|
|
currentGroupName: currentGroupName,
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
GroupNamesState groupNamesState(Ref ref) {
|
|
return GroupNamesState(
|
|
groupNames: ref.watch(
|
|
currentGroupsStateProvider.select(
|
|
(state) {
|
|
return state.value.map((e) => e.name).toList();
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
ProxyGroupSelectorState proxyGroupSelectorState(Ref ref, String groupName) {
|
|
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);
|
|
return ProxyGroupSelectorState(
|
|
testUrl: group?.testUrl,
|
|
proxiesSortType: proxiesStyle.sortType,
|
|
proxyCardType: proxiesStyle.cardType,
|
|
sortNum: sortNum,
|
|
groupType: group?.type ?? GroupType.Selector,
|
|
proxies: group?.all ?? [],
|
|
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(navigationsStateProvider.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 proxyCardState = ref.watch(
|
|
getProxyCardStateProvider(
|
|
proxyName,
|
|
),
|
|
);
|
|
final delay = ref.watch(
|
|
delayDataSourceProvider.select(
|
|
(state) {
|
|
final delayMap =
|
|
state[proxyCardState.testUrl.getSafeValue(currentTestUrl)];
|
|
return delayMap?[proxyCardState.proxyName];
|
|
},
|
|
),
|
|
);
|
|
return delay;
|
|
}
|
|
|
|
@riverpod
|
|
SelectedMap 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 viewWidth = ref.watch(viewWidthProvider);
|
|
final proxiesLayout =
|
|
ref.watch(proxiesStyleSettingProvider.select((state) => state.layout));
|
|
return utils.getProxiesColumns(viewWidth, proxiesLayout);
|
|
}
|
|
|
|
ProxyCardState _getProxyCardState(
|
|
List<Group> groups,
|
|
SelectedMap selectedMap,
|
|
ProxyCardState proxyDelayState,
|
|
) {
|
|
if (proxyDelayState.proxyName.isEmpty) return proxyDelayState;
|
|
final index =
|
|
groups.indexWhere((element) => element.name == proxyDelayState.proxyName);
|
|
if (index == -1) return proxyDelayState;
|
|
final group = groups[index];
|
|
final currentSelectedName = group
|
|
.getCurrentSelectedName(selectedMap[proxyDelayState.proxyName] ?? '');
|
|
if (currentSelectedName.isEmpty) {
|
|
return proxyDelayState;
|
|
}
|
|
return _getProxyCardState(
|
|
groups,
|
|
selectedMap,
|
|
proxyDelayState.copyWith(
|
|
proxyName: currentSelectedName,
|
|
testUrl: group.testUrl,
|
|
),
|
|
);
|
|
}
|
|
|
|
@riverpod
|
|
ProxyCardState getProxyCardState(Ref ref, String proxyName) {
|
|
final groups = ref.watch(groupsProvider);
|
|
final selectedMap = ref.watch(selectedMapProvider);
|
|
return _getProxyCardState(
|
|
groups, selectedMap, ProxyCardState(proxyName: proxyName));
|
|
}
|
|
|
|
@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(getProxyCardStateProvider(proxy.name));
|
|
return "${proxy.type}(${state.proxyName.isNotEmpty ? state.proxyName : '*'})";
|
|
}
|
|
}
|
|
|
|
@riverpod
|
|
class ProfileOverrideState extends _$ProfileOverrideState {
|
|
@override
|
|
ProfileOverrideStateModel build() {
|
|
return ProfileOverrideStateModel(
|
|
isEdit: false,
|
|
selectedRules: {},
|
|
);
|
|
}
|
|
|
|
updateState(
|
|
ProfileOverrideStateModel? Function(ProfileOverrideStateModel state)
|
|
builder,
|
|
) {
|
|
final value = builder(state);
|
|
if (value == null) {
|
|
return;
|
|
}
|
|
state = value;
|
|
}
|
|
}
|
|
|
|
@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
|
|
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,
|
|
);
|
|
}
|