Optimize back navigation

This commit is contained in:
chen08209
2026-08-05 10:58:23 +08:00
parent 1cffeaa29e
commit 4551e57ff4
20 changed files with 1020 additions and 187 deletions
+3
View File
@@ -11,6 +11,7 @@ import 'package:fl_clash/plugins/app.dart';
import 'package:fl_clash/providers/providers.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
@@ -46,6 +47,7 @@ class ApplicationState extends ConsumerState<Application> {
@override
void initState() {
super.initState();
SystemNavigator.setFrameworkHandlesBack(true);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
if (globalState.navigatorKey.currentContext != null) {
await globalState.attach();
@@ -141,6 +143,7 @@ class ApplicationState extends ConsumerState<Application> {
return MaterialApp(
debugShowCheckedModeBanner: false,
navigatorKey: globalState.navigatorKey,
onNavigationNotification: (_) => true,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
+5
View File
@@ -78,6 +78,8 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
shortcuts: {
utils.controlSingleActivator(LogicalKeyboardKey.keyW):
const CloseWindowIntent(),
const SingleActivator(LogicalKeyboardKey.escape):
const EscapeBackIntent(),
},
child: Actions(
actions: {
@@ -86,6 +88,9 @@ class _HotKeyManagerState extends ConsumerState<HotKeyManager> {
.read(systemActionProvider.notifier)
.handleClose(false),
),
EscapeBackIntent: CallbackAction<EscapeBackIntent>(
onInvoke: (_) => globalState.navigatorKey.currentState?.maybePop(),
),
DoNothingIntent: CallbackAction<DoNothingIntent>(
onInvoke: (_) => null,
),
-1
View File
@@ -14,7 +14,6 @@ typedef DelayMap = Map<String, Map<String, int?>>;
abstract class AppState with _$AppState {
const factory AppState({
@Default(false) bool isInit,
@Default(false) bool backBlock,
@Default(PageLabel.dashboard) PageLabel pageLabel,
@Default([]) List<Package> packages,
@Default(0) int sortNum,
+4
View File
@@ -581,6 +581,10 @@ class CloseWindowIntent extends Intent {
const CloseWindowIntent();
}
class EscapeBackIntent extends Intent {
const EscapeBackIntent();
}
@freezed
abstract class Result<T> with _$Result<T> {
const factory Result({
+16 -19
View File
@@ -14,7 +14,7 @@ T _$identity<T>(T value) => value;
/// @nodoc
mixin _$AppState {
bool get isInit; bool get backBlock; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; TunAuthorizationState get authorizedTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; CoreStatus get coreStatus;
bool get isInit; PageLabel get pageLabel; List<Package> get packages; int get sortNum; Size get viewSize; double get sideWidth; DelayMap get delayMap; List<Group> get groups; int get checkIpNum; Brightness get brightness; int? get runTime; List<ExternalProvider> get providers; String? get localIp; FixedList<TrackerInfo> get requests; int get version; FixedList<Log> get logs; FixedList<Traffic> get traffics; Traffic get totalTraffic; TunAuthorizationState get authorizedTunEnable; bool get loading; SystemUiOverlayStyle get systemUiOverlayStyle; CoreStatus get coreStatus;
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@@ -25,12 +25,12 @@ $AppStateCopyWith<AppState> get copyWith => _$AppStateCopyWithImpl<AppState>(thi
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.authorizedTunEnable, authorizedTunEnable) || other.authorizedTunEnable == authorizedTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
return identical(this, other) || (other.runtimeType == runtimeType&&other is AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other.packages, packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other.delayMap, delayMap)&&const DeepCollectionEquality().equals(other.groups, groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other.providers, providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.authorizedTunEnable, authorizedTunEnable) || other.authorizedTunEnable == authorizedTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
}
@override
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,authorizedTunEnable,loading,systemUiOverlayStyle,coreStatus]);
int get hashCode => Object.hashAll([runtimeType,isInit,pageLabel,const DeepCollectionEquality().hash(packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(delayMap),const DeepCollectionEquality().hash(groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(providers),localIp,requests,version,logs,traffics,totalTraffic,authorizedTunEnable,loading,systemUiOverlayStyle,coreStatus]);
@@ -41,7 +41,7 @@ abstract mixin class $AppStateCopyWith<$Res> {
factory $AppStateCopyWith(AppState value, $Res Function(AppState) _then) = _$AppStateCopyWithImpl;
@useResult
$Res call({
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
bool isInit, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
});
@@ -58,10 +58,9 @@ class _$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? authorizedTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
@pragma('vm:prefer-inline') @override $Res call({Object? isInit = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? authorizedTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
return _then(_self.copyWith(
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
as bool,pageLabel: null == pageLabel ? _self.pageLabel : pageLabel // ignore: cast_nullable_to_non_nullable
as PageLabel,packages: null == packages ? _self.packages : packages // ignore: cast_nullable_to_non_nullable
as List<Package>,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
@@ -177,10 +176,10 @@ return $default(_that);case _:
/// }
/// ```
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this;
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isInit, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,{required TResult orElse(),}) {final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return $default(_that.isInit,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return orElse();
}
@@ -198,10 +197,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
/// }
/// ```
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus) $default,) {final _that = this;
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isInit, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus) $default,) {final _that = this;
switch (_that) {
case _AppState():
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return $default(_that.isInit,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
throw StateError('Unexpected subclass');
}
@@ -218,10 +217,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
/// }
/// ```
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,) {final _that = this;
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isInit, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus)? $default,) {final _that = this;
switch (_that) {
case _AppState() when $default != null:
return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return $default(_that.isInit,_that.pageLabel,_that.packages,_that.sortNum,_that.viewSize,_that.sideWidth,_that.delayMap,_that.groups,_that.checkIpNum,_that.brightness,_that.runTime,_that.providers,_that.localIp,_that.requests,_that.version,_that.logs,_that.traffics,_that.totalTraffic,_that.authorizedTunEnable,_that.loading,_that.systemUiOverlayStyle,_that.coreStatus);case _:
return null;
}
@@ -233,11 +232,10 @@ return $default(_that.isInit,_that.backBlock,_that.pageLabel,_that.packages,_tha
class _AppState implements AppState {
const _AppState({this.isInit = false, this.backBlock = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.authorizedTunEnable = TunAuthorizationState.none, this.loading = false, required this.systemUiOverlayStyle, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers;
const _AppState({this.isInit = false, this.pageLabel = PageLabel.dashboard, final List<Package> packages = const [], this.sortNum = 0, required this.viewSize, this.sideWidth = 0, final DelayMap delayMap = const {}, final List<Group> groups = const [], this.checkIpNum = 0, required this.brightness, this.runTime, final List<ExternalProvider> providers = const [], this.localIp, required this.requests, required this.version, required this.logs, required this.traffics, required this.totalTraffic, this.authorizedTunEnable = TunAuthorizationState.none, this.loading = false, required this.systemUiOverlayStyle, this.coreStatus = CoreStatus.connecting}): _packages = packages,_delayMap = delayMap,_groups = groups,_providers = providers;
@override@JsonKey() final bool isInit;
@override@JsonKey() final bool backBlock;
@override@JsonKey() final PageLabel pageLabel;
final List<Package> _packages;
@override@JsonKey() List<Package> get packages {
@@ -294,12 +292,12 @@ _$AppStateCopyWith<_AppState> get copyWith => __$AppStateCopyWithImpl<_AppState>
@override
bool operator ==(Object other) {
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.backBlock, backBlock) || other.backBlock == backBlock)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.authorizedTunEnable, authorizedTunEnable) || other.authorizedTunEnable == authorizedTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
return identical(this, other) || (other.runtimeType == runtimeType&&other is _AppState&&(identical(other.isInit, isInit) || other.isInit == isInit)&&(identical(other.pageLabel, pageLabel) || other.pageLabel == pageLabel)&&const DeepCollectionEquality().equals(other._packages, _packages)&&(identical(other.sortNum, sortNum) || other.sortNum == sortNum)&&(identical(other.viewSize, viewSize) || other.viewSize == viewSize)&&(identical(other.sideWidth, sideWidth) || other.sideWidth == sideWidth)&&const DeepCollectionEquality().equals(other._delayMap, _delayMap)&&const DeepCollectionEquality().equals(other._groups, _groups)&&(identical(other.checkIpNum, checkIpNum) || other.checkIpNum == checkIpNum)&&(identical(other.brightness, brightness) || other.brightness == brightness)&&(identical(other.runTime, runTime) || other.runTime == runTime)&&const DeepCollectionEquality().equals(other._providers, _providers)&&(identical(other.localIp, localIp) || other.localIp == localIp)&&(identical(other.requests, requests) || other.requests == requests)&&(identical(other.version, version) || other.version == version)&&(identical(other.logs, logs) || other.logs == logs)&&(identical(other.traffics, traffics) || other.traffics == traffics)&&(identical(other.totalTraffic, totalTraffic) || other.totalTraffic == totalTraffic)&&(identical(other.authorizedTunEnable, authorizedTunEnable) || other.authorizedTunEnable == authorizedTunEnable)&&(identical(other.loading, loading) || other.loading == loading)&&(identical(other.systemUiOverlayStyle, systemUiOverlayStyle) || other.systemUiOverlayStyle == systemUiOverlayStyle)&&(identical(other.coreStatus, coreStatus) || other.coreStatus == coreStatus));
}
@override
int get hashCode => Object.hashAll([runtimeType,isInit,backBlock,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,authorizedTunEnable,loading,systemUiOverlayStyle,coreStatus]);
int get hashCode => Object.hashAll([runtimeType,isInit,pageLabel,const DeepCollectionEquality().hash(_packages),sortNum,viewSize,sideWidth,const DeepCollectionEquality().hash(_delayMap),const DeepCollectionEquality().hash(_groups),checkIpNum,brightness,runTime,const DeepCollectionEquality().hash(_providers),localIp,requests,version,logs,traffics,totalTraffic,authorizedTunEnable,loading,systemUiOverlayStyle,coreStatus]);
@@ -310,7 +308,7 @@ abstract mixin class _$AppStateCopyWith<$Res> implements $AppStateCopyWith<$Res>
factory _$AppStateCopyWith(_AppState value, $Res Function(_AppState) _then) = __$AppStateCopyWithImpl;
@override @useResult
$Res call({
bool isInit, bool backBlock, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
bool isInit, PageLabel pageLabel, List<Package> packages, int sortNum, Size viewSize, double sideWidth, DelayMap delayMap, List<Group> groups, int checkIpNum, Brightness brightness, int? runTime, List<ExternalProvider> providers, String? localIp, FixedList<TrackerInfo> requests, int version, FixedList<Log> logs, FixedList<Traffic> traffics, Traffic totalTraffic, TunAuthorizationState authorizedTunEnable, bool loading, SystemUiOverlayStyle systemUiOverlayStyle, CoreStatus coreStatus
});
@@ -327,10 +325,9 @@ class __$AppStateCopyWithImpl<$Res>
/// Create a copy of AppState
/// with the given fields replaced by the non-null parameter values.
@override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? backBlock = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? authorizedTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
@override @pragma('vm:prefer-inline') $Res call({Object? isInit = null,Object? pageLabel = null,Object? packages = null,Object? sortNum = null,Object? viewSize = null,Object? sideWidth = null,Object? delayMap = null,Object? groups = null,Object? checkIpNum = null,Object? brightness = null,Object? runTime = freezed,Object? providers = null,Object? localIp = freezed,Object? requests = null,Object? version = null,Object? logs = null,Object? traffics = null,Object? totalTraffic = null,Object? authorizedTunEnable = null,Object? loading = null,Object? systemUiOverlayStyle = null,Object? coreStatus = null,}) {
return _then(_AppState(
isInit: null == isInit ? _self.isInit : isInit // ignore: cast_nullable_to_non_nullable
as bool,backBlock: null == backBlock ? _self.backBlock : backBlock // ignore: cast_nullable_to_non_nullable
as bool,pageLabel: null == pageLabel ? _self.pageLabel : pageLabel // ignore: cast_nullable_to_non_nullable
as PageLabel,packages: null == packages ? _self._packages : packages // ignore: cast_nullable_to_non_nullable
as List<Package>,sortNum: null == sortNum ? _self.sortNum : sortNum // ignore: cast_nullable_to_non_nullable
+19 -12
View File
@@ -110,7 +110,24 @@ class HomePage extends ConsumerWidget {
onDidRemovePage: (_) {},
),
);
return view;
return Consumer(
key: ValueKey(navigationItem.label),
builder: (_, ref, child) {
final isActive = ref.watch(
currentPageLabelProvider.select(
(label) => label == navigationItem.label,
),
);
return PageActivityScope(
isActive: isActive,
child: ExcludeFocus(
excluding: !isActive,
child: child!,
),
);
},
child: view,
);
},
);
},
@@ -204,12 +221,6 @@ 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(),
@@ -224,11 +235,7 @@ class _HomePageViewState extends ConsumerState<_HomePageView> {
return index == -1 ? null : index;
},
itemBuilder: (context, index) {
return ExcludeFocus(
key: ValueKey(widget.navigationItems[index].label),
excluding: index != currentIndex,
child: widget.pageBuilder(context, index),
);
return widget.pageBuilder(context, index);
},
);
}
-3
View File
@@ -594,9 +594,6 @@ class SystemAction extends _$SystemAction {
}
Future<void> handleClose([bool exit = true]) async {
if (!system.isDesktop) {
if (ref.read(backBlockProvider)) return;
}
if (ref.read(appSettingProvider).minimizeOnExit || !exit) {
if (system.isDesktop) {
await preferences.saveConfig(ref.read(configProvider));
-17
View File
@@ -217,22 +217,6 @@ class CheckIpNum extends _$CheckIpNum with AutoDisposeNotifierMixin {
int add() => state++;
}
@Riverpod(keepAlive: true)
class BackBlock extends _$BackBlock with AutoDisposeNotifierMixin {
@override
bool build() {
return false;
}
void backBlock() {
value = true;
}
void unBackBlock() {
value = false;
}
}
@Riverpod(keepAlive: true)
class Version extends _$Version with AutoDisposeNotifierMixin {
@override
@@ -464,7 +448,6 @@ class LocationPermissions extends _$LocationPermissions
List<Override> buildAppStateOverrides(AppState appState) {
return [
initProvider.overrideWithBuild((_, _) => appState.isInit),
backBlockProvider.overrideWithBuild((_, _) => appState.backBlock),
currentPageLabelProvider.overrideWithBuild((_, _) => appState.pageLabel),
packagesProvider.overrideWithBuild((_, _) => appState.packages),
sortNumProvider.overrideWithBuild((_, _) => appState.sortNum),
+1 -1
View File
@@ -244,7 +244,7 @@ final class SystemActionProvider extends $NotifierProvider<SystemAction, void> {
}
}
String _$systemActionHash() => r'04f09af2702925aff276423a9c224fd8d3aca061';
String _$systemActionHash() => r'fe481ef6812b1aad42ed3f1a8a2459c9bbd289d9';
abstract class _$SystemAction extends $Notifier<void> {
void build();
-51
View File
@@ -999,57 +999,6 @@ abstract class _$CheckIpNum extends $Notifier<int> {
}
}
@ProviderFor(BackBlock)
final backBlockProvider = BackBlockProvider._();
final class BackBlockProvider extends $NotifierProvider<BackBlock, bool> {
BackBlockProvider._()
: super(
from: null,
argument: null,
retry: null,
name: r'backBlockProvider',
isAutoDispose: false,
dependencies: null,
$allTransitiveDependencies: null,
);
@override
String debugGetCreateSourceHash() => _$backBlockHash();
@$internal
@override
BackBlock create() => BackBlock();
/// {@macro riverpod.override_with_value}
Override overrideWithValue(bool value) {
return $ProviderOverride(
origin: this,
providerOverride: $SyncValueProvider<bool>(value),
);
}
}
String _$backBlockHash() => r'c867df920425f9b063807ca758dff8d849ca069e';
abstract class _$BackBlock extends $Notifier<bool> {
bool build();
@$mustCallSuper
@override
WhenComplete runBuild() {
final ref = this.ref as $Ref<bool, bool>;
final element =
ref.element
as $ClassProviderElement<
AnyNotifier<bool, bool>,
bool,
Object?,
Object?
>;
return element.handleCreate(ref, build);
}
}
@ProviderFor(Version)
final versionProvider = VersionProvider._();
+57 -25
View File
@@ -69,12 +69,12 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
? IconButton(
key: const ValueKey(true),
icon: const Icon(Icons.save, key: ValueKey('save-icon')),
onPressed: _handleUpdateIsEdit,
onPressed: _handleSaveAndExit,
)
: IconButton(
key: const ValueKey(false),
icon: const Icon(Icons.edit, key: ValueKey('edit-icon')),
onPressed: _handleUpdateIsEdit,
onPressed: _handleEnterEdit,
),
),
];
@@ -102,11 +102,32 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
);
}
Future<void> _handleUpdateIsEdit() async {
if (_isEditNotifier.value == true) {
await _handleSave();
void _handleEnterEdit() {
if (_isEditNotifier.value) {
return;
}
_isEditNotifier.value = true;
}
void _handleExitEdit() {
if (!_isEditNotifier.value) {
return;
}
final dashboardWidgets = _getDashboardWidgets(key.currentState);
if (dashboardWidgets != null) {
_saveDashboardWidgets(dashboardWidgets);
}
_isEditNotifier.value = false;
}
Future<void> _handleSaveAndExit() async {
if (!_isEditNotifier.value) {
return;
}
await _handleSave();
if (mounted) {
_isEditNotifier.value = false;
}
_isEditNotifier.value = !_isEditNotifier.value;
}
Future<void> _handleSave() async {
@@ -114,7 +135,7 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
if (currentState == null) {
return;
}
if (!mounted || currentState.children.isEmpty) {
if (!mounted || currentState.snapshotChildren.isEmpty) {
return;
}
final transformCompleted = await currentState.isTransformCompleter;
@@ -124,9 +145,25 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
!identical(key.currentState, currentState)) {
return;
}
final dashboardWidgets = currentState.children
.map((item) => DashboardWidget.getDashboardWidget(item))
.toList();
final dashboardWidgets = _getDashboardWidgets(currentState);
if (dashboardWidgets == null) {
return;
}
_saveDashboardWidgets(dashboardWidgets);
}
List<DashboardWidget>? _getDashboardWidgets(SuperGridState? currentState) {
if (currentState == null) {
return null;
}
final children = currentState.snapshotChildren;
if (children.isEmpty) {
return null;
}
return children.map(DashboardWidget.getDashboardWidget).toList();
}
void _saveDashboardWidgets(List<DashboardWidget> dashboardWidgets) {
ref
.read(appSettingProvider.notifier)
.update((state) => state.copyWith(dashboardWidgets: dashboardWidgets));
@@ -173,21 +210,16 @@ class _DashboardViewState extends ConsumerState<DashboardView> {
_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;
? BackLayerScope(
onBack: _handleExitEdit,
child: SuperGrid(
key: key,
crossAxisCount: columns,
crossAxisSpacing: spacing,
mainAxisSpacing: spacing,
children: children,
onUpdate: () {
_handleSave();
},
),
)
-10
View File
@@ -20,7 +20,6 @@ class ProxiesView extends ConsumerStatefulWidget {
}
class _ProxiesViewState extends ConsumerState<ProxiesView> {
final GlobalKey<CommonScaffoldState> _scaffoldKey = GlobalKey();
final GlobalKey<ProxiesTabViewState> _proxiesTabKey = GlobalKey();
bool _hasProviders = false;
bool _isTab = false;
@@ -122,14 +121,6 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
},
fireImmediately: true,
);
ref.listenManual(
currentPageLabelProvider.select((state) => state == PageLabel.proxies),
(prev, next) {
if (prev != next && next == false) {
_scaffoldKey.currentState?.handleExitSearching();
}
},
);
}
@override
@@ -139,7 +130,6 @@ class _ProxiesViewState extends ConsumerState<ProxiesView> {
);
final isLoading = ref.watch(loadingProvider(LoadingTag.proxies));
return CommonScaffold(
key: _scaffoldKey,
isLoading: isLoading,
resizeToAvoidBottomInset: false,
floatingActionButton: _buildFAB(),
+22
View File
@@ -2,6 +2,28 @@ import 'package:fl_clash/enum/enum.dart';
import 'package:fl_clash/widgets/sheet.dart';
import 'package:flutter/material.dart';
class PageActivityScope extends InheritedWidget {
final bool isActive;
const PageActivityScope({
super.key,
required this.isActive,
required super.child,
});
static bool isActiveOf(BuildContext context) {
return context
.dependOnInheritedWidgetOfExactType<PageActivityScope>()
?.isActive ??
true;
}
@override
bool updateShouldNotify(PageActivityScope oldWidget) {
return isActive != oldWidget.isActive;
}
}
class CommonScaffoldBackActionProvider extends InheritedWidget {
final VoidCallback? backAction;
+76 -15
View File
@@ -1,9 +1,8 @@
import 'dart:async';
import 'package:fl_clash/providers/app.dart';
import 'package:fl_clash/state.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'inherited.dart';
class CommonPopScope extends StatelessWidget {
final Widget child;
@@ -19,8 +18,10 @@ class CommonPopScope extends StatelessWidget {
@override
Widget build(BuildContext context) {
final route = ModalRoute.of(context);
final hasBackLayer = route?.willHandlePopInternally == true;
return PopScope(
canPop: onPop == null ? true : false,
canPop: onPop == null || hasBackLayer,
onPopInvokedWithResult: onPop == null
? null
: (didPop, _) async {
@@ -44,30 +45,90 @@ class CommonPopScope extends StatelessWidget {
}
}
class SystemBackBlock extends ConsumerStatefulWidget {
class BackLayerScope extends StatefulWidget {
final Widget child;
final VoidCallback onBack;
@visibleForTesting
final void Function(void Function(Duration) callback)?
schedulePostFrameCallback;
const SystemBackBlock({super.key, required this.child});
const BackLayerScope({
super.key,
required this.onBack,
required this.child,
@visibleForTesting this.schedulePostFrameCallback,
});
@override
ConsumerState<SystemBackBlock> createState() => _SystemBackBlockState();
State<BackLayerScope> createState() => _BackLayerScopeState();
}
class _SystemBackBlockState extends ConsumerState<SystemBackBlock> {
class _BackLayerScopeState extends State<BackLayerScope> {
ModalRoute<dynamic>? _route;
LocalHistoryEntry? _entry;
bool _isDetaching = false;
bool _isPageActive = true;
int _syncRevision = 0;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.container.read(backBlockProvider.notifier).backBlock();
void didChangeDependencies() {
super.didChangeDependencies();
final route = ModalRoute.of(context);
final isPageActive = PageActivityScope.isActiveOf(context);
if (identical(_route, route) && _isPageActive == isPageActive) {
return;
}
_detach();
_route = route;
_isPageActive = isPageActive;
final revision = ++_syncRevision;
final schedulePostFrameCallback =
widget.schedulePostFrameCallback ??
WidgetsBinding.instance.addPostFrameCallback;
schedulePostFrameCallback((_) {
if (!mounted || revision != _syncRevision) {
return;
}
if (!_isPageActive) {
widget.onBack();
return;
}
if (route == null) {
return;
}
final entry = LocalHistoryEntry(
impliesAppBarDismissal: false,
onRemove: _handleRemove,
);
_entry = entry;
route.addLocalHistoryEntry(entry);
});
}
void _handleRemove() {
_entry = null;
if (!_isDetaching && mounted) {
widget.onBack();
}
}
void _detach() {
final entry = _entry;
if (entry == null) {
return;
}
_entry = null;
_isDetaching = true;
entry.remove();
_isDetaching = false;
}
@override
void dispose() {
_syncRevision++;
_detach();
_route = null;
super.dispose();
WidgetsBinding.instance.addPostFrameCallback((_) {
globalState.container.read(backBlockProvider.notifier).unBackBlock();
});
}
@override
+18 -16
View File
@@ -139,7 +139,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
_handleClearInput();
return;
}
_updateSearchState((state) => state?.copyWith(query: null));
_popAppBarLayer();
}
void handleExitSearching() {
@@ -150,6 +150,20 @@ class CommonScaffoldState extends State<CommonScaffold> {
_updateSearchState((state) => state?.copyWith(query: null));
}
void _handleExitAppBarLayer() {
handleExitSearching();
if (_isEdit) {
_appBarState.value.editState?.onExit();
}
}
void _popAppBarLayer() {
if (!_isEdit && !_isSearch) {
return;
}
Navigator.of(context).pop();
}
@override
void dispose() {
_appBarState.dispose();
@@ -177,13 +191,13 @@ class CommonScaffoldState extends State<CommonScaffold> {
Widget? _buildLeading(VoidCallback? backAction) {
if (_isEdit) {
return IconButton(
onPressed: _appBarState.value.editState?.onExit,
onPressed: _popAppBarLayer,
icon: const Icon(Icons.close),
);
}
if (_isSearch) {
return IconButton(
onPressed: handleExitSearching,
onPressed: _popAppBarLayer,
icon: const Icon(Icons.arrow_back),
);
}
@@ -244,19 +258,7 @@ class CommonScaffoldState extends State<CommonScaffold> {
Widget _buildAppBarWrap(Widget child) {
final appBar = _isSearch ? _buildSearchingAppBarTheme(child) : child;
if (_isEdit || _isSearch) {
return SystemBackBlock(
child: CommonPopScope(
onPop: (context) {
if (_isEdit || _isSearch) {
handleExitSearching();
_appBarState.value.editState?.onExit();
return false;
}
return true;
},
child: appBar,
),
);
return BackLayerScope(onBack: _handleExitAppBarLayer, child: appBar);
}
return appBar;
}
+11 -3
View File
@@ -37,6 +37,10 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
late final ValueNotifier<List<GridItem>> _childrenNotifier;
List<GridItem> children = [];
List<GridItem>? _pendingChildren;
List<GridItem> get snapshotChildren =>
List<GridItem>.unmodifiable(_pendingChildren ?? children);
int get length => _childrenNotifier.value.length;
List<int> _tempIndexList = [];
@@ -355,13 +359,16 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_preTransformState();
final indexWhere = _tempIndexList.indexWhere((i) => i == index);
_tempIndexList.removeAt(indexWhere);
final nextChildren = List<GridItem>.from(_childrenNotifier.value)
..removeAt(index);
_pendingChildren = nextChildren;
final completed = await _transform();
if (!completed || !mounted) {
_pendingChildren = null;
return;
}
final children = List<GridItem>.from(_childrenNotifier.value);
children.removeAt(index);
_childrenNotifier.value = children;
_childrenNotifier.value = nextChildren;
_pendingChildren = null;
_initState();
}
@@ -606,6 +613,7 @@ class SuperGridState extends State<SuperGrid> with TickerProviderStateMixin {
_childrenNotifier.removeListener(_handleChildrenChanged);
_childrenNotifier.value = const [];
children = const [];
_pendingChildren = null;
_itemContexts = [];
_sizes = [];
_offsets = [];