desktop_drop 0.9.1 copy "desktop_drop: ^0.9.1" to clipboard
desktop_drop: ^0.9.1 copied to clipboard

Receive files, text, and URLs with per-target drag acceptance in Flutter applications.

example/lib/main.dart

import 'dart:math' as math;

import 'package:desktop_drop/desktop_drop.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';

import 'common/app_theme.dart';
import 'common/received_item.dart';
import 'pages/drop_content.dart';
import 'pages/drop_stage.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Desktop Drop',
    debugShowCheckedModeBanner: false,
    theme: AppTheme.lightTheme,
    home: const DropPlayground(),
  );
}

class DropPlayground extends StatefulWidget {
  const DropPlayground({super.key});

  @override
  State<DropPlayground> createState() => _DropPlaygroundState();
}

class _DropPlaygroundState extends State<DropPlayground> {
  DropDetails? _drop;
  String? _receiver;
  Map<DropItem, ReceivedItem> _content = {};
  double _stageFraction = .38;

  Future<void> _received(String target, DropDetails event) async {
    setState(() {
      _drop = event;
      _receiver = target;
      _content = {};
    });
    final content = await readDrop(
      event,
      onDirectoryAccessRequired: (directory) async {
        if (!mounted || !identical(_drop, event)) return false;
        final authorize = await showDialog<bool>(
          context: context,
          builder: (context) => AlertDialog(
            title: const Text('Directory access required'),
            content: Text(
              'Android has not granted access to the children of "${directory.name}". Authorize this folder to read its contents.',
            ),
            actions: [
              TextButton(
                onPressed: () => Navigator.pop(context, false),
                child: const Text('Cancel'),
              ),
              FilledButton(
                onPressed: () => Navigator.pop(context, true),
                child: const Text('Authorize folder'),
              ),
            ],
          ),
        );
        if (authorize != true || !mounted || !identical(_drop, event)) {
          return false;
        }
        await DesktopDrop.instance.requestDirectoryAccess(directory: directory);
        return mounted && identical(_drop, event);
      },
    );
    if (mounted && identical(_drop, event)) {
      setState(() => _content = content);
    }
    final failures = content.values.where((item) => item.error != null);
    if (failures.isNotEmpty) {
      throw StateError(
        'Could not receive ${failures.length} items: ${failures.first.error}',
      );
    }
  }

  void _clear() {
    setState(() {
      _drop = null;
      _receiver = null;
      _content = {};
    });
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    body: SafeArea(
      child: Column(
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
            child: Row(
              children: [
                const Icon(
                  Icons.move_to_inbox_outlined,
                  size: 22,
                  color: AppTheme.accent,
                ),
                const SizedBox(width: 10),
                const Text(
                  'Desktop Drop',
                  style: TextStyle(
                    fontSize: 18,
                    fontWeight: FontWeight.w600,
                    color: AppTheme.ink,
                  ),
                ),
                const Spacer(),
                Text(
                  kIsWeb ? 'Web' : defaultTargetPlatform.name,
                  style: const TextStyle(color: AppTheme.muted),
                ),
              ],
            ),
          ),
          const Divider(height: 1),
          Expanded(
            child: LayoutBuilder(
              builder: (context, constraints) {
                // Keep both panes visible on desktop. Very small hosts can pan
                // horizontally rather than moving results below the targets.
                final width = math.max(800.0, constraints.maxWidth);
                final left = (width * _stageFraction).clamp(300.0, width - 460);
                final panes = SizedBox(
                  width: width,
                  height: constraints.maxHeight,
                  child: Row(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: [
                      SizedBox(
                        width: left,
                        child: Padding(
                          padding: const EdgeInsets.fromLTRB(20, 18, 12, 18),
                          child: DropStage(onDrop: _received),
                        ),
                      ),
                      MouseRegion(
                        cursor: SystemMouseCursors.resizeColumn,
                        child: GestureDetector(
                          behavior: HitTestBehavior.opaque,
                          onHorizontalDragUpdate: (details) => setState(
                            () => _stageFraction =
                                ((left + details.delta.dx) / width).clamp(
                                  .28,
                                  .52,
                                ),
                          ),
                          child: const SizedBox(
                            width: 12,
                            child: Center(
                              child: SizedBox(
                                width: 1,
                                height: double.infinity,
                                child: ColoredBox(color: AppTheme.border),
                              ),
                            ),
                          ),
                        ),
                      ),
                      Expanded(
                        child: Padding(
                          padding: const EdgeInsets.fromLTRB(12, 18, 20, 18),
                          child: DropContent(
                            key: ObjectKey(_drop),
                            event: _drop,
                            content: _content,
                            receiver: _receiver,
                            onClear: _clear,
                          ),
                        ),
                      ),
                    ],
                  ),
                );
                return constraints.maxWidth < width
                    ? SingleChildScrollView(
                        scrollDirection: Axis.horizontal,
                        child: panes,
                      )
                    : panes;
              },
            ),
          ),
        ],
      ),
    ),
  );
}
470
likes
150
points
871k
downloads

Documentation

API reference

Publisher

verified publishermixin.dev

Weekly Downloads

Receive files, text, and URLs with per-target drag acceptance in Flutter applications.

Repository (GitHub)
View/report issues

License

Apache-2.0 (license)

Dependencies

cross_file, cross_file_platform_interface, cross_file_web, flutter, flutter_web_plugins, mime, web

More

Packages that depend on desktop_drop

Packages that implement desktop_drop