Unverified Commit 30cc01fb authored by Bonsai11's avatar Bonsai11 Committed by GitHub

Add callback when dismiss threshold is reached (#88736)

parent ab5dfe1d
...@@ -30,6 +30,11 @@ typedef DismissDirectionCallback = void Function(DismissDirection direction); ...@@ -30,6 +30,11 @@ typedef DismissDirectionCallback = void Function(DismissDirection direction);
/// Used by [Dismissible.confirmDismiss]. /// Used by [Dismissible.confirmDismiss].
typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction); typedef ConfirmDismissCallback = Future<bool?> Function(DismissDirection direction);
/// Signature used by [Dismissible] to indicate that the dismissible has been dragged.
///
/// Used by [Dismissible.onUpdate].
typedef DismissUpdateCallback = void Function(DismissUpdateDetails details);
/// The direction in which a [Dismissible] can be dismissed. /// The direction in which a [Dismissible] can be dismissed.
enum DismissDirection { enum DismissDirection {
/// The [Dismissible] can be dismissed by dragging either up or down. /// The [Dismissible] can be dismissed by dragging either up or down.
...@@ -98,6 +103,7 @@ class Dismissible extends StatefulWidget { ...@@ -98,6 +103,7 @@ class Dismissible extends StatefulWidget {
this.secondaryBackground, this.secondaryBackground,
this.confirmDismiss, this.confirmDismiss,
this.onResize, this.onResize,
this.onUpdate,
this.onDismissed, this.onDismissed,
this.direction = DismissDirection.horizontal, this.direction = DismissDirection.horizontal,
this.resizeDuration = const Duration(milliseconds: 300), this.resizeDuration = const Duration(milliseconds: 300),
...@@ -205,10 +211,44 @@ class Dismissible extends StatefulWidget { ...@@ -205,10 +211,44 @@ class Dismissible extends StatefulWidget {
/// This defaults to [HitTestBehavior.opaque]. /// This defaults to [HitTestBehavior.opaque].
final HitTestBehavior behavior; final HitTestBehavior behavior;
/// Called when the dismissible widget has been dragged.
///
/// If [onUpdate] is not null, then it will be invoked for every pointer event
/// to dispatch the latest state of the drag. For example, this callback
/// can be used to for example change the color of the background widget
/// depending on whether the dismiss threshold is currently reached.
final DismissUpdateCallback? onUpdate;
@override @override
State<Dismissible> createState() => _DismissibleState(); State<Dismissible> createState() => _DismissibleState();
} }
/// Details for [DismissUpdateCallback].
///
/// See also:
///
/// * [Dismissible.onUpdate], which receives this information.
class DismissUpdateDetails {
/// Create a new instance of [DismissUpdateDetails].
DismissUpdateDetails({
this.direction = DismissDirection.horizontal,
this.reached = false,
this.previousReached = false
});
/// The direction that the dismissible is being dragged.
final DismissDirection direction;
/// Whether the dismiss threshold is currently reached.
final bool reached;
/// Whether the dismiss threshold was reached the last time this callback was invoked.
///
/// This can be used in conjunction with [DismissUpdateDetails.reached] to catch the moment
/// that the [Dismissible] is dragged across the threshold.
final bool previousReached;
}
class _DismissibleClipper extends CustomClipper<Rect> { class _DismissibleClipper extends CustomClipper<Rect> {
_DismissibleClipper({ _DismissibleClipper({
required this.axis, required this.axis,
...@@ -254,7 +294,8 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin ...@@ -254,7 +294,8 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
void initState() { void initState() {
super.initState(); super.initState();
_moveController = AnimationController(duration: widget.movementDuration, vsync: this) _moveController = AnimationController(duration: widget.movementDuration, vsync: this)
..addStatusListener(_handleDismissStatusChanged); ..addStatusListener(_handleDismissStatusChanged)
..addListener(_handleDismissUpdateValueChanged);
_updateMoveAnimation(); _updateMoveAnimation();
} }
...@@ -268,6 +309,7 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin ...@@ -268,6 +309,7 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
bool _confirming = false; bool _confirming = false;
bool _dragUnderway = false; bool _dragUnderway = false;
Size? _sizePriorToCollapse; Size? _sizePriorToCollapse;
bool _dismissThresholdReached = false;
@override @override
bool get wantKeepAlive => _moveController?.isAnimating == true || _resizeController?.isAnimating == true; bool get wantKeepAlive => _moveController?.isAnimating == true || _resizeController?.isAnimating == true;
...@@ -388,6 +430,19 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin ...@@ -388,6 +430,19 @@ class _DismissibleState extends State<Dismissible> with TickerProviderStateMixin
} }
} }
void _handleDismissUpdateValueChanged() {
if(widget.onUpdate != null) {
final bool oldDismissThresholdReached = _dismissThresholdReached;
_dismissThresholdReached = _moveController!.value > (widget.dismissThresholds[_dismissDirection] ?? _kDismissThreshold);
final DismissUpdateDetails details = DismissUpdateDetails(
direction: _dismissDirection,
reached: _dismissThresholdReached,
previousReached: oldDismissThresholdReached,
);
widget.onUpdate!(details);
}
}
void _updateMoveAnimation() { void _updateMoveAnimation() {
final double end = _dragExtent.sign; final double end = _dragExtent.sign;
_moveAnimation = _moveController!.drive( _moveAnimation = _moveController!.drive(
......
...@@ -10,6 +10,9 @@ import 'package:flutter_test/flutter_test.dart'; ...@@ -10,6 +10,9 @@ import 'package:flutter_test/flutter_test.dart';
const DismissDirection defaultDismissDirection = DismissDirection.horizontal; const DismissDirection defaultDismissDirection = DismissDirection.horizontal;
const double crossAxisEndOffset = 0.5; const double crossAxisEndOffset = 0.5;
bool reportedDismissUpdateReached = false;
bool reportedDismissUpdatePreviousReached = false;
late DismissDirection reportedDismissUpdateReachedDirection;
DismissDirection reportedDismissDirection = DismissDirection.horizontal; DismissDirection reportedDismissDirection = DismissDirection.horizontal;
List<int> dismissedItems = <int>[]; List<int> dismissedItems = <int>[];
...@@ -46,6 +49,11 @@ Widget buildTest({ ...@@ -46,6 +49,11 @@ Widget buildTest({
onResize: () { onResize: () {
expect(dismissedItems.contains(item), isFalse); expect(dismissedItems.contains(item), isFalse);
}, },
onUpdate: (DismissUpdateDetails details) {
reportedDismissUpdateReachedDirection = details.direction;
reportedDismissUpdateReached = details.reached;
reportedDismissUpdatePreviousReached = details.previousReached;
},
background: background, background: background,
dismissThresholds: startToEndThreshold == null dismissThresholds: startToEndThreshold == null
? <DismissDirection, double>{} ? <DismissDirection, double>{}
...@@ -1053,4 +1061,50 @@ void main() { ...@@ -1053,4 +1061,50 @@ void main() {
expect(controller.offset, 100.0); expect(controller.offset, 100.0);
controller.dispose(); controller.dispose();
}); });
testWidgets('onUpdate', (WidgetTester tester) async {
await tester.pumpWidget(buildTest(
scrollDirection: Axis.horizontal,
));
expect(dismissedItems, isEmpty);
// Successful dismiss therefore threshold has been reached
await dismissItem(tester, 0, mechanism: flingElement, gestureDirection: AxisDirection.left);
expect(find.text('0'), findsNothing);
expect(dismissedItems, equals(<int>[0]));
expect(reportedDismissUpdateReachedDirection, DismissDirection.endToStart);
expect(reportedDismissUpdateReached, true);
expect(reportedDismissUpdatePreviousReached, true);
// Unsuccessful dismiss, threshold has not been reached
await checkFlingItemAfterMovement(tester, 1, gestureDirection: AxisDirection.right);
expect(find.text('1'), findsOneWidget);
expect(dismissedItems, equals(<int>[0]));
expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
expect(reportedDismissUpdateReached, false);
expect(reportedDismissUpdatePreviousReached, false);
// Another successful dismiss from another direction
await dismissItem(tester, 1, mechanism: flingElement, gestureDirection: AxisDirection.right);
expect(find.text('1'), findsNothing);
expect(dismissedItems, equals(<int>[0, 1]));
expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
expect(reportedDismissUpdateReached, true);
expect(reportedDismissUpdatePreviousReached, true);
await tester.pumpWidget(buildTest(
scrollDirection: Axis.horizontal,
confirmDismiss: (BuildContext context, DismissDirection dismissDirection) {
return Future<bool>.value(false);
},
));
// Threshold has been reached but dismiss was not confirmed
await dismissItem(tester, 2, mechanism: flingElement, gestureDirection: AxisDirection.right);
expect(find.text('2'), findsOneWidget);
expect(dismissedItems, equals(<int>[0, 1]));
expect(reportedDismissUpdateReachedDirection, DismissDirection.startToEnd);
expect(reportedDismissUpdateReached, false);
expect(reportedDismissUpdatePreviousReached, false);
});
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment