From de9c5ba9ccbe2a79d8150f4329a3338c9525983d Mon Sep 17 00:00:00 2001 From: chen08209 Date: Sun, 9 Mar 2025 01:58:46 +0800 Subject: [PATCH] Optimize dashboard performance Fix some issues --- lib/clash/core.dart | 4 +- lib/common/navigation.dart | 1 + lib/common/render.dart | 2 +- .../dashboard/widgets/memory_info.dart | 58 ++--- .../dashboard/widgets/traffic_usage.dart | 6 +- lib/fragments/profiles/custom_profile.dart | 54 ---- lib/fragments/profiles/gen_profile.dart | 87 +++++++ lib/fragments/profiles/profiles.dart | 19 +- lib/models/clash_config.dart | 13 +- .../generated/clash_config.freezed.dart | 240 ++++++++++++++++-- lib/models/generated/clash_config.g.dart | 25 +- lib/models/generated/selector.freezed.dart | 147 +++++++++++ lib/models/selector.dart | 22 +- lib/providers/generated/state.g.dart | 2 +- lib/providers/state.dart | 11 +- lib/widgets/donut_chart.dart | 12 + lib/widgets/wave.dart | 100 +++++--- macos/Runner/Info.plist | 2 - pubspec.yaml | 2 +- 19 files changed, 631 insertions(+), 176 deletions(-) delete mode 100644 lib/fragments/profiles/custom_profile.dart create mode 100644 lib/fragments/profiles/gen_profile.dart diff --git a/lib/clash/core.dart b/lib/clash/core.dart index f44ba03..7ea6269 100644 --- a/lib/clash/core.dart +++ b/lib/clash/core.dart @@ -235,12 +235,12 @@ class ClashCore { return int.parse(value); } - Future getProfile(String id) async { + Future getProfile(String id) async { final res = await clashInterface.getProfile(id); if (res.isEmpty) { return null; } - return ClashConfig.fromJson(json.decode(res)); + return ClashConfigSnippet.fromJson(json.decode(res)); } resetTraffic() { diff --git a/lib/common/navigation.dart b/lib/common/navigation.dart index 5d2ccab..efe512c 100644 --- a/lib/common/navigation.dart +++ b/lib/common/navigation.dart @@ -14,6 +14,7 @@ class Navigation { const NavigationItem( icon: Icon(Icons.space_dashboard), label: PageLabel.dashboard, + keep: false, fragment: DashboardFragment( key: GlobalObjectKey(PageLabel.dashboard), ), diff --git a/lib/common/render.dart b/lib/common/render.dart index ae48ac4..777ebbc 100644 --- a/lib/common/render.dart +++ b/lib/common/render.dart @@ -25,7 +25,7 @@ class Render { debouncer.call( DebounceTag.renderPause, _pause, - duration: Duration(seconds: 15), + duration: Duration(seconds: 5), ); } diff --git a/lib/fragments/dashboard/widgets/memory_info.dart b/lib/fragments/dashboard/widgets/memory_info.dart index 802bd61..ed239cd 100644 --- a/lib/fragments/dashboard/widgets/memory_info.dart +++ b/lib/fragments/dashboard/widgets/memory_info.dart @@ -62,12 +62,12 @@ class _MemoryInfoState extends State { onPressed: () { clashCore.requestGc(); }, - child: ValueListenableBuilder( - valueListenable: _memoryInfoStateNotifier, - builder: (_, trafficValue, __) { - return Column( - children: [ - Padding( + child: Column( + children: [ + ValueListenableBuilder( + valueListenable: _memoryInfoStateNotifier, + builder: (_, trafficValue, __) { + return Padding( padding: baseInfoEdgeInsets.copyWith( bottom: 0, top: 12, @@ -87,30 +87,30 @@ class _MemoryInfoState extends State { ) ], ), - ), - Flexible( - child: Stack( - children: [ - Positioned.fill( - child: WaveView( - waveAmplitude: 12.0, - waveFrequency: 0.35, - waveColor: darkenLighter, - ), - ), - Positioned.fill( - child: WaveView( - waveAmplitude: 12.0, - waveFrequency: 0.9, - waveColor: darken, - ), - ), - ], + ); + }, + ), + Flexible( + child: Stack( + children: [ + Positioned.fill( + child: WaveView( + waveAmplitude: 12.0, + waveFrequency: 0.35, + waveColor: darkenLighter, + ), ), - ) - ], - ); - }, + Positioned.fill( + child: WaveView( + waveAmplitude: 12.0, + waveFrequency: 0.9, + waveColor: darken, + ), + ), + ], + ), + ), + ], ), ), ); diff --git a/lib/fragments/dashboard/widgets/traffic_usage.dart b/lib/fragments/dashboard/widgets/traffic_usage.dart index 3633889..aee3be1 100644 --- a/lib/fragments/dashboard/widgets/traffic_usage.dart +++ b/lib/fragments/dashboard/widgets/traffic_usage.dart @@ -11,7 +11,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; class TrafficUsage extends StatelessWidget { const TrafficUsage({super.key}); - Widget getTrafficDataItem( + Widget _buildTrafficDataItem( BuildContext context, Icon icon, TrafficValue trafficValue, @@ -189,7 +189,7 @@ class TrafficUsage extends StatelessWidget { ), ), ), - getTrafficDataItem( + _buildTrafficDataItem( context, Icon( Icons.arrow_upward, @@ -201,7 +201,7 @@ class TrafficUsage extends StatelessWidget { const SizedBox( height: 8, ), - getTrafficDataItem( + _buildTrafficDataItem( context, Icon( Icons.arrow_downward, diff --git a/lib/fragments/profiles/custom_profile.dart b/lib/fragments/profiles/custom_profile.dart deleted file mode 100644 index 754fc93..0000000 --- a/lib/fragments/profiles/custom_profile.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:fl_clash/models/models.dart'; -import 'package:fl_clash/widgets/scaffold.dart'; -import 'package:flutter/material.dart'; - -class CustomProfile extends StatefulWidget { - final String profileId; - - const CustomProfile({ - super.key, - required this.profileId, - }); - - @override - State createState() => _CustomProfileState(); -} - -class _CustomProfileState extends State { - final _currentClashConfigNotifier = ValueNotifier(null); - - @override - void initState() { - super.initState(); - _initCurrentClashConfig(); - } - - _initCurrentClashConfig() async { - // final currentProfileId = globalState.config.currentProfileId; - // if (currentProfileId == null) { - // return; - // } - // _currentClashConfigNotifier.value = - // await clashCore.getProfile(currentProfileId); - } - - @override - Widget build(BuildContext context) { - return CommonScaffold( - body: ValueListenableBuilder( - valueListenable: _currentClashConfigNotifier, - builder: (_, clashConfig, ___) { - if (clashConfig == null) { - return Center( - child: CircularProgressIndicator(), - ); - } - return Column( - children: [], - ); - }, - ), - title: "自定义", - ); - } -} diff --git a/lib/fragments/profiles/gen_profile.dart b/lib/fragments/profiles/gen_profile.dart new file mode 100644 index 0000000..aa0d9c1 --- /dev/null +++ b/lib/fragments/profiles/gen_profile.dart @@ -0,0 +1,87 @@ +import 'package:fl_clash/clash/core.dart'; +import 'package:fl_clash/models/models.dart'; +import 'package:fl_clash/state.dart'; +import 'package:fl_clash/widgets/card.dart'; +import 'package:fl_clash/widgets/scaffold.dart'; +import 'package:flutter/material.dart'; + +class GenProfile extends StatefulWidget { + final String profileId; + + const GenProfile({ + super.key, + required this.profileId, + }); + + @override + State createState() => _GenProfileState(); +} + +class _GenProfileState extends State { + final _currentClashConfigNotifier = ValueNotifier(null); + + @override + void initState() { + super.initState(); + _initCurrentClashConfig(); + } + + _initCurrentClashConfig() async { + final currentProfileId = globalState.config.currentProfileId; + if (currentProfileId == null) { + return; + } + _currentClashConfigNotifier.value = + await clashCore.getProfile(currentProfileId); + } + + @override + Widget build(BuildContext context) { + return CommonScaffold( + body: ValueListenableBuilder( + valueListenable: _currentClashConfigNotifier, + builder: (_, clashConfig, ___) { + if (clashConfig == null) { + return Center( + child: CircularProgressIndicator(), + ); + } + return Padding( + padding: EdgeInsets.all(16), + child: CustomScrollView( + slivers: [ + SliverGrid.builder( + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 100, + mainAxisExtent: 50, + mainAxisSpacing: 8, + crossAxisSpacing: 8, + ), + itemCount: clashConfig.proxyGroups.length, + itemBuilder: (BuildContext context, int index) { + return CommonCard( + onPressed: () {}, + child: Text( + clashConfig.proxyGroups[index].name, + ), + ); + }, + ), + SliverList.builder( + itemBuilder: (BuildContext context, int index) { + final rule = clashConfig.rule[index]; + return Text( + rule, + ); + }, + itemCount: clashConfig.rule.length, + ) + ], + ), + ); + }, + ), + title: "自定义", + ); + } +} diff --git a/lib/fragments/profiles/profiles.dart b/lib/fragments/profiles/profiles.dart index 38cdbbc..7e0d8bf 100644 --- a/lib/fragments/profiles/profiles.dart +++ b/lib/fragments/profiles/profiles.dart @@ -11,6 +11,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'add_profile.dart'; +import 'gen_profile.dart'; class ProfilesFragment extends StatefulWidget { const ProfilesFragment({super.key}); @@ -273,14 +274,14 @@ class ProfileItem extends StatelessWidget { } } - // _handlePushCustomPage(BuildContext context, String id) { - // BaseNavigator.push( - // context, - // CustomProfile( - // profileId: id, - // ), - // ); - // } + _handlePushGenProfilePage(BuildContext context, String id) { + BaseNavigator.push( + context, + GenProfile( + profileId: id, + ), + ); + } @override Widget build(BuildContext context) { @@ -334,7 +335,7 @@ class ProfileItem extends StatelessWidget { // icon: Icons.extension_outlined, // label: "自定义", // onPressed: () { - // _handlePushCustomPage(context, profile.id); + // _handlePushGenProfilePage(context, profile.id); // }, // ), ActionItemData( diff --git a/lib/models/clash_config.dart b/lib/models/clash_config.dart index 3f9d967..c34795e 100644 --- a/lib/models/clash_config.dart +++ b/lib/models/clash_config.dart @@ -273,6 +273,17 @@ class GeoXUrl with _$GeoXUrl { } } +@freezed +class ClashConfigSnippet with _$ClashConfigSnippet { + const factory ClashConfigSnippet({ + @Default([]) @JsonKey(name: "proxy-groups") List proxyGroups, + @Default([]) List rule, + }) = _ClashConfigSnippet; + + factory ClashConfigSnippet.fromJson(Map json) => + _$ClashConfigSnippetFromJson(json); +} + @freezed class ClashConfig with _$ClashConfig { const factory ClashConfig({ @@ -301,7 +312,7 @@ class ClashConfig with _$ClashConfig { @JsonKey(name: "geodata-loader") GeodataLoader geodataLoader, @Default([]) @JsonKey(name: "proxy-groups") List proxyGroups, - @Default([]) List rules, + @Default([]) List rule, @JsonKey(name: "global-ua") String? globalUa, @Default(ExternalControllerStatus.close) @JsonKey(name: "external-controller") diff --git a/lib/models/generated/clash_config.freezed.dart b/lib/models/generated/clash_config.freezed.dart index 8f9232a..0a6a9b5 100644 --- a/lib/models/generated/clash_config.freezed.dart +++ b/lib/models/generated/clash_config.freezed.dart @@ -1834,6 +1834,202 @@ abstract class _GeoXUrl implements GeoXUrl { throw _privateConstructorUsedError; } +ClashConfigSnippet _$ClashConfigSnippetFromJson(Map json) { + return _ClashConfigSnippet.fromJson(json); +} + +/// @nodoc +mixin _$ClashConfigSnippet { + @JsonKey(name: "proxy-groups") + List get proxyGroups => throw _privateConstructorUsedError; + List get rule => throw _privateConstructorUsedError; + + /// Serializes this ClashConfigSnippet to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of ClashConfigSnippet + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $ClashConfigSnippetCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $ClashConfigSnippetCopyWith<$Res> { + factory $ClashConfigSnippetCopyWith( + ClashConfigSnippet value, $Res Function(ClashConfigSnippet) then) = + _$ClashConfigSnippetCopyWithImpl<$Res, ClashConfigSnippet>; + @useResult + $Res call( + {@JsonKey(name: "proxy-groups") List proxyGroups, + List rule}); +} + +/// @nodoc +class _$ClashConfigSnippetCopyWithImpl<$Res, $Val extends ClashConfigSnippet> + implements $ClashConfigSnippetCopyWith<$Res> { + _$ClashConfigSnippetCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of ClashConfigSnippet + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? proxyGroups = null, + Object? rule = null, + }) { + return _then(_value.copyWith( + proxyGroups: null == proxyGroups + ? _value.proxyGroups + : proxyGroups // ignore: cast_nullable_to_non_nullable + as List, + rule: null == rule + ? _value.rule + : rule // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$ClashConfigSnippetImplCopyWith<$Res> + implements $ClashConfigSnippetCopyWith<$Res> { + factory _$$ClashConfigSnippetImplCopyWith(_$ClashConfigSnippetImpl value, + $Res Function(_$ClashConfigSnippetImpl) then) = + __$$ClashConfigSnippetImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {@JsonKey(name: "proxy-groups") List proxyGroups, + List rule}); +} + +/// @nodoc +class __$$ClashConfigSnippetImplCopyWithImpl<$Res> + extends _$ClashConfigSnippetCopyWithImpl<$Res, _$ClashConfigSnippetImpl> + implements _$$ClashConfigSnippetImplCopyWith<$Res> { + __$$ClashConfigSnippetImplCopyWithImpl(_$ClashConfigSnippetImpl _value, + $Res Function(_$ClashConfigSnippetImpl) _then) + : super(_value, _then); + + /// Create a copy of ClashConfigSnippet + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? proxyGroups = null, + Object? rule = null, + }) { + return _then(_$ClashConfigSnippetImpl( + proxyGroups: null == proxyGroups + ? _value._proxyGroups + : proxyGroups // ignore: cast_nullable_to_non_nullable + as List, + rule: null == rule + ? _value._rule + : rule // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$ClashConfigSnippetImpl implements _ClashConfigSnippet { + const _$ClashConfigSnippetImpl( + {@JsonKey(name: "proxy-groups") + final List proxyGroups = const [], + final List rule = const []}) + : _proxyGroups = proxyGroups, + _rule = rule; + + factory _$ClashConfigSnippetImpl.fromJson(Map json) => + _$$ClashConfigSnippetImplFromJson(json); + + final List _proxyGroups; + @override + @JsonKey(name: "proxy-groups") + List get proxyGroups { + if (_proxyGroups is EqualUnmodifiableListView) return _proxyGroups; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_proxyGroups); + } + + final List _rule; + @override + @JsonKey() + List get rule { + if (_rule is EqualUnmodifiableListView) return _rule; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_rule); + } + + @override + String toString() { + return 'ClashConfigSnippet(proxyGroups: $proxyGroups, rule: $rule)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$ClashConfigSnippetImpl && + const DeepCollectionEquality() + .equals(other._proxyGroups, _proxyGroups) && + const DeepCollectionEquality().equals(other._rule, _rule)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_proxyGroups), + const DeepCollectionEquality().hash(_rule)); + + /// Create a copy of ClashConfigSnippet + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$ClashConfigSnippetImplCopyWith<_$ClashConfigSnippetImpl> get copyWith => + __$$ClashConfigSnippetImplCopyWithImpl<_$ClashConfigSnippetImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$ClashConfigSnippetImplToJson( + this, + ); + } +} + +abstract class _ClashConfigSnippet implements ClashConfigSnippet { + const factory _ClashConfigSnippet( + {@JsonKey(name: "proxy-groups") final List proxyGroups, + final List rule}) = _$ClashConfigSnippetImpl; + + factory _ClashConfigSnippet.fromJson(Map json) = + _$ClashConfigSnippetImpl.fromJson; + + @override + @JsonKey(name: "proxy-groups") + List get proxyGroups; + @override + List get rule; + + /// Create a copy of ClashConfigSnippet + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$ClashConfigSnippetImplCopyWith<_$ClashConfigSnippetImpl> get copyWith => + throw _privateConstructorUsedError; +} + ClashConfig _$ClashConfigFromJson(Map json) { return _ClashConfig.fromJson(json); } @@ -1866,7 +2062,7 @@ mixin _$ClashConfig { GeodataLoader get geodataLoader => throw _privateConstructorUsedError; @JsonKey(name: "proxy-groups") List get proxyGroups => throw _privateConstructorUsedError; - List get rules => throw _privateConstructorUsedError; + List get rule => throw _privateConstructorUsedError; @JsonKey(name: "global-ua") String? get globalUa => throw _privateConstructorUsedError; @JsonKey(name: "external-controller") @@ -1907,7 +2103,7 @@ abstract class $ClashConfigCopyWith<$Res> { GeoXUrl geoXUrl, @JsonKey(name: "geodata-loader") GeodataLoader geodataLoader, @JsonKey(name: "proxy-groups") List proxyGroups, - List rules, + List rule, @JsonKey(name: "global-ua") String? globalUa, @JsonKey(name: "external-controller") ExternalControllerStatus externalController, @@ -1947,7 +2143,7 @@ class _$ClashConfigCopyWithImpl<$Res, $Val extends ClashConfig> Object? geoXUrl = null, Object? geodataLoader = null, Object? proxyGroups = null, - Object? rules = null, + Object? rule = null, Object? globalUa = freezed, Object? externalController = null, Object? hosts = null, @@ -2009,9 +2205,9 @@ class _$ClashConfigCopyWithImpl<$Res, $Val extends ClashConfig> ? _value.proxyGroups : proxyGroups // ignore: cast_nullable_to_non_nullable as List, - rules: null == rules - ? _value.rules - : rules // ignore: cast_nullable_to_non_nullable + rule: null == rule + ? _value.rule + : rule // ignore: cast_nullable_to_non_nullable as List, globalUa: freezed == globalUa ? _value.globalUa @@ -2084,7 +2280,7 @@ abstract class _$$ClashConfigImplCopyWith<$Res> GeoXUrl geoXUrl, @JsonKey(name: "geodata-loader") GeodataLoader geodataLoader, @JsonKey(name: "proxy-groups") List proxyGroups, - List rules, + List rule, @JsonKey(name: "global-ua") String? globalUa, @JsonKey(name: "external-controller") ExternalControllerStatus externalController, @@ -2125,7 +2321,7 @@ class __$$ClashConfigImplCopyWithImpl<$Res> Object? geoXUrl = null, Object? geodataLoader = null, Object? proxyGroups = null, - Object? rules = null, + Object? rule = null, Object? globalUa = freezed, Object? externalController = null, Object? hosts = null, @@ -2187,9 +2383,9 @@ class __$$ClashConfigImplCopyWithImpl<$Res> ? _value._proxyGroups : proxyGroups // ignore: cast_nullable_to_non_nullable as List, - rules: null == rules - ? _value._rules - : rules // ignore: cast_nullable_to_non_nullable + rule: null == rule + ? _value._rule + : rule // ignore: cast_nullable_to_non_nullable as List, globalUa: freezed == globalUa ? _value.globalUa @@ -2230,13 +2426,13 @@ class _$ClashConfigImpl implements _ClashConfig { this.geodataLoader = GeodataLoader.memconservative, @JsonKey(name: "proxy-groups") final List proxyGroups = const [], - final List rules = const [], + final List rule = const [], @JsonKey(name: "global-ua") this.globalUa, @JsonKey(name: "external-controller") this.externalController = ExternalControllerStatus.close, final Map hosts = const {}}) : _proxyGroups = proxyGroups, - _rules = rules, + _rule = rule, _hosts = hosts; factory _$ClashConfigImpl.fromJson(Map json) => @@ -2290,13 +2486,13 @@ class _$ClashConfigImpl implements _ClashConfig { return EqualUnmodifiableListView(_proxyGroups); } - final List _rules; + final List _rule; @override @JsonKey() - List get rules { - if (_rules is EqualUnmodifiableListView) return _rules; + List get rule { + if (_rule is EqualUnmodifiableListView) return _rule; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_rules); + return EqualUnmodifiableListView(_rule); } @override @@ -2316,7 +2512,7 @@ class _$ClashConfigImpl implements _ClashConfig { @override String toString() { - return 'ClashConfig(mixedPort: $mixedPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, proxyGroups: $proxyGroups, rules: $rules, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)'; + return 'ClashConfig(mixedPort: $mixedPort, mode: $mode, allowLan: $allowLan, logLevel: $logLevel, ipv6: $ipv6, findProcessMode: $findProcessMode, keepAliveInterval: $keepAliveInterval, unifiedDelay: $unifiedDelay, tcpConcurrent: $tcpConcurrent, tun: $tun, dns: $dns, geoXUrl: $geoXUrl, geodataLoader: $geodataLoader, proxyGroups: $proxyGroups, rule: $rule, globalUa: $globalUa, externalController: $externalController, hosts: $hosts)'; } @override @@ -2347,7 +2543,7 @@ class _$ClashConfigImpl implements _ClashConfig { other.geodataLoader == geodataLoader) && const DeepCollectionEquality() .equals(other._proxyGroups, _proxyGroups) && - const DeepCollectionEquality().equals(other._rules, _rules) && + const DeepCollectionEquality().equals(other._rule, _rule) && (identical(other.globalUa, globalUa) || other.globalUa == globalUa) && (identical(other.externalController, externalController) || @@ -2373,7 +2569,7 @@ class _$ClashConfigImpl implements _ClashConfig { geoXUrl, geodataLoader, const DeepCollectionEquality().hash(_proxyGroups), - const DeepCollectionEquality().hash(_rules), + const DeepCollectionEquality().hash(_rule), globalUa, externalController, const DeepCollectionEquality().hash(_hosts)); @@ -2412,7 +2608,7 @@ abstract class _ClashConfig implements ClashConfig { final GeoXUrl geoXUrl, @JsonKey(name: "geodata-loader") final GeodataLoader geodataLoader, @JsonKey(name: "proxy-groups") final List proxyGroups, - final List rules, + final List rule, @JsonKey(name: "global-ua") final String? globalUa, @JsonKey(name: "external-controller") final ExternalControllerStatus externalController, @@ -2462,7 +2658,7 @@ abstract class _ClashConfig implements ClashConfig { @JsonKey(name: "proxy-groups") List get proxyGroups; @override - List get rules; + List get rule; @override @JsonKey(name: "global-ua") String? get globalUa; diff --git a/lib/models/generated/clash_config.g.dart b/lib/models/generated/clash_config.g.dart index 2be1de2..1d4ac49 100644 --- a/lib/models/generated/clash_config.g.dart +++ b/lib/models/generated/clash_config.g.dart @@ -206,6 +206,25 @@ Map _$$GeoXUrlImplToJson(_$GeoXUrlImpl instance) => 'geosite': instance.geosite, }; +_$ClashConfigSnippetImpl _$$ClashConfigSnippetImplFromJson( + Map json) => + _$ClashConfigSnippetImpl( + proxyGroups: (json['proxy-groups'] as List?) + ?.map((e) => ProxyGroup.fromJson(e as Map)) + .toList() ?? + const [], + rule: + (json['rule'] as List?)?.map((e) => e as String).toList() ?? + const [], + ); + +Map _$$ClashConfigSnippetImplToJson( + _$ClashConfigSnippetImpl instance) => + { + 'proxy-groups': instance.proxyGroups, + 'rule': instance.rule, + }; + _$ClashConfigImpl _$$ClashConfigImplFromJson(Map json) => _$ClashConfigImpl( mixedPort: (json['mixed-port'] as num?)?.toInt() ?? defaultMixedPort, @@ -238,8 +257,8 @@ _$ClashConfigImpl _$$ClashConfigImplFromJson(Map json) => ?.map((e) => ProxyGroup.fromJson(e as Map)) .toList() ?? const [], - rules: - (json['rules'] as List?)?.map((e) => e as String).toList() ?? + rule: + (json['rule'] as List?)?.map((e) => e as String).toList() ?? const [], globalUa: json['global-ua'] as String?, externalController: $enumDecodeNullable( @@ -267,7 +286,7 @@ Map _$$ClashConfigImplToJson(_$ClashConfigImpl instance) => 'geox-url': instance.geoXUrl, 'geodata-loader': _$GeodataLoaderEnumMap[instance.geodataLoader]!, 'proxy-groups': instance.proxyGroups, - 'rules': instance.rules, + 'rule': instance.rule, 'global-ua': instance.globalUa, 'external-controller': _$ExternalControllerStatusEnumMap[instance.externalController]!, diff --git a/lib/models/generated/selector.freezed.dart b/lib/models/generated/selector.freezed.dart index b57d750..8346e0e 100644 --- a/lib/models/generated/selector.freezed.dart +++ b/lib/models/generated/selector.freezed.dart @@ -14,6 +14,153 @@ T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); +/// @nodoc +mixin _$VM2 { + A get a => throw _privateConstructorUsedError; + B get b => throw _privateConstructorUsedError; + + /// Create a copy of VM2 + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $VM2CopyWith> get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $VM2CopyWith { + factory $VM2CopyWith(VM2 value, $Res Function(VM2) then) = + _$VM2CopyWithImpl>; + @useResult + $Res call({A a, B b}); +} + +/// @nodoc +class _$VM2CopyWithImpl> + implements $VM2CopyWith { + _$VM2CopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of VM2 + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? a = freezed, + Object? b = freezed, + }) { + return _then(_value.copyWith( + a: freezed == a + ? _value.a + : a // ignore: cast_nullable_to_non_nullable + as A, + b: freezed == b + ? _value.b + : b // ignore: cast_nullable_to_non_nullable + as B, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$VM2ImplCopyWith + implements $VM2CopyWith { + factory _$$VM2ImplCopyWith( + _$VM2Impl value, $Res Function(_$VM2Impl) then) = + __$$VM2ImplCopyWithImpl; + @override + @useResult + $Res call({A a, B b}); +} + +/// @nodoc +class __$$VM2ImplCopyWithImpl + extends _$VM2CopyWithImpl> + implements _$$VM2ImplCopyWith { + __$$VM2ImplCopyWithImpl( + _$VM2Impl _value, $Res Function(_$VM2Impl) _then) + : super(_value, _then); + + /// Create a copy of VM2 + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? a = freezed, + Object? b = freezed, + }) { + return _then(_$VM2Impl( + a: freezed == a + ? _value.a + : a // ignore: cast_nullable_to_non_nullable + as A, + b: freezed == b + ? _value.b + : b // ignore: cast_nullable_to_non_nullable + as B, + )); + } +} + +/// @nodoc + +class _$VM2Impl implements _VM2 { + const _$VM2Impl({required this.a, required this.b}); + + @override + final A a; + @override + final B b; + + @override + String toString() { + return 'VM2<$A, $B>(a: $a, b: $b)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$VM2Impl && + const DeepCollectionEquality().equals(other.a, a) && + const DeepCollectionEquality().equals(other.b, b)); + } + + @override + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(a), + const DeepCollectionEquality().hash(b)); + + /// Create a copy of VM2 + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$VM2ImplCopyWith> get copyWith => + __$$VM2ImplCopyWithImpl>(this, _$identity); +} + +abstract class _VM2 implements VM2 { + const factory _VM2({required final A a, required final B b}) = + _$VM2Impl; + + @override + A get a; + @override + B get b; + + /// Create a copy of VM2 + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$VM2ImplCopyWith> get copyWith => + throw _privateConstructorUsedError; +} + /// @nodoc mixin _$StartButtonSelectorState { bool get isInit => throw _privateConstructorUsedError; diff --git a/lib/models/selector.dart b/lib/models/selector.dart index c047e6e..c45f60a 100644 --- a/lib/models/selector.dart +++ b/lib/models/selector.dart @@ -7,6 +7,15 @@ import 'package:freezed_annotation/freezed_annotation.dart'; part 'generated/selector.freezed.dart'; +@freezed +class VM2 with _$VM2 { + const factory VM2({ + required A a, + required B b, + }) = _VM2; +} + + @freezed class StartButtonSelectorState with _$StartButtonSelectorState { const factory StartButtonSelectorState({ @@ -134,18 +143,19 @@ extension PackageListSelectorStateExt on PackageListSelectorState { return packages .where((item) => isFilterSystemApp ? item.isSystem == false : true) .sorted( - (a, b) { + (a, b) { return switch (sort) { AccessSortType.none => 0, - AccessSortType.name => other.sortByChar( - other.getPinyin(a.label), - other.getPinyin(b.label), - ), + AccessSortType.name => + other.sortByChar( + other.getPinyin(a.label), + other.getPinyin(b.label), + ), AccessSortType.time => b.lastUpdateTime.compareTo(a.lastUpdateTime), }; }, ).sorted( - (a, b) { + (a, b) { final isSelectA = selectedList.contains(a.packageName); final isSelectB = selectedList.contains(b.packageName); if (isSelectA && isSelectB) return 0; diff --git a/lib/providers/generated/state.g.dart b/lib/providers/generated/state.g.dart index 282e543..59f2273 100644 --- a/lib/providers/generated/state.g.dart +++ b/lib/providers/generated/state.g.dart @@ -95,7 +95,7 @@ final clashConfigStateProvider = AutoDisposeProvider.internal( @Deprecated('Will be removed in 3.0. Use Ref instead') // ignore: unused_element typedef ClashConfigStateRef = AutoDisposeProviderRef; -String _$proxyStateHash() => r'61ec20fcf35118aca445719c83e77e7d237f5570'; +String _$proxyStateHash() => r'22478fb593aaca11dfe2cf64472013190475a5bc'; /// See also [proxyState]. @ProviderFor(proxyState) diff --git a/lib/providers/state.dart b/lib/providers/state.dart index f96475a..baf96c6 100644 --- a/lib/providers/state.dart +++ b/lib/providers/state.dart @@ -81,14 +81,19 @@ ClashConfigState clashConfigState(Ref ref) { @riverpod ProxyState proxyState(Ref ref) { final isStart = ref.watch(runTimeProvider.select((state) => state != null)); - final networkProps = ref.watch(networkSettingProvider); + 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: networkProps.systemProxy, - bassDomain: networkProps.bypassDomain, + systemProxy: vm2.a, + bassDomain: vm2.b, port: mixedPort, ); } diff --git a/lib/widgets/donut_chart.dart b/lib/widgets/donut_chart.dart index a57959f..aa6793d 100644 --- a/lib/widgets/donut_chart.dart +++ b/lib/widgets/donut_chart.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:fl_clash/common/common.dart'; import 'package:flutter/material.dart'; @@ -18,6 +19,17 @@ class DonutChartData { String toString() { return 'DonutChartData{_value: $_value}'; } + + @override + bool operator ==(Object other) => + identical(this, other) || + other is DonutChartData && + runtimeType == other.runtimeType && + _value == other._value && + color == other.color; + + @override + int get hashCode => _value.hashCode ^ color.hashCode; } class DonutChart extends StatefulWidget { diff --git a/lib/widgets/wave.dart b/lib/widgets/wave.dart index e1ead3b..b4c9c3c 100644 --- a/lib/widgets/wave.dart +++ b/lib/widgets/wave.dart @@ -1,4 +1,5 @@ import 'dart:math'; + import 'package:flutter/material.dart'; class WaveView extends StatefulWidget { @@ -40,23 +41,25 @@ class _WaveViewState extends State @override Widget build(BuildContext context) { - return LayoutBuilder(builder: (_, container) { - return AnimatedBuilder( - animation: _controller, - builder: (context, child) { - return CustomPaint( - painter: WavePainter( - animationValue: _controller.value, - waveAmplitude: widget.waveAmplitude, - waveFrequency: widget.waveFrequency, - waveColor: widget.waveColor, - ), - size: Size( - container.maxHeight, - container.maxHeight, - ), - ); - }, + return LayoutBuilder(builder: (_, constraints) { + return RepaintBoundary( + child: AnimatedBuilder( + animation: _controller, + builder: (context, child) { + return CustomPaint( + painter: WavePainter( + animationValue: _controller.value, + waveAmplitude: widget.waveAmplitude, + waveFrequency: widget.waveFrequency, + waveColor: widget.waveColor, + ), + size: Size( + constraints.maxWidth, + constraints.maxHeight, + ), + ); + }, + ), ); }); } @@ -68,41 +71,60 @@ class WavePainter extends CustomPainter { final double waveFrequency; final Color waveColor; + late Paint _paint; + final Path _path = Path(); + Color _lastColor; + WavePainter({ required this.animationValue, required this.waveAmplitude, required this.waveFrequency, required this.waveColor, - }); + }) : _lastColor = waveColor { + _paint = Paint() + ..color = waveColor + ..style = PaintingStyle.fill; + } @override void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = waveColor - ..style = PaintingStyle.fill; - - final path = Path(); - - final baseHeight = size.height / 3; - - path.moveTo(0, baseHeight); - - for (double x = 0; x <= size.width; x++) { - final y = waveAmplitude * - sin((x / size.width * 2 * pi * waveFrequency) + - (animationValue * 2 * pi)); - path.lineTo(x, baseHeight + y); + if (waveColor != _lastColor) { + _paint = Paint() + ..color = waveColor + ..style = PaintingStyle.fill; + _lastColor = waveColor; } - path.lineTo(size.width, size.height); - path.lineTo(0, size.height); - path.close(); + _path.reset(); - canvas.drawPath(path, paint); + final baseHeight = size.height / 3; + final phase = animationValue * 2 * pi; + final widthFactor = 2 * pi * waveFrequency / size.width; + + _path.moveTo(0, baseHeight); + + for (double x = 0; x <= size.width; x += size.width / 20) { + final y = waveAmplitude * sin(x * widthFactor + phase); + _path.lineTo(x, baseHeight + y); + } + + _path.lineTo( + size.width, + baseHeight + waveAmplitude * sin(size.width * widthFactor + phase), + ); + + _path.lineTo(size.width, size.height); + _path.lineTo(0, size.height); + _path.close(); + + canvas.drawPath(_path, _paint); } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; + bool shouldRepaint(covariant WavePainter oldDelegate) { + return oldDelegate.animationValue != animationValue || + oldDelegate.waveAmplitude != waveAmplitude || + oldDelegate.waveFrequency != waveFrequency || + oldDelegate.waveColor != waveColor; } } diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index cfa1963..df57c8d 100755 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -43,7 +43,5 @@ MainMenu NSPrincipalClass NSApplication - FLTEnableImpeller - diff --git a/pubspec.yaml b/pubspec.yaml index e908d06..738c439 100755 --- 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.8.79+202503071 +version: 0.8.80+202503101 environment: sdk: '>=3.1.0 <4.0.0'