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'; import 'database.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.select( (state) => state.map((item) { return item.copyWith( now: '', all: item.all.map((proxy) => proxy.copyWith(now: '')).toList(), ); }), ), ); 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(state.systemProxy, 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(state.mode, state.mixedPort, state.tun.enable), ), ); final appSettingVm3 = ref.watch( appSettingProvider.select( (state) => VM3(state.autoLaunch, state.locale, 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 ProfilesState profilesState(Ref ref) { final currentProfileId = ref.watch(currentProfileIdProvider); final profiles = ref.watch(profilesProvider); final columns = ref.watch( contentWidthProvider.select((state) => utils.getProfilesColumns(state)), ); return ProfilesState( 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 currentGroups.copyWith(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, String?> proxiesTabControllerState(Ref ref) { return ref.watch( proxiesTabStateProvider.select( (state) => VM2( state.groups.map((group) => group.name).toList(), 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 accessControlProps = ref.watch( vpnSettingProvider.select((state) => state.accessControlProps), ); return PackageListSelectorState( packages: packages, accessControlProps: accessControlProps, ); } @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 realTestUrl(Ref ref, [String? testUrl]) { final currentTestUrl = ref.watch(appSettingProvider).testUrl; return testUrl.takeFirstValid([currentTestUrl]); } @riverpod int? getDelay(Ref ref, {required String proxyName, String? testUrl}) { final currentTestUrl = ref.watch(realTestUrlProvider(testUrl)); final proxyState = ref.watch(realSelectedProxyStateProvider(proxyName)); final delay = ref.watch( delayDataSourceProvider.select((state) { final delayMap = state[proxyState.testUrl.takeFirstValid([currentTestUrl])]; return delayMap?[proxyState.proxyName]; }), ); return delay; } @riverpod Map selectedMap(Ref ref) { final selectedMap = ref.watch( currentProfileProvider.select((state) => state?.selectedMap ?? {}), ); return selectedMap; } @riverpod Set 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 VM3 checkIp(Ref ref) { final isInit = ref.watch(initProvider); final checkIpNum = ref.watch(checkIpNumProvider); final containsDetection = ref.watch( dashboardStateProvider.select( (state) => state.dashboardWidgets.contains(DashboardWidget.networkDetection), ), ); return VM3(isInit, checkIpNum, containsDetection); } @riverpod ColorScheme genColorScheme( Ref ref, Brightness brightness, { Color? color, bool ignoreConfig = false, }) { final vm2 = ref.watch( themeSettingProvider.select( (state) => VM2(state.primaryColor, 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 SetupState? currentSetupState(Ref ref) { final profileId = ref.watch(currentProfileIdProvider); return ref.watch(setupStateProvider(profileId)).value; } @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 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(isStart ? realTunEnable : false, autoSetSystemDns); } @riverpod VM3 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(isProxies, sortNum, sortType); } @riverpod SharedState sharedState(Ref ref) { ref.watch((appSettingProvider).select((state) => state.locale)); final currentProfileVM2 = ref.watch( currentProfileProvider.select( (state) => VM2(state?.label ?? '', state?.selectedMap ?? {}), ), ); final appSettingVM3 = ref.watch( appSettingProvider.select( (state) => VM3(state.onlyStatisticsProxy, state.crashlytics, state.testUrl), ), ); final bypassDomain = ref.watch( networkSettingProvider.select((state) => state.bypassDomain), ); final clashConfigVM2 = ref.watch( patchClashConfigProvider.select( (state) => VM2(state.tun.stack.name, state.mixedPort), ), ); final vpnSetting = ref.watch(vpnSettingProvider); final currentProfileName = currentProfileVM2.a; final selectedMap = currentProfileVM2.b; final onlyStatisticsProxy = appSettingVM3.a; final crashlytics = appSettingVM3.b; final testUrl = appSettingVM3.c; final stack = clashConfigVM2.a; final port = clashConfigVM2.b; return SharedState( currentProfileName: currentProfileName, onlyStatisticsProxy: onlyStatisticsProxy, stopText: appLocalizations.stop, crashlytics: crashlytics, stopTip: appLocalizations.stopVpn, startTip: appLocalizations.startVpn, setupParams: SetupParams(selectedMap: selectedMap, testUrl: testUrl), vpnOptions: VpnOptions( enable: vpnSetting.enable, stack: stack, systemProxy: vpnSetting.systemProxy, port: port, ipv6: vpnSetting.ipv6, dnsHijacking: vpnSetting.dnsHijacking, accessControlProps: vpnSetting.accessControlProps, allowBypass: vpnSetting.allowBypass, bypassDomain: bypassDomain, ), ); } @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, int? profileId) { return ref.watch( profilesProvider.select((state) => state.getProfile(profileId)), ); } @riverpod OverwriteType overwriteType(Ref ref, int? profileId) { return ref.watch( profileProvider( profileId, ).select((state) => state?.overwriteType ?? OverwriteType.standard), ); } @riverpod Future script(Ref ref, int? scriptId) async { final script = await ref.watch( (scriptsProvider.future.select((state) async { final scripts = await state; return scripts.get(scriptId); })), ); return script; } @riverpod Future setupState(Ref ref, int? profileId) async { final profile = ref.watch(profileProvider(profileId)); final scriptId = profile?.scriptId; final profileLastUpdateDate = profile?.lastUpdateDate?.millisecondsSinceEpoch; final overwriteType = profile?.overwriteType ?? OverwriteType.standard; final dns = ref.watch(patchClashConfigProvider.select((state) => state.dns)); final script = await ref.watch(scriptProvider(scriptId).future); final overrideDns = ref.watch(overrideDnsProvider); final List addedRules = profileId != null ? await ref.watch(addedRuleStreamProvider(profileId).future) : []; return SetupState( profileId: profileId, profileLastUpdateDate: profileLastUpdateDate, overwriteType: overwriteType, addedRules: addedRules, script: script, overrideDns: overrideDns, dns: dns, ); } @riverpod class AccessControlState extends _$AccessControlState with AutoDisposeNotifierMixin { @override AccessControlProps build() => AccessControlProps(); }