Commit 4b7e3494 authored by P.Y. Laligand's avatar P.Y. Laligand Committed by GitHub

Add drag completion callback to Draggable. (#10455)

Fixes #10350
parent 03e7ebe6
......@@ -94,7 +94,8 @@ class Draggable<T> extends StatefulWidget {
this.affinity,
this.maxSimultaneousDrags,
this.onDragStarted,
this.onDraggableCanceled
this.onDraggableCanceled,
this.onDragCompleted,
}) : assert(child != null),
assert(feedback != null),
assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
......@@ -182,6 +183,16 @@ class Draggable<T> extends StatefulWidget {
/// callback is still in the tree.
final DraggableCanceledCallback onDraggableCanceled;
/// Called when the draggable is dropped and accepted by a [DragTarget].
///
/// This function might be called after this widget has been removed from the
/// tree. For example, if a drag was in progress when this widget was removed
/// from the tree and the drag ended up completing, this callback will
/// still be called. For this reason, implementations of this callback might
/// need to check [State.mounted] to check whether the state receiving the
/// callback is still in the tree.
final VoidCallback onDragCompleted;
/// Creates a gesture recognizer that recognizes the start of the drag.
///
/// Subclasses can override this function to customize when they start
......@@ -313,6 +324,8 @@ class _DraggableState<T> extends State<Draggable<T>> {
_activeCount -= 1;
_disposeRecognizerIfInactive();
}
if (wasAccepted && widget.onDragCompleted != null)
widget.onDragCompleted();
if (!wasAccepted && widget.onDraggableCanceled != null)
widget.onDraggableCanceled(velocity, offset);
}
......
......@@ -518,7 +518,7 @@ void main() {
events.clear();
});
testWidgets('Drag and drop - onDraggableDropped not called if dropped on accepting target', (WidgetTester tester) async {
testWidgets('Drag and drop - onDraggableCanceled not called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false;
......@@ -579,7 +579,7 @@ void main() {
expect(onDraggableCanceledCalled, isFalse);
});
testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target', (WidgetTester tester) async {
testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false;
Velocity onDraggableCanceledVelocity;
......@@ -649,7 +649,7 @@ void main() {
expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.dx, secondLocation.dy)));
});
testWidgets('Drag and drop - onDraggableDropped called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with correct velocity', (WidgetTester tester) async {
final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false;
Velocity onDraggableCanceledVelocity;
......@@ -699,6 +699,131 @@ void main() {
expect(onDraggableCanceledOffset, equals(new Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0)));
});
testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(new MaterialApp(
home: new Column(
children: <Widget>[
new Draggable<int>(
data: 1,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
}
),
new DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
return new Container(
height: 100.0,
child: const Text('Target')
);
},
onWillAccept: (int data) => false
),
]
)
));
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset firstLocation = tester.getTopLeft(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
});
testWidgets('Drag and drop - onDragCompleted called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(new MaterialApp(
home: new Column(
children: <Widget>[
new Draggable<int>(
data: 1,
child: const Text('Source'),
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
}
),
new DragTarget<int>(
builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
return new Container(height: 100.0, child: const Text('Target'));
},
onAccept: accepted.add
),
]
)
));
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset firstLocation = tester.getCenter(find.text('Source'));
final TestGesture gesture = await tester.startGesture(firstLocation, pointer: 7);
await tester.pump();
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, equals(<int>[1]));
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isTrue);
});
testWidgets('Drag and drop - allow pass thru of unaccepted data test', (WidgetTester tester) async {
final List<int> acceptedInts = <int>[];
final List<double> acceptedDoubles = <double>[];
......
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