Unverified Commit 347f7bac authored by Chinmoy's avatar Chinmoy Committed by GitHub

Adds callback onWillAcceptWithDetails in DragTarget. (#131545)

This PR adds onWillAcceptWithDetails callback to DragTarget to get information about offset.

Fixes: #131378 

This PR is subject to changes based on #131542
parent 72bd3602
...@@ -20,6 +20,12 @@ import 'view.dart'; ...@@ -20,6 +20,12 @@ import 'view.dart';
/// Used by [DragTarget.onWillAccept]. /// Used by [DragTarget.onWillAccept].
typedef DragTargetWillAccept<T> = bool Function(T? data); typedef DragTargetWillAccept<T> = bool Function(T? data);
/// Signature for determining whether the given data will be accepted by a [DragTarget],
/// based on provided information.
///
/// Used by [DragTarget.onWillAcceptWithDetails].
typedef DragTargetWillAcceptWithDetails<T> = bool Function(DragTargetDetails<T> details);
/// Signature for causing a [DragTarget] to accept the given data. /// Signature for causing a [DragTarget] to accept the given data.
/// ///
/// Used by [DragTarget.onAccept]. /// Used by [DragTarget.onAccept].
...@@ -612,12 +618,13 @@ class DragTarget<T extends Object> extends StatefulWidget { ...@@ -612,12 +618,13 @@ class DragTarget<T extends Object> extends StatefulWidget {
super.key, super.key,
required this.builder, required this.builder,
this.onWillAccept, this.onWillAccept,
this.onWillAcceptWithDetails,
this.onAccept, this.onAccept,
this.onAcceptWithDetails, this.onAcceptWithDetails,
this.onLeave, this.onLeave,
this.onMove, this.onMove,
this.hitTestBehavior = HitTestBehavior.translucent, this.hitTestBehavior = HitTestBehavior.translucent,
}); }) : assert(onWillAccept == null || onWillAcceptWithDetails == null, "Don't pass both onWillAccept and onWillAcceptWithDetails.");
/// Called to build the contents of this widget. /// Called to build the contents of this widget.
/// ///
...@@ -631,8 +638,25 @@ class DragTarget<T extends Object> extends StatefulWidget { ...@@ -631,8 +638,25 @@ class DragTarget<T extends Object> extends StatefulWidget {
/// Called when a piece of data enters the target. This will be followed by /// Called when a piece of data enters the target. This will be followed by
/// either [onAccept] and [onAcceptWithDetails], if the data is dropped, or /// either [onAccept] and [onAcceptWithDetails], if the data is dropped, or
/// [onLeave], if the drag leaves the target. /// [onLeave], if the drag leaves the target.
///
/// Equivalent to [onWillAcceptWithDetails], but only includes the data.
///
/// Must not be provided if [onWillAcceptWithDetails] is provided.
final DragTargetWillAccept<T>? onWillAccept; final DragTargetWillAccept<T>? onWillAccept;
/// Called to determine whether this widget is interested in receiving a given
/// piece of data being dragged over this drag target.
///
/// Called when a piece of data enters the target. This will be followed by
/// either [onAccept] and [onAcceptWithDetails], if the data is dropped, or
/// [onLeave], if the drag leaves the target.
///
/// Equivalent to [onWillAccept], but with information, including the data,
/// in a [DragTargetDetails].
///
/// Must not be provided if [onWillAccept] is provided.
final DragTargetWillAcceptWithDetails<T>? onWillAcceptWithDetails;
/// Called when an acceptable piece of data was dropped over this drag target. /// Called when an acceptable piece of data was dropped over this drag target.
/// ///
/// Equivalent to [onAcceptWithDetails], but only includes the data. /// Equivalent to [onAcceptWithDetails], but only includes the data.
...@@ -684,7 +708,13 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> { ...@@ -684,7 +708,13 @@ class _DragTargetState<T extends Object> extends State<DragTarget<T>> {
bool didEnter(_DragAvatar<Object> avatar) { bool didEnter(_DragAvatar<Object> avatar) {
assert(!_candidateAvatars.contains(avatar)); assert(!_candidateAvatars.contains(avatar));
assert(!_rejectedAvatars.contains(avatar)); assert(!_rejectedAvatars.contains(avatar));
if (widget.onWillAccept == null || widget.onWillAccept!(avatar.data as T?)) { final bool resolvedWillAccept = (widget.onWillAccept == null &&
widget.onWillAcceptWithDetails == null) ||
(widget.onWillAccept != null &&
widget.onWillAccept!(avatar.data as T?)) ||
(widget.onWillAcceptWithDetails != null &&
widget.onWillAcceptWithDetails!(DragTargetDetails<T>(data: avatar.data! as T, offset: avatar._lastOffset!)));
if (resolvedWillAccept) {
setState(() { setState(() {
_candidateAvatars.add(avatar); _candidateAvatars.add(avatar);
}); });
......
...@@ -1293,6 +1293,83 @@ void main() { ...@@ -1293,6 +1293,83 @@ void main() {
expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy))); expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy)));
}); });
testWidgets('Drag and drop - onDraggableCanceled called if dropped on non-accepting target with details', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDraggableCanceledCalled = false;
late Velocity onDraggableCanceledVelocity;
late Offset onDraggableCanceledOffset;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDraggableCanceled: (Velocity velocity, Offset offset) {
onDraggableCanceledCalled = true;
onDraggableCanceledVelocity = velocity;
onDraggableCanceledOffset = offset;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(
height: 100.0,
child: Text('Target'),
);
},
onWillAcceptWithDetails: (DragTargetDetails<int> details) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, 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(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDraggableCanceledCalled, isTrue);
expect(onDraggableCanceledVelocity, equals(Velocity.zero));
expect(onDraggableCanceledOffset, equals(Offset(secondLocation.dx, secondLocation.dy)));
});
testWidgets('Drag and drop - onDraggableCanceled 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>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[]; final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
...@@ -1421,6 +1498,82 @@ void main() { ...@@ -1421,6 +1498,82 @@ void main() {
); );
}); });
testWidgets('Drag and drop - onDragEnd not called if dropped on non-accepting target with details', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragEndCalled = false;
late DraggableDetails onDragEndDraggableDetails;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragEnd: (DraggableDetails details) {
onDragEndCalled = true;
onDragEndDraggableDetails = details;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onWillAcceptWithDetails: (DragTargetDetails<int> data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, 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(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
final Offset secondLocation = tester.getCenter(find.text('Target'));
await gesture.moveTo(secondLocation);
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsOneWidget);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isFalse);
await gesture.up();
await tester.pump();
expect(accepted, isEmpty);
expect(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragEndCalled, isTrue);
expect(onDragEndDraggableDetails, isNotNull);
expect(onDragEndDraggableDetails.wasAccepted, isFalse);
expect(onDragEndDraggableDetails.velocity, equals(Velocity.zero));
expect(
onDragEndDraggableDetails.offset,
equals(Offset(secondLocation.dx, secondLocation.dy - firstLocation.dy)),
);
});
testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async { testWidgets('Drag and drop - DragTarget rebuilds with and without rejected data when a rejected draggable enters and leaves', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Column( home: Column(
...@@ -1628,6 +1781,77 @@ void main() { ...@@ -1628,6 +1781,77 @@ void main() {
expect(onDragCompletedCalled, isFalse); expect(onDragCompletedCalled, isFalse);
}); });
testWidgets('Drag and drop - onDragCompleted not called if dropped on non-accepting target with details', (WidgetTester tester) async {
final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
bool onDragCompletedCalled = false;
await tester.pumpWidget(MaterialApp(
home: Column(
children: <Widget>[
Draggable<int>(
data: 1,
feedback: const Text('Dragging'),
onDragCompleted: () {
onDragCompletedCalled = true;
},
child: const Text('Source'),
),
DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(
height: 100.0,
child: Text('Target'),
);
},
onWillAcceptWithDetails: (DragTargetDetails<int> data) => false,
onAccept: accepted.add,
onAcceptWithDetails: acceptedDetails.add,
),
],
),
));
expect(accepted, isEmpty);
expect(acceptedDetails, 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(acceptedDetails, 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(acceptedDetails, 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(acceptedDetails, isEmpty);
expect(find.text('Source'), findsOneWidget);
expect(find.text('Dragging'), findsNothing);
expect(find.text('Target'), findsOneWidget);
expect(onDragCompletedCalled, isFalse);
});
testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async { testWidgets('Drag and drop - onDragEnd called if dropped on accepting target', (WidgetTester tester) async {
final List<int> accepted = <int>[]; final List<int> accepted = <int>[];
final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[]; final List<DragTargetDetails<int>> acceptedDetails = <DragTargetDetails<int>>[];
...@@ -3237,6 +3461,16 @@ void main() { ...@@ -3237,6 +3461,16 @@ void main() {
expect(find.text('Dragging'), findsNothing); expect(find.text('Dragging'), findsNothing);
await gesture3.up(); await gesture3.up();
}); });
testWidgets('throws error when both onWillAccept and onWillAcceptWithDetails are provided', (WidgetTester tester) async {
expect(() => DragTarget<int>(
builder: (BuildContext context, List<int?> data, List<dynamic> rejects) {
return const SizedBox(height: 100.0, child: Text('Target'));
},
onWillAccept: (int? data) => true,
onWillAcceptWithDetails: (DragTargetDetails<int> details) => false,
), throwsAssertionError);
});
} }
Future<void> _testLongPressDraggableHapticFeedback({ required WidgetTester tester, required bool hapticFeedbackOnStart, required int expectedHapticFeedbackCount }) async { Future<void> _testLongPressDraggableHapticFeedback({ required WidgetTester tester, required bool hapticFeedbackOnStart, required int expectedHapticFeedbackCount }) async {
......
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