diff --git a/core/common.go b/core/common.go index e555263..c8c4d31 100644 --- a/core/common.go +++ b/core/common.go @@ -3,6 +3,7 @@ package main import "C" import ( "github.com/metacubex/mihomo/adapter/inbound" + ap "github.com/metacubex/mihomo/adapter/provider" "github.com/metacubex/mihomo/component/dialer" "github.com/metacubex/mihomo/component/process" "github.com/metacubex/mihomo/component/resolver" @@ -16,16 +17,53 @@ import ( "math" "os" "os/exec" + "path/filepath" "runtime" "strings" "sync" "syscall" ) +type healthCheckSchema struct { + Enable bool `provider:"enable"` + URL string `provider:"url"` + Interval int `provider:"interval"` + TestTimeout int `provider:"timeout,omitempty"` + Lazy bool `provider:"lazy,omitempty"` + ExpectedStatus string `provider:"expected-status,omitempty"` +} + +type proxyProviderSchema struct { + Type string `provider:"type"` + Path string `provider:"path,omitempty"` + URL string `provider:"url,omitempty"` + Proxy string `provider:"proxy,omitempty"` + Interval int `provider:"interval,omitempty"` + Filter string `provider:"filter,omitempty"` + ExcludeFilter string `provider:"exclude-filter,omitempty"` + ExcludeType string `provider:"exclude-type,omitempty"` + DialerProxy string `provider:"dialer-proxy,omitempty"` + + HealthCheck healthCheckSchema `provider:"health-check,omitempty"` + Override ap.OverrideSchema `provider:"override,omitempty"` + Header map[string][]string `provider:"header,omitempty"` +} + +type ruleProviderSchema struct { + Type string `provider:"type"` + Behavior string `provider:"behavior"` + Path string `provider:"path,omitempty"` + URL string `provider:"url,omitempty"` + Proxy string `provider:"proxy,omitempty"` + Format string `provider:"format,omitempty"` + Interval int `provider:"interval,omitempty"` +} + type GenerateConfigParams struct { - ProfilePath *string `json:"profile-path"` - Config *config.RawConfig `json:"config" ` - IsPatch *bool `json:"is-patch"` + ProfilePath *string `json:"profile-path"` + Config *config.RawConfig `json:"config" ` + IsPatch *bool `json:"is-patch"` + IsCompatible *bool `json:"is-compatible"` } type ChangeProxyParams struct { @@ -91,6 +129,19 @@ func readFile(path string) ([]byte, error) { return data, err } +func removeFile(path string) error { + absPath, err := filepath.Abs(path) + if err != nil { + return err + } + err = os.Remove(absPath) + if err != nil { + return err + } + + return nil +} + func getRawConfigWithPath(path *string) *config.RawConfig { if path == nil { return config.DefaultRawConfig() @@ -109,9 +160,9 @@ func getRawConfigWithPath(path *string) *config.RawConfig { } } -func decorationConfig(profilePath *string, cfg config.RawConfig) *config.RawConfig { +func decorationConfig(profilePath *string, cfg config.RawConfig, compatible bool) *config.RawConfig { prof := getRawConfigWithPath(profilePath) - overwriteConfig(prof, cfg) + overwriteConfig(prof, cfg, compatible) return prof } @@ -261,12 +312,12 @@ func generateProxyGroupAndRule(proxyGroup *[]map[string]any, rule *[]string) { *rule = computedRule } -func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig) { +func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfig, compatible bool) { targetConfig.ExternalController = "" targetConfig.ExternalUI = "" targetConfig.Interface = "" targetConfig.ExternalUIURL = "" - targetConfig.IPv6 = patchConfig.IPv6 + //targetConfig.IPv6 = patchConfig.IPv6 targetConfig.LogLevel = patchConfig.LogLevel targetConfig.FindProcessMode = process.FindProcessAlways targetConfig.AllowLan = patchConfig.AllowLan @@ -286,9 +337,12 @@ func overwriteConfig(targetConfig *config.RawConfig, patchConfig config.RawConfi } else if runtime.GOOS == "windows" { targetConfig.DNS.NameServer = append(targetConfig.DNS.NameServer, dns.SystemDNSPlaceholder) } - targetConfig.ProxyProvider = make(map[string]map[string]any) - targetConfig.RuleProvider = make(map[string]map[string]any) - generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule) + if compatible == false { + targetConfig.ProxyProvider = make(map[string]map[string]any) + targetConfig.RuleProvider = make(map[string]map[string]any) + generateProxyGroupAndRule(&targetConfig.ProxyGroup, &targetConfig.Rule) + } + } func patchConfig(general *config.General) { diff --git a/core/hub.go b/core/hub.go index 97aee20..6c09908 100644 --- a/core/hub.go +++ b/core/hub.go @@ -8,6 +8,7 @@ import ( "github.com/metacubex/mihomo/adapter" "github.com/metacubex/mihomo/adapter/outboundgroup" "github.com/metacubex/mihomo/adapter/provider" + "github.com/metacubex/mihomo/common/structure" "github.com/metacubex/mihomo/common/utils" "github.com/metacubex/mihomo/config" "github.com/metacubex/mihomo/constant" @@ -74,7 +75,7 @@ func updateConfig(s *C.char, port C.longlong) { bridge.SendToPort(i, err.Error()) return } - prof := decorationConfig(params.ProfilePath, *params.Config) + prof := decorationConfig(params.ProfilePath, *params.Config, *params.IsCompatible) currentConfig = prof if *params.IsPatch { applyConfig(true) @@ -85,6 +86,39 @@ func updateConfig(s *C.char, port C.longlong) { }() } +//export clearEffect +func clearEffect(s *C.char) { + path := C.GoString(s) + go func() { + rawCfg := getRawConfigWithPath(&path) + for _, mapping := range rawCfg.RuleProvider { + schema := &ruleProviderSchema{} + decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) + if err := decoder.Decode(mapping, schema); err != nil { + return + } + if schema.Type == "http" { + _ = removeFile(constant.Path.Resolve(schema.Path)) + } + } + for _, mapping := range rawCfg.ProxyProvider { + schema := &proxyProviderSchema{ + HealthCheck: healthCheckSchema{ + Lazy: true, + }, + } + decoder := structure.NewDecoder(structure.Option{TagName: "provider", WeaklyTypedInput: true}) + if err := decoder.Decode(mapping, schema); err != nil { + return + } + if schema.Type == "http" { + _ = removeFile(constant.Path.Resolve(schema.Path)) + } + } + _ = removeFile(path) + }() +} + //export getProxies func getProxies() *C.char { data, err := json.Marshal(tunnel.ProxiesWithProviders()) @@ -137,8 +171,7 @@ func getTraffic() *C.char { } //export asyncTestDelay -func asyncTestDelay(s *C.char, port C.longlong) { - i := int64(port) +func asyncTestDelay(s *C.char) { go func() { paramsString := C.GoString(s) var params = &TestDelayParams{} @@ -146,34 +179,42 @@ func asyncTestDelay(s *C.char, port C.longlong) { if err != nil { return } + expectedStatus, err := utils.NewUnsignedRanges[uint16]("") if err != nil { return } + ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(params.Timeout)) defer cancel() + proxies := tunnel.ProxiesWithProviders() proxy := proxies[params.ProxyName] + delayData := &Delay{ Name: params.ProxyName, } + message := bridge.Message{ Type: bridge.Delay, Data: delayData, } + if proxy == nil { delayData.Value = -1 - bridge.SendToPort(i, message.Json()) + bridge.SendMessage(message) return } + delay, err := proxy.URLTest(ctx, constant.DefaultTestURL, expectedStatus) if err != nil || delay == 0 { delayData.Value = -1 - bridge.SendToPort(i, message.Json()) + bridge.SendMessage(message) return } + delayData.Value = int32(delay) - bridge.SendToPort(i, message.Json()) + bridge.SendMessage(message) }() } diff --git a/lib/application.dart b/lib/application.dart index 3ecddc6..435cf4e 100644 --- a/lib/application.dart +++ b/lib/application.dart @@ -31,7 +31,8 @@ runAppWithPreferences( create: (_) => appState, update: (_, config, clashConfig, appState) { appState?.mode = clashConfig.mode; - appState?.currentProxyName = config.currentProxyName; + appState?.isCompatible = config.isCompatible; + appState?.selectedMap = config.currentSelectedMap; return appState!; }, ) @@ -75,6 +76,7 @@ class ApplicationState extends State { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { appController.afterInit(); appController.initLink(); + _updateGroups(); }); } @@ -107,6 +109,18 @@ class ApplicationState extends State { }); } + _updateGroups() { + if (globalState.groupsUpdateTimer != null) { + globalState.groupsUpdateTimer?.cancel(); + globalState.groupsUpdateTimer = null; + } + globalState.groupsUpdateTimer ??= + Timer.periodic(appConstant.httpTimeoutDuration, (timer) async { + await appController.updateGroups(); + appController.appState.sortNum++; + }); + } + @override Widget build(context) { return AppStateContainer( diff --git a/lib/clash/core.dart b/lib/clash/core.dart index f7038fc..26e160c 100644 --- a/lib/clash/core.dart +++ b/lib/clash/core.dart @@ -107,43 +107,47 @@ class ClashCore { }); } + Future getDelayMap() { + final proxiesRaw = clashFFI.getProxies(); + final proxiesRawString = proxiesRaw.cast().toDartString(); + return Isolate.run(() { + final proxies = json.decode(proxiesRawString) as Map; + return proxies.map( + (k, v) { + final history = v["history"] as List; + if (history.isEmpty) { + return MapEntry( + k, + null, + ); + } else { + final delay = history.last["delay"]; + return MapEntry( + k, + delay != 0 ? delay : -1, + ); + } + }, + ); + }); + } + bool changeProxy(ChangeProxyParams changeProxyParams) { final params = json.encode(changeProxyParams); return clashFFI.changeProxy(params.toNativeUtf8().cast()) == 1; } - Future delay(String proxyName) { - final completer = Completer(); - final receiver = ReceivePort(); - receiver.listen((message) { - if (!completer.isCompleted) { - final m = Message.fromJson(json.decode(message)); - final delay = Delay.fromJson(m.data); - completer.complete(delay); - receiver.close(); - } - }); + bool delay(String proxyName) { final delayParams = { "proxy-name": proxyName, "timeout": appConstant.httpTimeoutDuration.inMilliseconds, }; - clashFFI.asyncTestDelay( - json.encode(delayParams).toNativeUtf8().cast(), - receiver.sendPort.nativePort, - ); - Future.delayed(appConstant.httpTimeoutDuration + appConstant.moreDuration, - () { - if (!completer.isCompleted) { - receiver.close(); - completer.complete( - Delay( - name: proxyName, - value: -1, - ), - ); - } - }); - return completer.future; + clashFFI.asyncTestDelay(json.encode(delayParams).toNativeUtf8().cast()); + return true; + } + + clearEffect(String path) { + clashFFI.clearEffect(path.toNativeUtf8().cast()); } healthcheck() { diff --git a/lib/clash/generated/clash_ffi.dart b/lib/clash/generated/clash_ffi.dart index b723e4d..cc30e78 100644 --- a/lib/clash/generated/clash_ffi.dart +++ b/lib/clash/generated/clash_ffi.dart @@ -924,6 +924,20 @@ class ClashFFI { late final _updateConfig = _updateConfigPtr.asFunction, int)>(); + void clearEffect( + ffi.Pointer s, + ) { + return _clearEffect( + s, + ); + } + + late final _clearEffectPtr = + _lookup)>>( + 'clearEffect'); + late final _clearEffect = + _clearEffectPtr.asFunction)>(); + ffi.Pointer getProxies() { return _getProxies(); } @@ -960,20 +974,17 @@ class ClashFFI { void asyncTestDelay( ffi.Pointer s, - int port, ) { return _asyncTestDelay( s, - port, ); } - late final _asyncTestDelayPtr = _lookup< - ffi.NativeFunction< - ffi.Void Function( - ffi.Pointer, ffi.LongLong)>>('asyncTestDelay'); - late final _asyncTestDelay = _asyncTestDelayPtr - .asFunction, int)>(); + late final _asyncTestDelayPtr = + _lookup)>>( + 'asyncTestDelay'); + late final _asyncTestDelay = + _asyncTestDelayPtr.asFunction)>(); ffi.Pointer getVersionInfo() { return _getVersionInfo(); diff --git a/lib/clash/service.dart b/lib/clash/service.dart index 4d83d60..2bab4c7 100644 --- a/lib/clash/service.dart +++ b/lib/clash/service.dart @@ -2,6 +2,7 @@ import 'dart:io'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; import 'package:flutter/services.dart'; + import 'core.dart'; class ClashService { diff --git a/lib/controller.dart b/lib/controller.dart index 4d22dd8..75b4d81 100644 --- a/lib/controller.dart +++ b/lib/controller.dart @@ -1,6 +1,4 @@ import 'dart:async'; -import 'dart:io'; -import 'dart:isolate'; import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; @@ -87,13 +85,7 @@ class AppController { config.deleteProfileById(id); final profilePath = await appPath.getProfilePath(id); if (profilePath == null) return; - final file = File(profilePath); - Isolate.run(() async { - final isExists = await file.exists(); - if (isExists) { - file.delete(); - } - }); + clashCore.clearEffect(profilePath); if (config.currentProfileId == id) { if (config.profiles.isNotEmpty) { final updateId = config.profiles.first.id; @@ -229,7 +221,6 @@ class AppController { } healthcheck() { - if (globalState.healthcheckLock) return; for (final delay in appState.delayMap.entries) { setDelay( Delay( @@ -245,6 +236,10 @@ class AppController { appState.setDelay(delay); } + updateDelayMap() async { + appState.delayMap = await clashCore.getDelayMap(); + } + toPage(int index, {bool hasAnimate = false}) { final nextLabel = globalState.currentNavigationItems[index].label; appState.currentLabel = nextLabel; @@ -355,4 +350,13 @@ class AppController { }, ); } + + clearShowProxyDelay() { + final showProxyDelay = appState.getRealProxyName(appState.showProxyName); + if (showProxyDelay != null) { + appState.setDelay( + Delay(name: showProxyDelay, value: null), + ); + } + } } diff --git a/lib/fragments/application_setting.dart b/lib/fragments/application_setting.dart index 2cf2ac0..63aa18d 100644 --- a/lib/fragments/application_setting.dart +++ b/lib/fragments/application_setting.dart @@ -35,6 +35,26 @@ class ApplicationSettingFragment extends StatelessWidget { ); }, ), + Selector( + selector: (_, config) => config.isCompatible, + builder: (_, isCompatible, __) { + return ListItem.switchItem( + leading: const Icon(Icons.device_hub), + title: const Text("兼容模式"), + subtitle: const Text("开启将失去部分应用能力,获得全量的Clash的支持"), + delegate: SwitchDelegate( + value: isCompatible, + onChanged: (bool value) async { + final appController = context.appController; + appController.config.isCompatible = value; + await appController.updateClashConfig(isPatch: false); + await appController.updateGroups(); + appController.changeProxy(); + }, + ), + ); + }, + ), if (system.isDesktop) Selector( selector: (_, config) => config.autoLaunch, diff --git a/lib/fragments/config.dart b/lib/fragments/config.dart index fa052f9..536a499 100644 --- a/lib/fragments/config.dart +++ b/lib/fragments/config.dart @@ -120,7 +120,7 @@ class _ConfigFragmentState extends State { return ListView.separated( itemBuilder: (_, index) { return Container( - height: 84, + padding: kMaterialListPadding, alignment: Alignment.center, child: items[index], ); diff --git a/lib/fragments/dashboard/network_detection.dart b/lib/fragments/dashboard/network_detection.dart index 8bd6c9d..240503b 100644 --- a/lib/fragments/dashboard/network_detection.dart +++ b/lib/fragments/dashboard/network_detection.dart @@ -1,6 +1,6 @@ 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:fl_clash/widgets/widgets.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -25,11 +25,6 @@ class _NetworkDetectionState extends State { ), ); } - if (currentProxyName == UsedProxy.DIRECT.name) { - return const Icon( - Icons.offline_bolt_outlined, - ); - } if (delay == 0 || delay == null) { return const AspectRatio( aspectRatio: 1, @@ -78,6 +73,52 @@ class _NetworkDetectionState extends State { ); } + _updateCurrentDelay( + String? currentProxyName, + int? delay, + bool isCurrent, + bool isInit, + ) { + if (!isCurrent || currentProxyName == null || !isInit) return; + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + if (delay == null) { + context.appController.setDelay( + Delay( + name: currentProxyName, + value: 0, + ), + ); + globalState.updateCurrentDelay( + currentProxyName, + ); + } + }); + } + + _updateCurrentDelayContainer(Widget child) { + return Selector2( + selector: (_, appState, config) { + return UpdateCurrentDelaySelectorState( + isInit: appState.isInit, + currentProxyName: appState.getRealProxyName(appState.showProxyName), + delay: appState + .delayMap[appState.getRealProxyName(appState.showProxyName)], + isCurrent: appState.currentLabel == 'dashboard', + ); + }, + builder: (_, state, __) { + _updateCurrentDelay( + state.currentProxyName, + state.delay, + state.isCurrent, + state.isInit, + ); + return child; + }, + child: child, + ); + } + @override Widget build(BuildContext context) { return CommonCard( @@ -85,55 +126,57 @@ class _NetworkDetectionState extends State { iconData: Icons.network_check, label: appLocalizations.networkDetection, ), - child: Selector3( - selector: (_, appState, config, clashConfig) { - final proxyName = appState.currentProxyName; - return NetworkDetectionSelectorState( - currentProxyName: proxyName, - delay: appState.getDelay( - proxyName, - ), - ); - }, - builder: (_, state, __) { - return Container( - padding: const EdgeInsets.all(16).copyWith(top: 0), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - flex: 0, - child: TooltipText( - text: Text( - state.currentProxyName ?? appLocalizations.noProxy, - overflow: TextOverflow.ellipsis, - maxLines: 1, - style: - Theme.of(context).textTheme.titleMedium?.toSoftBold(), - ), - ), - ), - const SizedBox( - height: 8, - ), - Flexible( - child: Container( - height: context.appController.measure.titleLargeHeight, - alignment: Alignment.centerLeft, - child: FadeBox( - child: _buildDescription( - state.currentProxyName, - state.delay, + child: _updateCurrentDelayContainer( + Selector( + selector: (_, appState) { + return NetworkDetectionSelectorState( + currentProxyName: appState.showProxyName, + delay: appState.getDelay( + appState.showProxyName, + ), + ); + }, + builder: (_, state, __) { + return Container( + padding: const EdgeInsets.all(16).copyWith(top: 0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + flex: 0, + child: TooltipText( + text: Text( + state.currentProxyName ?? appLocalizations.noProxy, + overflow: TextOverflow.ellipsis, + maxLines: 1, + style: Theme.of(context) + .textTheme + .titleMedium + ?.toSoftBold(), ), ), ), - ), - ], - ), - ); - }, + const SizedBox( + height: 8, + ), + Flexible( + child: Container( + height: context.appController.measure.titleLargeHeight, + alignment: Alignment.centerLeft, + child: FadeBox( + child: _buildDescription( + state.currentProxyName, + state.delay, + ), + ), + ), + ), + ], + ), + ); + }, + ), ), ); } diff --git a/lib/fragments/dashboard/outbound_mode.dart b/lib/fragments/dashboard/outbound_mode.dart index e87f6bb..22ea84a 100644 --- a/lib/fragments/dashboard/outbound_mode.dart +++ b/lib/fragments/dashboard/outbound_mode.dart @@ -11,10 +11,18 @@ class OutboundMode extends StatelessWidget { _changeMode(BuildContext context, Mode? value) async { final appController = context.appController; - final clashConfig = context.read(); + final clashConfig = appController.clashConfig; + final config = appController.config; if (value == null || clashConfig.mode == value) return; clashConfig.mode = value; await appController.updateClashConfig(); + if (!config.isCompatible) { + final proxySelected = config.currentSelectedMap[GroupName.Proxy.name]; + final globalSelected = config.currentSelectedMap[GroupName.GLOBAL.name]; + if (proxySelected != null && globalSelected == null) { + config.updateCurrentSelectedMap(GroupName.GLOBAL.name, proxySelected); + } + } appController.changeProxy(); } @@ -54,7 +62,8 @@ class OutboundMode extends StatelessWidget { ), title: Text( Intl.message(item.name), - style: Theme.of(context) + style: Theme + .of(context) .textTheme .titleMedium ?.toSoftBold(), diff --git a/lib/fragments/dashboard/start_button.dart b/lib/fragments/dashboard/start_button.dart index 793b7a9..f5ed28f 100644 --- a/lib/fragments/dashboard/start_button.dart +++ b/lib/fragments/dashboard/start_button.dart @@ -1,7 +1,5 @@ -import 'package:fl_clash/clash/clash.dart'; import 'package:fl_clash/common/common.dart'; import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -52,6 +50,9 @@ class _StartButtonState extends State updateSystemProxy() async { final appController = context.appController; await appController.updateSystemProxy(isStart); + if (isStart && mounted) { + appController.clearShowProxyDelay(); + } } @override diff --git a/lib/fragments/proxies.dart b/lib/fragments/proxies.dart index e791929..71c9a2a 100644 --- a/lib/fragments/proxies.dart +++ b/lib/fragments/proxies.dart @@ -1,3 +1,4 @@ +import 'package:fl_clash/state.dart'; import 'package:flutter/material.dart'; import 'package:flutter_adaptive_scaffold/flutter_adaptive_scaffold.dart'; import 'package:provider/provider.dart'; @@ -16,6 +17,8 @@ class ProxiesFragment extends StatefulWidget { class _ProxiesFragmentState extends State with TickerProviderStateMixin { + TabController? _tabController; + _initActions() { WidgetsBinding.instance.addPostFrameCallback((timeStamp) { final commonScaffoldState = @@ -53,6 +56,87 @@ class _ProxiesFragmentState extends State }); } + @override + Widget build(BuildContext context) { + return DelayTestButtonContainer( + child: Selector( + selector: (_, appState) => appState.currentLabel == 'proxies', + builder: (_, isCurrent, child) { + if (isCurrent) { + _initActions(); + } + return child!; + }, + child: Selector3( + selector: (_, appState, config, clashConfig) { + final currentGroups = appState.currentGroups; + final groupNames = currentGroups.map((e) => e.name).toList(); + return ProxiesSelectorState( + groupNames: groupNames, + ); + }, + shouldRebuild: (prev, next) { + if (prev.groupNames.length != next.groupNames.length) { + _tabController?.dispose(); + _tabController = null; + } + return prev != next; + }, + builder: (_, state, __) { + _tabController ??= TabController( + length: state.groupNames.length, + vsync: this, + ); + return Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TabBar( + controller: _tabController, + padding: const EdgeInsets.symmetric(horizontal: 16), + dividerColor: Colors.transparent, + isScrollable: true, + tabAlignment: TabAlignment.start, + overlayColor: + const MaterialStatePropertyAll(Colors.transparent), + tabs: [ + for (final groupName in state.groupNames) + Tab( + text: groupName, + ), + ], + ), + Expanded( + child: TabBarView( + controller: _tabController, + children: [ + for (final groupName in state.groupNames) + KeepContainer( + key: ObjectKey(groupName), + child: ProxiesTabView( + groupName: groupName, + ), + ), + ], + ), + ) + ], + ); + }, + ), + ), + ); + } +} + +class ProxiesTabView extends StatelessWidget { + final String groupName; + + const ProxiesTabView({ + super.key, + required this.groupName, + }); + List _sortOfName(List proxies) { return List.of(proxies) ..sort( @@ -62,12 +146,11 @@ class _ProxiesFragmentState extends State List _sortOfDelay(BuildContext context, List proxies) { final appState = context.read(); - final delayMap = appState.delayMap; return proxies = List.of(proxies) ..sort( (a, b) { - final aDelay = delayMap[a.name]; - final bDelay = delayMap[b.name]; + final aDelay = appState.delayMap[a.name]; + final bDelay = appState.delayMap[b.name]; if (aDelay == null && bDelay == null) { return 0; } @@ -83,6 +166,7 @@ class _ProxiesFragmentState extends State } _getProxies( + BuildContext context, List proxies, ProxiesSortType proxiesSortType, ) { @@ -102,7 +186,8 @@ class _ProxiesFragmentState extends State 8 * 2; } - _card({ + _card( + BuildContext context, { required void Function() onPressed, required bool isSelected, required Proxy proxy, @@ -168,7 +253,9 @@ class _ProxiesFragmentState extends State SizedBox( height: measure.labelSmallHeight, child: Selector( - selector: (context, appState) => appState.getDelay(proxy.name), + selector: (context, appState) => appState.getDelay( + proxy.name, + ), builder: (_, delay, __) { return FadeBox( child: Builder( @@ -222,13 +309,36 @@ class _ProxiesFragmentState extends State itemCount: proxies.length, itemBuilder: (_, index) { final proxy = proxies[index]; - return Selector( - selector: (_, appState) => proxy.name == appState.currentProxyName, - builder: (_, isSelected, __) { - return _card( + return Selector3( + selector: (_, appState, config, clashConfig) { + final group = appState.getGroupWithName(groupName)!; + bool isSelected = config.currentSelectedMap[group.name] == proxy.name || + (config.currentSelectedMap[group.name] == null && + group.now == proxy.name); + return ProxiesCardSelectorState( isSelected: isSelected, + ); + }, + builder: (_, state, __) { + return _card( + context, + isSelected: state.isSelected, onPressed: () { - context.appController.config.currentProxyName = proxy.name; + final appController = context.appController; + final group = + appController.appState.getGroupWithName(groupName)!; + if (group.type != GroupType.Selector) { + globalState.showSnackBar( + context, + message: "当前代理组无法选择", + ); + return; + } + context.appController.config.updateCurrentSelectedMap( + groupName, + proxy.name, + ); context.appController.changeProxy(); }, proxy: proxy, @@ -241,63 +351,52 @@ class _ProxiesFragmentState extends State @override Widget build(BuildContext context) { - return DelayTestButtonContainer( - child: Selector( - selector: (_, appState) => appState.currentLabel == 'proxies', - builder: (_, isCurrent, child) { - if (isCurrent) { - _initActions(); - } - return child!; - }, - child: Selector2( - selector: (_, appState, config) { - return ProxiesSelectorState( - proxiesSortType: config.proxiesSortType, - sortNum: appState.sortNum, - group: appState.currentGroup, - ); - }, - builder: (_, state, __) { - if (state.group == null) return Container(); - final proxies = _getProxies( - state.group!.all, - state.proxiesSortType, - ); - return Align( - alignment: Alignment.topCenter, - child: SlotLayout( - config: { - Breakpoints.small: SlotLayout.from( - key: const Key('proxies_grid_small'), - builder: (_) => _buildGrid( - context, - proxies: proxies, - columns: 2, - ), - ), - Breakpoints.medium: SlotLayout.from( - key: const Key('proxies_grid_medium'), - builder: (_) => _buildGrid( - context, - proxies: proxies, - columns: 3, - ), - ), - Breakpoints.large: SlotLayout.from( - key: const Key('proxies_grid_large'), - builder: (_) => _buildGrid( - context, - proxies: proxies, - columns: 4, - ), - ), - }, + return Selector2( + selector: (_, appState, config) { + return ProxiesTabViewSelectorState( + proxiesSortType: config.proxiesSortType, + sortNum: appState.sortNum, + group: appState.getGroupWithName(groupName)!, + ); + }, + builder: (_, state, __) { + final proxies = _getProxies( + context, + state.group.all, + state.proxiesSortType, + ); + return Align( + alignment: Alignment.topCenter, + child: SlotLayout( + config: { + Breakpoints.small: SlotLayout.from( + key: const Key('proxies_grid_small'), + builder: (_) => _buildGrid( + context, + proxies: proxies, + columns: 2, + ), ), - ); - }, - ), - ), + Breakpoints.medium: SlotLayout.from( + key: const Key('proxies_grid_medium'), + builder: (_) => _buildGrid( + context, + proxies: proxies, + columns: 3, + ), + ), + Breakpoints.large: SlotLayout.from( + key: const Key('proxies_grid_large'), + builder: (_) => _buildGrid( + context, + proxies: proxies, + columns: 4, + ), + ), + }, + ), + ); + }, ); } } diff --git a/lib/main.dart b/lib/main.dart index 1e9e26a..a11ae2d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,8 @@ Future main() async { final clashConfig = await preferences.getClashConfig() ?? ClashConfig(); final appState = AppState( mode: clashConfig.mode, - currentProxyName: config.currentProxyName, + isCompatible: config.isCompatible, + selectedMap: config.currentSelectedMap, ); await globalState.init( appState: appState, @@ -46,7 +47,8 @@ Future vpnService() async { final clashConfig = await preferences.getClashConfig() ?? ClashConfig(); final appState = AppState( mode: clashConfig.mode, - currentProxyName: config.currentProxyName, + isCompatible: config.isCompatible, + selectedMap: config.currentSelectedMap, ); clashMessage.addListener(ClashMessageListenerWithVpn(onTun: (String fd) { proxyManager.setProtect( diff --git a/lib/models/app.dart b/lib/models/app.dart index 381b9ce..5aaae6e 100644 --- a/lib/models/app.dart +++ b/lib/models/app.dart @@ -5,39 +5,47 @@ import 'ffi.dart'; import 'log.dart'; import 'navigation.dart'; import 'package.dart'; +import 'profile.dart'; import 'proxy.dart'; import 'system_color_scheme.dart'; import 'traffic.dart'; import 'version.dart'; +typedef DelayMap = Map; + class AppState with ChangeNotifier { List _navigationItems; int? _runTime; bool _isInit; - DelayMap _delayMap; VersionInfo? _versionInfo; List _traffics; List _logs; List _packages; String _currentLabel; SystemColorSchemes _systemColorSchemes; - List _groups; num _sortNum; Mode _mode; - String? _currentProxyName; + DelayMap _delayMap; + SelectedMap _selectedMap; + bool _isCompatible; + List _groups; - AppState({required Mode mode, required currentProxyName}) - : _navigationItems = [], - _delayMap = {}, + AppState({ + required Mode mode, + required bool isCompatible, + required SelectedMap selectedMap, + }) : _navigationItems = [], _isInit = false, _currentLabel = "dashboard", _traffics = [], _logs = [], - _groups = [], + _selectedMap = selectedMap, _packages = [], _sortNum = 0, _mode = mode, - _currentProxyName = currentProxyName, + _delayMap = {}, + _groups = [], + _isCompatible = isCompatible, _systemColorSchemes = SystemColorSchemes(); String get currentLabel => _currentLabel; @@ -76,49 +84,38 @@ class AppState with ChangeNotifier { } } - DelayMap get delayMap => _delayMap; - - set delayMap(DelayMap value) { - if (_delayMap != value) { - _delayMap = value; - notifyListeners(); - } - } - String getDesc(String type, String? proxyName) { final groupTypeNamesList = GroupType.values.map((e) => e.name).toList(); if (!groupTypeNamesList.contains(type)) { return type; - }else{ + } else { final index = groups.indexWhere((element) => element.name == proxyName); - if(index == -1) return type; + if (index == -1) return type; return "$type(${groups[index].now})"; } } - int? getDelay(String? proxyName) { + String? getRealProxyName(String? proxyName) { if (proxyName == null) return null; final index = groups.indexWhere((element) => element.name == proxyName); - if (index == -1) return _delayMap[proxyName]; + if (index == -1) return proxyName; final group = groups[index]; - if (group.now == null) return null; - return _delayMap[group.now]; + return getRealProxyName(selectedMap.containsKey(proxyName) + ? selectedMap[proxyName] + : group.now); } - String? get realCurrentProxyName { - if (currentProxyName == null) return null; - final index = groups.indexWhere((element) => element.name == currentProxyName); - if (index == -1) return currentProxyName; - final group = groups[index]; - if (group.now == null) return null; - return group.now; - } - - setDelay(Delay delay) { - if (_delayMap[delay.name] != delay.value) { - _delayMap = Map.from(_delayMap)..[delay.name] = delay.value; - notifyListeners(); + String? get showProxyName { + if (currentGroups.isEmpty) { + return UsedProxy.DIRECT.name; } + final firstGroup = currentGroups.first; + final firstGroupName = firstGroup.name; + return selectedMap[firstGroupName] ?? firstGroup.now; + } + + int? getDelay(String? proxyName) { + return _delayMap[getRealProxyName(proxyName)]; } VersionInfo? get versionInfo => _versionInfo; @@ -203,39 +200,77 @@ class AppState with ChangeNotifier { } } - String? get currentProxyName { - if (mode == Mode.direct) return UsedProxy.DIRECT.name; - if (_currentProxyName != null) return _currentProxyName!; - return currentGroup?.now; + // String? get currentProxyName { + // if (mode == Mode.direct) return UsedProxy.DIRECT.name; + // if (_currentProxyName != null) return _currentProxyName!; + // return currentGroup?.now; + // } + // + // set currentProxyName(String? value) { + // if (_currentProxyName != value) { + // _currentProxyName = value; + // notifyListeners(); + // } + // } + + bool get isCompatible { + return _isCompatible; } - set currentProxyName(String? value) { - if (_currentProxyName != value) { - _currentProxyName = value; + set isCompatible(bool value) { + if (_isCompatible != value) { + _isCompatible = value; notifyListeners(); } } - Group? get currentGroup { - switch (mode) { - case Mode.direct: - return null; - case Mode.global: - return globalGroup; - case Mode.rule: - return ruleGroup; + SelectedMap get selectedMap { + return _selectedMap; + } + + set selectedMap(SelectedMap value) { + if (!const MapEquality().equals(_selectedMap, value)) { + _selectedMap = value; + notifyListeners(); } } - Group? get globalGroup { - final index = - groups.indexWhere((element) => element.name == GroupName.GLOBAL.name); - return index != -1 ? groups[index] : null; + List get currentGroups { + switch (mode) { + case Mode.direct: + return []; + case Mode.global: + return groups + .where((element) => element.name == GroupName.GLOBAL.name) + .toList(); + case Mode.rule: + return groups + .where((element) => element.name != GroupName.GLOBAL.name) + .toList(); + } } - Group? get ruleGroup { + DelayMap get delayMap { + return _delayMap; + } + + set delayMap(DelayMap value) { + if (!const MapEquality().equals(_delayMap, value)) { + _delayMap = value; + notifyListeners(); + } + } + + setDelay(Delay delay) { + if (_delayMap[delay.name] != delay.value) { + _delayMap = Map.from(_delayMap)..[delay.name] = delay.value; + notifyListeners(); + } + } + + Group? getGroupWithName(String groupName) { final index = - groups.indexWhere((element) => element.name == GroupName.Proxy.name); - return index != -1 ? groups[index] : null; + currentGroups.indexWhere((element) => element.name == groupName); + return index != -1 ? currentGroups[index] : null; } } diff --git a/lib/models/config.dart b/lib/models/config.dart index ecb7d69..1cebc7c 100644 --- a/lib/models/config.dart +++ b/lib/models/config.dart @@ -61,6 +61,7 @@ class AccessControl { @JsonSerializable() class Config extends ChangeNotifier { List _profiles; + bool _isCompatible; String? _currentProfileId; bool _autoLaunch; bool _silentLaunch; @@ -82,6 +83,7 @@ class Config extends ChangeNotifier { _autoRun = false, _themeMode = ThemeMode.system, _openLog = false, + _isCompatible = false, _primaryColor = appConstant.defaultPrimaryColor.value, _proxiesSortType = ProxiesSortType.none, _isMinimizeOnExit = true, @@ -159,11 +161,15 @@ class Config extends ChangeNotifier { } } - String? get currentProxyName => currentProfile?.proxyName; - set currentProxyName(String? value){ - if (currentProfile?.proxyName != value) { - currentProfile?.proxyName = value; + SelectedMap get currentSelectedMap { + return currentProfile?.selectedMap ?? {}; + } + + updateCurrentSelectedMap(String groupName, String proxyName) { + if (currentProfile?.selectedMap[groupName] != proxyName) { + currentProfile?.selectedMap = Map.from(currentProfile?.selectedMap ?? {}) + ..[groupName] = proxyName; notifyListeners(); } } @@ -294,6 +300,18 @@ class Config extends ChangeNotifier { } } + @JsonKey(defaultValue: false) + bool get isCompatible { + return _isCompatible; + } + + set isCompatible(bool value) { + if (_isCompatible != value) { + _isCompatible = value; + notifyListeners(); + } + } + update() { notifyListeners(); } diff --git a/lib/models/ffi.dart b/lib/models/ffi.dart index 421c6cc..0854c9e 100644 --- a/lib/models/ffi.dart +++ b/lib/models/ffi.dart @@ -13,7 +13,8 @@ class UpdateConfigParams with _$UpdateConfigParams { const factory UpdateConfigParams({ @JsonKey(name: "profile-path") String? profilePath, required ClashConfig config, - @JsonKey(name: "is-patch") bool? isPatch, + @JsonKey(name: "is-patch") required bool isPatch, + @JsonKey(name: "is-compatible") required bool isCompatible, }) = _UpdateConfigParams; factory UpdateConfigParams.fromJson(Map json) => diff --git a/lib/models/generated/config.g.dart b/lib/models/generated/config.g.dart index 1701e47..8aebf1f 100644 --- a/lib/models/generated/config.g.dart +++ b/lib/models/generated/config.g.dart @@ -55,7 +55,8 @@ Config _$ConfigFromJson(Map json) => Config() ..isAccessControl = json['isAccessControl'] as bool? ?? false ..accessControl = AccessControl.fromJson(json['accessControl'] as Map) - ..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true; + ..isAnimateToPage = json['isAnimateToPage'] as bool? ?? true + ..isCompatible = json['isCompatible'] as bool? ?? false; Map _$ConfigToJson(Config instance) => { 'profiles': instance.profiles, @@ -72,6 +73,7 @@ Map _$ConfigToJson(Config instance) => { 'isAccessControl': instance.isAccessControl, 'accessControl': instance.accessControl, 'isAnimateToPage': instance.isAnimateToPage, + 'isCompatible': instance.isCompatible, }; const _$ThemeModeEnumMap = { diff --git a/lib/models/generated/ffi.freezed.dart b/lib/models/generated/ffi.freezed.dart index c493299..53ec3b5 100644 --- a/lib/models/generated/ffi.freezed.dart +++ b/lib/models/generated/ffi.freezed.dart @@ -24,7 +24,9 @@ mixin _$UpdateConfigParams { String? get profilePath => throw _privateConstructorUsedError; ClashConfig get config => throw _privateConstructorUsedError; @JsonKey(name: "is-patch") - bool? get isPatch => throw _privateConstructorUsedError; + bool get isPatch => throw _privateConstructorUsedError; + @JsonKey(name: "is-compatible") + bool get isCompatible => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -41,7 +43,8 @@ abstract class $UpdateConfigParamsCopyWith<$Res> { $Res call( {@JsonKey(name: "profile-path") String? profilePath, ClashConfig config, - @JsonKey(name: "is-patch") bool? isPatch}); + @JsonKey(name: "is-patch") bool isPatch, + @JsonKey(name: "is-compatible") bool isCompatible}); } /// @nodoc @@ -59,7 +62,8 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams> $Res call({ Object? profilePath = freezed, Object? config = null, - Object? isPatch = freezed, + Object? isPatch = null, + Object? isCompatible = null, }) { return _then(_value.copyWith( profilePath: freezed == profilePath @@ -70,10 +74,14 @@ class _$UpdateConfigParamsCopyWithImpl<$Res, $Val extends UpdateConfigParams> ? _value.config : config // ignore: cast_nullable_to_non_nullable as ClashConfig, - isPatch: freezed == isPatch + isPatch: null == isPatch ? _value.isPatch : isPatch // ignore: cast_nullable_to_non_nullable - as bool?, + as bool, + isCompatible: null == isCompatible + ? _value.isCompatible + : isCompatible // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } } @@ -89,7 +97,8 @@ abstract class _$$UpdateConfigParamsImplCopyWith<$Res> $Res call( {@JsonKey(name: "profile-path") String? profilePath, ClashConfig config, - @JsonKey(name: "is-patch") bool? isPatch}); + @JsonKey(name: "is-patch") bool isPatch, + @JsonKey(name: "is-compatible") bool isCompatible}); } /// @nodoc @@ -105,7 +114,8 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res> $Res call({ Object? profilePath = freezed, Object? config = null, - Object? isPatch = freezed, + Object? isPatch = null, + Object? isCompatible = null, }) { return _then(_$UpdateConfigParamsImpl( profilePath: freezed == profilePath @@ -116,10 +126,14 @@ class __$$UpdateConfigParamsImplCopyWithImpl<$Res> ? _value.config : config // ignore: cast_nullable_to_non_nullable as ClashConfig, - isPatch: freezed == isPatch + isPatch: null == isPatch ? _value.isPatch : isPatch // ignore: cast_nullable_to_non_nullable - as bool?, + as bool, + isCompatible: null == isCompatible + ? _value.isCompatible + : isCompatible // ignore: cast_nullable_to_non_nullable + as bool, )); } } @@ -130,7 +144,8 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams { const _$UpdateConfigParamsImpl( {@JsonKey(name: "profile-path") this.profilePath, required this.config, - @JsonKey(name: "is-patch") this.isPatch}); + @JsonKey(name: "is-patch") required this.isPatch, + @JsonKey(name: "is-compatible") required this.isCompatible}); factory _$UpdateConfigParamsImpl.fromJson(Map json) => _$$UpdateConfigParamsImplFromJson(json); @@ -142,11 +157,14 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams { final ClashConfig config; @override @JsonKey(name: "is-patch") - final bool? isPatch; + final bool isPatch; + @override + @JsonKey(name: "is-compatible") + final bool isCompatible; @override String toString() { - return 'UpdateConfigParams(profilePath: $profilePath, config: $config, isPatch: $isPatch)'; + return 'UpdateConfigParams(profilePath: $profilePath, config: $config, isPatch: $isPatch, isCompatible: $isCompatible)'; } @override @@ -157,12 +175,15 @@ class _$UpdateConfigParamsImpl implements _UpdateConfigParams { (identical(other.profilePath, profilePath) || other.profilePath == profilePath) && (identical(other.config, config) || other.config == config) && - (identical(other.isPatch, isPatch) || other.isPatch == isPatch)); + (identical(other.isPatch, isPatch) || other.isPatch == isPatch) && + (identical(other.isCompatible, isCompatible) || + other.isCompatible == isCompatible)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, profilePath, config, isPatch); + int get hashCode => + Object.hash(runtimeType, profilePath, config, isPatch, isCompatible); @JsonKey(ignore: true) @override @@ -183,7 +204,8 @@ abstract class _UpdateConfigParams implements UpdateConfigParams { const factory _UpdateConfigParams( {@JsonKey(name: "profile-path") final String? profilePath, required final ClashConfig config, - @JsonKey(name: "is-patch") final bool? isPatch}) = + @JsonKey(name: "is-patch") required final bool isPatch, + @JsonKey(name: "is-compatible") required final bool isCompatible}) = _$UpdateConfigParamsImpl; factory _UpdateConfigParams.fromJson(Map json) = @@ -196,7 +218,10 @@ abstract class _UpdateConfigParams implements UpdateConfigParams { ClashConfig get config; @override @JsonKey(name: "is-patch") - bool? get isPatch; + bool get isPatch; + @override + @JsonKey(name: "is-compatible") + bool get isCompatible; @override @JsonKey(ignore: true) _$$UpdateConfigParamsImplCopyWith<_$UpdateConfigParamsImpl> get copyWith => diff --git a/lib/models/generated/ffi.g.dart b/lib/models/generated/ffi.g.dart index 233ba14..37217d7 100644 --- a/lib/models/generated/ffi.g.dart +++ b/lib/models/generated/ffi.g.dart @@ -11,7 +11,8 @@ _$UpdateConfigParamsImpl _$$UpdateConfigParamsImplFromJson( _$UpdateConfigParamsImpl( profilePath: json['profile-path'] as String?, config: ClashConfig.fromJson(json['config'] as Map), - isPatch: json['is-patch'] as bool?, + isPatch: json['is-patch'] as bool, + isCompatible: json['is-compatible'] as bool, ); Map _$$UpdateConfigParamsImplToJson( @@ -20,6 +21,7 @@ Map _$$UpdateConfigParamsImplToJson( 'profile-path': instance.profilePath, 'config': instance.config, 'is-patch': instance.isPatch, + 'is-compatible': instance.isCompatible, }; _$ChangeProxyParamsImpl _$$ChangeProxyParamsImplFromJson( diff --git a/lib/models/generated/profile.g.dart b/lib/models/generated/profile.g.dart index 9242066..2c72dec 100644 --- a/lib/models/generated/profile.g.dart +++ b/lib/models/generated/profile.g.dart @@ -31,6 +31,9 @@ Profile _$ProfileFromJson(Map json) => Profile( lastUpdateDate: json['lastUpdateDate'] == null ? null : DateTime.parse(json['lastUpdateDate'] as String), + selectedMap: (json['selectedMap'] as Map?)?.map( + (k, e) => MapEntry(k, e as String), + ), autoUpdateDuration: json['autoUpdateDuration'] == null ? null : Duration(microseconds: (json['autoUpdateDuration'] as num).toInt()), @@ -46,4 +49,5 @@ Map _$ProfileToJson(Profile instance) => { 'autoUpdateDuration': instance.autoUpdateDuration.inMicroseconds, 'userInfo': instance.userInfo, 'autoUpdate': instance.autoUpdate, + 'selectedMap': instance.selectedMap, }; diff --git a/lib/models/generated/proxy.freezed.dart b/lib/models/generated/proxy.freezed.dart index fd40361..f963e39 100644 --- a/lib/models/generated/proxy.freezed.dart +++ b/lib/models/generated/proxy.freezed.dart @@ -219,6 +219,7 @@ Proxy _$ProxyFromJson(Map json) { mixin _$Proxy { String get name => throw _privateConstructorUsedError; String get type => throw _privateConstructorUsedError; + String? get now => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -230,7 +231,7 @@ abstract class $ProxyCopyWith<$Res> { factory $ProxyCopyWith(Proxy value, $Res Function(Proxy) then) = _$ProxyCopyWithImpl<$Res, Proxy>; @useResult - $Res call({String name, String type}); + $Res call({String name, String type, String? now}); } /// @nodoc @@ -248,6 +249,7 @@ class _$ProxyCopyWithImpl<$Res, $Val extends Proxy> $Res call({ Object? name = null, Object? type = null, + Object? now = freezed, }) { return _then(_value.copyWith( name: null == name @@ -258,6 +260,10 @@ class _$ProxyCopyWithImpl<$Res, $Val extends Proxy> ? _value.type : type // ignore: cast_nullable_to_non_nullable as String, + now: freezed == now + ? _value.now + : now // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -269,7 +275,7 @@ abstract class _$$ProxyImplCopyWith<$Res> implements $ProxyCopyWith<$Res> { __$$ProxyImplCopyWithImpl<$Res>; @override @useResult - $Res call({String name, String type}); + $Res call({String name, String type, String? now}); } /// @nodoc @@ -285,6 +291,7 @@ class __$$ProxyImplCopyWithImpl<$Res> $Res call({ Object? name = null, Object? type = null, + Object? now = freezed, }) { return _then(_$ProxyImpl( name: null == name @@ -295,6 +302,10 @@ class __$$ProxyImplCopyWithImpl<$Res> ? _value.type : type // ignore: cast_nullable_to_non_nullable as String, + now: freezed == now + ? _value.now + : now // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -302,21 +313,21 @@ class __$$ProxyImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$ProxyImpl implements _Proxy { - const _$ProxyImpl({this.name = "", this.type = ""}); + const _$ProxyImpl({required this.name, required this.type, this.now}); factory _$ProxyImpl.fromJson(Map json) => _$$ProxyImplFromJson(json); @override - @JsonKey() final String name; @override - @JsonKey() final String type; + @override + final String? now; @override String toString() { - return 'Proxy(name: $name, type: $type)'; + return 'Proxy(name: $name, type: $type, now: $now)'; } @override @@ -325,12 +336,13 @@ class _$ProxyImpl implements _Proxy { (other.runtimeType == runtimeType && other is _$ProxyImpl && (identical(other.name, name) || other.name == name) && - (identical(other.type, type) || other.type == type)); + (identical(other.type, type) || other.type == type) && + (identical(other.now, now) || other.now == now)); } @JsonKey(ignore: true) @override - int get hashCode => Object.hash(runtimeType, name, type); + int get hashCode => Object.hash(runtimeType, name, type, now); @JsonKey(ignore: true) @override @@ -347,7 +359,10 @@ class _$ProxyImpl implements _Proxy { } abstract class _Proxy implements Proxy { - const factory _Proxy({final String name, final String type}) = _$ProxyImpl; + const factory _Proxy( + {required final String name, + required final String type, + final String? now}) = _$ProxyImpl; factory _Proxy.fromJson(Map json) = _$ProxyImpl.fromJson; @@ -356,6 +371,8 @@ abstract class _Proxy implements Proxy { @override String get type; @override + String? get now; + @override @JsonKey(ignore: true) _$$ProxyImplCopyWith<_$ProxyImpl> get copyWith => throw _privateConstructorUsedError; diff --git a/lib/models/generated/proxy.g.dart b/lib/models/generated/proxy.g.dart index 30ea819..1c59180 100644 --- a/lib/models/generated/proxy.g.dart +++ b/lib/models/generated/proxy.g.dart @@ -31,12 +31,14 @@ const _$GroupTypeEnumMap = { }; _$ProxyImpl _$$ProxyImplFromJson(Map json) => _$ProxyImpl( - name: json['name'] as String? ?? "", - type: json['type'] as String? ?? "", + name: json['name'] as String, + type: json['type'] as String, + now: json['now'] as String?, ); Map _$$ProxyImplToJson(_$ProxyImpl instance) => { 'name': instance.name, 'type': instance.type, + 'now': instance.now, }; diff --git a/lib/models/generated/selector.freezed.dart b/lib/models/generated/selector.freezed.dart index 76a2fa9..07b637c 100644 --- a/lib/models/generated/selector.freezed.dart +++ b/lib/models/generated/selector.freezed.dart @@ -1724,7 +1724,6 @@ abstract class _HomeNavigationSelectorState /// @nodoc mixin _$ProxiesCardSelectorState { - String? get currentProxyName => throw _privateConstructorUsedError; bool get isSelected => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -1738,7 +1737,7 @@ abstract class $ProxiesCardSelectorStateCopyWith<$Res> { $Res Function(ProxiesCardSelectorState) then) = _$ProxiesCardSelectorStateCopyWithImpl<$Res, ProxiesCardSelectorState>; @useResult - $Res call({String? currentProxyName, bool isSelected}); + $Res call({bool isSelected}); } /// @nodoc @@ -1755,14 +1754,9 @@ class _$ProxiesCardSelectorStateCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ - Object? currentProxyName = freezed, Object? isSelected = null, }) { return _then(_value.copyWith( - currentProxyName: freezed == currentProxyName - ? _value.currentProxyName - : currentProxyName // ignore: cast_nullable_to_non_nullable - as String?, isSelected: null == isSelected ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable @@ -1780,7 +1774,7 @@ abstract class _$$ProxiesCardSelectorStateImplCopyWith<$Res> __$$ProxiesCardSelectorStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({String? currentProxyName, bool isSelected}); + $Res call({bool isSelected}); } /// @nodoc @@ -1796,14 +1790,9 @@ class __$$ProxiesCardSelectorStateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? currentProxyName = freezed, Object? isSelected = null, }) { return _then(_$ProxiesCardSelectorStateImpl( - currentProxyName: freezed == currentProxyName - ? _value.currentProxyName - : currentProxyName // ignore: cast_nullable_to_non_nullable - as String?, isSelected: null == isSelected ? _value.isSelected : isSelected // ignore: cast_nullable_to_non_nullable @@ -1815,17 +1804,14 @@ class __$$ProxiesCardSelectorStateImplCopyWithImpl<$Res> /// @nodoc class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState { - const _$ProxiesCardSelectorStateImpl( - {required this.currentProxyName, required this.isSelected}); + const _$ProxiesCardSelectorStateImpl({required this.isSelected}); - @override - final String? currentProxyName; @override final bool isSelected; @override String toString() { - return 'ProxiesCardSelectorState(currentProxyName: $currentProxyName, isSelected: $isSelected)'; + return 'ProxiesCardSelectorState(isSelected: $isSelected)'; } @override @@ -1833,14 +1819,12 @@ class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProxiesCardSelectorStateImpl && - (identical(other.currentProxyName, currentProxyName) || - other.currentProxyName == currentProxyName) && (identical(other.isSelected, isSelected) || other.isSelected == isSelected)); } @override - int get hashCode => Object.hash(runtimeType, currentProxyName, isSelected); + int get hashCode => Object.hash(runtimeType, isSelected); @JsonKey(ignore: true) @override @@ -1851,12 +1835,9 @@ class _$ProxiesCardSelectorStateImpl implements _ProxiesCardSelectorState { } abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState { - const factory _ProxiesCardSelectorState( - {required final String? currentProxyName, - required final bool isSelected}) = _$ProxiesCardSelectorStateImpl; + const factory _ProxiesCardSelectorState({required final bool isSelected}) = + _$ProxiesCardSelectorStateImpl; - @override - String? get currentProxyName; @override bool get isSelected; @override @@ -1867,9 +1848,7 @@ abstract class _ProxiesCardSelectorState implements ProxiesCardSelectorState { /// @nodoc mixin _$ProxiesSelectorState { - ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError; - num get sortNum => throw _privateConstructorUsedError; - Group? get group => throw _privateConstructorUsedError; + List get groupNames => throw _privateConstructorUsedError; @JsonKey(ignore: true) $ProxiesSelectorStateCopyWith get copyWith => @@ -1882,9 +1861,7 @@ abstract class $ProxiesSelectorStateCopyWith<$Res> { $Res Function(ProxiesSelectorState) then) = _$ProxiesSelectorStateCopyWithImpl<$Res, ProxiesSelectorState>; @useResult - $Res call({ProxiesSortType proxiesSortType, num sortNum, Group? group}); - - $GroupCopyWith<$Res>? get group; + $Res call({List groupNames}); } /// @nodoc @@ -1901,37 +1878,15 @@ class _$ProxiesSelectorStateCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ - Object? proxiesSortType = null, - Object? sortNum = null, - Object? group = freezed, + Object? groupNames = null, }) { return _then(_value.copyWith( - proxiesSortType: null == proxiesSortType - ? _value.proxiesSortType - : proxiesSortType // ignore: cast_nullable_to_non_nullable - as ProxiesSortType, - sortNum: null == sortNum - ? _value.sortNum - : sortNum // ignore: cast_nullable_to_non_nullable - as num, - group: freezed == group - ? _value.group - : group // ignore: cast_nullable_to_non_nullable - as Group?, + groupNames: null == groupNames + ? _value.groupNames + : groupNames // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } - - @override - @pragma('vm:prefer-inline') - $GroupCopyWith<$Res>? get group { - if (_value.group == null) { - return null; - } - - return $GroupCopyWith<$Res>(_value.group!, (value) { - return _then(_value.copyWith(group: value) as $Val); - }); - } } /// @nodoc @@ -1942,10 +1897,7 @@ abstract class _$$ProxiesSelectorStateImplCopyWith<$Res> __$$ProxiesSelectorStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({ProxiesSortType proxiesSortType, num sortNum, Group? group}); - - @override - $GroupCopyWith<$Res>? get group; + $Res call({List groupNames}); } /// @nodoc @@ -1959,23 +1911,13 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? proxiesSortType = null, - Object? sortNum = null, - Object? group = freezed, + Object? groupNames = null, }) { return _then(_$ProxiesSelectorStateImpl( - proxiesSortType: null == proxiesSortType - ? _value.proxiesSortType - : proxiesSortType // ignore: cast_nullable_to_non_nullable - as ProxiesSortType, - sortNum: null == sortNum - ? _value.sortNum - : sortNum // ignore: cast_nullable_to_non_nullable - as num, - group: freezed == group - ? _value.group - : group // ignore: cast_nullable_to_non_nullable - as Group?, + groupNames: null == groupNames + ? _value._groupNames + : groupNames // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -1983,21 +1925,20 @@ class __$$ProxiesSelectorStateImplCopyWithImpl<$Res> /// @nodoc class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState { - const _$ProxiesSelectorStateImpl( - {required this.proxiesSortType, - required this.sortNum, - required this.group}); + const _$ProxiesSelectorStateImpl({required final List groupNames}) + : _groupNames = groupNames; + final List _groupNames; @override - final ProxiesSortType proxiesSortType; - @override - final num sortNum; - @override - final Group? group; + List get groupNames { + if (_groupNames is EqualUnmodifiableListView) return _groupNames; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_groupNames); + } @override String toString() { - return 'ProxiesSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum, group: $group)'; + return 'ProxiesSelectorState(groupNames: $groupNames)'; } @override @@ -2005,14 +1946,13 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$ProxiesSelectorStateImpl && - (identical(other.proxiesSortType, proxiesSortType) || - other.proxiesSortType == proxiesSortType) && - (identical(other.sortNum, sortNum) || other.sortNum == sortNum) && - (identical(other.group, group) || other.group == group)); + const DeepCollectionEquality() + .equals(other._groupNames, _groupNames)); } @override - int get hashCode => Object.hash(runtimeType, proxiesSortType, sortNum, group); + int get hashCode => Object.hash( + runtimeType, const DeepCollectionEquality().hash(_groupNames)); @JsonKey(ignore: true) @override @@ -2025,18 +1965,190 @@ class _$ProxiesSelectorStateImpl implements _ProxiesSelectorState { abstract class _ProxiesSelectorState implements ProxiesSelectorState { const factory _ProxiesSelectorState( + {required final List groupNames}) = _$ProxiesSelectorStateImpl; + + @override + List get groupNames; + @override + @JsonKey(ignore: true) + _$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +mixin _$ProxiesTabViewSelectorState { + ProxiesSortType get proxiesSortType => throw _privateConstructorUsedError; + num get sortNum => throw _privateConstructorUsedError; + Group get group => throw _privateConstructorUsedError; + + @JsonKey(ignore: true) + $ProxiesTabViewSelectorStateCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ProxiesTabViewSelectorStateCopyWith<$Res> { + factory $ProxiesTabViewSelectorStateCopyWith( + ProxiesTabViewSelectorState value, + $Res Function(ProxiesTabViewSelectorState) then) = + _$ProxiesTabViewSelectorStateCopyWithImpl<$Res, + ProxiesTabViewSelectorState>; + @useResult + $Res call({ProxiesSortType proxiesSortType, num sortNum, Group group}); + + $GroupCopyWith<$Res> get group; +} + +/// @nodoc +class _$ProxiesTabViewSelectorStateCopyWithImpl<$Res, + $Val extends ProxiesTabViewSelectorState> + implements $ProxiesTabViewSelectorStateCopyWith<$Res> { + _$ProxiesTabViewSelectorStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? proxiesSortType = null, + Object? sortNum = null, + Object? group = null, + }) { + return _then(_value.copyWith( + proxiesSortType: null == proxiesSortType + ? _value.proxiesSortType + : proxiesSortType // ignore: cast_nullable_to_non_nullable + as ProxiesSortType, + sortNum: null == sortNum + ? _value.sortNum + : sortNum // ignore: cast_nullable_to_non_nullable + as num, + group: null == group + ? _value.group + : group // ignore: cast_nullable_to_non_nullable + as Group, + ) as $Val); + } + + @override + @pragma('vm:prefer-inline') + $GroupCopyWith<$Res> get group { + return $GroupCopyWith<$Res>(_value.group, (value) { + return _then(_value.copyWith(group: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> + implements $ProxiesTabViewSelectorStateCopyWith<$Res> { + factory _$$ProxiesTabViewSelectorStateImplCopyWith( + _$ProxiesTabViewSelectorStateImpl value, + $Res Function(_$ProxiesTabViewSelectorStateImpl) then) = + __$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({ProxiesSortType proxiesSortType, num sortNum, Group group}); + + @override + $GroupCopyWith<$Res> get group; +} + +/// @nodoc +class __$$ProxiesTabViewSelectorStateImplCopyWithImpl<$Res> + extends _$ProxiesTabViewSelectorStateCopyWithImpl<$Res, + _$ProxiesTabViewSelectorStateImpl> + implements _$$ProxiesTabViewSelectorStateImplCopyWith<$Res> { + __$$ProxiesTabViewSelectorStateImplCopyWithImpl( + _$ProxiesTabViewSelectorStateImpl _value, + $Res Function(_$ProxiesTabViewSelectorStateImpl) _then) + : super(_value, _then); + + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? proxiesSortType = null, + Object? sortNum = null, + Object? group = null, + }) { + return _then(_$ProxiesTabViewSelectorStateImpl( + proxiesSortType: null == proxiesSortType + ? _value.proxiesSortType + : proxiesSortType // ignore: cast_nullable_to_non_nullable + as ProxiesSortType, + sortNum: null == sortNum + ? _value.sortNum + : sortNum // ignore: cast_nullable_to_non_nullable + as num, + group: null == group + ? _value.group + : group // ignore: cast_nullable_to_non_nullable + as Group, + )); + } +} + +/// @nodoc + +class _$ProxiesTabViewSelectorStateImpl + implements _ProxiesTabViewSelectorState { + const _$ProxiesTabViewSelectorStateImpl( + {required this.proxiesSortType, + required this.sortNum, + required this.group}); + + @override + final ProxiesSortType proxiesSortType; + @override + final num sortNum; + @override + final Group group; + + @override + String toString() { + return 'ProxiesTabViewSelectorState(proxiesSortType: $proxiesSortType, sortNum: $sortNum, group: $group)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ProxiesTabViewSelectorStateImpl && + (identical(other.proxiesSortType, proxiesSortType) || + other.proxiesSortType == proxiesSortType) && + (identical(other.sortNum, sortNum) || other.sortNum == sortNum) && + (identical(other.group, group) || other.group == group)); + } + + @override + int get hashCode => Object.hash(runtimeType, proxiesSortType, sortNum, group); + + @JsonKey(ignore: true) + @override + @pragma('vm:prefer-inline') + _$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl> + get copyWith => __$$ProxiesTabViewSelectorStateImplCopyWithImpl< + _$ProxiesTabViewSelectorStateImpl>(this, _$identity); +} + +abstract class _ProxiesTabViewSelectorState + implements ProxiesTabViewSelectorState { + const factory _ProxiesTabViewSelectorState( {required final ProxiesSortType proxiesSortType, required final num sortNum, - required final Group? group}) = _$ProxiesSelectorStateImpl; + required final Group group}) = _$ProxiesTabViewSelectorStateImpl; @override ProxiesSortType get proxiesSortType; @override num get sortNum; @override - Group? get group; + Group get group; @override @JsonKey(ignore: true) - _$$ProxiesSelectorStateImplCopyWith<_$ProxiesSelectorStateImpl> + _$$ProxiesTabViewSelectorStateImplCopyWith<_$ProxiesTabViewSelectorStateImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/profile.dart b/lib/models/profile.dart index 4f44129..554cc43 100644 --- a/lib/models/profile.dart +++ b/lib/models/profile.dart @@ -12,6 +12,8 @@ import 'common.dart'; part 'generated/profile.g.dart'; +typedef SelectedMap = Map; + @JsonSerializable() class UserInfo { int upload; @@ -68,6 +70,7 @@ class Profile { Duration autoUpdateDuration; UserInfo? userInfo; bool autoUpdate; + SelectedMap selectedMap; Profile({ String? id, @@ -76,11 +79,13 @@ class Profile { this.userInfo, this.proxyName, this.lastUpdateDate, + SelectedMap? selectedMap, Duration? autoUpdateDuration, this.autoUpdate = true, }) : id = id ?? DateTime.now().millisecondsSinceEpoch.toString(), autoUpdateDuration = - autoUpdateDuration ?? appConstant.defaultUpdateDuration; + autoUpdateDuration ?? appConstant.defaultUpdateDuration, + selectedMap = selectedMap ?? {}; ProfileType get type => url == null ? ProfileType.file : ProfileType.url; @@ -188,6 +193,7 @@ class Profile { DateTime? lastUpdateDate, Duration? autoUpdateDuration, bool? autoUpdate, + SelectedMap? selectedMap, }) { return Profile( id: id, @@ -195,6 +201,7 @@ class Profile { url: url ?? this.url, proxyName: proxyName ?? this.proxyName, userInfo: userInfo ?? this.userInfo, + selectedMap: selectedMap ?? this.selectedMap, lastUpdateDate: lastUpdateDate ?? this.lastUpdateDate, autoUpdateDuration: autoUpdateDuration ?? this.autoUpdateDuration, autoUpdate: autoUpdate ?? this.autoUpdate, diff --git a/lib/models/proxy.dart b/lib/models/proxy.dart index c65901d..ab2654a 100644 --- a/lib/models/proxy.dart +++ b/lib/models/proxy.dart @@ -6,7 +6,7 @@ part 'generated/proxy.g.dart'; part 'generated/proxy.freezed.dart'; -typedef DelayMap = Map; +typedef ProxyMap = Map; @freezed class Group with _$Group { @@ -23,8 +23,9 @@ class Group with _$Group { @freezed class Proxy with _$Proxy { const factory Proxy({ - @Default("") String name, - @Default("") String type, + required String name, + required String type, + String? now, }) = _Proxy; factory Proxy.fromJson(Map json) => _$ProxyFromJson(json); diff --git a/lib/models/selector.dart b/lib/models/selector.dart index 88913b7..b5e0929 100644 --- a/lib/models/selector.dart +++ b/lib/models/selector.dart @@ -105,7 +105,6 @@ class HomeNavigationSelectorState with _$HomeNavigationSelectorState{ @freezed class ProxiesCardSelectorState with _$ProxiesCardSelectorState{ const factory ProxiesCardSelectorState({ - required String? currentProxyName, required bool isSelected, }) = _ProxiesCardSelectorState; } @@ -113,8 +112,15 @@ class ProxiesCardSelectorState with _$ProxiesCardSelectorState{ @freezed class ProxiesSelectorState with _$ProxiesSelectorState{ const factory ProxiesSelectorState({ - required ProxiesSortType proxiesSortType, - required num sortNum, - required Group? group, + required List groupNames, }) = _ProxiesSelectorState; } + +@freezed +class ProxiesTabViewSelectorState with _$ProxiesTabViewSelectorState{ + const factory ProxiesTabViewSelectorState({ + required ProxiesSortType proxiesSortType, + required num sortNum, + required Group group, + }) = _ProxiesTabViewSelectorState; +} diff --git a/lib/state.dart b/lib/state.dart index cfd3edf..b39560a 100644 --- a/lib/state.dart +++ b/lib/state.dart @@ -15,6 +15,8 @@ import 'common/common.dart'; class GlobalState { Timer? timer; Function? updateSortNumDebounce; + Timer? groupsUpdateTimer; + Function? updateCurrentDelayDebounce; PageController? pageController; final navigatorKey = GlobalKey(); final Map packageNameMap = {}; @@ -22,7 +24,6 @@ class GlobalState { List updateFunctionLists = []; List currentNavigationItems = []; bool updatePackagesLock = false; - bool healthcheckLock = false; startListenUpdate() { if (timer != null && timer!.isActive == true) return; @@ -49,6 +50,7 @@ class GlobalState { profilePath: profilePath, config: clashConfig, isPatch: isPatch, + isCompatible: config.isCompatible, )); } @@ -74,7 +76,6 @@ class GlobalState { stopListenUpdate(); } - applyProfile({ required AppState appState, required Config config, @@ -125,30 +126,14 @@ class GlobalState { stopSystemProxy(); return; } - final currentProxyName = appState.currentProxyName; - if (currentProxyName == null) return; - final index1 = appState.globalGroup?.all.indexWhere( - (element) => element.name == currentProxyName, - ); - if (index1 != null && index1 != -1) { + config.currentSelectedMap.forEach((key, value) { clashCore.changeProxy( ChangeProxyParams( - groupName: GroupName.GLOBAL.name, - proxyName: currentProxyName, + groupName: key, + proxyName: value, ), ); - } - final index2 = appState.ruleGroup?.all.indexWhere( - (element) => element.name == currentProxyName, - ); - if (index2 != null && index2 != -1) { - clashCore.changeProxy( - ChangeProxyParams( - groupName: GroupName.Proxy.name, - proxyName: currentProxyName, - ), - ); - } + }); }); } @@ -165,11 +150,11 @@ class GlobalState { required Config config, required ClashConfig clashConfig, }) { - final group = appState.currentGroup; + final group = appState.currentGroups; final hasProfile = config.profiles.isNotEmpty; appState.navigationItems = navigation.getItems( openLogs: config.openLogs, - hasProxies: group != null && hasProfile, + hasProxies: group.isNotEmpty && hasProfile, ); } @@ -253,6 +238,52 @@ class GlobalState { ); } } + + showSnackBar( + BuildContext context, { + required String message, + SnackBarAction? action, + }) { + final width = context.width; + EdgeInsets margin; + if (width < 600) { + margin = const EdgeInsets.only( + bottom: 96, + right: 16, + left: 16, + ); + } else { + margin = EdgeInsets.only( + bottom: 16, + left: 16, + right: width - 316, + ); + } + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + action: action, + content: Text(message), + behavior: SnackBarBehavior.floating, + duration: const Duration(milliseconds: 1500), + margin: margin, + ), + ); + } + + void updateCurrentDelay( + String? proxyName, + ) { + updateCurrentDelayDebounce ??= debounce((proxyName) { + if (proxyName != null) { + debugPrint("[delay]=====> $proxyName"); + clashCore.delay( + proxyName, + ); + } + }); + updateCurrentDelayDebounce!([proxyName]); + } + } final globalState = GlobalState(); diff --git a/lib/widgets/app_state_container.dart b/lib/widgets/app_state_container.dart index 91f5c61..a955302 100644 --- a/lib/widgets/app_state_container.dart +++ b/lib/widgets/app_state_container.dart @@ -25,11 +25,11 @@ class AppStateContainer extends StatelessWidget { _updateNavigationsContainer(Widget child) { return Selector2( selector: (_, appState, config) { - final group = appState.currentGroup; + final group = appState.currentGroups; final hasProfile = config.profiles.isNotEmpty; return UpdateNavigationsSelector( openLogs: config.openLogs, - hasProxies: group != null && hasProfile, + hasProxies: group.isNotEmpty && hasProfile, ); }, builder: (context, state, child) { diff --git a/lib/widgets/clash_message_container.dart b/lib/widgets/clash_message_container.dart index d9b8e55..ac4d5c2 100644 --- a/lib/widgets/clash_message_container.dart +++ b/lib/widgets/clash_message_container.dart @@ -40,24 +40,11 @@ class _ClashMessageContainerState extends State void onDelay(Delay delay) { final appController = context.appController; appController.setDelay(delay); - globalState.healthcheckLock = true; - WidgetsBinding.instance.addPostFrameCallback((_) { - globalState.updateSortNumDebounce ??= debounce( - () async { - await appController.updateGroups(); - appController.appState.sortNum++; - globalState.healthcheckLock = false; - }, - milliseconds: 5000, - ); - globalState.updateSortNumDebounce!(); - }); super.onDelay(delay); } @override void onLog(Log log) { - print("$log"); context.appController.appState.addLog(log); super.onLog(log); } diff --git a/pubspec.yaml b/pubspec.yaml index 867664a..c8c2609 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: fl_clash description: A multi-platform proxy client based on ClashMeta, simple and easy to use, open-source and ad-free. publish_to: 'none' -version: 0.7.14 +version: 0.8.0 environment: sdk: '>=3.1.0 <4.0.0'