From a7af92db6a4802db93909a9dc053675c0cdc4a30 Mon Sep 17 00:00:00 2001 From: Ian Wilkinson Date: Tue, 2 Mar 2021 11:59:04 -0800 Subject: [PATCH] Declare DragTarget onMove event as taking a generic parameter (#76842) --- .../flutter/lib/src/widgets/drag_target.dart | 12 +- .../flutter/test/widgets/draggable_test.dart | 152 ++++++++++++++++++ 2 files changed, 158 insertions(+), 6 deletions(-) diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 4acf609561..48d99654f9 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -59,12 +59,12 @@ typedef DragEndCallback = void Function(DraggableDetails details); /// Signature for when a [Draggable] leaves a [DragTarget]. /// /// Used by [DragTarget.onLeave]. -typedef DragTargetLeave = void Function(Object? data); +typedef DragTargetLeave = void Function(T? data); /// Signature for when a [Draggable] moves within a [DragTarget]. /// /// Used by [DragTarget.onMove]. -typedef DragTargetMove = void Function(DragTargetDetails details); +typedef DragTargetMove = void Function(DragTargetDetails details); /// Where the [Draggable] should be anchored during a drag. enum DragAnchor { @@ -653,12 +653,12 @@ class DragTarget extends StatefulWidget { /// Called when a given piece of data being dragged over this target leaves /// the target. - final DragTargetLeave? onLeave; + final DragTargetLeave? onLeave; /// Called when a [Draggable] moves within this [DragTarget]. /// /// Note that this includes entering and leaving the target. - final DragTargetMove? onMove; + final DragTargetMove? onMove; /// How to behave during hit testing. /// @@ -712,7 +712,7 @@ class _DragTargetState extends State> { _rejectedAvatars.remove(avatar); }); if (widget.onLeave != null) - widget.onLeave!(avatar.data); + widget.onLeave!(avatar.data as T?); } void didDrop(_DragAvatar avatar) { @@ -732,7 +732,7 @@ class _DragTargetState extends State> { if (!mounted) return; if (widget.onMove != null) - widget.onMove!(DragTargetDetails(data: avatar.data, offset: avatar._lastOffset!)); + widget.onMove!(DragTargetDetails(data: avatar.data! as T, offset: avatar._lastOffset!)); } @override diff --git a/packages/flutter/test/widgets/draggable_test.dart b/packages/flutter/test/widgets/draggable_test.dart index cc32c11f39..d7bf95a606 100644 --- a/packages/flutter/test/widgets/draggable_test.dart +++ b/packages/flutter/test/widgets/draggable_test.dart @@ -85,6 +85,83 @@ void main() { expect(moveCount, 1); }); + // Regression test for https://github.com/flutter/flutter/issues/76825 + testWidgets('Drag and drop - onLeave callback fires correctly with generic parameter', + (WidgetTester tester) async { + final Map leftBehind = { + 'Target 1': 0, + 'Target 2': 0, + }; + + await tester.pumpWidget(MaterialApp( + home: Column( + children: [ + const Draggable( + data: 1, + child: Text('Source'), + feedback: Text('Dragging'), + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return Container(height: 100.0, child: const Text('Target 1')); + }, + onLeave: (int? data) { + if (data != null) { + leftBehind['Target 1'] = leftBehind['Target 1']! + data; + } + }, + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return Container(height: 100.0, child: const Text('Target 2')); + }, + onLeave: (int? data) { + if (data != null) { + leftBehind['Target 2'] = leftBehind['Target 2']! + data; + } + }, + ), + ], + ), + )); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset firstLocation = tester.getCenter(find.text('Source')); + final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset secondLocation = tester.getCenter(find.text('Target 1')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(0)); + expect(leftBehind['Target 2'], equals(0)); + + final Offset thirdLocation = tester.getCenter(find.text('Target 2')); + await gesture.moveTo(thirdLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(0)); + + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(1)); + + await gesture.up(); + await tester.pump(); + + expect(leftBehind['Target 1'], equals(1)); + expect(leftBehind['Target 2'], equals(1)); + }); + testWidgets('Drag and drop - onLeave callback fires correctly', (WidgetTester tester) async { final Map leftBehind = { 'Target 1': 0, @@ -160,6 +237,81 @@ void main() { expect(leftBehind['Target 2'], equals(1)); }); + // Regression test for https://github.com/flutter/flutter/issues/76825 + testWidgets('Drag and drop - onMove callback fires correctly with generic parameter', + (WidgetTester tester) async { + final Map targetMoveCount = { + 'Target 1': 0, + 'Target 2': 0, + }; + + await tester.pumpWidget(MaterialApp( + home: Column( + children: [ + const Draggable( + data: 1, + child: Text('Source'), + feedback: Text('Dragging'), + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return Container(height: 100.0, child: const Text('Target 1')); + }, + onMove: (DragTargetDetails details) { + targetMoveCount['Target 1'] = + targetMoveCount['Target 1']! + details.data; + }, + ), + DragTarget( + builder: (BuildContext context, List data, List rejects) { + return Container(height: 100.0, child: const Text('Target 2')); + }, + onMove: (DragTargetDetails details) { + targetMoveCount['Target 2'] = + targetMoveCount['Target 2']! + details.data; + }, + ), + ], + ), + )); + + expect(targetMoveCount['Target 1'], equals(0)); + expect(targetMoveCount['Target 2'], equals(0)); + + final Offset firstLocation = tester.getCenter(find.text('Source')); + final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(0)); + expect(targetMoveCount['Target 2'], equals(0)); + + final Offset secondLocation = tester.getCenter(find.text('Target 1')); + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(1)); + expect(targetMoveCount['Target 2'], equals(0)); + + final Offset thirdLocation = tester.getCenter(find.text('Target 2')); + await gesture.moveTo(thirdLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(1)); + expect(targetMoveCount['Target 2'], equals(1)); + + await gesture.moveTo(secondLocation); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(2)); + expect(targetMoveCount['Target 2'], equals(1)); + + await gesture.up(); + await tester.pump(); + + expect(targetMoveCount['Target 1'], equals(2)); + expect(targetMoveCount['Target 2'], equals(1)); + }); + testWidgets('Drag and drop - onMove callback fires correctly', (WidgetTester tester) async { final Map targetMoveCount = { 'Target 1': 0,