Support core status check and force restart Optimize proxies page and access page Update flutter and pub dependencies Update go version Optimize more details
656 lines
18 KiB
Dart
656 lines
18 KiB
Dart
import 'dart:ui';
|
|
|
|
import 'package:fl_clash/common/color.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:flutter/rendering.dart';
|
|
|
|
const Duration _bottomSheetEnterDuration = Duration(milliseconds: 300);
|
|
const Duration _bottomSheetExitDuration = Duration(milliseconds: 200);
|
|
const Curve _modalBottomSheetCurve = Easing.standardDecelerate;
|
|
const double _defaultScrollControlDisabledMaxHeightRatio = 9.0 / 16.0;
|
|
|
|
class SideSheet extends StatefulWidget {
|
|
const SideSheet({
|
|
super.key,
|
|
this.animationController,
|
|
this.enableDrag = true,
|
|
this.showDragHandle,
|
|
this.dragHandleColor,
|
|
this.dragHandleSize,
|
|
this.onDragStart,
|
|
this.onDragEnd,
|
|
this.backgroundColor,
|
|
this.shadowColor,
|
|
this.elevation,
|
|
this.shape,
|
|
this.clipBehavior,
|
|
this.constraints,
|
|
required this.onClosing,
|
|
required this.builder,
|
|
}) : assert(elevation == null || elevation >= 0.0);
|
|
|
|
final AnimationController? animationController;
|
|
|
|
final VoidCallback onClosing;
|
|
|
|
final WidgetBuilder builder;
|
|
|
|
final bool enableDrag;
|
|
|
|
final bool? showDragHandle;
|
|
|
|
final Color? dragHandleColor;
|
|
|
|
final Size? dragHandleSize;
|
|
|
|
final BottomSheetDragStartHandler? onDragStart;
|
|
|
|
final BottomSheetDragEndHandler? onDragEnd;
|
|
|
|
final Color? backgroundColor;
|
|
|
|
final Color? shadowColor;
|
|
|
|
final double? elevation;
|
|
|
|
final ShapeBorder? shape;
|
|
|
|
final Clip? clipBehavior;
|
|
|
|
final BoxConstraints? constraints;
|
|
|
|
@override
|
|
State<SideSheet> createState() => _SideSheetState();
|
|
|
|
static AnimationController createAnimationController(TickerProvider vsync) {
|
|
return AnimationController(
|
|
duration: _bottomSheetEnterDuration,
|
|
reverseDuration: _bottomSheetExitDuration,
|
|
debugLabel: 'SideSheet',
|
|
vsync: vsync,
|
|
);
|
|
}
|
|
}
|
|
|
|
class _SideSheetState extends State<SideSheet> {
|
|
final GlobalKey _childKey = GlobalKey(debugLabel: 'SideSheet child');
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
final Color color = widget.backgroundColor ?? colorScheme.surface;
|
|
final Color surfaceTintColor = colorScheme.surfaceTint;
|
|
final Color shadowColor = widget.shadowColor ?? Colors.transparent;
|
|
final double elevation = widget.elevation ?? 0;
|
|
final ShapeBorder shape =
|
|
widget.shape ??
|
|
RoundedSuperellipseBorder(borderRadius: BorderRadius.circular(0));
|
|
|
|
final BoxConstraints constraints =
|
|
widget.constraints ??
|
|
const BoxConstraints(maxWidth: 320, minWidth: 320);
|
|
|
|
final Clip clipBehavior = widget.clipBehavior ?? Clip.none;
|
|
|
|
Widget sideSheet = Material(
|
|
key: _childKey,
|
|
color: color,
|
|
elevation: elevation,
|
|
surfaceTintColor: surfaceTintColor,
|
|
shadowColor: shadowColor,
|
|
shape: shape,
|
|
clipBehavior: clipBehavior,
|
|
child: widget.builder(context),
|
|
);
|
|
|
|
return ConstrainedBox(constraints: constraints, child: sideSheet);
|
|
}
|
|
}
|
|
|
|
typedef _SizeChangeCallback<Size> = void Function(Size);
|
|
|
|
class _SideSheetLayoutWithSizeListener extends SingleChildRenderObjectWidget {
|
|
const _SideSheetLayoutWithSizeListener({
|
|
required this.onChildSizeChanged,
|
|
required this.animationValue,
|
|
required this.isScrollControlled,
|
|
required this.scrollControlDisabledMaxHeightRatio,
|
|
super.child,
|
|
});
|
|
|
|
final _SizeChangeCallback<Size> onChildSizeChanged;
|
|
final double animationValue;
|
|
final bool isScrollControlled;
|
|
final double scrollControlDisabledMaxHeightRatio;
|
|
|
|
@override
|
|
_RenderSideSheetLayoutWithSizeListener createRenderObject(
|
|
BuildContext context,
|
|
) {
|
|
return _RenderSideSheetLayoutWithSizeListener(
|
|
onChildSizeChanged: onChildSizeChanged,
|
|
animationValue: animationValue,
|
|
isScrollControlled: isScrollControlled,
|
|
scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
|
|
);
|
|
}
|
|
|
|
@override
|
|
void updateRenderObject(
|
|
BuildContext context,
|
|
_RenderSideSheetLayoutWithSizeListener renderObject,
|
|
) {
|
|
renderObject.onChildSizeChanged = onChildSizeChanged;
|
|
renderObject.animationValue = animationValue;
|
|
renderObject.isScrollControlled = isScrollControlled;
|
|
renderObject.scrollControlDisabledMaxHeightRatio =
|
|
scrollControlDisabledMaxHeightRatio;
|
|
}
|
|
}
|
|
|
|
class _RenderSideSheetLayoutWithSizeListener extends RenderShiftedBox {
|
|
_RenderSideSheetLayoutWithSizeListener({
|
|
RenderBox? child,
|
|
required _SizeChangeCallback<Size> onChildSizeChanged,
|
|
required double animationValue,
|
|
required bool isScrollControlled,
|
|
required double scrollControlDisabledMaxHeightRatio,
|
|
}) : _onChildSizeChanged = onChildSizeChanged,
|
|
_animationValue = animationValue,
|
|
_isScrollControlled = isScrollControlled,
|
|
_scrollControlDisabledMaxHeightRatio =
|
|
scrollControlDisabledMaxHeightRatio,
|
|
super(child);
|
|
|
|
Size _lastSize = Size.zero;
|
|
|
|
_SizeChangeCallback<Size> get onChildSizeChanged => _onChildSizeChanged;
|
|
_SizeChangeCallback<Size> _onChildSizeChanged;
|
|
|
|
set onChildSizeChanged(_SizeChangeCallback<Size> newCallback) {
|
|
if (_onChildSizeChanged == newCallback) {
|
|
return;
|
|
}
|
|
|
|
_onChildSizeChanged = newCallback;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double get animationValue => _animationValue;
|
|
double _animationValue;
|
|
|
|
set animationValue(double newValue) {
|
|
if (_animationValue == newValue) {
|
|
return;
|
|
}
|
|
|
|
_animationValue = newValue;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
bool get isScrollControlled => _isScrollControlled;
|
|
bool _isScrollControlled;
|
|
|
|
set isScrollControlled(bool newValue) {
|
|
if (_isScrollControlled == newValue) {
|
|
return;
|
|
}
|
|
|
|
_isScrollControlled = newValue;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
double get scrollControlDisabledMaxHeightRatio =>
|
|
_scrollControlDisabledMaxHeightRatio;
|
|
double _scrollControlDisabledMaxHeightRatio;
|
|
|
|
set scrollControlDisabledMaxHeightRatio(double newValue) {
|
|
if (_scrollControlDisabledMaxHeightRatio == newValue) {
|
|
return;
|
|
}
|
|
|
|
_scrollControlDisabledMaxHeightRatio = newValue;
|
|
markNeedsLayout();
|
|
}
|
|
|
|
Size _getSize(BoxConstraints constraints) {
|
|
return constraints.constrain(constraints.biggest);
|
|
}
|
|
|
|
@override
|
|
double computeMinIntrinsicWidth(double height) {
|
|
final double width = _getSize(
|
|
BoxConstraints.tightForFinite(height: height),
|
|
).width;
|
|
if (width.isFinite) {
|
|
return width;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
@override
|
|
double computeMaxIntrinsicWidth(double height) {
|
|
final double width = _getSize(
|
|
BoxConstraints.tightForFinite(height: height),
|
|
).width;
|
|
if (width.isFinite) {
|
|
return width;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
@override
|
|
double computeMinIntrinsicHeight(double width) {
|
|
final double height = _getSize(
|
|
BoxConstraints.tightForFinite(width: width),
|
|
).height;
|
|
if (height.isFinite) {
|
|
return height;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
@override
|
|
double computeMaxIntrinsicHeight(double width) {
|
|
final double height = _getSize(
|
|
BoxConstraints.tightForFinite(width: width),
|
|
).height;
|
|
if (height.isFinite) {
|
|
return height;
|
|
}
|
|
return 0.0;
|
|
}
|
|
|
|
@override
|
|
Size computeDryLayout(BoxConstraints constraints) {
|
|
return _getSize(constraints);
|
|
}
|
|
|
|
BoxConstraints _getConstraintsForChild(BoxConstraints constraints) {
|
|
return BoxConstraints(maxHeight: constraints.maxHeight);
|
|
}
|
|
|
|
Offset _getPositionForChild(Size size, Size childSize) {
|
|
return Offset(size.width - childSize.width * animationValue, 0.0);
|
|
}
|
|
|
|
@override
|
|
void performLayout() {
|
|
size = _getSize(constraints);
|
|
if (child != null) {
|
|
final BoxConstraints childConstraints = _getConstraintsForChild(
|
|
constraints,
|
|
);
|
|
assert(childConstraints.debugAssertIsValid(isAppliedConstraint: true));
|
|
child!.layout(
|
|
childConstraints,
|
|
parentUsesSize: !childConstraints.isTight,
|
|
);
|
|
final BoxParentData childParentData = child!.parentData! as BoxParentData;
|
|
childParentData.offset = _getPositionForChild(
|
|
size,
|
|
childConstraints.isTight ? childConstraints.smallest : child!.size,
|
|
);
|
|
final Size childSize = childConstraints.isTight
|
|
? childConstraints.smallest
|
|
: child!.size;
|
|
|
|
if (_lastSize != childSize) {
|
|
_lastSize = childSize;
|
|
_onChildSizeChanged.call(_lastSize);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
class _ModalSideSheet<T> extends StatefulWidget {
|
|
const _ModalSideSheet({
|
|
super.key,
|
|
required this.route,
|
|
this.backgroundColor,
|
|
this.elevation,
|
|
this.shape,
|
|
this.clipBehavior,
|
|
this.constraints,
|
|
this.isScrollControlled = false,
|
|
this.scrollControlDisabledMaxHeightRatio =
|
|
_defaultScrollControlDisabledMaxHeightRatio,
|
|
this.enableDrag = true,
|
|
this.showDragHandle = false,
|
|
});
|
|
|
|
final ModalSideSheetRoute<T> route;
|
|
final bool isScrollControlled;
|
|
final double scrollControlDisabledMaxHeightRatio;
|
|
final Color? backgroundColor;
|
|
final double? elevation;
|
|
final ShapeBorder? shape;
|
|
final Clip? clipBehavior;
|
|
final BoxConstraints? constraints;
|
|
final bool enableDrag;
|
|
final bool showDragHandle;
|
|
|
|
@override
|
|
_ModalSideSheetState<T> createState() => _ModalSideSheetState<T>();
|
|
}
|
|
|
|
class _ModalSideSheetState<T> extends State<_ModalSideSheet<T>> {
|
|
ParametricCurve<double> animationCurve = _modalBottomSheetCurve;
|
|
|
|
String _getRouteLabel(MaterialLocalizations localizations) {
|
|
switch (Theme.of(context).platform) {
|
|
case TargetPlatform.iOS:
|
|
case TargetPlatform.macOS:
|
|
return '';
|
|
case TargetPlatform.android:
|
|
case TargetPlatform.fuchsia:
|
|
case TargetPlatform.linux:
|
|
case TargetPlatform.windows:
|
|
return localizations.dialogLabel;
|
|
}
|
|
}
|
|
|
|
EdgeInsets _getNewClipDetails(Size topLayerSize) {
|
|
return EdgeInsets.fromLTRB(0, 0, 0, topLayerSize.height);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
assert(debugCheckHasMediaQuery(context));
|
|
assert(debugCheckHasMaterialLocalizations(context));
|
|
final MaterialLocalizations localizations = MaterialLocalizations.of(
|
|
context,
|
|
);
|
|
final String routeLabel = _getRouteLabel(localizations);
|
|
|
|
return AnimatedBuilder(
|
|
animation: widget.route.animation!,
|
|
child: SideSheet(
|
|
animationController: widget.route._animationController,
|
|
onClosing: () {
|
|
if (widget.route.isCurrent) {
|
|
Navigator.pop(context);
|
|
}
|
|
},
|
|
builder: widget.route.builder,
|
|
backgroundColor: widget.backgroundColor,
|
|
elevation: widget.elevation,
|
|
shape: widget.shape,
|
|
clipBehavior: widget.clipBehavior,
|
|
constraints: widget.constraints,
|
|
enableDrag: widget.enableDrag,
|
|
showDragHandle: widget.showDragHandle,
|
|
),
|
|
builder: (BuildContext context, Widget? child) {
|
|
final double animationValue = animationCurve.transform(
|
|
widget.route.animation!.value,
|
|
);
|
|
return Semantics(
|
|
scopesRoute: true,
|
|
namesRoute: true,
|
|
label: routeLabel,
|
|
explicitChildNodes: true,
|
|
child: ClipRect(
|
|
child: _SideSheetLayoutWithSizeListener(
|
|
onChildSizeChanged: (Size size) {
|
|
widget.route._didChangeBarrierSemanticsClip(
|
|
_getNewClipDetails(size),
|
|
);
|
|
},
|
|
animationValue: animationValue,
|
|
isScrollControlled: widget.isScrollControlled,
|
|
scrollControlDisabledMaxHeightRatio:
|
|
widget.scrollControlDisabledMaxHeightRatio,
|
|
child: child,
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
class ModalSideSheetRoute<T> extends PopupRoute<T> {
|
|
ModalSideSheetRoute({
|
|
required this.builder,
|
|
this.capturedThemes,
|
|
this.barrierLabel,
|
|
this.barrierOnTapHint,
|
|
this.backgroundColor,
|
|
this.elevation,
|
|
this.shape,
|
|
this.clipBehavior,
|
|
this.constraints,
|
|
this.modalBarrierColor,
|
|
this.isDismissible = true,
|
|
this.isScrollControlled = false,
|
|
this.scrollControlDisabledMaxHeightRatio =
|
|
_defaultScrollControlDisabledMaxHeightRatio,
|
|
super.settings,
|
|
this.transitionAnimationController,
|
|
this.anchorPoint,
|
|
this.useSafeArea = false,
|
|
super.filter,
|
|
});
|
|
|
|
final WidgetBuilder builder;
|
|
|
|
final CapturedThemes? capturedThemes;
|
|
|
|
final bool isScrollControlled;
|
|
|
|
final double scrollControlDisabledMaxHeightRatio;
|
|
|
|
final Color? backgroundColor;
|
|
|
|
final double? elevation;
|
|
|
|
final ShapeBorder? shape;
|
|
|
|
final Clip? clipBehavior;
|
|
|
|
final BoxConstraints? constraints;
|
|
|
|
final Color? modalBarrierColor;
|
|
|
|
final bool isDismissible;
|
|
|
|
final AnimationController? transitionAnimationController;
|
|
|
|
final Offset? anchorPoint;
|
|
|
|
final bool useSafeArea;
|
|
|
|
final String? barrierOnTapHint;
|
|
|
|
final ValueNotifier<EdgeInsets> _clipDetailsNotifier =
|
|
ValueNotifier<EdgeInsets>(EdgeInsets.zero);
|
|
|
|
@override
|
|
void dispose() {
|
|
_clipDetailsNotifier.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
bool _didChangeBarrierSemanticsClip(EdgeInsets newClipDetails) {
|
|
if (_clipDetailsNotifier.value == newClipDetails) {
|
|
return false;
|
|
}
|
|
_clipDetailsNotifier.value = newClipDetails;
|
|
return true;
|
|
}
|
|
|
|
@override
|
|
Duration get transitionDuration => _bottomSheetEnterDuration;
|
|
|
|
@override
|
|
Duration get reverseTransitionDuration => _bottomSheetExitDuration;
|
|
|
|
@override
|
|
bool get barrierDismissible => isDismissible;
|
|
|
|
@override
|
|
final String? barrierLabel;
|
|
|
|
@override
|
|
Color get barrierColor => modalBarrierColor ?? Colors.black54;
|
|
|
|
AnimationController? _animationController;
|
|
|
|
@override
|
|
AnimationController createAnimationController() {
|
|
assert(_animationController == null);
|
|
if (transitionAnimationController != null) {
|
|
_animationController = transitionAnimationController;
|
|
willDisposeAnimationController = false;
|
|
} else {
|
|
_animationController = SideSheet.createAnimationController(navigator!);
|
|
}
|
|
return _animationController!;
|
|
}
|
|
|
|
@override
|
|
Widget buildPage(
|
|
BuildContext context,
|
|
Animation<double> animation,
|
|
Animation<double> secondaryAnimation,
|
|
) {
|
|
final Widget content = DisplayFeatureSubScreen(
|
|
anchorPoint: anchorPoint,
|
|
child: Builder(
|
|
builder: (BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
return _ModalSideSheet<T>(
|
|
route: this,
|
|
backgroundColor: backgroundColor ?? colorScheme.surface,
|
|
elevation: elevation ?? 0,
|
|
shape: shape,
|
|
clipBehavior: clipBehavior,
|
|
constraints: constraints,
|
|
isScrollControlled: isScrollControlled,
|
|
scrollControlDisabledMaxHeightRatio:
|
|
scrollControlDisabledMaxHeightRatio,
|
|
);
|
|
},
|
|
),
|
|
);
|
|
|
|
final Widget sideSheet = content;
|
|
|
|
return capturedThemes?.wrap(sideSheet) ?? sideSheet;
|
|
}
|
|
|
|
@override
|
|
Widget buildModalBarrier() {
|
|
if (barrierColor.a != 0 && !offstage) {
|
|
assert(barrierColor != barrierColor.opacity0);
|
|
final Animation<Color?> color = animation!.drive(
|
|
ColorTween(
|
|
begin: barrierColor.opacity0,
|
|
end: barrierColor,
|
|
).chain(CurveTween(curve: barrierCurve)),
|
|
);
|
|
return AnimatedModalBarrier(
|
|
color: color,
|
|
dismissible: barrierDismissible,
|
|
semanticsLabel: barrierLabel,
|
|
barrierSemanticsDismissible: semanticsDismissible,
|
|
clipDetailsNotifier: _clipDetailsNotifier,
|
|
semanticsOnTapHint: barrierOnTapHint,
|
|
);
|
|
} else {
|
|
return ModalBarrier(
|
|
dismissible: barrierDismissible,
|
|
semanticsLabel: barrierLabel,
|
|
barrierSemanticsDismissible: semanticsDismissible,
|
|
clipDetailsNotifier: _clipDetailsNotifier,
|
|
semanticsOnTapHint: barrierOnTapHint,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
Future<T?> showModalSideSheet<T>({
|
|
required BuildContext context,
|
|
required WidgetBuilder builder,
|
|
Color? backgroundColor,
|
|
String? barrierLabel,
|
|
double? elevation,
|
|
ShapeBorder? shape,
|
|
Clip? clipBehavior,
|
|
BoxConstraints? constraints,
|
|
Color? barrierColor,
|
|
bool isScrollControlled = false,
|
|
double scrollControlDisabledMaxHeightRatio =
|
|
_defaultScrollControlDisabledMaxHeightRatio,
|
|
bool useRootNavigator = false,
|
|
bool isDismissible = true,
|
|
bool useSafeArea = false,
|
|
RouteSettings? routeSettings,
|
|
AnimationController? transitionAnimationController,
|
|
Offset? anchorPoint,
|
|
ImageFilter? filter,
|
|
}) {
|
|
assert(debugCheckHasMediaQuery(context));
|
|
assert(debugCheckHasMaterialLocalizations(context));
|
|
|
|
final NavigatorState navigator = Navigator.of(
|
|
context,
|
|
rootNavigator: useRootNavigator,
|
|
);
|
|
final MaterialLocalizations localizations = MaterialLocalizations.of(context);
|
|
return navigator.push(
|
|
ModalSideSheetRoute<T>(
|
|
builder: builder,
|
|
filter: filter,
|
|
capturedThemes: InheritedTheme.capture(
|
|
from: context,
|
|
to: navigator.context,
|
|
),
|
|
isScrollControlled: isScrollControlled,
|
|
scrollControlDisabledMaxHeightRatio: scrollControlDisabledMaxHeightRatio,
|
|
barrierLabel: barrierLabel ?? localizations.scrimLabel,
|
|
barrierOnTapHint: localizations.scrimOnTapHint(
|
|
localizations.bottomSheetLabel,
|
|
),
|
|
backgroundColor: backgroundColor,
|
|
elevation: elevation,
|
|
shape: shape,
|
|
clipBehavior: clipBehavior,
|
|
constraints: constraints,
|
|
isDismissible: isDismissible,
|
|
modalBarrierColor:
|
|
barrierColor ?? Theme.of(context).bottomSheetTheme.modalBarrierColor,
|
|
settings: routeSettings,
|
|
transitionAnimationController: transitionAnimationController,
|
|
anchorPoint: anchorPoint,
|
|
useSafeArea: useSafeArea,
|
|
),
|
|
);
|
|
}
|
|
|
|
// class ModalAppBar extends StatelessWidget {
|
|
// final String title;
|
|
//
|
|
// const ModalAppBar({
|
|
// super.key,
|
|
// required this.title,
|
|
// });
|
|
//
|
|
// @override
|
|
// Widget build(BuildContext context) {
|
|
// return AppBar(
|
|
// automaticallyImplyLeading: false,
|
|
// title: Text(title),
|
|
// centerTitle: false,
|
|
// actions: const [
|
|
// SizedBox(
|
|
// height: kToolbarHeight,
|
|
// width: kToolbarHeight,
|
|
// child: CloseButton(),
|
|
// )
|
|
// ],
|
|
// );
|
|
// }
|
|
// }
|