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 { ...@@ -94,7 +94,8 @@ class Draggable<T> extends StatefulWidget {
this.affinity, this.affinity,
this.maxSimultaneousDrags, this.maxSimultaneousDrags,
this.onDragStarted, this.onDragStarted,
this.onDraggableCanceled this.onDraggableCanceled,
this.onDragCompleted,
}) : assert(child != null), }) : assert(child != null),
assert(feedback != null), assert(feedback != null),
assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0), assert(maxSimultaneousDrags == null || maxSimultaneousDrags >= 0),
...@@ -182,6 +183,16 @@ class Draggable<T> extends StatefulWidget { ...@@ -182,6 +183,16 @@ class Draggable<T> extends StatefulWidget {
/// callback is still in the tree. /// callback is still in the tree.
final DraggableCanceledCallback onDraggableCanceled; 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. /// Creates a gesture recognizer that recognizes the start of the drag.
/// ///
/// Subclasses can override this function to customize when they start /// Subclasses can override this function to customize when they start
...@@ -313,6 +324,8 @@ class _DraggableState<T> extends State<Draggable<T>> { ...@@ -313,6 +324,8 @@ class _DraggableState<T> extends State<Draggable<T>> {
_activeCount -= 1; _activeCount -= 1;
_disposeRecognizerIfInactive(); _disposeRecognizerIfInactive();
} }
if (wasAccepted && widget.onDragCompleted != null)
widget.onDragCompleted();
if (!wasAccepted && widget.onDraggableCanceled != null) if (!wasAccepted && widget.onDraggableCanceled != null)
widget.onDraggableCanceled(velocity, offset); widget.onDraggableCanceled(velocity, offset);
} }
......
...@@ -518,7 +518,7 @@ void main() { ...@@ -518,7 +518,7 @@ void main() {
events.clear(); 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>[]; final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false; bool onDraggableCanceledCalled = false;
...@@ -579,7 +579,7 @@ void main() { ...@@ -579,7 +579,7 @@ void main() {
expect(onDraggableCanceledCalled, isFalse); 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>[]; final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false; bool onDraggableCanceledCalled = false;
Velocity onDraggableCanceledVelocity; Velocity onDraggableCanceledVelocity;
...@@ -649,7 +649,7 @@ void main() { ...@@ -649,7 +649,7 @@ void main() {
expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.dx, secondLocation.dy))); 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>[]; final List<int> accepted = <int>[];
bool onDraggableCanceledCalled = false; bool onDraggableCanceledCalled = false;
Velocity onDraggableCanceledVelocity; Velocity onDraggableCanceledVelocity;
...@@ -699,6 +699,131 @@ void main() { ...@@ -699,6 +699,131 @@ void main() {
expect(onDraggableCanceledOffset, equals(new Offset(flingStart.dx, flingStart.dy) + const Offset(0.0, 100.0))); 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 { testWidgets('Drag and drop - allow pass thru of unaccepted data test', (WidgetTester tester) async {
final List<int> acceptedInts = <int>[]; final List<int> acceptedInts = <int>[];
final List<double> acceptedDoubles = <double>[]; 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