Files
MWClash/lib/widgets/open_container.dart
chen08209 4e679f776e Optimize performance
Update core

Optimize core stability

Fix linux tun authority check error

Fix some issues
2025-03-05 10:21:51 +08:00

590 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
typedef CloseContainerActionCallback<S> = void Function({S? returnValue});
typedef OpenContainerBuilder<S> = Widget Function(
BuildContext context,
CloseContainerActionCallback<S> action,
);
typedef CloseContainerBuilder = Widget Function(
BuildContext context,
VoidCallback action,
);
enum ContainerTransitionType {
fade,
fadeThrough,
}
typedef ClosedCallback<S> = void Function(S data);
@optionalTypeArgs
class OpenContainer<T extends Object?> extends StatefulWidget {
const OpenContainer({
super.key,
this.middleColor,
this.onClosed,
required this.closedBuilder,
required this.openBuilder,
this.tappable = true,
this.transitionDuration = const Duration(milliseconds: 300),
this.transitionType = ContainerTransitionType.fade,
this.useRootNavigator = false,
this.routeSettings,
this.clipBehavior = Clip.antiAlias,
});
final Color? middleColor;
final ClosedCallback<T?>? onClosed;
final CloseContainerBuilder closedBuilder;
final OpenContainerBuilder<T> openBuilder;
final bool tappable;
final Duration transitionDuration;
final ContainerTransitionType transitionType;
final bool useRootNavigator;
final RouteSettings? routeSettings;
final Clip clipBehavior;
@override
State<OpenContainer<T?>> createState() => _OpenContainerState<T>();
}
class _OpenContainerState<T> extends State<OpenContainer<T?>> {
final GlobalKey<_HideableState> _hideableKey = GlobalKey<_HideableState>();
final GlobalKey _closedBuilderKey = GlobalKey();
Future<void> openContainer() async {
final Color middleColor =
widget.middleColor ?? Theme.of(context).canvasColor;
final T? data = await Navigator.of(
context,
rootNavigator: widget.useRootNavigator,
).push(_OpenContainerRoute<T>(
middleColor: middleColor,
closedBuilder: widget.closedBuilder,
openBuilder: widget.openBuilder,
hideableKey: _hideableKey,
closedBuilderKey: _closedBuilderKey,
transitionDuration: widget.transitionDuration,
transitionType: widget.transitionType,
useRootNavigator: widget.useRootNavigator,
routeSettings: widget.routeSettings,
));
if (widget.onClosed != null) {
widget.onClosed!(data);
}
}
@override
Widget build(BuildContext context) {
return _Hideable(
key: _hideableKey,
child: GestureDetector(
onTap: widget.tappable ? openContainer : null,
child: Material(
clipBehavior: widget.clipBehavior,
child: Builder(
key: _closedBuilderKey,
builder: (BuildContext context) {
return widget.closedBuilder(context, openContainer);
},
),
),
),
);
}
}
class _Hideable extends StatefulWidget {
const _Hideable({
super.key,
required this.child,
});
final Widget child;
@override
State<_Hideable> createState() => _HideableState();
}
class _HideableState extends State<_Hideable> {
Size? get placeholderSize => _placeholderSize;
Size? _placeholderSize;
set placeholderSize(Size? value) {
if (_placeholderSize == value) {
return;
}
setState(() {
_placeholderSize = value;
});
}
bool get isVisible => _visible;
bool _visible = true;
set isVisible(bool value) {
if (_visible == value) {
return;
}
setState(() {
_visible = value;
});
}
bool get isInTree => _placeholderSize == null;
@override
Widget build(BuildContext context) {
if (_placeholderSize != null) {
return SizedBox.fromSize(size: _placeholderSize);
}
return Visibility(
visible: _visible,
maintainSize: true,
maintainState: true,
maintainAnimation: true,
child: widget.child,
);
}
}
class _OpenContainerRoute<T> extends ModalRoute<T> {
_OpenContainerRoute({
required this.middleColor,
required this.closedBuilder,
required this.openBuilder,
required this.hideableKey,
required this.closedBuilderKey,
required this.transitionDuration,
required this.transitionType,
required this.useRootNavigator,
required RouteSettings? routeSettings,
}) : _closedOpacityTween = _getClosedOpacityTween(transitionType),
_openOpacityTween = _getOpenOpacityTween(transitionType),
super(settings: routeSettings);
static _FlippableTweenSequence<Color?> _getColorTween({
required ContainerTransitionType transitionType,
required Color closedColor,
required Color openColor,
required Color middleColor,
}) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(closedColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: openColor),
weight: 1 / 5,
),
TweenSequenceItem<Color>(
tween: ConstantTween<Color>(openColor),
weight: 3 / 5,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<Color?>(
<TweenSequenceItem<Color?>>[
TweenSequenceItem<Color?>(
tween: ColorTween(begin: closedColor, end: middleColor),
weight: 1 / 5,
),
TweenSequenceItem<Color?>(
tween: ColorTween(begin: middleColor, end: openColor),
weight: 4 / 5,
),
],
);
}
}
static _FlippableTweenSequence<double> _getClosedOpacityTween(
ContainerTransitionType transitionType) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 1,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: Tween<double>(begin: 1.0, end: 0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 4 / 5,
),
],
);
}
}
static _FlippableTweenSequence<double> _getOpenOpacityTween(
ContainerTransitionType transitionType) {
switch (transitionType) {
case ContainerTransitionType.fade:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: ConstantTween<double>(1.0),
weight: 3 / 5,
),
],
);
case ContainerTransitionType.fadeThrough:
return _FlippableTweenSequence<double>(
<TweenSequenceItem<double>>[
TweenSequenceItem<double>(
tween: ConstantTween<double>(0.0),
weight: 1 / 5,
),
TweenSequenceItem<double>(
tween: Tween<double>(begin: 0.0, end: 1.0),
weight: 4 / 5,
),
],
);
}
}
final Color middleColor;
final CloseContainerBuilder closedBuilder;
final OpenContainerBuilder<T> openBuilder;
final GlobalKey<_HideableState> hideableKey;
final GlobalKey closedBuilderKey;
@override
final Duration transitionDuration;
final ContainerTransitionType transitionType;
final bool useRootNavigator;
final _FlippableTweenSequence<double> _closedOpacityTween;
final _FlippableTweenSequence<double> _openOpacityTween;
late _FlippableTweenSequence<Color?> _colorTween;
final GlobalKey _openBuilderKey = GlobalKey();
final RectTween _rectTween = RectTween();
AnimationStatus? _lastAnimationStatus;
AnimationStatus? _currentAnimationStatus;
@override
TickerFuture didPush() {
_takeMeasurements(navigatorContext: hideableKey.currentContext!);
animation!.addStatusListener((AnimationStatus status) {
_lastAnimationStatus = _currentAnimationStatus;
_currentAnimationStatus = status;
switch (status) {
case AnimationStatus.dismissed:
_toggleHideable(hide: false);
break;
case AnimationStatus.completed:
_toggleHideable(hide: true);
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
break;
}
});
return super.didPush();
}
@override
bool didPop(T? result) {
_takeMeasurements(
navigatorContext: subtreeContext!,
delayForSourceRoute: true,
);
return super.didPop(result);
}
@override
void dispose() {
if (hideableKey.currentState?.isVisible == false) {
SchedulerBinding.instance
.addPostFrameCallback((Duration d) => _toggleHideable(hide: false));
}
super.dispose();
}
void _toggleHideable({required bool hide}) {
if (hideableKey.currentState != null) {
hideableKey.currentState!
..placeholderSize = null
..isVisible = !hide;
}
}
void _takeMeasurements({
required BuildContext navigatorContext,
bool delayForSourceRoute = false,
}) {
final RenderBox navigator = Navigator.of(
navigatorContext,
rootNavigator: useRootNavigator,
).context.findRenderObject()! as RenderBox;
final Size navSize = _getSize(navigator);
_rectTween.end = Offset.zero & navSize;
void takeMeasurementsInSourceRoute([Duration? _]) {
if (!navigator.attached || hideableKey.currentContext == null) {
return;
}
_rectTween.begin = _getRect(hideableKey, navigator);
hideableKey.currentState!.placeholderSize = _rectTween.begin!.size;
}
if (delayForSourceRoute) {
SchedulerBinding.instance
.addPostFrameCallback(takeMeasurementsInSourceRoute);
} else {
takeMeasurementsInSourceRoute();
}
}
Size _getSize(RenderBox render) {
assert(render.hasSize);
return render.size;
}
Rect _getRect(GlobalKey key, RenderBox ancestor) {
assert(key.currentContext != null);
assert(ancestor.hasSize);
final RenderBox render =
key.currentContext!.findRenderObject()! as RenderBox;
assert(render.hasSize);
return MatrixUtils.transformRect(
render.getTransformTo(ancestor),
Offset.zero & render.size,
);
}
bool get _transitionWasInterrupted {
bool wasInProgress = false;
bool isInProgress = false;
switch (_currentAnimationStatus) {
case AnimationStatus.completed:
case AnimationStatus.dismissed:
isInProgress = false;
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
isInProgress = true;
break;
case null:
break;
}
switch (_lastAnimationStatus) {
case AnimationStatus.completed:
case AnimationStatus.dismissed:
wasInProgress = false;
break;
case AnimationStatus.forward:
case AnimationStatus.reverse:
wasInProgress = true;
break;
case null:
break;
}
return wasInProgress && isInProgress;
}
void closeContainer({T? returnValue}) {
Navigator.of(subtreeContext!).pop(returnValue);
}
@override
Widget buildPage(
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
) {
_colorTween = _getColorTween(
transitionType: transitionType,
closedColor: Theme.of(context).colorScheme.surface,
openColor: Theme.of(context).colorScheme.surface,
middleColor: middleColor,
);
return Align(
alignment: Alignment.topLeft,
child: AnimatedBuilder(
animation: animation,
builder: (BuildContext context, Widget? child) {
if (animation.isCompleted) {
return SizedBox.expand(
child: Material(
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
);
}
final Animation<double> curvedAnimation = CurvedAnimation(
parent: animation,
curve: Curves.fastOutSlowIn,
reverseCurve:
_transitionWasInterrupted ? null : Curves.fastOutSlowIn.flipped,
);
TweenSequence<Color?>? colorTween;
TweenSequence<double>? closedOpacityTween, openOpacityTween;
switch (animation.status) {
case AnimationStatus.dismissed:
case AnimationStatus.forward:
closedOpacityTween = _closedOpacityTween;
openOpacityTween = _openOpacityTween;
colorTween = _colorTween;
break;
case AnimationStatus.reverse:
if (_transitionWasInterrupted) {
closedOpacityTween = _closedOpacityTween;
openOpacityTween = _openOpacityTween;
colorTween = _colorTween;
break;
}
closedOpacityTween = _closedOpacityTween.flipped;
openOpacityTween = _openOpacityTween.flipped;
colorTween = _colorTween.flipped;
break;
case AnimationStatus.completed:
assert(false); // Unreachable.
break;
}
assert(colorTween != null);
assert(closedOpacityTween != null);
assert(openOpacityTween != null);
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: 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.
FittedBox(
fit: BoxFit.fitWidth,
alignment: Alignment.topLeft,
child: SizedBox(
width: _rectTween.end!.width,
height: _rectTween.end!.height,
child: FadeTransition(
opacity: openOpacityTween!.animate(animation),
child: Builder(
key: _openBuilderKey,
builder: (BuildContext context) {
return openBuilder(context, closeContainer);
},
),
),
),
),
],
),
),
),
),
),
);
},
),
);
}
@override
bool get maintainState => true;
@override
Color? get barrierColor => null;
@override
bool get opaque => true;
@override
bool get barrierDismissible => false;
@override
String? get barrierLabel => null;
}
class _FlippableTweenSequence<T> extends TweenSequence<T> {
_FlippableTweenSequence(this._items) : super(_items);
final List<TweenSequenceItem<T>> _items;
_FlippableTweenSequence<T>? _flipped;
_FlippableTweenSequence<T>? get flipped {
if (_flipped == null) {
final List<TweenSequenceItem<T>> newItems = <TweenSequenceItem<T>>[];
for (int i = 0; i < _items.length; i++) {
newItems.add(TweenSequenceItem<T>(
tween: _items[i].tween,
weight: _items[_items.length - 1 - i].weight,
));
}
_flipped = _FlippableTweenSequence<T>(newItems);
}
return _flipped;
}
}