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
This commit is contained in:
parent
72bd36026f
commit
347f7bac94
@ -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 {
|
|||||||
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 {
|
|||||||
/// 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>> {
|
|||||||
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() {
|
|||||||
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() {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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() {
|
|||||||
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() {
|
|||||||
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 {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user