Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions packages/desktop_drop/.metadata

This file was deleted.

9 changes: 9 additions & 0 deletions packages/desktop_drop/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## 0.8.0

* [macOS] Add app-wide drops from Dock, Finder, and Open With via
`DropTarget.catchAppWideDrops`.
* [macOS] Add Dock text/link drops through Services, delivered as
memory-backed `DropItem`s with text and URI helpers.
* Document and demonstrate the required macOS `Info.plist` and `AppDelegate`
setup for global file, folder, text, and link drops.

## 0.7.1

* update worksapce
Expand Down
147 changes: 147 additions & 0 deletions packages/desktop_drop/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,153 @@ class _ExampleDragTargetState extends State<ExampleDragTarget> {

```

## macOS: Global Drops

On macOS there are two ways users can drop content into your app:

- In-window drag & drop over your UI (`DropTarget`).
- Drop on the app's Dock icon, or use Open With from Finder, which is an
application-level open.

The application-level path needs small macOS app configuration in addition to
the Dart `DropTarget`.

### Files and folders via Dock or Finder

Add document types to your macOS `Info.plist` so Finder can route files and
folders to the app:

```xml
<!-- Advertise broad document types so Dock/Finder route drops to the app. -->
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeRole</key>
<string>Viewer</string>
<key>LSItemContentTypes</key>
<array>
<string>public.data</string>
<string>public.folder</string>
</array>
</dict>
</array>
```

Initialize the channel early. You can observe application-level drops with a raw
listener:

```dart
void main() {
WidgetsFlutterBinding.ensureInitialized();

DesktopDrop.instance.addRawDropEventListener((event) async {
if (event is DropDoneEvent && event.location == Offset.zero) {
// Process files and directories in event.files.
}
});

DesktopDrop.instance.init();
runApp(const MyApp());
}
```

You can also opt a primary `DropTarget` into app-wide drops:

```dart
DropTarget(
catchAppWideDrops: true,
onDragDone: (details) {
// Handles normal in-window drops and app-wide macOS drops.
},
child: child,
)
```

If multiple `DropTarget`s set `catchAppWideDrops: true`, each target can receive
the same app-wide drop. In most apps, enable it only on the primary drop area.

### Text and links via Dock Services

macOS delivers selected text and links dropped on the Dock icon through
Services. To accept those drops, configure both `Info.plist` and
`AppDelegate.swift`.

1. Add an `NSServices` entry to your macOS `Info.plist`:

```xml
<key>NSServices</key>
<array>
<dict>
<key>NSMenuItem</key>
<dict>
<key>default</key>
<string>Drop Text into My App</string>
</dict>
<key>NSMessage</key>
<string>desktopDropAcceptDroppedText</string>
<key>NSSendTypes</key>
<array>
<string>NSStringPboardType</string>
<string>public.text</string>
<string>public.plain-text</string>
<string>public.utf8-plain-text</string>
<string>public.utf16-plain-text</string>
<string>public.utf16-external-plain-text</string>
<string>public.html</string>
<string>public.rtf</string>
<string>public.url</string>
</array>
</dict>
</array>
```

2. Install the Services provider in `AppDelegate.swift` before the app finishes
launching:

```swift
import Cocoa
import FlutterMacOS

@main
class AppDelegate: FlutterAppDelegate {
override func applicationWillFinishLaunching(_ notification: Notification) {
if NSApp.servicesProvider == nil,
let cls = NSClassFromString("DesktopDropServicesProvider") as? NSObject.Type {
NSApp.servicesProvider = cls.init()
}
super.applicationWillFinishLaunching(notification)
}
}
```

Dock text and link drops are delivered as memory-backed `DropItem`s. The package
exports helpers for reading those values:

```dart
import 'package:desktop_drop/desktop_drop.dart';

onDragDone: (details) async {
for (final item in details.files) {
if (item.isMemoryBacked && item.isTextLike) {
final uris = await item.readAsUris();
if (uris.isNotEmpty) {
// Handle text/uri-list links.
continue;
}

final text = await item.readAsText();
// Handle plain text, HTML, or raw RTF content.
continue;
}

// Handle real files and directories as before.
}
}
```

The example app includes the required `Info.plist` and `AppDelegate.swift`
setup, plus a `TextDropDemo` that displays dropped text and links.

## LICENSE

see LICENSE file
30 changes: 0 additions & 30 deletions packages/desktop_drop/example/.metadata

This file was deleted.

62 changes: 6 additions & 56 deletions packages/desktop_drop/example/README.md
Original file line number Diff line number Diff line change
@@ -1,66 +1,16 @@
# desktop_drop_example

Demonstrates how to use the desktop_drop plugin.
A new Flutter project.

## Getting Started

This project is a starting point for a Flutter application.

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

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

For help getting started with Flutter, view our
[online documentation](https://flutter.dev/docs), which offers tutorials, samples, guidance on mobile development, and a
full API reference.

## Example

```dart
class ExampleDragTarget extends StatefulWidget {
const ExampleDragTarget({Key? key}) : super(key: key);

@override
_ExampleDragTargetState createState() => _ExampleDragTargetState();
}

class _ExampleDragTargetState extends State<ExampleDragTarget> {
final List<Uri> _list = [];

bool _dragging = false;

@override
Widget build(BuildContext context) {
return DropTarget(
onDragDone: (urls) {
setState(() {
for (final uri in urls) {
debugPrint("uri: ${uri.toFilePath()} "
"${File(uri.toFilePath()).existsSync()}");
}
_list.addAll(urls);
});
},
onDragEntered: () {
setState(() {
_dragging = true;
});
},
onDragExited: () {
setState(() {
_dragging = false;
});
},
child: Container(
height: 200,
width: 200,
color: _dragging ? Colors.blue.withOpacity(0.4) : Colors.black26,
child: _list.isEmpty
? const Center(child: Text("Drop here"))
: Text(_list.join("\n")),
),
);
}
}
```
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.
3 changes: 1 addition & 2 deletions packages/desktop_drop/example/analysis_options.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
# and their documentation is published at https://dart.dev/lints.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
Expand Down
6 changes: 3 additions & 3 deletions packages/desktop_drop/example/android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@ android {
ndkVersion = flutter.ndkVersion

compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
jvmTarget = JavaVersion.VERSION_17.toString()
}

defaultConfig {
Expand Down
5 changes: 4 additions & 1 deletion packages/desktop_drop/example/android/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ allprojects {
}
}

val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get()
val newBuildDir: Directory =
rootProject.layout.buildDirectory
.dir("../../build")
.get()
rootProject.layout.buildDirectory.value(newBuildDir)

subprojects {
Expand Down
1 change: 0 additions & 1 deletion packages/desktop_drop/example/android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip
19 changes: 10 additions & 9 deletions packages/desktop_drop/example/android/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
pluginManagement {
val flutterSdkPath = run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}
val flutterSdkPath =
run {
val properties = java.util.Properties()
file("local.properties").inputStream().use { properties.load(it) }
val flutterSdkPath = properties.getProperty("flutter.sdk")
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
flutterSdkPath
}

includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")

Expand All @@ -18,8 +19,8 @@ pluginManagement {

plugins {
id("dev.flutter.flutter-plugin-loader") version "1.0.0"
id("com.android.application") version "8.7.0" apply false
id("org.jetbrains.kotlin.android") version "1.8.22" apply false
id("com.android.application") version "8.11.1" apply false
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
}

include(":app")
Loading
Loading