11import 'dart:convert' ;
22
3+ import 'package:collection/collection.dart' ;
34import 'package:cross_file/cross_file.dart' ;
5+ import 'package:flutter/rendering.dart' ;
46import 'package:flutter/services.dart' ;
57import 'package:flutter/widgets.dart' ;
68
79import 'drop_item.dart' ;
810import 'events.dart' ;
9- import 'utils/platform.dart' if (dart.library.html) 'utils/platform_web.dart' ;
1011
1112abstract class RawDropListener {
12- void onEvent (DropEvent event);
13- bool isInBounds (DropEvent event);
13+ /// Returns true if event was handled, false otherwise
14+ bool handleDropEvent (DropEvent event);
15+
16+ Offset globalToLocalOffset (Offset global);
1417}
1518
1619class DesktopDrop {
@@ -20,10 +23,9 @@ class DesktopDrop {
2023
2124 static final instance = DesktopDrop ._();
2225
23- final _listeners = < RawDropListener > {};
24-
2526 var _initialized = false ;
2627
28+ RawDropListener ? _currentTargetListener;
2729 Offset ? _offset;
2830
2931 void init () {
@@ -47,25 +49,25 @@ class DesktopDrop {
4749 case "entered" :
4850 final position = (call.arguments as List ).cast <double >();
4951 _offset = Offset (position[0 ], position[1 ]);
50- _notifyEvent (DropEnterEvent (location: _offset! ));
52+ _notifyPositionEvent (DropEnterEvent (location: _offset! ));
5153 break ;
5254 case "updated" :
5355 final position = (call.arguments as List ).cast <double >();
5456 final previousOffset = _offset;
5557 _offset = Offset (position[0 ], position[1 ]);
5658 if (previousOffset == null ) {
57- _notifyEvent (DropEnterEvent (location: _offset! ));
59+ _notifyPositionEvent (DropEnterEvent (location: _offset! ));
5860 } else {
59- _notifyEvent (DropUpdateEvent (location: _offset! ));
61+ _notifyPositionEvent (DropUpdateEvent (location: _offset! ));
6062 }
6163 break ;
6264 case "exited" :
63- _notifyEvent (DropExitEvent (location: _offset ?? Offset .zero));
65+ _notifyPositionEvent (DropExitEvent (location: _offset ?? Offset .zero));
6466 _offset = null ;
6567 break ;
6668 case "performOperation" :
6769 final paths = (call.arguments as List ).cast <String >();
68- _notifyEvent (
70+ _notifyDoneEvent (
6971 DropDoneEvent (
7072 location: _offset ?? Offset .zero,
7173 files: paths.map ((e) => XFile (e)).toList (),
@@ -85,10 +87,12 @@ class DesktopDrop {
8587 }
8688 return '' ;
8789 }).where ((e) => e.isNotEmpty);
88- _notifyEvent (DropDoneEvent (
89- location: Offset (offset[0 ], offset[1 ]),
90- files: paths.map ((e) => XFile (e)).toList (),
91- ));
90+ _notifyDoneEvent (
91+ DropDoneEvent (
92+ location: Offset (offset[0 ], offset[1 ]),
93+ files: paths.map ((e) => XFile (e)).toList (),
94+ ),
95+ );
9296 break ;
9397 case "performOperation_web" :
9498 final results = (call.arguments as List )
@@ -102,7 +106,7 @@ class DesktopDrop {
102106 mimeType: e.type,
103107 ))
104108 .toList ();
105- _notifyEvent (
109+ _notifyDoneEvent (
106110 DropDoneEvent (location: _offset ?? Offset .zero, files: results),
107111 );
108112 _offset = null ;
@@ -112,29 +116,62 @@ class DesktopDrop {
112116 }
113117 }
114118
115- void _notifyEvent (DropEvent event) {
116- final reversedListeners = _listeners.toList (growable: false ).reversed;
117- var foundTargetListener = false ;
118- for (final listener in reversedListeners) {
119- final isInBounds = listener.isInBounds (event);
120- if (isInBounds && ! foundTargetListener) {
121- foundTargetListener = true ;
122- listener.onEvent (event);
119+ void _notifyPositionEvent (DropEvent event) {
120+ final RawDropListener ? target;
121+
122+ if (event is DropExitEvent ) {
123+ target = null ;
124+ } else {
125+ final result = BoxHitTestResult ();
126+ WidgetsBinding .instance.renderView.hitTest (result, position: event.location);
127+
128+ target = result.path.firstWhereOrNull ((entry) => entry.target is RawDropListener )? .target as RawDropListener ? ;
129+ }
130+
131+ if (_currentTargetListener != target) {
132+ final previous = _currentTargetListener;
133+ if (previous != null ) {
134+ previous.handleDropEvent (
135+ DropExitEvent (
136+ location: previous.globalToLocalOffset (event.location),
137+ ),
138+ );
139+ }
140+ }
141+ if (target != null ) {
142+ final position = target.globalToLocalOffset (event.location);
143+ if (_currentTargetListener == null ) {
144+ target.handleDropEvent (DropEnterEvent (location: position));
123145 } else {
124- listener. onEvent ( DropExitEvent (location: event.location ));
146+ target. handleDropEvent ( DropUpdateEvent (location: position ));
125147 }
126148 }
127-
128- _channel.invokeMethod ('updateDroppableStatus' , foundTargetListener );
149+ _currentTargetListener = target;
150+ _channel.invokeMethod ('updateDroppableStatus' , target != null );
129151 }
130152
131- void addRawDropEventListener (RawDropListener listener) {
132- assert (! _listeners.contains (listener));
133- _listeners.add (listener);
134- }
153+ void _notifyDoneEvent (DropDoneEvent event) {
154+ final result = BoxHitTestResult ();
155+ WidgetsBinding .instance.renderView.hitTest (result, position: event.location);
135156
136- void removeRawDropEventListener (RawDropListener listener) {
137- assert (_listeners.contains (listener));
138- _listeners.remove (listener);
157+ final target = result.path.firstWhereOrNull ((entry) => entry.target is RawDropListener )? .target as RawDropListener ? ;
158+ final previous = _currentTargetListener;
159+ if (previous != null ) {
160+ previous.handleDropEvent (
161+ DropExitEvent (
162+ location: previous.globalToLocalOffset (event.location),
163+ ),
164+ );
165+ _currentTargetListener = null ;
166+ }
167+ if (target != null ) {
168+ target.handleDropEvent (
169+ DropDoneEvent (
170+ location: target.globalToLocalOffset (event.location),
171+ files: event.files,
172+ ),
173+ );
174+ }
175+ _channel.invokeMethod ('updateDroppableStatus' , false );
139176 }
140177}
0 commit comments