Skip to content

Commit cf2aa14

Browse files
committed
Enhance desktop_drop macOS drag and drop
1 parent 5119508 commit cf2aa14

104 files changed

Lines changed: 3055 additions & 633 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/desktop_drop/.metadata

Lines changed: 0 additions & 42 deletions
This file was deleted.

packages/desktop_drop/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 0.8.0
4+
5+
* [macOS] Add app-wide drops from Dock, Finder, and Open With via
6+
`DropTarget.catchAppWideDrops`.
7+
* [macOS] Add Dock text/link drops through Services, delivered as
8+
memory-backed `DropItem`s with text and URI helpers.
9+
* Document and demonstrate the required macOS `Info.plist` and `AppDelegate`
10+
setup for global file, folder, text, and link drops.
11+
312
## 0.7.1
413

514
* update worksapce

packages/desktop_drop/README.md

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,153 @@ class _ExampleDragTargetState extends State<ExampleDragTarget> {
6767
6868
```
6969

70+
## macOS: Global Drops
71+
72+
On macOS there are two ways users can drop content into your app:
73+
74+
- In-window drag & drop over your UI (`DropTarget`).
75+
- Drop on the app's Dock icon, or use Open With from Finder, which is an
76+
application-level open.
77+
78+
The application-level path needs small macOS app configuration in addition to
79+
the Dart `DropTarget`.
80+
81+
### Files and folders via Dock or Finder
82+
83+
Add document types to your macOS `Info.plist` so Finder can route files and
84+
folders to the app:
85+
86+
```xml
87+
<!-- Advertise broad document types so Dock/Finder route drops to the app. -->
88+
<key>CFBundleDocumentTypes</key>
89+
<array>
90+
<dict>
91+
<key>CFBundleTypeRole</key>
92+
<string>Viewer</string>
93+
<key>LSItemContentTypes</key>
94+
<array>
95+
<string>public.data</string>
96+
<string>public.folder</string>
97+
</array>
98+
</dict>
99+
</array>
100+
```
101+
102+
Initialize the channel early. You can observe application-level drops with a raw
103+
listener:
104+
105+
```dart
106+
void main() {
107+
WidgetsFlutterBinding.ensureInitialized();
108+
109+
DesktopDrop.instance.addRawDropEventListener((event) async {
110+
if (event is DropDoneEvent && event.location == Offset.zero) {
111+
// Process files and directories in event.files.
112+
}
113+
});
114+
115+
DesktopDrop.instance.init();
116+
runApp(const MyApp());
117+
}
118+
```
119+
120+
You can also opt a primary `DropTarget` into app-wide drops:
121+
122+
```dart
123+
DropTarget(
124+
catchAppWideDrops: true,
125+
onDragDone: (details) {
126+
// Handles normal in-window drops and app-wide macOS drops.
127+
},
128+
child: child,
129+
)
130+
```
131+
132+
If multiple `DropTarget`s set `catchAppWideDrops: true`, each target can receive
133+
the same app-wide drop. In most apps, enable it only on the primary drop area.
134+
135+
### Text and links via Dock Services
136+
137+
macOS delivers selected text and links dropped on the Dock icon through
138+
Services. To accept those drops, configure both `Info.plist` and
139+
`AppDelegate.swift`.
140+
141+
1. Add an `NSServices` entry to your macOS `Info.plist`:
142+
143+
```xml
144+
<key>NSServices</key>
145+
<array>
146+
<dict>
147+
<key>NSMenuItem</key>
148+
<dict>
149+
<key>default</key>
150+
<string>Drop Text into My App</string>
151+
</dict>
152+
<key>NSMessage</key>
153+
<string>desktopDropAcceptDroppedText</string>
154+
<key>NSSendTypes</key>
155+
<array>
156+
<string>NSStringPboardType</string>
157+
<string>public.text</string>
158+
<string>public.plain-text</string>
159+
<string>public.utf8-plain-text</string>
160+
<string>public.utf16-plain-text</string>
161+
<string>public.utf16-external-plain-text</string>
162+
<string>public.html</string>
163+
<string>public.rtf</string>
164+
<string>public.url</string>
165+
</array>
166+
</dict>
167+
</array>
168+
```
169+
170+
2. Install the Services provider in `AppDelegate.swift` before the app finishes
171+
launching:
172+
173+
```swift
174+
import Cocoa
175+
import FlutterMacOS
176+
177+
@main
178+
class AppDelegate: FlutterAppDelegate {
179+
override func applicationWillFinishLaunching(_ notification: Notification) {
180+
if NSApp.servicesProvider == nil,
181+
let cls = NSClassFromString("DesktopDropServicesProvider") as? NSObject.Type {
182+
NSApp.servicesProvider = cls.init()
183+
}
184+
super.applicationWillFinishLaunching(notification)
185+
}
186+
}
187+
```
188+
189+
Dock text and link drops are delivered as memory-backed `DropItem`s. The package
190+
exports helpers for reading those values:
191+
192+
```dart
193+
import 'package:desktop_drop/desktop_drop.dart';
194+
195+
onDragDone: (details) async {
196+
for (final item in details.files) {
197+
if (item.isMemoryBacked && item.isTextLike) {
198+
final uris = await item.readAsUris();
199+
if (uris.isNotEmpty) {
200+
// Handle text/uri-list links.
201+
continue;
202+
}
203+
204+
final text = await item.readAsText();
205+
// Handle plain text, HTML, or raw RTF content.
206+
continue;
207+
}
208+
209+
// Handle real files and directories as before.
210+
}
211+
}
212+
```
213+
214+
The example app includes the required `Info.plist` and `AppDelegate.swift`
215+
setup, plus a `TextDropDemo` that displays dropped text and links.
216+
70217
## LICENSE
71218

72219
see LICENSE file

packages/desktop_drop/example/.metadata

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 6 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,16 @@
11
# desktop_drop_example
22

3-
Demonstrates how to use the desktop_drop plugin.
3+
A new Flutter project.
44

55
## Getting Started
66

77
This project is a starting point for a Flutter application.
88

99
A few resources to get you started if this is your first Flutter project:
1010

11-
- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab)
12-
- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook)
11+
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
12+
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
1313

14-
For help getting started with Flutter, view our
15-
[online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a
16-
full API reference.
17-
18-
## Example
19-
20-
```dart
21-
class ExampleDragTarget extends StatefulWidget {
22-
const ExampleDragTarget({Key? key}) : super(key: key);
23-
24-
@override
25-
_ExampleDragTargetState createState() => _ExampleDragTargetState();
26-
}
27-
28-
class _ExampleDragTargetState extends State<ExampleDragTarget> {
29-
final List<Uri> _list = [];
30-
31-
bool _dragging = false;
32-
33-
@override
34-
Widget build(BuildContext context) {
35-
return DropTarget(
36-
onDragDone: (urls) {
37-
setState(() {
38-
for (final uri in urls) {
39-
debugPrint("uri: ${uri.toFilePath()} "
40-
"${File(uri.toFilePath()).existsSync()}");
41-
}
42-
_list.addAll(urls);
43-
});
44-
},
45-
onDragEntered: () {
46-
setState(() {
47-
_dragging = true;
48-
});
49-
},
50-
onDragExited: () {
51-
setState(() {
52-
_dragging = false;
53-
});
54-
},
55-
child: Container(
56-
height: 200,
57-
width: 200,
58-
color: _dragging ? Colors.blue.withOpacity(0.4) : Colors.black26,
59-
child: _list.isEmpty
60-
? const Center(child: Text("Drop here"))
61-
: Text(_list.join("\n")),
62-
),
63-
);
64-
}
65-
}
66-
```
14+
For help getting started with Flutter development, view the
15+
[online documentation](https://docs.flutter.dev/), which offers tutorials,
16+
samples, guidance on mobile development, and a full API reference.

packages/desktop_drop/example/analysis_options.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,7 @@ linter:
1313
# The lint rules applied to this project can be customized in the
1414
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
1515
# included above or to enable additional rules. A list of all available lints
16-
# and their documentation is published at
17-
# https://dart-lang.github.io/linter/lints/index.html.
16+
# and their documentation is published at https://dart.dev/lints.
1817
#
1918
# Instead of disabling a lint rule for the entire project in the
2019
# section below, it can also be suppressed for a single line of code

packages/desktop_drop/example/android/app/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ android {
1111
ndkVersion = flutter.ndkVersion
1212

1313
compileOptions {
14-
sourceCompatibility = JavaVersion.VERSION_11
15-
targetCompatibility = JavaVersion.VERSION_11
14+
sourceCompatibility = JavaVersion.VERSION_17
15+
targetCompatibility = JavaVersion.VERSION_17
1616
}
1717

1818
kotlinOptions {
19-
jvmTarget = JavaVersion.VERSION_11.toString()
19+
jvmTarget = JavaVersion.VERSION_17.toString()
2020
}
2121

2222
defaultConfig {

packages/desktop_drop/example/android/build.gradle.kts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@ allprojects {
55
}
66
}
77

8-
val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
8+
val newBuildDir: Directory =
9+
rootProject.layout.buildDirectory
10+
.dir("../../build")
11+
.get()
912
rootProject.layout.buildDirectory.value(newBuildDir)
1013

1114
subprojects {
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,2 @@
11
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
22
android.useAndroidX=true
3-
android.enableJetifier=true

packages/desktop_drop/example/android/gradle/wrapper/gradle-wrapper.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
22
distributionPath=wrapper/dists
33
zipStoreBase=GRADLE_USER_HOME
44
zipStorePath=wrapper/dists
5-
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
5+
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip

0 commit comments

Comments
 (0)