Optimize app layout

This commit is contained in:
chen08209
2026-08-05 10:56:40 +08:00
parent 425d32b50d
commit c493df386d
27 changed files with 3639 additions and 737 deletions
+64 -80
View File
@@ -4,6 +4,7 @@ import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:ffi/ffi.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/desktop/helper_client.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/state.dart';
@@ -13,6 +14,7 @@ import 'package:path/path.dart';
class System {
static System? _instance;
bool _isTV = false;
System._internal();
@@ -31,8 +33,19 @@ class System {
bool get isLinux => Platform.isLinux;
Future<int> get version async {
bool get isTV => _isTV;
Future<int> init() async {
final deviceInfo = await DeviceInfoPlugin().deviceInfo;
_isTV = switch (deviceInfo) {
AndroidDeviceInfo(:final systemFeatures) => systemFeatures.any(
const {
'android.hardware.type.television',
'android.software.leanback',
}.contains,
),
_ => false,
};
return switch (Platform.operatingSystem) {
'macos' => (deviceInfo as MacOsDeviceInfo).majorVersion,
'android' => (deviceInfo as AndroidDeviceInfo).version.sdkInt,
@@ -41,11 +54,15 @@ class System {
};
}
Future<bool> didCrashOnPreviousExecution() async {
if (!isAndroid) return false;
return await app?.didCrashOnPreviousExecution() ?? false;
}
Future<bool> checkIsAdmin() async {
final corePath = appPath.corePath.replaceAll(' ', '\\\\ ');
if (system.isWindows) {
final result = await windows?.checkService();
return result == WindowsHelperServiceStatus.running;
return windowsHelperClient.isReady();
} else if (system.isMacOS) {
final result = await Process.run('stat', ['-f', '%Su:%Sg %Sp', corePath]);
final output = result.stdout.trim();
@@ -72,19 +89,14 @@ class System {
if (system.isAndroid) {
return AuthorizeCode.error;
}
if (system.isWindows) {
return await windows?.registerService() ?? AuthorizeCode.error;
}
final isAdmin = await checkIsAdmin();
if (isAdmin) {
return AuthorizeCode.none;
}
if (system.isWindows) {
final result = await windows?.registerService();
if (result == true) {
return AuthorizeCode.success;
}
return AuthorizeCode.error;
}
if (system.isMacOS) {
final escapedPath = _shellEscape(appPath.corePath);
final shell = 'chown root:admin $escapedPath && chmod +sx $escapedPath';
@@ -134,7 +146,6 @@ class System {
if (system.isAndroid) {
await SystemNavigator.pop();
}
await window?.close();
window?.forceExit();
}
}
@@ -203,79 +214,52 @@ class Windows {
return true;
}
// Future<void> _killProcess(int port) async {
// final result = await Process.run('netstat', ['-ano']);
// final lines = result.stdout.toString().trim().split('\n');
// for (final line in lines) {
// if (!line.contains(':$port') || !line.contains('LISTENING')) {
// continue;
// }
// final parts = line.trim().split(RegExp(r'\s+'));
// final pid = int.tryParse(parts.last);
// if (pid != null) {
// await Process.run('taskkill', ['/PID', pid.toString(), '/F']);
// }
// }
// }
Future<AuthorizeCode> registerService() async {
if (await windowsHelperClient.isReady()) {
commonPrint.log('helper service is ready');
return AuthorizeCode.none;
}
Future<WindowsHelperServiceStatus> checkService() async {
// final qcResult = await Process.run('sc', ['qc', appHelperService]);
// final qcOutput = qcResult.stdout.toString();
// if (qcResult.exitCode != 0 || !qcOutput.contains(appPath.helperPath)) {
// return WindowsHelperServiceStatus.none;
// }
final result = await Process.run('sc', ['query', appHelperService]);
if (result.exitCode != 0) {
return WindowsHelperServiceStatus.none;
commonPrint.log(
'helper service is unavailable, requesting elevated installation',
logLevel: LogLevel.warning,
);
if (!runas(appPath.helperPath, 'install')) {
commonPrint.log(
'failed to launch elevated helper installation',
logLevel: LogLevel.error,
);
return AuthorizeCode.error;
}
final output = result.stdout.toString();
if (output.contains('RUNNING') && await request.pingHelper()) {
return WindowsHelperServiceStatus.running;
}
return WindowsHelperServiceStatus.presence;
final isRunning = await _waitForHelperService();
commonPrint.log(
isRunning
? 'helper service installation completed'
: 'helper service did not become ready after installation',
logLevel: isRunning ? LogLevel.info : LogLevel.error,
);
return isRunning ? AuthorizeCode.success : AuthorizeCode.error;
}
Future<bool> registerService() async {
final status = await checkService();
if (status == WindowsHelperServiceStatus.running) {
return true;
Future<bool> _waitForHelperService() async {
const timeout = Duration(seconds: 6);
const interval = Duration(seconds: 1);
const maxAttempts = 6;
final stopwatch = Stopwatch()..start();
for (var attempt = 0; attempt < maxAttempts; attempt++) {
final remaining = timeout - stopwatch.elapsed;
if (remaining <= Duration.zero) return false;
final isRunning = await windowsHelperClient.isReady(
timeout: remaining,
logFailure: false,
);
if (isRunning) return true;
final delay = timeout - stopwatch.elapsed;
if (delay <= Duration.zero || attempt == maxAttempts - 1) return false;
await Future.delayed(delay < interval ? delay : interval);
}
final command = [
'/c',
if (status == WindowsHelperServiceStatus.presence) ...[
'taskkill',
'/F',
'/IM',
'$appHelperService.exe'
' & '
'sc',
'delete',
appHelperService,
'&',
],
'sc',
'create',
appHelperService,
'binPath= "${appPath.helperPath}"',
'start= auto',
'&&',
'sc',
'start',
appHelperService,
].join(' ');
final res = runas('cmd.exe', command);
await Future.delayed(const Duration(milliseconds: 300));
final retryStatus = await retry(
task: checkService,
maxAttempts: 5,
retryIf: (status) => status != WindowsHelperServiceStatus.running,
delay: const Duration(seconds: 1),
);
return res && retryStatus == WindowsHelperServiceStatus.running;
return false;
}
Future<bool> registerTask(String appName) async {
+1 -1
View File
@@ -16,7 +16,7 @@ Future<void> main() async {
if (system.isDesktop) {
await RustLib.init();
}
final version = await system.version;
final version = await system.init();
final container = await globalState.init(version);
HttpOverrides.global = FlClashHttpOverrides();
runApp(
+100 -115
View File
@@ -3520,7 +3520,7 @@ as bool,
/// @nodoc
mixin _$ProfilesState {
List<Profile> get profiles; int? get currentProfileId; int get columns;
List<Profile> get profiles; int? get currentProfileId;
/// Create a copy of ProfilesState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -3531,16 +3531,16 @@ $ProfilesStateCopyWith<ProfilesState> get copyWith => _$ProfilesStateCopyWithImp
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfilesState&&const DeepCollectionEquality().equals(other.profiles, profiles)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProfilesState&&const DeepCollectionEquality().equals(other.profiles, profiles)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(profiles),currentProfileId,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(profiles),currentProfileId);
@override
String toString() {
return 'ProfilesState(profiles: $profiles, currentProfileId: $currentProfileId, columns: $columns)';
return 'ProfilesState(profiles: $profiles, currentProfileId: $currentProfileId)';
}
@@ -3551,7 +3551,7 @@ abstract mixin class $ProfilesStateCopyWith<$Res> {
factory $ProfilesStateCopyWith(ProfilesState value, $Res Function(ProfilesState) _then) = _$ProfilesStateCopyWithImpl;
@useResult
$Res call({
List<Profile> profiles, int? currentProfileId, int columns
List<Profile> profiles, int? currentProfileId
});
@@ -3568,12 +3568,11 @@ class _$ProfilesStateCopyWithImpl<$Res>
/// Create a copy of ProfilesState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? profiles = null,Object? currentProfileId = freezed,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? profiles = null,Object? currentProfileId = freezed,}) {
return _then(_self.copyWith(
profiles: null == profiles ? _self.profiles : profiles // ignore: cast_nullable_to_non_nullable
as List<Profile>,currentProfileId: freezed == currentProfileId ? _self.currentProfileId : currentProfileId // ignore: cast_nullable_to_non_nullable
as int?,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as int?,
));
}
@@ -3658,10 +3657,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Profile> profiles, int? currentProfileId, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Profile> profiles, int? currentProfileId)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProfilesState() when $default != null:
return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
return $default(_that.profiles,_that.currentProfileId);case _:
return orElse();
}
@@ -3679,10 +3678,10 @@ return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Profile> profiles, int? currentProfileId, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Profile> profiles, int? currentProfileId) $default,) {final _that = this;
switch (_that) {
case _ProfilesState():
return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
return $default(_that.profiles,_that.currentProfileId);case _:
throw StateError('Unexpected subclass');
}
@@ -3699,10 +3698,10 @@ return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Profile> profiles, int? currentProfileId, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Profile> profiles, int? currentProfileId)? $default,) {final _that = this;
switch (_that) {
case _ProfilesState() when $default != null:
return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
return $default(_that.profiles,_that.currentProfileId);case _:
return null;
}
@@ -3714,7 +3713,7 @@ return $default(_that.profiles,_that.currentProfileId,_that.columns);case _:
class _ProfilesState implements ProfilesState {
const _ProfilesState({required final List<Profile> profiles, required this.currentProfileId, required this.columns}): _profiles = profiles;
const _ProfilesState({required final List<Profile> profiles, required this.currentProfileId}): _profiles = profiles;
final List<Profile> _profiles;
@@ -3725,7 +3724,6 @@ class _ProfilesState implements ProfilesState {
}
@override final int? currentProfileId;
@override final int columns;
/// Create a copy of ProfilesState
/// with the given fields replaced by the non-null parameter values.
@@ -3737,16 +3735,16 @@ _$ProfilesStateCopyWith<_ProfilesState> get copyWith => __$ProfilesStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfilesState&&const DeepCollectionEquality().equals(other._profiles, _profiles)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProfilesState&&const DeepCollectionEquality().equals(other._profiles, _profiles)&&(identical(other.currentProfileId, currentProfileId) || other.currentProfileId == currentProfileId));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_profiles),currentProfileId,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_profiles),currentProfileId);
@override
String toString() {
return 'ProfilesState(profiles: $profiles, currentProfileId: $currentProfileId, columns: $columns)';
return 'ProfilesState(profiles: $profiles, currentProfileId: $currentProfileId)';
}
@@ -3757,7 +3755,7 @@ abstract mixin class _$ProfilesStateCopyWith<$Res> implements $ProfilesStateCopy
factory _$ProfilesStateCopyWith(_ProfilesState value, $Res Function(_ProfilesState) _then) = __$ProfilesStateCopyWithImpl;
@override @useResult
$Res call({
List<Profile> profiles, int? currentProfileId, int columns
List<Profile> profiles, int? currentProfileId
});
@@ -3774,12 +3772,11 @@ class __$ProfilesStateCopyWithImpl<$Res>
/// Create a copy of ProfilesState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? profiles = null,Object? currentProfileId = freezed,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? profiles = null,Object? currentProfileId = freezed,}) {
return _then(_ProfilesState(
profiles: null == profiles ? _self._profiles : profiles // ignore: cast_nullable_to_non_nullable
as List<Profile>,currentProfileId: freezed == currentProfileId ? _self.currentProfileId : currentProfileId // ignore: cast_nullable_to_non_nullable
as int?,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as int?,
));
}
@@ -5451,7 +5448,7 @@ as List<NavigationItem>,
/// @nodoc
mixin _$ProxiesListState {
List<Group> get groups; Set<String> get currentUnfoldSet; ProxyCardType get proxyCardType; int get columns;
List<Group> get groups; Set<String> get currentUnfoldSet; ProxyCardType get proxyCardType;
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -5462,16 +5459,16 @@ $ProxiesListStateCopyWith<ProxiesListState> get copyWith => _$ProxiesListStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesListState&&const DeepCollectionEquality().equals(other.groups, groups)&&const DeepCollectionEquality().equals(other.currentUnfoldSet, currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxyCardType,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),const DeepCollectionEquality().hash(currentUnfoldSet),proxyCardType);
@override
String toString() {
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType)';
}
@@ -5482,7 +5479,7 @@ abstract mixin class $ProxiesListStateCopyWith<$Res> {
factory $ProxiesListStateCopyWith(ProxiesListState value, $Res Function(ProxiesListState) _then) = _$ProxiesListStateCopyWithImpl;
@useResult
$Res call({
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType
});
@@ -5499,13 +5496,12 @@ class _$ProxiesListStateCopyWithImpl<$Res>
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,}) {
return _then(_self.copyWith(
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self.currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as ProxyCardType,
));
}
@@ -5590,10 +5586,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxiesListState() when $default != null:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType);case _:
return orElse();
}
@@ -5611,10 +5607,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.co
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType) $default,) {final _that = this;
switch (_that) {
case _ProxiesListState():
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType);case _:
throw StateError('Unexpected subclass');
}
@@ -5631,10 +5627,10 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.co
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType)? $default,) {final _that = this;
switch (_that) {
case _ProxiesListState() when $default != null:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType);case _:
return null;
}
@@ -5646,7 +5642,7 @@ return $default(_that.groups,_that.currentUnfoldSet,_that.proxyCardType,_that.co
class _ProxiesListState implements ProxiesListState {
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxyCardType, required this.columns}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
const _ProxiesListState({required final List<Group> groups, required final Set<String> currentUnfoldSet, required this.proxyCardType}): _groups = groups,_currentUnfoldSet = currentUnfoldSet;
final List<Group> _groups;
@@ -5664,7 +5660,6 @@ class _ProxiesListState implements ProxiesListState {
}
@override final ProxyCardType proxyCardType;
@override final int columns;
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@@ -5676,16 +5671,16 @@ _$ProxiesListStateCopyWith<_ProxiesListState> get copyWith => __$ProxiesListStat
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesListState&&const DeepCollectionEquality().equals(other._groups, _groups)&&const DeepCollectionEquality().equals(other._currentUnfoldSet, _currentUnfoldSet)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxyCardType,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),const DeepCollectionEquality().hash(_currentUnfoldSet),proxyCardType);
@override
String toString() {
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType, columns: $columns)';
return 'ProxiesListState(groups: $groups, currentUnfoldSet: $currentUnfoldSet, proxyCardType: $proxyCardType)';
}
@@ -5696,7 +5691,7 @@ abstract mixin class _$ProxiesListStateCopyWith<$Res> implements $ProxiesListSta
factory _$ProxiesListStateCopyWith(_ProxiesListState value, $Res Function(_ProxiesListState) _then) = __$ProxiesListStateCopyWithImpl;
@override @useResult
$Res call({
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType, int columns
List<Group> groups, Set<String> currentUnfoldSet, ProxyCardType proxyCardType
});
@@ -5713,13 +5708,12 @@ class __$ProxiesListStateCopyWithImpl<$Res>
/// Create a copy of ProxiesListState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentUnfoldSet = null,Object? proxyCardType = null,}) {
return _then(_ProxiesListState(
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentUnfoldSet: null == currentUnfoldSet ? _self._currentUnfoldSet : currentUnfoldSet // ignore: cast_nullable_to_non_nullable
as Set<String>,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as ProxyCardType,
));
}
@@ -5729,7 +5723,7 @@ as int,
/// @nodoc
mixin _$ProxiesTabState {
List<Group> get groups; String? get currentGroupName; ProxyCardType get proxyCardType; int get columns;
List<Group> get groups; String? get currentGroupName; ProxyCardType get proxyCardType;
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -5740,16 +5734,16 @@ $ProxiesTabStateCopyWith<ProxiesTabState> get copyWith => _$ProxiesTabStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxiesTabState&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxyCardType,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(groups),currentGroupName,proxyCardType);
@override
String toString() {
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType)';
}
@@ -5760,7 +5754,7 @@ abstract mixin class $ProxiesTabStateCopyWith<$Res> {
factory $ProxiesTabStateCopyWith(ProxiesTabState value, $Res Function(ProxiesTabState) _then) = _$ProxiesTabStateCopyWithImpl;
@useResult
$Res call({
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType
});
@@ -5777,13 +5771,12 @@ class _$ProxiesTabStateCopyWithImpl<$Res>
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,}) {
return _then(_self.copyWith(
groups: null == groups ? _self.groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as ProxyCardType,
));
}
@@ -5868,10 +5861,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxiesTabState() when $default != null:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType);case _:
return orElse();
}
@@ -5889,10 +5882,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.co
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType) $default,) {final _that = this;
switch (_that) {
case _ProxiesTabState():
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType);case _:
throw StateError('Unexpected subclass');
}
@@ -5909,10 +5902,10 @@ return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.co
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType)? $default,) {final _that = this;
switch (_that) {
case _ProxiesTabState() when $default != null:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.columns);case _:
return $default(_that.groups,_that.currentGroupName,_that.proxyCardType);case _:
return null;
}
@@ -5924,7 +5917,7 @@ return $default(_that.groups,_that.currentGroupName,_that.proxyCardType,_that.co
class _ProxiesTabState implements ProxiesTabState {
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxyCardType, required this.columns}): _groups = groups;
const _ProxiesTabState({required final List<Group> groups, required this.currentGroupName, required this.proxyCardType}): _groups = groups;
final List<Group> _groups;
@@ -5936,7 +5929,6 @@ class _ProxiesTabState implements ProxiesTabState {
@override final String? currentGroupName;
@override final ProxyCardType proxyCardType;
@override final int columns;
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@@ -5948,16 +5940,16 @@ _$ProxiesTabStateCopyWith<_ProxiesTabState> get copyWith => __$ProxiesTabStateCo
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxiesTabState&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.currentGroupName, currentGroupName) || other.currentGroupName == currentGroupName)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxyCardType,columns);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_groups),currentGroupName,proxyCardType);
@override
String toString() {
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType, columns: $columns)';
return 'ProxiesTabState(groups: $groups, currentGroupName: $currentGroupName, proxyCardType: $proxyCardType)';
}
@@ -5968,7 +5960,7 @@ abstract mixin class _$ProxiesTabStateCopyWith<$Res> implements $ProxiesTabState
factory _$ProxiesTabStateCopyWith(_ProxiesTabState value, $Res Function(_ProxiesTabState) _then) = __$ProxiesTabStateCopyWithImpl;
@override @useResult
$Res call({
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType, int columns
List<Group> groups, String? currentGroupName, ProxyCardType proxyCardType
});
@@ -5985,13 +5977,12 @@ class __$ProxiesTabStateCopyWithImpl<$Res>
/// Create a copy of ProxiesTabState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? groups = null,Object? currentGroupName = freezed,Object? proxyCardType = null,}) {
return _then(_ProxiesTabState(
groups: null == groups ? _self._groups : groups // ignore: cast_nullable_to_non_nullable
as List<Group>,currentGroupName: freezed == currentGroupName ? _self.currentGroupName : currentGroupName // ignore: cast_nullable_to_non_nullable
as String?,proxyCardType: null == proxyCardType ? _self.proxyCardType : proxyCardType // ignore: cast_nullable_to_non_nullable
as ProxyCardType,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as ProxyCardType,
));
}
@@ -6001,7 +5992,7 @@ as int,
/// @nodoc
mixin _$ProxyGroupSelectorState {
String? get testUrl; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; GroupType get groupType; List<Proxy> get proxies; int get columns;
String? get testUrl; ProxiesSortType get proxiesSortType; ProxyCardType get proxyCardType; num get sortNum; GroupType get groupType; List<Proxy> get proxies;
/// Create a copy of ProxyGroupSelectorState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -6012,16 +6003,16 @@ $ProxyGroupSelectorStateCopyWith<ProxyGroupSelectorState> get copyWith => _$Prox
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxyGroupSelectorState&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.groupType, groupType) || other.groupType == groupType)&&const DeepCollectionEquality().equals(other.proxies, proxies)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is ProxyGroupSelectorState&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.groupType, groupType) || other.groupType == groupType)&&const DeepCollectionEquality().equals(other.proxies, proxies));
}
@override
int get hashCode => Object.hash(runtimeType,testUrl,proxiesSortType,proxyCardType,sortNum,groupType,const DeepCollectionEquality().hash(proxies),columns);
int get hashCode => Object.hash(runtimeType,testUrl,proxiesSortType,proxyCardType,sortNum,groupType,const DeepCollectionEquality().hash(proxies));
@override
String toString() {
return 'ProxyGroupSelectorState(testUrl: $testUrl, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, groupType: $groupType, proxies: $proxies, columns: $columns)';
return 'ProxyGroupSelectorState(testUrl: $testUrl, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, groupType: $groupType, proxies: $proxies)';
}
@@ -6032,7 +6023,7 @@ abstract mixin class $ProxyGroupSelectorStateCopyWith<$Res> {
factory $ProxyGroupSelectorStateCopyWith(ProxyGroupSelectorState value, $Res Function(ProxyGroupSelectorState) _then) = _$ProxyGroupSelectorStateCopyWithImpl;
@useResult
$Res call({
String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies, int columns
String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies
});
@@ -6049,7 +6040,7 @@ class _$ProxyGroupSelectorStateCopyWithImpl<$Res>
/// Create a copy of ProxyGroupSelectorState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? testUrl = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? groupType = null,Object? proxies = null,Object? columns = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? testUrl = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? groupType = null,Object? proxies = null,}) {
return _then(_self.copyWith(
testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
@@ -6057,8 +6048,7 @@ as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType :
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,groupType: null == groupType ? _self.groupType : groupType // ignore: cast_nullable_to_non_nullable
as GroupType,proxies: null == proxies ? _self.proxies : proxies // ignore: cast_nullable_to_non_nullable
as List<Proxy>,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as List<Proxy>,
));
}
@@ -6143,10 +6133,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies, int columns)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _ProxyGroupSelectorState() when $default != null:
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies,_that.columns);case _:
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies);case _:
return orElse();
}
@@ -6164,10 +6154,10 @@ return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.so
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies, int columns) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies) $default,) {final _that = this;
switch (_that) {
case _ProxyGroupSelectorState():
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies,_that.columns);case _:
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies);case _:
throw StateError('Unexpected subclass');
}
@@ -6184,10 +6174,10 @@ return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.so
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies, int columns)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies)? $default,) {final _that = this;
switch (_that) {
case _ProxyGroupSelectorState() when $default != null:
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies,_that.columns);case _:
return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.sortNum,_that.groupType,_that.proxies);case _:
return null;
}
@@ -6199,7 +6189,7 @@ return $default(_that.testUrl,_that.proxiesSortType,_that.proxyCardType,_that.so
class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
const _ProxyGroupSelectorState({required this.testUrl, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.groupType, required final List<Proxy> proxies, required this.columns}): _proxies = proxies;
const _ProxyGroupSelectorState({required this.testUrl, required this.proxiesSortType, required this.proxyCardType, required this.sortNum, required this.groupType, required final List<Proxy> proxies}): _proxies = proxies;
@override final String? testUrl;
@@ -6214,7 +6204,6 @@ class _ProxyGroupSelectorState implements ProxyGroupSelectorState {
return EqualUnmodifiableListView(_proxies);
}
@override final int columns;
/// Create a copy of ProxyGroupSelectorState
/// with the given fields replaced by the non-null parameter values.
@@ -6226,16 +6215,16 @@ _$ProxyGroupSelectorStateCopyWith<_ProxyGroupSelectorState> get copyWith => __$P
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxyGroupSelectorState&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.groupType, groupType) || other.groupType == groupType)&&const DeepCollectionEquality().equals(other._proxies, _proxies)&&(identical(other.columns, columns) || other.columns == columns));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ProxyGroupSelectorState&&(identical(other.testUrl, testUrl) || other.testUrl == testUrl)&&(identical(other.proxiesSortType, proxiesSortType) || other.proxiesSortType == proxiesSortType)&&(identical(other.proxyCardType, proxyCardType) || other.proxyCardType == proxyCardType)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.groupType, groupType) || other.groupType == groupType)&&const DeepCollectionEquality().equals(other._proxies, _proxies));
}
@override
int get hashCode => Object.hash(runtimeType,testUrl,proxiesSortType,proxyCardType,sortNum,groupType,const DeepCollectionEquality().hash(_proxies),columns);
int get hashCode => Object.hash(runtimeType,testUrl,proxiesSortType,proxyCardType,sortNum,groupType,const DeepCollectionEquality().hash(_proxies));
@override
String toString() {
return 'ProxyGroupSelectorState(testUrl: $testUrl, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, groupType: $groupType, proxies: $proxies, columns: $columns)';
return 'ProxyGroupSelectorState(testUrl: $testUrl, proxiesSortType: $proxiesSortType, proxyCardType: $proxyCardType, sortNum: $sortNum, groupType: $groupType, proxies: $proxies)';
}
@@ -6246,7 +6235,7 @@ abstract mixin class _$ProxyGroupSelectorStateCopyWith<$Res> implements $ProxyGr
factory _$ProxyGroupSelectorStateCopyWith(_ProxyGroupSelectorState value, $Res Function(_ProxyGroupSelectorState) _then) = __$ProxyGroupSelectorStateCopyWithImpl;
@override @useResult
$Res call({
String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies, int columns
String? testUrl, ProxiesSortType proxiesSortType, ProxyCardType proxyCardType, num sortNum, GroupType groupType, List<Proxy> proxies
});
@@ -6263,7 +6252,7 @@ class __$ProxyGroupSelectorStateCopyWithImpl<$Res>
/// Create a copy of ProxyGroupSelectorState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? testUrl = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? groupType = null,Object? proxies = null,Object? columns = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? testUrl = freezed,Object? proxiesSortType = null,Object? proxyCardType = null,Object? sortNum = null,Object? groupType = null,Object? proxies = null,}) {
return _then(_ProxyGroupSelectorState(
testUrl: freezed == testUrl ? _self.testUrl : testUrl // ignore: cast_nullable_to_non_nullable
as String?,proxiesSortType: null == proxiesSortType ? _self.proxiesSortType : proxiesSortType // ignore: cast_nullable_to_non_nullable
@@ -6271,8 +6260,7 @@ as ProxiesSortType,proxyCardType: null == proxyCardType ? _self.proxyCardType :
as ProxyCardType,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
as num,groupType: null == groupType ? _self.groupType : groupType // ignore: cast_nullable_to_non_nullable
as GroupType,proxies: null == proxies ? _self._proxies : proxies // ignore: cast_nullable_to_non_nullable
as List<Proxy>,columns: null == columns ? _self.columns : columns // ignore: cast_nullable_to_non_nullable
as int,
as List<Proxy>,
));
}
@@ -7624,7 +7612,7 @@ as int,
/// @nodoc
mixin _$DashboardState {
List<DashboardWidget> get dashboardWidgets; double get contentWidth;
List<DashboardWidget> get dashboardWidgets;
/// Create a copy of DashboardState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -7635,16 +7623,16 @@ $DashboardStateCopyWith<DashboardState> get copyWith => _$DashboardStateCopyWith
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is DashboardState&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets)&&(identical(other.contentWidth, contentWidth) || other.contentWidth == contentWidth));
return identical(this, other) || (other.runtimeType == runtimeType&&other is DashboardState&&const DeepCollectionEquality().equals(other.dashboardWidgets, dashboardWidgets));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(dashboardWidgets),contentWidth);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(dashboardWidgets));
@override
String toString() {
return 'DashboardState(dashboardWidgets: $dashboardWidgets, contentWidth: $contentWidth)';
return 'DashboardState(dashboardWidgets: $dashboardWidgets)';
}
@@ -7655,7 +7643,7 @@ abstract mixin class $DashboardStateCopyWith<$Res> {
factory $DashboardStateCopyWith(DashboardState value, $Res Function(DashboardState) _then) = _$DashboardStateCopyWithImpl;
@useResult
$Res call({
List<DashboardWidget> dashboardWidgets, double contentWidth
List<DashboardWidget> dashboardWidgets
});
@@ -7672,11 +7660,10 @@ class _$DashboardStateCopyWithImpl<$Res>
/// Create a copy of DashboardState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? dashboardWidgets = null,Object? contentWidth = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? dashboardWidgets = null,}) {
return _then(_self.copyWith(
dashboardWidgets: null == dashboardWidgets ? _self.dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
as List<DashboardWidget>,contentWidth: null == contentWidth ? _self.contentWidth : contentWidth // ignore: cast_nullable_to_non_nullable
as double,
as List<DashboardWidget>,
));
}
@@ -7761,10 +7748,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<DashboardWidget> dashboardWidgets, double contentWidth)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<DashboardWidget> dashboardWidgets)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _DashboardState() when $default != null:
return $default(_that.dashboardWidgets,_that.contentWidth);case _:
return $default(_that.dashboardWidgets);case _:
return orElse();
}
@@ -7782,10 +7769,10 @@ return $default(_that.dashboardWidgets,_that.contentWidth);case _:
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<DashboardWidget> dashboardWidgets, double contentWidth) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<DashboardWidget> dashboardWidgets) $default,) {final _that = this;
switch (_that) {
case _DashboardState():
return $default(_that.dashboardWidgets,_that.contentWidth);case _:
return $default(_that.dashboardWidgets);case _:
throw StateError('Unexpected subclass');
}
@@ -7802,10 +7789,10 @@ return $default(_that.dashboardWidgets,_that.contentWidth);case _:
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<DashboardWidget> dashboardWidgets, double contentWidth)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<DashboardWidget> dashboardWidgets)? $default,) {final _that = this;
switch (_that) {
case _DashboardState() when $default != null:
return $default(_that.dashboardWidgets,_that.contentWidth);case _:
return $default(_that.dashboardWidgets);case _:
return null;
}
@@ -7817,7 +7804,7 @@ return $default(_that.dashboardWidgets,_that.contentWidth);case _:
class _DashboardState implements DashboardState {
const _DashboardState({required final List<DashboardWidget> dashboardWidgets, required this.contentWidth}): _dashboardWidgets = dashboardWidgets;
const _DashboardState({required final List<DashboardWidget> dashboardWidgets}): _dashboardWidgets = dashboardWidgets;
final List<DashboardWidget> _dashboardWidgets;
@@ -7827,7 +7814,6 @@ class _DashboardState implements DashboardState {
return EqualUnmodifiableListView(_dashboardWidgets);
}
@override final double contentWidth;
/// Create a copy of DashboardState
/// with the given fields replaced by the non-null parameter values.
@@ -7839,16 +7825,16 @@ _$DashboardStateCopyWith<_DashboardState> get copyWith => __$DashboardStateCopyW
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DashboardState&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets)&&(identical(other.contentWidth, contentWidth) || other.contentWidth == contentWidth));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _DashboardState&&const DeepCollectionEquality().equals(other._dashboardWidgets, _dashboardWidgets));
}
@override
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_dashboardWidgets),contentWidth);
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_dashboardWidgets));
@override
String toString() {
return 'DashboardState(dashboardWidgets: $dashboardWidgets, contentWidth: $contentWidth)';
return 'DashboardState(dashboardWidgets: $dashboardWidgets)';
}
@@ -7859,7 +7845,7 @@ abstract mixin class _$DashboardStateCopyWith<$Res> implements $DashboardStateCo
factory _$DashboardStateCopyWith(_DashboardState value, $Res Function(_DashboardState) _then) = __$DashboardStateCopyWithImpl;
@override @useResult
$Res call({
List<DashboardWidget> dashboardWidgets, double contentWidth
List<DashboardWidget> dashboardWidgets
});
@@ -7876,11 +7862,10 @@ class __$DashboardStateCopyWithImpl<$Res>
/// Create a copy of DashboardState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? dashboardWidgets = null,Object? contentWidth = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? dashboardWidgets = null,}) {
return _then(_DashboardState(
dashboardWidgets: null == dashboardWidgets ? _self._dashboardWidgets : dashboardWidgets // ignore: cast_nullable_to_non_nullable
as List<DashboardWidget>,contentWidth: null == contentWidth ? _self.contentWidth : contentWidth // ignore: cast_nullable_to_non_nullable
as double,
as List<DashboardWidget>,
));
}
-5
View File
@@ -108,7 +108,6 @@ abstract class ProfilesState with _$ProfilesState {
const factory ProfilesState({
required List<Profile> profiles,
required int? currentProfileId,
required int columns,
}) = _ProfilesState;
}
@@ -173,7 +172,6 @@ abstract class ProxiesListState with _$ProxiesListState {
required List<Group> groups,
required Set<String> currentUnfoldSet,
required ProxyCardType proxyCardType,
required int columns,
}) = _ProxiesListState;
}
@@ -183,7 +181,6 @@ abstract class ProxiesTabState with _$ProxiesTabState {
required List<Group> groups,
required String? currentGroupName,
required ProxyCardType proxyCardType,
required int columns,
}) = _ProxiesTabState;
}
@@ -196,7 +193,6 @@ abstract class ProxyGroupSelectorState with _$ProxyGroupSelectorState {
required num sortNum,
required GroupType groupType,
required List<Proxy> proxies,
required int columns,
}) = _ProxyGroupSelectorState;
}
@@ -279,7 +275,6 @@ abstract class ProxyState with _$ProxyState {
abstract class DashboardState with _$DashboardState {
const factory DashboardState({
required List<DashboardWidget> dashboardWidgets,
required double contentWidth,
}) = _DashboardState;
}
+43 -17
View File
@@ -49,21 +49,25 @@ class HomePage extends StatelessWidget {
selectedIndex: currentIndex,
),
);
if (isMobile) {
return Column(
children: [
Flexible(
flex: 1,
return Column(
children: [
Flexible(
flex: 1,
child: FocusTraversalGroup(
policy: PageTraversalPolicy(),
child: MediaQuery.removePadding(
removeTop: false,
removeBottom: true,
removeLeft: true,
removeRight: true,
removeBottom: isMobile,
removeLeft: isMobile,
removeRight: isMobile,
context: context,
child: child!,
),
),
MediaQuery.removePadding(
),
AnimatedVisibility.bottomNavigation(
visible: isMobile,
child: MediaQuery.removePadding(
removeTop: true,
removeBottom: false,
removeLeft: true,
@@ -71,11 +75,9 @@ class HomePage extends StatelessWidget {
context: context,
child: bottomNavigationBar,
),
],
);
} else {
return child!;
}
),
],
);
},
child: Consumer(
builder: (_, ref, _) {
@@ -88,12 +90,17 @@ class HomePage extends StatelessWidget {
pageBuilder: (_, index) {
final navigationItem = navigationItems[index];
final navigationView = navigationItem.builder(context);
final scopedView = PageFocusScope(child: navigationView);
final view = KeepScope(
key: ValueKey(navigationItem.label),
keep: navigationItem.keep,
child: isMobile
? navigationView
? scopedView
: Navigator(
pages: [MaterialPage(child: navigationView)],
key: ValueKey(
'${navigationItem.label.name}_navigator',
),
pages: [MaterialPage(child: scopedView)],
onDidRemovePage: (_) {},
),
);
@@ -191,12 +198,31 @@ class _HomePageViewState extends ConsumerState<_HomePageView> {
final itemCount = ref.watch(
currentNavigationItemsStateProvider.select((state) => state.value.length),
);
final currentIndex = ref.watch(
currentPageLabelProvider.select(
(label) =>
widget.navigationItems.indexWhere((item) => item.label == label),
),
);
return PageView.builder(
controller: _pageController,
physics: const NeverScrollableScrollPhysics(),
itemCount: itemCount,
findChildIndexCallback: (key) {
if (key is! ValueKey<PageLabel>) {
return null;
}
final index = widget.navigationItems.indexWhere(
(item) => item.label == key.value,
);
return index == -1 ? null : index;
},
itemBuilder: (context, index) {
return widget.pageBuilder(context, index);
return ExcludeFocus(
key: ValueKey(widget.navigationItems[index].label),
excluding: index != currentIndex,
child: widget.pageBuilder(context, index),
);
},
);
}
+12 -93
View File
@@ -395,47 +395,6 @@ final class NavigationStateProvider
String _$navigationStateHash() => r'657dc47ecc35ba0807b58cb37e7f1baa14f6c2f9';
@ProviderFor(contentWidth)
final contentWidthProvider = ContentWidthProvider._();
final class ContentWidthProvider
extends $FunctionalProvider<double, double, double>
with $Provider<double> {
ContentWidthProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'contentWidthProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$contentWidthHash();
@$internal
@override
$ProviderElement<double> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
double create(Ref ref) {
return contentWidth(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(double value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<double>(value),
);
}
}
String _$contentWidthHash() => r'4710fee8f91b08dc88520798fb6b1d4f61f090e6';
@ProviderFor(dashboardState)
final dashboardStateProvider = DashboardStateProvider._();
@@ -475,7 +434,7 @@ final class DashboardStateProvider
}
}
String _$dashboardStateHash() => r'e8616e327c1b96658d917e4e4950f70ecb099d85';
String _$dashboardStateHash() => r'33838f85f2b6a0ab601891aa2f26adc8870302b6';
@ProviderFor(proxiesActionsState)
final proxiesActionsStateProvider = ProxiesActionsStateProvider._();
@@ -564,7 +523,7 @@ final class ProfilesStateProvider
}
}
String _$profilesStateHash() => r'8b07eeacb83b9002ba7e6283ff7a7f451a0845a6';
String _$profilesStateHash() => r'6bcfd61de84c930251ade72b9fe804c4f5ac2be9';
@ProviderFor(filterGroupsState)
final filterGroupsStateProvider = FilterGroupsStateFamily._();
@@ -687,7 +646,7 @@ final class ProxiesListStateProvider
}
}
String _$proxiesListStateHash() => r'4d83ff3e3d337c9a6eadddfa747c11939676b8bd';
String _$proxiesListStateHash() => r'212d21f79e9c149076e13d1d19ccd83ccb4b471b';
@ProviderFor(proxiesTabState)
final proxiesTabStateProvider = ProxiesTabStateProvider._();
@@ -729,7 +688,7 @@ final class ProxiesTabStateProvider
}
}
String _$proxiesTabStateHash() => r'b16734b2578c4e9a91880af8af41faa2e293b257';
String _$proxiesTabStateHash() => r'e4eccd77c3848489c8ec620f4e515cec7cdd5a31';
@ProviderFor(isStart)
final isStartProvider = IsStartProvider._();
@@ -884,7 +843,7 @@ final class ProxyGroupSelectorStateProvider
}
String _$proxyGroupSelectorStateHash() =>
r'd393702a1fea16f8ab0362c2888b70300795e829';
r'26326b400a0a2570188560a553ba8c600ad13b80';
final class ProxyGroupSelectorStateFamily extends $Family
with $FunctionalFamilyOverride<ProxyGroupSelectorState, (String, String)> {
@@ -1463,46 +1422,6 @@ final class CurrentProfileProvider
String _$currentProfileHash() => r'55f3cb9570a0aa6b9e0b83a36693b69d52e753ab';
@ProviderFor(proxiesColumns)
final proxiesColumnsProvider = ProxiesColumnsProvider._();
final class ProxiesColumnsProvider extends $FunctionalProvider<int, int, int>
with $Provider<int> {
ProxiesColumnsProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'proxiesColumnsProvider',
isAutoDispose: true,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$proxiesColumnsHash();
@$internal
@override
$ProviderElement<int> $createElement($ProviderPointer pointer) =>
$ProviderElement(pointer);
@override
int create(Ref ref) {
return proxiesColumns(ref);
}
/// {@macro riverpod.override_with_value}
Override overrideWithValue(int value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<int>(value),
);
}
}
String _$proxiesColumnsHash() => r'e93f980da3d857bd38957635ef944a0db60aaef3';
@ProviderFor(realSelectedProxyState)
final realSelectedProxyStateProvider = RealSelectedProxyStateFamily._();
@@ -2041,7 +1960,7 @@ final class AutoSetSystemDnsStateProvider
}
String _$autoSetSystemDnsStateHash() =>
r'a8805965efe78241613bfde55f4ea8fa12a6ea32';
r'60135ce263c5c31200006f4a9f342f8241a872cc';
@ProviderFor(needUpdateGroups)
final needUpdateGroupsProvider = NeedUpdateGroupsProvider._();
@@ -3021,7 +2940,7 @@ abstract class _$AccessControlState extends $Notifier<AccessControlProps> {
AccessControlProps build();
@$mustCallSuper
@override
void runBuild() {
WhenComplete runBuild() {
final ref = this.ref as $Ref<AccessControlProps, AccessControlProps>;
final element =
ref.element
@@ -3031,7 +2950,7 @@ abstract class _$AccessControlState extends $Notifier<AccessControlProps> {
Object?,
Object?
>;
element.handleCreate(ref, build);
return element.handleCreate(ref, build);
}
}
@@ -3074,7 +2993,7 @@ abstract class _$ProxyGroupProvider extends $Notifier<ProxyGroup> {
ProxyGroup build();
@$mustCallSuper
@override
void runBuild() {
WhenComplete runBuild() {
final ref = this.ref as $Ref<ProxyGroup, ProxyGroup>;
final element =
ref.element
@@ -3084,7 +3003,7 @@ abstract class _$ProxyGroupProvider extends $Notifier<ProxyGroup> {
Object?,
Object?
>;
element.handleCreate(ref, build);
return element.handleCreate(ref, build);
}
}
@@ -3125,7 +3044,7 @@ abstract class _$RuleProvider extends $Notifier<Rule> {
Rule build();
@$mustCallSuper
@override
void runBuild() {
WhenComplete runBuild() {
final ref = this.ref as $Ref<Rule, Rule>;
final element =
ref.element
@@ -3135,7 +3054,7 @@ abstract class _$RuleProvider extends $Notifier<Rule> {
Object?,
Object?
>;
element.handleCreate(ref, build);
return element.handleCreate(ref, build);
}
}
+9 -38
View File
@@ -195,23 +195,12 @@ NavigationState navigationState(Ref ref) {
);
}
@riverpod
double contentWidth(Ref ref) {
final viewWidth = ref.watch(viewWidthProvider);
final sideWidth = ref.watch(sideWidthProvider);
return viewWidth - sideWidth;
}
@riverpod
DashboardState dashboardState(Ref ref) {
final dashboardWidgets = ref.watch(
appSettingProvider.select((state) => state.dashboardWidgets),
);
final contentWidth = ref.watch(contentWidthProvider);
return DashboardState(
dashboardWidgets: dashboardWidgets,
contentWidth: contentWidth,
);
return DashboardState(dashboardWidgets: dashboardWidgets);
}
@riverpod
@@ -234,14 +223,7 @@ ProxiesActionsState proxiesActionsState(Ref ref) {
ProfilesState profilesState(Ref ref) {
final currentProfileId = ref.watch(currentProfileIdProvider);
final profiles = ref.watch(profilesProvider);
final columns = ref.watch(
contentWidthProvider.select((state) => utils.getProfilesColumns(state)),
);
return ProfilesState(
profiles: profiles,
currentProfileId: currentProfileId,
columns: columns,
);
return ProfilesState(profiles: profiles, currentProfileId: currentProfileId);
}
@riverpod
@@ -272,13 +254,10 @@ ProxiesListState proxiesListState(Ref ref) {
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final columns = ref.watch(proxiesColumnsProvider);
return ProxiesListState(
groups: currentGroups.value,
currentUnfoldSet: currentUnfoldSet,
proxyCardType: cardType,
columns: columns,
);
}
@@ -292,12 +271,10 @@ ProxiesTabState proxiesTabState(Ref ref) {
final cardType = ref.watch(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
final columns = ref.watch(proxiesColumnsProvider);
return ProxiesTabState(
groups: currentGroups.value,
currentGroupName: currentGroupName,
proxyCardType: cardType,
columns: columns,
);
}
@@ -331,7 +308,6 @@ ProxyGroupSelectorState proxyGroupSelectorState(
),
);
final sortNum = ref.watch(sortNumProvider);
final columns = ref.watch(proxiesColumnsProvider);
final lowQuery = query.toLowerCase();
final proxies =
group?.all.where((item) {
@@ -345,7 +321,6 @@ ProxyGroupSelectorState proxyGroupSelectorState(
sortNum: sortNum,
groupType: group?.type ?? GroupType.Selector,
proxies: proxies,
columns: columns,
);
}
@@ -457,15 +432,6 @@ Profile? currentProfile(Ref ref) {
);
}
@riverpod
int proxiesColumns(Ref ref) {
final contentWidth = ref.watch(contentWidthProvider);
final proxiesLayout = ref.watch(
proxiesStyleSettingProvider.select((state) => state.layout),
);
return utils.getProxiesColumns(contentWidth, proxiesLayout);
}
@riverpod
SelectedProxyState realSelectedProxyState(Ref ref, String proxyName) {
final groups = ref.watch(groupsProvider);
@@ -570,11 +536,16 @@ Brightness currentBrightness(Ref ref) {
@riverpod
VM2<bool, bool> autoSetSystemDnsState(Ref ref) {
final isStart = ref.watch(runTimeProvider.select((state) => state != null));
final realTunEnable = ref.watch(realTunEnableProvider);
final tunEnable = ref.watch(
patchClashConfigProvider.select((state) => state.tun.enable),
);
final authorizationState = ref.watch(authorizedTunEnableProvider);
final autoSetSystemDns = ref.watch(
networkSettingProvider.select((state) => state.autoSetSystemDns),
);
return VM2(isStart ? realTunEnable : false, autoSetSystemDns);
final effectiveTunEnable =
tunEnable && authorizationState == TunAuthorizationState.authorized;
return VM2(isStart ? effectiveTunEnable : false, autoSetSystemDns);
}
@riverpod
+60 -144
View File
@@ -1,19 +1,22 @@
import 'dart:math';
import 'package:defer_pointer/defer_pointer.dart';
import 'package:dynamic_color/dynamic_color.dart';
import 'package:fl_clash/common/common.dart';
import 'package:fl_clash/core/core.dart';
import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:fl_clash/widgets/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'widgets/core_status_button.dart';
import 'widgets/start_button.dart';
typedef _IsEditWidgetBuilder = Widget Function(bool isEdit);
const _maxCrossAxisCount = 12;
const _maxGridWidth = 280.0 * _maxCrossAxisCount / 4;
class DashboardView extends ConsumerStatefulWidget {
const DashboardView({super.key});
@@ -42,114 +45,9 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
);
}
Future<void> _handleConnection() async {
final coreStatus = ref.read(coreStatusProvider);
if (coreStatus == CoreStatus.connecting) {
return;
}
final tip = coreStatus == CoreStatus.connected
? context.appLocalizations.forceRestartCoreTip
: context.appLocalizations.restartCoreTip;
final res = await globalState.showMessage(message: TextSpan(text: tip));
if (res != true) {
return;
}
globalState.container.read(coreActionProvider.notifier).restartCore();
}
List<Widget> _buildActions(bool isEdit) {
final appLocalizations = context.appLocalizations;
return [
if (!isEdit)
Consumer(
builder: (_, ref, _) {
final coreStatus = ref.watch(coreStatusProvider);
return Tooltip(
message: appLocalizations.coreStatus,
child: FadeScaleBox(
alignment: Alignment.centerRight,
child: coreStatus == CoreStatus.connected
? IconButton.filled(
visualDensity: VisualDensity.compact,
iconSize: 20,
padding: EdgeInsets.zero,
style: IconButton.styleFrom(
backgroundColor: Colors.green.harmonizeWith(
context.colorScheme.primary,
),
foregroundColor: switch (Theme.brightnessOf(
context,
)) {
Brightness.light =>
context.colorScheme.onSurfaceVariant,
Brightness.dark =>
context.colorScheme.onPrimaryFixedVariant,
},
),
onPressed: _handleConnection,
icon: const Icon(
Icons.check,
fontWeight: FontWeight.w900,
),
)
: FilledButton.icon(
key: ValueKey(coreStatus),
onPressed: _handleConnection,
style: FilledButton.styleFrom(
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.symmetric(horizontal: 12),
backgroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => Colors.greenAccent,
CoreStatus.disconnected =>
context.colorScheme.error,
},
foregroundColor: switch (coreStatus) {
CoreStatus.connecting => null,
CoreStatus.connected => switch (Theme.brightnessOf(
context,
)) {
Brightness.light =>
context.colorScheme.onSurfaceVariant,
Brightness.dark => null,
},
CoreStatus.disconnected =>
context.colorScheme.onError,
},
),
icon: SizedBox(
height: globalState.measure.bodyMediumHeight,
width: globalState.measure.bodyMediumHeight,
child: switch (coreStatus) {
CoreStatus.connecting => Padding(
padding: const EdgeInsets.all(2),
child: CircularProgressIndicator(
strokeWidth: 3,
color: context.colorScheme.onPrimary,
backgroundColor: Colors.transparent,
),
),
CoreStatus.connected => const Icon(
Icons.check_sharp,
fontWeight: FontWeight.w900,
),
CoreStatus.disconnected => const Icon(
Icons.restart_alt_sharp,
fontWeight: FontWeight.w900,
),
},
),
label: Text(switch (coreStatus) {
CoreStatus.connecting => appLocalizations.connecting,
CoreStatus.connected => appLocalizations.connected,
CoreStatus.disconnected =>
appLocalizations.disconnected,
}),
),
),
);
},
),
if (!isEdit && coreLib == null) const CoreStatusButton(),
if (isEdit)
ValueListenableBuilder(
valueListenable: _addedWidgetsNotifier,
@@ -216,23 +114,27 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
if (currentState == null) {
return;
}
if (mounted && currentState.children.isNotEmpty) {
await currentState.isTransformCompleter;
final dashboardWidgets = currentState.children
.map((item) => DashboardWidget.getDashboardWidget(item))
.toList();
ref
.read(appSettingProvider.notifier)
.update(
(state) => state.copyWith(dashboardWidgets: dashboardWidgets),
);
if (!mounted || currentState.children.isEmpty) {
return;
}
final transformCompleted = await currentState.isTransformCompleter;
if (!transformCompleted ||
!mounted ||
!currentState.mounted ||
!identical(key.currentState, currentState)) {
return;
}
final dashboardWidgets = currentState.children
.map((item) => DashboardWidget.getDashboardWidget(item))
.toList();
ref
.read(appSettingProvider.notifier)
.update((state) => state.copyWith(dashboardWidgets: dashboardWidgets));
}
@override
Widget build(BuildContext context) {
final dashboardState = ref.watch(dashboardStateProvider);
final columns = max(4 * ((dashboardState.contentWidth / 280).ceil()), 8);
final spacing = 14.mAp;
final children = [
...dashboardState.dashboardWidgets
@@ -260,31 +162,45 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
alignment: Alignment.topCenter,
child: SingleChildScrollView(
padding: const EdgeInsets.all(16).copyWith(bottom: 88),
child: isEdit
? SystemBackBlock(
child: CommonPopScope(
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
onUpdate: () {
_handleSave();
},
),
onPop: (context) {
_handleUpdateIsEdit();
return false;
},
),
)
: Grid(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
),
child: Align(
alignment: Alignment.topCenter,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: _maxGridWidth),
child: LayoutBuilder(
builder: (_, constraints) {
final columns = min(
max(4 * ((constraints.maxWidth / 280).ceil()), 8),
_maxCrossAxisCount,
);
return isEdit
? SystemBackBlock(
child: CommonPopScope(
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
onUpdate: () {
_handleSave();
},
),
onPop: (context) {
_handleUpdateIsEdit();
return false;
},
),
)
: Grid(
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
);
},
),
),
),
),
),
),
@@ -70,12 +70,10 @@ class OutboundMode extends StatelessWidget {
left: 12.ap,
right: 16.ap,
),
delegate: RadioDelegate(
onTab: () {
_handleChangeMode(item);
},
value: item,
),
onTap: () {
_handleChangeMode(item);
},
value: item,
title: Text(
Intl.message(item.name),
style: Theme.of(
+1 -1
View File
@@ -47,7 +47,7 @@ class ProxyCard extends StatelessWidget {
height: measure.labelSmallHeight,
width: measure.labelSmallHeight,
child: delay == 0
? const CircularProgressIndicator(strokeWidth: 2)
? const CommonCircleLoading()
: IconButton(
icon: const Icon(Icons.bolt),
iconSize: globalState.measure.labelSmallHeight,
+23 -17
View File
@@ -30,12 +30,6 @@ List<Group> getGroups() {
return globalState.container.read(groupsProvider);
}
String? getCurrentGroupName() {
return globalState.container.read(
currentProfileProvider.select((state) => state?.currentGroupName),
);
}
void updateCurrentGroupName(String groupName) {
globalState.container
.read(proxiesActionProvider.notifier)
@@ -68,19 +62,31 @@ Future<void> proxyDelayTest(Proxy proxy, [String? testUrl]) async {
ref
.read(proxiesActionProvider.notifier)
.setDelay(Delay(url: currentTestUrl, name: state.proxyName, value: 0));
ref
.read(proxiesActionProvider.notifier)
.setDelay(await coreController.getDelay(currentTestUrl, state.proxyName));
try {
final delay = await coreController.getDelay(
currentTestUrl,
state.proxyName,
);
ref.read(proxiesActionProvider.notifier).setDelay(delay);
} catch (error) {
commonPrint.log(
'Delay test failed for ${state.proxyName}: $error',
logLevel: LogLevel.error,
);
ref
.read(proxiesActionProvider.notifier)
.setDelay(Delay(url: currentTestUrl, name: state.proxyName, value: -1));
}
}
Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
final delayProxies = proxies.map<Future>((proxy) async {
await proxyDelayTest(proxy, testUrl);
}).toList();
final batchesDelayProxies = delayProxies.batch(100);
for (final batchDelayProxies in batchesDelayProxies) {
await Future.wait(batchDelayProxies);
final batches = proxies.batch(100);
for (final batch in batches) {
await Future.wait(
batch.map((proxy) async {
await proxyDelayTest(proxy, testUrl);
}),
);
}
globalState.container.read(sortNumProvider.notifier).add();
}
@@ -88,9 +94,9 @@ Future<void> delayTest(List<Proxy> proxies, [String? testUrl]) async {
double getScrollToSelectedOffset({
required String groupName,
required List<Proxy> proxies,
required int columns,
}) {
final ref = globalState.container;
final columns = ref.read(proxiesColumnsProvider);
final proxyCardType = ref.read(
proxiesStyleSettingProvider.select((state) => state.cardType),
);
+2 -1
View File
@@ -133,6 +133,7 @@ class ProviderItem extends StatelessWidget {
runSpacing: 6,
spacing: 12,
runAlignment: WrapAlignment.center,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
CommonChip(
avatar: const Icon(Icons.upload),
@@ -151,7 +152,7 @@ class ProviderItem extends StatelessWidget {
width: 30,
child: Padding(
padding: EdgeInsets.all(2),
child: CircularProgressIndicator(),
child: CommonCircleLoading(),
),
)
: CommonChip(
+148
View File
@@ -0,0 +1,148 @@
import 'package:flutter/material.dart';
const _transitionDuration = Duration(milliseconds: 300);
enum _VisibilityMotion {
sidebar(
axis: Axis.horizontal,
alignment: AlignmentDirectional.topStart,
hiddenOffset: Offset(-1, 0),
),
bottomNavigation(
axis: Axis.vertical,
alignment: AlignmentDirectional.bottomStart,
hiddenOffset: Offset(0, 1),
);
final Axis axis;
final AlignmentGeometry alignment;
final Offset hiddenOffset;
const _VisibilityMotion({
required this.axis,
required this.alignment,
required this.hiddenOffset,
});
}
/// Animates navigation visibility together with the surrounding layout.
class AnimatedVisibility extends StatefulWidget {
final bool visible;
final _VisibilityMotion _motion;
final Widget child;
/// Creates a sidebar transition that moves to the left when hidden.
const AnimatedVisibility.sidebar({
super.key,
required this.visible,
required this.child,
}) : _motion = _VisibilityMotion.sidebar;
/// Creates a bottom-navigation transition that moves through the bottom.
const AnimatedVisibility.bottomNavigation({
super.key,
required this.visible,
required this.child,
}) : _motion = _VisibilityMotion.bottomNavigation;
@override
State<AnimatedVisibility> createState() => _AnimatedVisibilityState();
}
class _AnimatedVisibilityState extends State<AnimatedVisibility>
with SingleTickerProviderStateMixin {
late final AnimationController _controller;
late final CurvedAnimation _animation;
late Widget _presentedChild;
late bool _includeChild;
@override
void initState() {
super.initState();
_presentedChild = widget.child;
_includeChild = widget.visible;
_controller = AnimationController(
value: widget.visible ? 1 : 0,
duration: _transitionDuration,
reverseDuration: _transitionDuration,
vsync: this,
)..addStatusListener(_handleAnimationStatus);
_animation = CurvedAnimation(
parent: _controller,
curve: Easing.standardDecelerate,
reverseCurve: const FlippedCurve(Easing.standardAccelerate),
);
}
@override
void didUpdateWidget(covariant AnimatedVisibility oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.visible == oldWidget.visible) {
if (widget.visible) {
_presentedChild = widget.child;
}
return;
}
if (widget.visible) {
_presentedChild = widget.child;
_includeChild = true;
_controller.forward();
return;
}
if (_controller.isDismissed) {
_includeChild = false;
return;
}
_controller.reverse();
}
void _handleAnimationStatus(AnimationStatus status) {
if (status != AnimationStatus.dismissed ||
widget.visible ||
!_includeChild) {
return;
}
setState(() {
_includeChild = false;
});
}
@override
void dispose() {
_controller.removeStatusListener(_handleAnimationStatus);
_animation.dispose();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final motion = widget._motion;
final content = _includeChild
? SizeTransition(
sizeFactor: _animation,
axis: motion.axis,
alignment: motion.alignment,
child: FadeTransition(
opacity: _animation,
child: SlideTransition(
position: _animation.drive(
Tween(begin: motion.hiddenOffset, end: Offset.zero),
),
child: _presentedChild,
),
),
)
: const SizedBox.shrink();
return ExcludeSemantics(
excluding: !widget.visible,
child: ExcludeFocus(
excluding: !widget.visible,
child: IgnorePointer(
ignoring: !widget.visible,
child: ClipRect(child: content),
),
),
);
}
}
+12 -1
View File
@@ -1,18 +1,29 @@
import 'package:fl_clash/common/system.dart';
import 'package:flutter/material.dart';
class FloatLayout extends StatelessWidget {
final Widget floatingWidget;
final Widget child;
final bool? isTV;
const FloatLayout({
super.key,
required this.floatingWidget,
this.isTV,
required this.child,
});
@override
Widget build(BuildContext context) {
if (isTV ?? system.isTV) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
floatingWidget,
Expanded(child: Center(child: child)),
],
);
}
return Stack(
fit: StackFit.loose,
children: [
+333 -46
View File
@@ -1,11 +1,34 @@
import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
enum LoadingIndicatorM3EVariant { defaultStyle, contained }
class CommonCircleLoading extends StatefulWidget {
static const double defaultDimension = 32;
static const double defaultDimension = 48;
final LoadingIndicatorM3EVariant variant;
final Color? color;
final Color? containerColor;
final List<StarBorder>? polygons;
final BoxConstraints? constraints;
final EdgeInsetsGeometry? padding;
final String? semanticLabel;
final String? semanticValue;
const CommonCircleLoading({super.key, this.color});
const CommonCircleLoading({
super.key,
this.variant = LoadingIndicatorM3EVariant.defaultStyle,
this.color,
this.containerColor,
this.polygons,
this.constraints,
this.padding,
this.semanticLabel,
this.semanticValue,
}) : assert(polygons == null || polygons.length > 1);
@override
State<CommonCircleLoading> createState() => _CommonCircleLoadingState();
@@ -13,53 +36,195 @@ class CommonCircleLoading extends StatefulWidget {
class _CommonCircleLoadingState extends State<CommonCircleLoading>
with TickerProviderStateMixin {
late AnimationController _rotateController;
late AnimationController _pointsController;
late Animation<double> _pointsAnimation;
static const _globalRotationDuration = Duration(milliseconds: 4666);
static const _morphInterval = Duration(milliseconds: 650);
static const _fullRotation = 360.0;
static const _quarterRotation = _fullRotation / 4;
static const _activeIndicatorScale = 38 / 48;
static const _defaultConstraints = BoxConstraints.tightFor(
width: CommonCircleLoading.defaultDimension,
height: CommonCircleLoading.defaultDimension,
);
static const List<_ShapeSpec> _defaultShapeSequence = [
// Soft burst.
_ShapeSpec(
points: 10,
innerRadiusRatio: 0.78,
pointRounding: 0.55,
valleyRounding: 0.35,
),
// Nine-sided cookie.
_ShapeSpec(
points: 9,
innerRadiusRatio: 0.86,
pointRounding: 0.62,
valleyRounding: 0.28,
),
// Pentagon.
_ShapeSpec(points: 5, innerRadiusRatio: 0.81, pointRounding: 0.25),
// Horizontal pill.
_ShapeSpec(
points: 2,
innerRadiusRatio: 1,
pointRounding: 0.5,
valleyRounding: 0.5,
widthScale: 0.95,
heightScale: 0.52,
squash: 1,
),
// Sunny.
_ShapeSpec(
points: 8,
innerRadiusRatio: 0.58,
pointRounding: 0.52,
valleyRounding: 0.18,
),
// Four-sided cookie.
_ShapeSpec(
points: 4,
innerRadiusRatio: 0.78,
pointRounding: 0.62,
valleyRounding: 0.28,
),
// Vertical oval.
_ShapeSpec(
points: 2,
innerRadiusRatio: 1,
pointRounding: 0.5,
valleyRounding: 0.5,
widthScale: 0.62,
heightScale: 0.95,
squash: 1,
),
];
final SpringSimulation _morphAnimation = SpringSimulation(
SpringDescription.withDampingRatio(mass: 1, stiffness: 200, ratio: 0.6),
0,
1,
5,
snapToEnd: true,
);
late final AnimationController _morphController;
late final AnimationController _globalRotationController;
late final Listenable _animation;
var _currentMorphIndex = 0;
var _morphRotationTargetAngle = _quarterRotation;
@override
void initState() {
super.initState();
_rotateController = AnimationController(
duration: const Duration(seconds: 3),
_morphController = AnimationController.unbounded(vsync: this);
_globalRotationController = AnimationController(
duration: _globalRotationDuration,
vsync: this,
)..repeat();
_pointsController = AnimationController(
duration: const Duration(seconds: 1),
vsync: this,
)..repeat(reverse: true);
_pointsAnimation = Tween<double>(begin: 3.0, end: 9.0).animate(
CurvedAnimation(parent: _pointsController, curve: Curves.easeInOut),
);
_animation = Listenable.merge([
_morphController,
_globalRotationController,
]);
unawaited(_runMorphLoop());
}
@override
void dispose() {
_rotateController.dispose();
_pointsController.dispose();
_morphController.dispose();
_globalRotationController.dispose();
super.dispose();
}
@override
void didUpdateWidget(covariant CommonCircleLoading oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.polygons != oldWidget.polygons &&
_currentMorphIndex >= _shapeCount) {
_currentMorphIndex = 0;
}
}
@override
Widget build(BuildContext context) {
final color = widget.color ?? Theme.of(context).colorScheme.primary;
final colorScheme = Theme.of(context).colorScheme;
final activeColor = switch (widget.variant) {
LoadingIndicatorM3EVariant.defaultStyle =>
widget.color ?? colorScheme.primary,
LoadingIndicatorM3EVariant.contained =>
widget.color ?? colorScheme.onPrimaryContainer,
};
final backgroundColor = switch (widget.variant) {
LoadingIndicatorM3EVariant.defaultStyle =>
widget.containerColor ?? Colors.transparent,
LoadingIndicatorM3EVariant.contained =>
widget.containerColor ?? colorScheme.primaryContainer,
};
final shapeSequence =
widget.polygons
?.map(_ShapeSpec.fromStarBorder)
.toList(growable: false) ??
_defaultShapeSequence;
final padding = (widget.padding ?? EdgeInsets.zero).resolve(
Directionality.of(context),
);
return LayoutBuilder(
builder: (context, constraints) {
final dimension = _resolveDimension(constraints);
final indicatorConstraints = constraints.deflate(padding);
final dimension = _resolveDimension(
indicatorConstraints,
widget.constraints ?? _defaultConstraints,
);
final currentMorphIndex = _currentMorphIndex % shapeSequence.length;
return Align(
widthFactor: 1,
heightFactor: 1,
child: RepaintBoundary(
child: RotationTransition(
turns: _rotateController,
child: CustomPaint(
size: Size.square(dimension),
painter: _StarPainter(
points: _pointsAnimation.value,
color: color,
child: Semantics(
label: widget.semanticLabel,
value: widget.semanticValue,
child: DecoratedBox(
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(999),
),
child: Padding(
padding: padding,
child: RepaintBoundary(
child: SizedBox.square(
dimension: dimension,
child: AnimatedBuilder(
animation: _animation,
builder: (context, child) {
final morphProgress = _morphController.value.clamp(
0.0,
1.0,
);
final rotationDegrees =
morphProgress * _quarterRotation +
_morphRotationTargetAngle +
_globalRotationController.value * _fullRotation;
final shape = _ShapeSpec.lerp(
shapeSequence[currentMorphIndex],
shapeSequence[(currentMorphIndex + 1) %
shapeSequence.length],
morphProgress,
);
return Transform.rotate(
angle: rotationDegrees * math.pi / 180,
child: CustomPaint(
painter: _ShapePainter(
shape: shape,
color: activeColor,
scaleFactor: _activeIndicatorScale,
),
child: child,
),
);
},
child: const SizedBox.expand(),
),
),
),
),
),
@@ -69,9 +234,21 @@ class _CommonCircleLoadingState extends State<CommonCircleLoading>
);
}
double _resolveDimension(BoxConstraints constraints) {
final maxWidth = constraints.maxWidth;
final maxHeight = constraints.maxHeight;
int get _shapeCount =>
widget.polygons?.length ?? _defaultShapeSequence.length;
double _resolveDimension(
BoxConstraints parentConstraints,
BoxConstraints preferredConstraints,
) {
final effectiveConstraints = preferredConstraints.enforce(
BoxConstraints(
maxWidth: parentConstraints.maxWidth,
maxHeight: parentConstraints.maxHeight,
),
);
final maxWidth = effectiveConstraints.maxWidth;
final maxHeight = effectiveConstraints.maxHeight;
if (maxWidth.isFinite && maxHeight.isFinite) {
return maxWidth < maxHeight ? maxWidth : maxHeight;
@@ -87,33 +264,143 @@ class _CommonCircleLoadingState extends State<CommonCircleLoading>
return CommonCircleLoading.defaultDimension;
}
Future<void> _runMorphLoop() async {
while (mounted) {
final startedAt = DateTime.now();
try {
await _morphController.animateWith(_morphAnimation).orCancel;
} on TickerCanceled {
return;
}
final elapsed = DateTime.now().difference(startedAt);
if (elapsed < _morphInterval) {
await Future<void>.delayed(_morphInterval - elapsed);
}
if (!mounted) {
return;
}
setState(() {
_currentMorphIndex = (_currentMorphIndex + 1) % _shapeCount;
_morphRotationTargetAngle =
(_morphRotationTargetAngle + _quarterRotation) % _fullRotation;
_morphController.value = 0;
});
}
}
}
class _StarPainter extends CustomPainter {
class _ShapeSpec {
final double points;
final double innerRadiusRatio;
final double pointRounding;
final double valleyRounding;
final double widthScale;
final double heightScale;
final double squash;
const _ShapeSpec({
required this.points,
required this.innerRadiusRatio,
required this.pointRounding,
this.valleyRounding = 0,
this.widthScale = 1,
this.heightScale = 1,
this.squash = 0,
});
static _ShapeSpec lerp(_ShapeSpec begin, _ShapeSpec end, double progress) {
return _ShapeSpec(
points: _lerp(begin.points, end.points, progress),
innerRadiusRatio: _lerp(
begin.innerRadiusRatio,
end.innerRadiusRatio,
progress,
),
pointRounding: _lerp(begin.pointRounding, end.pointRounding, progress),
valleyRounding: _lerp(begin.valleyRounding, end.valleyRounding, progress),
widthScale: _lerp(begin.widthScale, end.widthScale, progress),
heightScale: _lerp(begin.heightScale, end.heightScale, progress),
squash: _lerp(begin.squash, end.squash, progress),
);
}
static _ShapeSpec fromStarBorder(StarBorder border) {
return _ShapeSpec(
points: border.points,
innerRadiusRatio: border.innerRadiusRatio,
pointRounding: border.pointRounding,
valleyRounding: border.valleyRounding,
squash: border.squash,
);
}
static double _lerp(double begin, double end, double progress) {
return begin + (end - begin) * progress;
}
@override
bool operator ==(Object other) {
return other is _ShapeSpec &&
other.points == points &&
other.innerRadiusRatio == innerRadiusRatio &&
other.pointRounding == pointRounding &&
other.valleyRounding == valleyRounding &&
other.widthScale == widthScale &&
other.heightScale == heightScale &&
other.squash == squash;
}
@override
int get hashCode => Object.hash(
points,
innerRadiusRatio,
pointRounding,
valleyRounding,
widthScale,
heightScale,
squash,
);
}
class _ShapePainter extends CustomPainter {
final _ShapeSpec shape;
final Color color;
final double scaleFactor;
final Paint _paint;
_StarPainter({required this.points, required this.color})
: _paint = Paint()..color = color;
_ShapePainter({
required this.shape,
required this.color,
required this.scaleFactor,
}) : _paint = Paint()
..style = PaintingStyle.fill
..isAntiAlias = true
..color = color;
@override
void paint(Canvas canvas, Size size) {
final rect = Offset.zero & size;
final starBorder = StarBorder(
points: points,
innerRadiusRatio: 0.8,
pointRounding: 0.5,
valleyRounding: 0.1,
squash: 0.5,
final rect = Rect.fromCenter(
center: size.center(Offset.zero),
width: size.width * scaleFactor * shape.widthScale,
height: size.height * scaleFactor * shape.heightScale,
);
final path = starBorder.getOuterPath(rect);
canvas.drawPath(path, _paint);
final border = StarBorder(
points: shape.points,
innerRadiusRatio: shape.innerRadiusRatio,
pointRounding: shape.pointRounding,
valleyRounding: shape.valleyRounding,
squash: shape.squash,
);
canvas.drawPath(border.getOuterPath(rect), _paint);
}
@override
bool shouldRepaint(covariant _StarPainter oldDelegate) {
return oldDelegate.points != points || oldDelegate.color != color;
bool shouldRepaint(covariant _ShapePainter oldDelegate) {
return oldDelegate.shape != shape ||
oldDelegate.color != color ||
oldDelegate.scaleFactor != scaleFactor;
}
}
+59 -51
View File
@@ -269,6 +269,10 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
final _FlippableTweenSequence<double> _closedOpacityTween;
final _FlippableTweenSequence<double> _openOpacityTween;
final Animatable<Color?> _scrimTween = ColorTween(
begin: Colors.transparent,
end: Colors.transparent,
);
late _FlippableTweenSequence<Color?> _colorTween;
final GlobalKey _openBuilderKey = GlobalKey();
final RectTween _rectTween = RectTween();
@@ -475,60 +479,64 @@ class _OpenContainerRoute<T> extends ModalRoute<T> {
final Rect rect = _rectTween.evaluate(curvedAnimation)!;
return SizedBox.expand(
child: Align(
alignment: Alignment.topLeft,
child: Transform.translate(
offset: Offset(rect.left, rect.top),
child: SizedBox(
width: rect.width,
height: rect.height,
child: Material(
clipBehavior: Clip.antiAlias,
animationDuration: Duration.zero,
color: colorTween!.evaluate(animation),
child: Stack(
fit: StackFit.passthrough,
children: <Widget>[
// Closed child fading out.
FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.topLeft,
child: SizedBox(
width: _rectTween.begin!.width,
height: _rectTween.begin!.height,
child: (hideableKey.currentState?.isInTree ?? false)
? null
: FadeTransition(
opacity: closedOpacityTween!.animate(
animation,
child: Container(
color: _scrimTween.evaluate(curvedAnimation),
child: Align(
alignment: Alignment.topLeft,
child: Transform.translate(
offset: Offset(rect.left, rect.top),
child: SizedBox(
width: rect.width,
height: rect.height,
child: Material(
clipBehavior: Clip.antiAlias,
animationDuration: Duration.zero,
color: colorTween!.evaluate(animation),
child: Stack(
fit: StackFit.passthrough,
children: <Widget>[
// Closed child fading out.
FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.topLeft,
child: SizedBox(
width: _rectTween.begin!.width,
height: _rectTween.begin!.height,
child:
(hideableKey.currentState?.isInTree ?? false)
? null
: FadeTransition(
opacity: closedOpacityTween!.animate(
animation,
),
child: Builder(
key: closedBuilderKey,
builder: (BuildContext context) {
// Use dummy "open container" callback
// since we are in the process of opening.
return closedBuilder(context, () {});
},
),
),
child: Builder(
key: closedBuilderKey,
builder: (BuildContext context) {
// Use dummy "open container" callback
// since we are in the process of opening.
return closedBuilder(context, () {});
},
),
),
),
),
// Open child fading in.
OverflowBox(
maxWidth: _rectTween.end!.width,
maxHeight: _rectTween.end!.height,
alignment: Alignment.topLeft,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
),
],
// Open child fading in.
OverflowBox(
maxWidth: _rectTween.end!.width,
maxHeight: _rectTween.end!.height,
alignment: Alignment.topLeft,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
),
],
),
),
),
),
+12 -1
View File
@@ -22,6 +22,7 @@ class CommonScaffold extends StatefulWidget {
final List<Widget>? actions;
final bool? centerTitle;
final Widget? floatingActionButton;
final bool? isTV;
final AppBarEditState? editState;
final AppBarSearchState? searchState;
final OnKeywordsUpdateCallback? onKeywordsUpdate;
@@ -39,6 +40,7 @@ class CommonScaffold extends StatefulWidget {
this.isLoading = false,
this.searchState,
this.floatingActionButton,
this.isTV,
this.onKeywordsUpdate,
this.resizeToAvoidBottomInset,
});
@@ -305,10 +307,19 @@ class CommonScaffoldState extends State<CommonScaffold> {
Widget build(BuildContext context) {
assert(widget.appBar != null || widget.title != null);
final backActionProvider = CommonScaffoldBackActionProvider.of(context);
final isTV = widget.isTV ?? system.isTV;
final body = SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (isTV && widget.floatingActionButton != null)
Padding(
padding: const EdgeInsets.all(16),
child: CommonScaffoldFabExtendedProvider(
isExtended: true,
child: widget.floatingActionButton!,
),
),
ValueListenableBuilder(
valueListenable: _keywordsNotifier,
builder: (_, keywords, _) {
@@ -361,7 +372,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
),
resizeToAvoidBottomInset: widget.resizeToAvoidBottomInset,
backgroundColor: widget.backgroundColor,
floatingActionButton: widget.floatingActionButton != null
floatingActionButton: !isTV && widget.floatingActionButton != null
? ValueListenableBuilder<bool>(
valueListenable: _isFabExtendedNotifier,
builder: (_, isExtended, child) {
+165 -120
View File
@@ -10,8 +10,6 @@ import 'package:fl_clash/widgets/grid.dart';
import 'package:flutter/material.dart';
import 'package:flutter/physics.dart';
typedef VoidCallback = void Function();
class SuperGrid extends StatefulWidget {
final List<GridItem> children;
final double mainAxisSpacing;
@@ -33,7 +31,11 @@ class SuperGrid extends StatefulWidget {
}
class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
final ValueNotifier<List<GridItem>> _childrenNotifier = ValueNotifier([]);
static const _reorderDuration = Duration(milliseconds: 420);
static const _shakeDuration = Duration(milliseconds: 480);
static const _reorderCurve = Cubic(0.22, 0.72, 0.24, 1.08);
late final ValueNotifier<List<GridItem>> _childrenNotifier;
List<GridItem> children = [];
int get length => _childrenNotifier.value.length;
@@ -46,7 +48,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
List<Offset> _offsets = [];
Offset _parentOffset = Offset.zero;
EdgeDraggingAutoScroller? _edgeDraggingAutoScroller;
Map<int, Tween<Offset>> _transformTweenMap = {};
final ValueNotifier<bool> _animating = ValueNotifier(false);
@@ -57,7 +58,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
late AnimationController _transformController;
Future<bool> get isTransformCompleter =>
_transformCompleter?.future ?? Future(() => true);
_transformCompleter?.future ?? Future<bool>.value(true);
Completer<bool>? _transformCompleter;
@@ -67,15 +68,21 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
Animation<Offset>? _fakeDragWidgetAnimation;
late AnimationController _shakeController;
late Animation<double> _shakeAnimation;
Rect _dragRect = Rect.zero;
Scrollable? _scrollable;
bool _isDragging = false;
int get crossCount => widget.crossAxisCount;
void _onChildrenChange() {
void _stopAutoScroll() {
_edgeDraggingAutoScroller?.stopAutoScroll();
}
void _handleChildrenChanged() {
children = List<GridItem>.of(_childrenNotifier.value);
_tempIndexList = List.generate(length, (index) => index);
_itemContexts = List.filled(length, null);
widget.onUpdate?.call();
}
void _preTransformState() {
@@ -99,7 +106,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_transformController.value = 0;
_sizes = List.generate(length, (index) => Size.zero);
_offsets = [];
_transformTweenMap.clear();
_transformAnimationMap.clear();
_containerSize = Size.zero;
_dragIndexNotifier.value = -1;
@@ -113,36 +119,28 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
@override
void initState() {
super.initState();
children = widget.children;
_childrenNotifier.addListener(() {
children = _childrenNotifier.value;
if (widget.onUpdate != null) {
widget.onUpdate!();
}
});
_childrenNotifier.value = widget.children;
children = List<GridItem>.of(widget.children);
_childrenNotifier = ValueNotifier(children)
..addListener(_handleChildrenChanged);
_tempIndexList = List.generate(length, (index) => index);
_itemContexts = List.filled(length, null);
_fakeDragWidgetController = AnimationController.unbounded(vsync: this);
_shakeController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 120),
);
_shakeAnimation = Tween<double>(begin: -0.012, end: 0.012).animate(
CurvedAnimation(parent: _shakeController, curve: Curves.easeInOut),
);
duration: _shakeDuration,
)..repeat();
_transformController = AnimationController(
vsync: this,
duration: commonDuration,
duration: _reorderDuration,
);
_initState();
}
void handleAdd(GridItem gridItem) {
_childrenNotifier.value = List.from(_childrenNotifier.value)..add(gridItem);
_childrenNotifier.value = [..._childrenNotifier.value, gridItem];
}
@override
@@ -151,20 +149,33 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
final scrollable = context.findAncestorWidgetOfExactType<Scrollable>();
if (scrollable == null) {
_stopAutoScroll();
_scrollable = null;
_edgeDraggingAutoScroller = null;
return;
}
if (_scrollable != scrollable) {
_edgeDraggingAutoScroller = EdgeDraggingAutoScroller(
Scrollable.of(context),
onScrollViewScrolled: () {
_edgeDraggingAutoScroller?.startAutoScrollIfNecessary(_dragRect);
},
velocityScalar: 40,
);
if (_scrollable == scrollable) {
return;
}
_stopAutoScroll();
_scrollable = scrollable;
late final EdgeDraggingAutoScroller autoScroller;
autoScroller = EdgeDraggingAutoScroller(
Scrollable.of(context),
onScrollViewScrolled: () {
if (!mounted ||
!_isDragging ||
!identical(_edgeDraggingAutoScroller, autoScroller)) {
return;
}
autoScroller.startAutoScrollIfNecessary(_dragRect);
},
velocityScalar: 40,
);
_edgeDraggingAutoScroller = autoScroller;
}
Future _transform() async {
Future<bool> _transform() async {
final List<Offset> layoutOffsets = [Offset(_containerSize.width, 0)];
final List<Offset> nextOffsets = [];
@@ -210,42 +221,35 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
layoutOffsets.removeRange(min(startIndex + 1, endIndex), endIndex);
}
final Map<int, Tween<Offset>> transformTweenMap = {};
for (final index in _tempIndexList) {
final nextIndex = _tempIndexList.indexWhere((i) => i == index);
transformTweenMap[index] = Tween(
begin: _transformTweenMap[index]?.begin ?? Offset.zero,
final transformAnimationMap = <int, Animation<Offset>>{};
final transformCurve = CurvedAnimation(
parent: _transformController,
curve: _reorderCurve,
);
for (var nextIndex = 0; nextIndex < _tempIndexList.length; nextIndex++) {
final index = _tempIndexList[nextIndex];
transformAnimationMap[index] = Tween<Offset>(
begin: _transformAnimationMap[index]?.value ?? Offset.zero,
end: nextOffsets[nextIndex] - _offsets[index],
);
).animate(transformCurve);
}
_transformTweenMap = transformTweenMap;
_transformAnimationMap = transformTweenMap.map((key, value) {
final preAnimationValue = _transformAnimationMap[key]?.value;
return MapEntry(
key,
Tween(begin: preAnimationValue ?? Offset.zero, end: value.end).animate(
_transformController.drive(
Tween<double>(
begin: 0.0,
end: 1,
).chain(CurveTween(curve: Curves.fastOutSlowIn)),
),
),
);
});
_transformAnimationMap = transformAnimationMap;
if (_targetIndex != -1) {
_targetOffset = nextOffsets[_targetIndex];
}
return _transformController.forward(from: 0);
try {
await _transformController.forward(from: 0).orCancel;
return true;
} on TickerCanceled {
return false;
}
}
void _handleDragStarted(int index) {
_initState();
_preTransformState();
_isDragging = true;
_dragIndexNotifier.value = index;
_dragWidgetSizeNotifier.value = _sizes[index];
_targetIndex = index;
@@ -259,41 +263,71 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
}
Future<void> _handleDragEnd(DraggableDetails details) async {
final children = List<GridItem>.from(_childrenNotifier.value);
children.insert(_targetIndex, children.removeAt(_dragIndexNotifier.value));
this.children = children;
_isDragging = false;
_stopAutoScroll();
debouncer.cancel(FunctionTag.handleWill);
if (_targetIndex == -1) {
final dragIndex = _dragIndexNotifier.value;
if (_targetIndex < 0 ||
_targetIndex >= length ||
dragIndex < 0 ||
dragIndex >= length) {
_initState();
return;
}
const tolerance = Tolerance(distance: 0.5, velocity: 0.01);
const spring = SpringDescription(mass: 1, stiffness: 100, damping: 10);
final nextChildren = List<GridItem>.of(_childrenNotifier.value);
nextChildren.insert(_targetIndex, nextChildren.removeAt(dragIndex));
children = nextChildren;
const tolerance = Tolerance(distance: 0.001, velocity: 0.01);
const spring = SpringDescription(mass: 1, stiffness: 180, damping: 18);
final simulation = SpringSimulation(spring, 0, 1, 0, tolerance: tolerance);
_fakeDragWidgetAnimation = Tween(
_fakeDragWidgetAnimation = Tween<Offset>(
begin: details.offset - _parentOffset,
end: _targetOffset,
).animate(_fakeDragWidgetController);
_animating.value = true;
_transformCompleter = Completer<bool>();
await _fakeDragWidgetController.animateWith(simulation);
_transformCompleter?.complete(true);
_animating.value = false;
_fakeDragWidgetAnimation = null;
_transformTweenMap.clear();
_transformAnimationMap.clear();
_childrenNotifier.value = children;
_initState();
final completer = Completer<bool>();
_transformCompleter = completer;
try {
await _fakeDragWidgetController.animateWith(simulation).orCancel;
if (!mounted) {
return;
}
_animating.value = false;
_fakeDragWidgetAnimation = null;
_transformAnimationMap.clear();
_childrenNotifier.value = nextChildren;
_initState();
completer.complete(true);
} on TickerCanceled {
if (mounted) {
_animating.value = false;
_fakeDragWidgetAnimation = null;
_initState();
}
} finally {
if (!completer.isCompleted) {
completer.complete(false);
}
if (identical(_transformCompleter, completer)) {
_transformCompleter = null;
}
}
}
void _handleDragUpdate(DragUpdateDetails details) {
if (!_isDragging) {
return;
}
_dragRect = _dragRect.translate(0, details.delta.dy);
_edgeDraggingAutoScroller?.startAutoScrollIfNecessary(_dragRect);
}
Future<void> _handleWill(int index) async {
final dragIndex = _dragIndexNotifier.value;
if (dragIndex < 0 || dragIndex > _offsets.length - 1) {
if (dragIndex < 0 || dragIndex >= _offsets.length) {
return;
}
final targetIndex = _tempIndexList.indexWhere((i) => i == index);
@@ -321,7 +355,10 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_preTransformState();
final indexWhere = _tempIndexList.indexWhere((i) => i == index);
_tempIndexList.removeAt(indexWhere);
await _transform();
final completed = await _transform();
if (!completed || !mounted) {
return;
}
final children = List<GridItem>.from(_childrenNotifier.value);
children.removeAt(index);
_childrenNotifier.value = children;
@@ -333,7 +370,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
valueListenable: _animating,
builder: (_, animating, child) {
if (animating && _dragIndexNotifier.value == index) {
return _buildSizeBox(Container(), index);
return _buildSizeBox(const SizedBox.shrink());
}
return child!;
},
@@ -382,7 +419,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
return nextOffset;
}
Widget _buildSizeBox(Widget child, int index) {
Widget _buildSizeBox(Widget child) {
return ValueListenableBuilder(
valueListenable: _dragWidgetSizeNotifier,
builder: (_, size, child) {
@@ -396,27 +433,19 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
return ValueListenableBuilder(
valueListenable: _animating,
builder: (_, animating, child) {
if (animating) {
return ActivateBox(child: child!);
} else {
return child!;
}
return animating ? ActivateBox(child: child!) : child!;
},
child: child,
);
}
Widget _buildShake(Widget child) {
final random = 0.7 + Random().nextDouble() * 0.3;
_shakeController.stop();
_shakeController.repeat(reverse: true);
Widget _buildShake(Widget child, int index) {
return AnimatedBuilder(
animation: _shakeAnimation,
animation: _shakeController,
builder: (_, child) {
return Transform.rotate(
angle: _shakeAnimation.value * random,
child: child!,
);
final phase = index * pi / 2;
final angle = sin(_shakeController.value * 2 * pi + phase) * 0.01;
return Transform.rotate(angle: angle, child: child!);
},
child: child,
);
@@ -464,64 +493,64 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
},
child: child!,
),
index,
);
},
child: target,
),
);
void onDragStarted() {
_handleDragStarted(index);
}
void onDragUpdate(DragUpdateDetails details) {
_handleDragUpdate(details);
}
void onDragEnd(DraggableDetails details) {
_handleDragEnd(details);
}
final draggableChild = system.isDesktop
? Draggable(
childWhenDragging: childWhenDragging,
data: index,
feedback: feedback,
onDragStarted: () {
_handleDragStarted(index);
},
onDragUpdate: (details) {
_handleDragUpdate(details);
},
onDragEnd: (details) {
_handleDragEnd(details);
},
onDragStarted: onDragStarted,
onDragUpdate: onDragUpdate,
onDragEnd: onDragEnd,
child: shakeTarget,
)
: LongPressDraggable(
childWhenDragging: childWhenDragging,
data: index,
feedback: feedback,
onDragStarted: () {
_handleDragStarted(index);
},
onDragUpdate: (details) {
_handleDragUpdate(details);
},
onDragEnd: (details) {
_handleDragEnd(details);
},
onDragStarted: onDragStarted,
onDragUpdate: onDragUpdate,
onDragEnd: onDragEnd,
child: shakeTarget,
);
return draggableChild;
}
Widget _builderItem(int index) {
final girdItem = _childrenNotifier.value[index];
final child = girdItem.child;
final gridItem = _childrenNotifier.value[index];
final child = gridItem.child;
return GridItem(
mainAxisCellCount: girdItem.mainAxisCellCount,
crossAxisCellCount: girdItem.crossAxisCellCount,
mainAxisCellCount: gridItem.mainAxisCellCount,
crossAxisCellCount: gridItem.crossAxisCellCount,
child: Builder(
builder: (context) {
_itemContexts[index] = context;
final childWhenDragging = ActivateBox(
child: Opacity(
opacity: 0.6,
child: _buildSizeBox(CommonCard(child: child), index),
child: _buildSizeBox(CommonCard(child: child)),
),
);
final feedback = ActivateBox(
child: _buildSizeBox(
CommonCard(child: Material(elevation: 6, child: child)),
index,
),
);
return _buildTransform(
@@ -544,7 +573,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
builder: (_, animating, _) {
final index = _dragIndexNotifier.value;
if (!animating || _fakeDragWidgetAnimation == null || index == -1) {
return Container();
return const SizedBox.shrink();
}
return _buildSizeBox(
AnimatedBuilder(
@@ -557,7 +586,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
},
child: ActivateBox(child: _childrenNotifier.value[index].child),
),
index,
);
},
);
@@ -565,10 +593,28 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
@override
void dispose() {
_isDragging = false;
_stopAutoScroll();
_edgeDraggingAutoScroller = null;
_scrollable = null;
debouncer.cancel(FunctionTag.handleWill);
final completer = _transformCompleter;
if (completer != null && !completer.isCompleted) {
completer.complete(false);
}
_transformCompleter = null;
_childrenNotifier.removeListener(_handleChildrenChanged);
_childrenNotifier.value = const [];
children = const [];
_itemContexts = [];
_sizes = [];
_offsets = [];
_transformAnimationMap.clear();
_fakeDragWidgetAnimation = null;
_fakeDragWidgetController.dispose();
_shakeController.dispose();
_transformController.dispose();
_dragWidgetSizeNotifier.dispose();
_dragIndexNotifier.dispose();
_animating.dispose();
_childrenNotifier.dispose();
@@ -584,7 +630,6 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
ValueListenableBuilder(
valueListenable: _childrenNotifier,
builder: (_, children, _) {
_onChildrenChange();
return Grid(
axisDirection: AxisDirection.down,
crossAxisCount: crossCount,