bump flutter (#859)

Signed-off-by: bggRGjQaUbCoE <githubaccount56556@proton.me>
This commit is contained in:
dom
2025-05-16 23:05:02 +08:00
committed by GitHub
parent cf0bf1e587
commit 7ae92970ef
96 changed files with 604 additions and 521 deletions

View File

@@ -46,15 +46,25 @@ const Duration _kIndicatorScaleDuration = Duration(milliseconds: 200);
/// Used by [RefreshIndicator.onRefresh].
typedef RefreshCallback = Future<void> Function();
// The state machine moves through these modes only when the scrollable
// identified by scrollableKey has been scrolled to its min or max limit.
enum _RefreshIndicatorMode {
drag, // Pointer is down.
armed, // Dragged far enough that an up event will run the onRefresh callback.
snap, // Animating to the indicator's final "displacement".
refresh, // Running the refresh callback.
done, // Animating the indicator's fade-out after refreshing.
canceled, // Animating the indicator's fade-out after not arming.
/// Indicates current status of Material `RefreshIndicator`.
enum RefreshIndicatorStatus {
/// Pointer is down.
drag,
/// Dragged far enough that an up event will run the onRefresh callback.
armed,
/// Animating to the indicator's final "displacement".
snap,
/// Running the refresh callback.
refresh,
/// Animating the indicator's fade-out after refreshing.
done,
/// Animating the indicator's fade-out after not arming.
canceled,
}
/// Used to configure how [RefreshIndicator] can be triggered.
@@ -68,7 +78,7 @@ enum RefreshIndicatorTriggerMode {
onEdge,
}
enum _IndicatorType { material, adaptive }
enum _IndicatorType { material, adaptive, noSpinner }
/// A widget that supports the Material "swipe to refresh" idiom.
///
@@ -96,6 +106,12 @@ enum _IndicatorType { material, adaptive }
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.1.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This example shows how to use [RefreshIndicator] without the spinner.
///
/// ** See code in examples/api/lib/material/refresh_indicator/refresh_indicator.2.dart **
/// {@end-tool}
///
/// ## Troubleshooting
///
/// ### Refresh indicator does not show up
@@ -149,7 +165,10 @@ class RefreshIndicator extends StatefulWidget {
this.semanticsValue,
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
}) : _indicatorType = _IndicatorType.material;
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.material,
onStatusChange = null,
assert(elevation >= 0.0);
/// Creates an adaptive [RefreshIndicator] based on whether the target
/// platform is iOS or macOS, following Material design's
@@ -180,7 +199,35 @@ class RefreshIndicator extends StatefulWidget {
this.semanticsValue,
this.strokeWidth = RefreshProgressIndicator.defaultStrokeWidth,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
}) : _indicatorType = _IndicatorType.adaptive;
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.adaptive,
onStatusChange = null,
assert(elevation >= 0.0);
/// Creates a [RefreshIndicator] with no spinner and calls `onRefresh` when
/// successfully armed by a drag event.
///
/// Events can be optionally listened by using the `onStatusChange` callback.
const RefreshIndicator.noSpinner({
super.key,
required this.child,
required this.onRefresh,
this.onStatusChange,
this.notificationPredicate = defaultScrollNotificationPredicate,
this.semanticsLabel,
this.semanticsValue,
this.triggerMode = RefreshIndicatorTriggerMode.onEdge,
this.elevation = 2.0,
}) : _indicatorType = _IndicatorType.noSpinner,
// The following parameters aren't used because [_IndicatorType.noSpinner] is being used,
// which involves showing no spinner, hence the following parameters are useless since
// their only use is to change the spinner's appearance.
displacement = 0.0,
edgeOffset = 0.0,
color = null,
backgroundColor = null,
strokeWidth = 0.0,
assert(elevation >= 0.0);
/// The widget below this widget in the tree.
///
@@ -220,6 +267,10 @@ class RefreshIndicator extends StatefulWidget {
/// [Future] must complete when the refresh operation is finished.
final RefreshCallback onRefresh;
/// Called to get the current status of the [RefreshIndicator] to update the UI as needed.
/// This is an optional parameter, used to fine tune app cases.
final ValueChanged<RefreshIndicatorStatus?>? onStatusChange;
/// The progress indicator's foreground color. The current theme's
/// [ColorScheme.primary] by default.
final Color? color;
@@ -266,6 +317,11 @@ class RefreshIndicator extends StatefulWidget {
/// Defaults to [RefreshIndicatorTriggerMode.onEdge].
final RefreshIndicatorTriggerMode triggerMode;
/// Defines the elevation of the underlying [RefreshIndicator].
///
/// Defaults to 2.0.
final double elevation;
@override
RefreshIndicatorState createState() => RefreshIndicatorState();
}
@@ -281,7 +337,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
late Animation<double> _value;
late Animation<Color?> _valueColor;
_RefreshIndicatorMode? _mode;
RefreshIndicatorStatus? _status;
late Future<void> _pendingRefreshFuture;
bool? _isIndicatorAtTop;
double? _dragOffset;
@@ -290,29 +346,37 @@ class RefreshIndicatorState extends State<RefreshIndicator>
static final Animatable<double> _threeQuarterTween =
Tween<double>(begin: 0.0, end: 0.75);
static final Animatable<double> _kDragSizeFactorLimitTween =
Tween<double>(begin: 0.0, end: _kDragSizeFactorLimit);
static final Animatable<double> _kDragSizeFactorLimitTween = Tween<double>(
begin: 0.0,
end: _kDragSizeFactorLimit,
);
static final Animatable<double> _oneToZeroTween =
Tween<double>(begin: 1.0, end: 0.0);
@protected
@override
void initState() {
super.initState();
_positionController = AnimationController(vsync: this);
_positionFactor = _positionController.drive(_kDragSizeFactorLimitTween);
_value = _positionController.drive(
_threeQuarterTween); // The "value" of the circular progress indicator during a drag.
// The "value" of the circular progress indicator during a drag.
_value = _positionController.drive(_threeQuarterTween);
_scaleController = AnimationController(vsync: this);
_scaleFactor = _scaleController.drive(_oneToZeroTween);
}
@protected
@override
void didChangeDependencies() {
_setupColorTween();
super.didChangeDependencies();
}
@protected
@override
void didUpdateWidget(covariant RefreshIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
@@ -321,6 +385,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
}
@protected
@override
void dispose() {
_positionController.dispose();
@@ -342,11 +407,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
ColorTween(
begin: color.withAlpha(0),
end: color.withAlpha(color.alpha),
).chain(
CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit),
),
),
).chain(CurveTween(
curve: const Interval(0.0, 1.0 / _kDragSizeFactorLimit))),
);
}
}
@@ -364,7 +426,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
notification.metrics.extentAfter == 0.0) ||
(notification.metrics.axisDirection == AxisDirection.down &&
notification.metrics.extentBefore == 0.0)) &&
_mode == null &&
_status == null &&
_start(notification.metrics.axisDirection);
}
@@ -374,7 +436,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
if (_shouldStart(notification)) {
setState(() {
_mode = _RefreshIndicatorMode.drag;
_status = RefreshIndicatorStatus.drag;
widget.onStatusChange?.call(_status);
});
return false;
}
@@ -384,13 +447,13 @@ class RefreshIndicatorState extends State<RefreshIndicator>
AxisDirection.left || AxisDirection.right => null,
};
if (indicatorAtTopNow != _isIndicatorAtTop) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
_dismiss(_RefreshIndicatorMode.canceled);
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
_dismiss(RefreshIndicatorStatus.canceled);
}
} else if (notification is ScrollUpdateNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
if (notification.metrics.axisDirection == AxisDirection.down) {
_dragOffset = _dragOffset! - notification.scrollDelta!;
} else if (notification.metrics.axisDirection == AxisDirection.up) {
@@ -398,7 +461,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
_checkDragOffset(notification.metrics.viewportDimension);
}
if (_mode == _RefreshIndicatorMode.armed &&
if (_status == RefreshIndicatorStatus.armed &&
notification.dragDetails == null) {
// On iOS start the refresh when the Scrollable bounces back from the
// overscroll (ScrollNotification indicating this don't have dragDetails
@@ -406,8 +469,8 @@ class RefreshIndicatorState extends State<RefreshIndicator>
_show();
}
} else if (notification is OverscrollNotification) {
if (_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed) {
if (notification.metrics.axisDirection == AxisDirection.down) {
_dragOffset = _dragOffset! - notification.overscroll;
} else if (notification.metrics.axisDirection == AxisDirection.up) {
@@ -416,19 +479,19 @@ class RefreshIndicatorState extends State<RefreshIndicator>
_checkDragOffset(notification.metrics.viewportDimension);
}
} else if (notification is ScrollEndNotification) {
switch (_mode) {
case _RefreshIndicatorMode.armed:
switch (_status) {
case RefreshIndicatorStatus.armed:
if (_positionController.value < 1.0) {
_dismiss(_RefreshIndicatorMode.canceled);
_dismiss(RefreshIndicatorStatus.canceled);
} else {
_show();
}
case _RefreshIndicatorMode.drag:
_dismiss(_RefreshIndicatorMode.canceled);
case _RefreshIndicatorMode.canceled:
case _RefreshIndicatorMode.done:
case _RefreshIndicatorMode.refresh:
case _RefreshIndicatorMode.snap:
case RefreshIndicatorStatus.drag:
_dismiss(RefreshIndicatorStatus.canceled);
case RefreshIndicatorStatus.canceled:
case RefreshIndicatorStatus.done:
case RefreshIndicatorStatus.refresh:
case RefreshIndicatorStatus.snap:
case null:
// do nothing
break;
@@ -442,7 +505,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
if (notification.depth != 0 || !notification.leading) {
return false;
}
if (_mode == _RefreshIndicatorMode.drag) {
if (_status == RefreshIndicatorStatus.drag) {
notification.disallowIndicator();
return true;
}
@@ -450,7 +513,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
bool _start(AxisDirection direction) {
assert(_mode == null);
assert(_status == null);
assert(_isIndicatorAtTop == null);
assert(_dragOffset == null);
switch (direction) {
@@ -470,74 +533,78 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}
void _checkDragOffset(double containerExtent) {
assert(_mode == _RefreshIndicatorMode.drag ||
_mode == _RefreshIndicatorMode.armed);
assert(_status == RefreshIndicatorStatus.drag ||
_status == RefreshIndicatorStatus.armed);
double newValue =
_dragOffset! / (containerExtent * kDragContainerExtentPercentage);
if (_mode == _RefreshIndicatorMode.armed) {
if (_status == RefreshIndicatorStatus.armed) {
newValue = math.max(newValue, 1.0 / _kDragSizeFactorLimit);
}
_positionController.value =
clampDouble(newValue, 0.0, 1.0); // this triggers various rebuilds
if (_mode == _RefreshIndicatorMode.drag &&
clampDouble(newValue, 0.0, 1.0); // This triggers various rebuilds.
if (_status == RefreshIndicatorStatus.drag &&
_valueColor.value!.alpha == _effectiveValueColor.alpha) {
_mode = _RefreshIndicatorMode.armed;
_status = RefreshIndicatorStatus.armed;
widget.onStatusChange?.call(_status);
}
}
// Stop showing the refresh indicator.
Future<void> _dismiss(_RefreshIndicatorMode newMode) async {
Future<void> _dismiss(RefreshIndicatorStatus newMode) async {
await Future<void>.value();
// This can only be called from _show() when refreshing and
// _handleScrollNotification in response to a ScrollEndNotification or
// direction change.
assert(newMode == _RefreshIndicatorMode.canceled ||
newMode == _RefreshIndicatorMode.done);
assert(newMode == RefreshIndicatorStatus.canceled ||
newMode == RefreshIndicatorStatus.done);
setState(() {
_mode = newMode;
_status = newMode;
widget.onStatusChange?.call(_status);
});
switch (_mode!) {
case _RefreshIndicatorMode.done:
switch (_status!) {
case RefreshIndicatorStatus.done:
await _scaleController.animateTo(1.0,
duration: _kIndicatorScaleDuration);
case _RefreshIndicatorMode.canceled:
case RefreshIndicatorStatus.canceled:
await _positionController.animateTo(0.0,
duration: _kIndicatorScaleDuration);
case _RefreshIndicatorMode.armed:
case _RefreshIndicatorMode.drag:
case _RefreshIndicatorMode.refresh:
case _RefreshIndicatorMode.snap:
case RefreshIndicatorStatus.armed:
case RefreshIndicatorStatus.drag:
case RefreshIndicatorStatus.refresh:
case RefreshIndicatorStatus.snap:
assert(false);
}
if (mounted && _mode == newMode) {
if (mounted && _status == newMode) {
_dragOffset = null;
_isIndicatorAtTop = null;
setState(() {
_mode = null;
_status = null;
});
}
}
void _show() {
assert(_mode != _RefreshIndicatorMode.refresh);
assert(_mode != _RefreshIndicatorMode.snap);
assert(_status != RefreshIndicatorStatus.refresh);
assert(_status != RefreshIndicatorStatus.snap);
final Completer<void> completer = Completer<void>();
_pendingRefreshFuture = completer.future;
_mode = _RefreshIndicatorMode.snap;
_status = RefreshIndicatorStatus.snap;
widget.onStatusChange?.call(_status);
_positionController
.animateTo(1.0 / _kDragSizeFactorLimit,
duration: _kIndicatorSnapDuration)
.then<void>((void value) {
if (mounted && _mode == _RefreshIndicatorMode.snap) {
if (mounted && _status == RefreshIndicatorStatus.snap) {
setState(() {
// Show the indeterminate progress indicator.
_mode = _RefreshIndicatorMode.refresh;
_status = RefreshIndicatorStatus.refresh;
});
widget.onRefresh().whenComplete(() {
if (mounted && _mode == _RefreshIndicatorMode.refresh) {
final Future<void> refreshResult = widget.onRefresh();
refreshResult.whenComplete(() {
if (mounted && _status == RefreshIndicatorStatus.refresh) {
completer.complete();
_dismiss(_RefreshIndicatorMode.done);
_dismiss(RefreshIndicatorStatus.done);
}
});
}
@@ -561,9 +628,9 @@ class RefreshIndicatorState extends State<RefreshIndicator>
/// actual scroll view. It defaults to showing the indicator at the top. To
/// show it at the bottom, set `atTop` to false.
Future<void> show({bool atTop = true}) {
if (_mode != _RefreshIndicatorMode.refresh &&
_mode != _RefreshIndicatorMode.snap) {
if (_mode == null) {
if (_status != RefreshIndicatorStatus.refresh &&
_status != RefreshIndicatorStatus.snap) {
if (_status == null) {
_start(atTop ? AxisDirection.down : AxisDirection.up);
}
_show();
@@ -571,6 +638,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
return _pendingRefreshFuture;
}
@protected
@override
Widget build(BuildContext context) {
assert(debugCheckHasMaterialLocalizations(context));
@@ -582,7 +650,7 @@ class RefreshIndicatorState extends State<RefreshIndicator>
),
);
assert(() {
if (_mode == null) {
if (_status == null) {
assert(_dragOffset == null);
assert(_isIndicatorAtTop == null);
} else {
@@ -593,14 +661,14 @@ class RefreshIndicatorState extends State<RefreshIndicator>
}());
final bool showIndeterminateIndicator =
_mode == _RefreshIndicatorMode.refresh ||
_mode == _RefreshIndicatorMode.done;
_status == RefreshIndicatorStatus.refresh ||
_status == RefreshIndicatorStatus.done;
return Stack(
clipBehavior: Clip.none,
children: <Widget>[
child,
if (_mode != null)
if (_status != null)
Positioned(
top: _isIndicatorAtTop! ? widget.edgeOffset : null,
bottom: !_isIndicatorAtTop! ? widget.edgeOffset : null,
@@ -608,41 +676,44 @@ class RefreshIndicatorState extends State<RefreshIndicator>
right: 0.0,
child: SizeTransition(
axisAlignment: _isIndicatorAtTop! ? 1.0 : -1.0,
sizeFactor: _positionFactor, // this is what brings it down
child: Container(
sizeFactor: _positionFactor, // This is what brings it down.
child: Padding(
padding: _isIndicatorAtTop!
? EdgeInsets.only(top: widget.displacement)
: EdgeInsets.only(bottom: widget.displacement),
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator = RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value: showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
);
child: Align(
alignment: _isIndicatorAtTop!
? Alignment.topCenter
: Alignment.bottomCenter,
child: ScaleTransition(
scale: _scaleFactor,
child: AnimatedBuilder(
animation: _positionController,
builder: (BuildContext context, Widget? child) {
final Widget materialIndicator =
RefreshProgressIndicator(
semanticsLabel: widget.semanticsLabel ??
MaterialLocalizations.of(context)
.refreshIndicatorSemanticLabel,
semanticsValue: widget.semanticsValue,
value:
showIndeterminateIndicator ? null : _value.value,
valueColor: _valueColor,
backgroundColor: widget.backgroundColor,
strokeWidth: widget.strokeWidth,
elevation: widget.elevation,
);
final Widget cupertinoIndicator =
CupertinoActivityIndicator(
color: widget.color,
);
final Widget cupertinoIndicator =
CupertinoActivityIndicator(
color: widget.color,
);
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
switch (widget._indicatorType) {
case _IndicatorType.material:
return materialIndicator;
case _IndicatorType.adaptive:
{
case _IndicatorType.adaptive:
final ThemeData theme = Theme.of(context);
switch (theme.platform) {
case TargetPlatform.android:
@@ -654,9 +725,12 @@ class RefreshIndicatorState extends State<RefreshIndicator>
case TargetPlatform.macOS:
return cupertinoIndicator;
}
}
}
},
case _IndicatorType.noSpinner:
return Container();
}
},
),
),
),
),