super_drag_and_drop 0.9.0-dev.5 copy "super_drag_and_drop: ^0.9.0-dev.5" to clipboard
super_drag_and_drop: ^0.9.0-dev.5 copied to clipboard

Native Drag and Drop support for Flutter. Allows dragging contents across applications.

example/lib/main.dart

import 'dart:convert';
import 'dart:ui' as ui;

import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:super_drag_and_drop/super_drag_and_drop.dart';
import 'package:super_clipboard_example/widget_for_reader.dart';
import 'package:super_sliver_list/super_sliver_list.dart';

void main() async {
  runApp(const MyApp());
}

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        snackBarTheme: const SnackBarThemeData(
          behavior: SnackBarBehavior.floating,
        ),
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Super Drag and Drop Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class DragableWidget extends StatefulWidget {
  const DragableWidget({
    super.key,
    required this.name,
    required this.color,
    required this.dragItemProvider,
  });

  final String name;
  final Color color;
  final DragItemProvider dragItemProvider;

  @override
  State<DragableWidget> createState() => _DragableWidgetState();
}

class _DragableWidgetState extends State<DragableWidget> {
  bool _dragging = false;

  Future<DragItem?> provideDragItem(DragItemRequest request) async {
    final item = await widget.dragItemProvider(request);
    if (item != null) {
      void updateDraggingState() {
        setState(() {
          _dragging = request.session.dragging.value;
        });
      }

      request.session.dragging.addListener(updateDraggingState);
      updateDraggingState();
    }
    return item;
  }

  @override
  Widget build(BuildContext context) {
    return DragItemWidget(
      allowedOperations: () => [DropOperation.copy],
      canAddItemToExistingSession: true,
      dragItemProvider: provideDragItem,
      child: DraggableWidget(
        child: AnimatedOpacity(
          opacity: _dragging ? 0.5 : 1,
          duration: const Duration(milliseconds: 200),
          child: Container(
            decoration: BoxDecoration(
              color: widget.color,
              borderRadius: BorderRadius.circular(14),
            ),
            padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 14),
            child: Text(
              widget.name,
              style: const TextStyle(fontSize: 20, color: Colors.white),
              textAlign: TextAlign.center,
            ),
          ),
        ),
      ),
    );
  }
}

Future<Uint8List> createImageData(Color color) async {
  final recorder = ui.PictureRecorder();
  final canvas = Canvas(recorder);
  final paint = Paint()..color = color;
  canvas.drawOval(const Rect.fromLTWH(0, 0, 200, 200), paint);
  final picture = recorder.endRecording();
  final image = await picture.toImage(200, 200);
  final data = await image.toByteData(format: ui.ImageByteFormat.png);
  return data!.buffer.asUint8List();
}

class HomeLayout extends StatelessWidget {
  const HomeLayout({
    super.key,
    required this.draggable,
    required this.dropZone,
  });

  final List<Widget> draggable;
  final Widget dropZone;

  @override
  Widget build(BuildContext context) {
    return SafeArea(
      child: LayoutBuilder(builder: (context, constraints) {
        if (constraints.maxWidth < 500) {
          return Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Container(
                padding: const EdgeInsets.all(16),
                child: Wrap(
                  direction: Axis.horizontal,
                  runSpacing: 8,
                  spacing: 10,
                  children: draggable,
                ),
              ),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(16.0).copyWith(top: 0),
                  child: dropZone,
                ),
              ),
            ],
          );
        } else {
          return Row(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            textDirection: TextDirection.rtl,
            children: [
              SingleChildScrollView(
                padding: const EdgeInsets.all(16),
                child: IntrinsicWidth(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.stretch,
                    children: draggable
                        .intersperse(
                          const SizedBox(height: 10),
                        )
                        .toList(growable: false),
                  ),
                ),
              ),
              Expanded(
                child: Padding(
                  padding: const EdgeInsets.all(16.0).copyWith(right: 0),
                  child: dropZone,
                ),
              ),
            ],
          );
        }
      }),
    );
  }
}

extension on DragSession {
  Future<bool> hasLocalData(Object data) async {
    final localData = await getLocalData() ?? [];
    return localData.contains(data);
  }
}

class _MyHomePageState extends State<MyHomePage> {
  void showMessage(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: const Duration(milliseconds: 1500),
      ),
    );
  }

  Future<DragItem?> textDragItem(DragItemRequest request) async {
    // For multi drag on iOS check if this item is already in the session
    if (await request.session.hasLocalData('text-item')) {
      return null;
    }
    final item = DragItem(
      localData: 'text-item',
      suggestedName: 'PlainText.txt',
    );
    item.add(Formats.plainText('Plain Text Value'));
    return item;
  }

  Future<DragItem?> imageDragItem(DragItemRequest request) async {
    // For multi drag on iOS check if this item is already in the session
    if (await request.session.hasLocalData('image-item')) {
      return null;
    }
    final item = DragItem(
      localData: 'image-item',
      suggestedName: 'Green.png',
    );
    item.add(Formats.png(await createImageData(Colors.green)));
    return item;
  }

  Future<DragItem?> lazyImageDragItem(DragItemRequest request) async {
    // For multi drag on iOS check if this item is already in the session
    if (await request.session.hasLocalData('lazy-image-item')) {
      return null;
    }
    final item = DragItem(
      localData: 'lazy-image-item',
      suggestedName: 'LazyBlue.png',
    );
    item.add(Formats.png.lazy(() async {
      showMessage('Requested lazy image.');
      return await createImageData(Colors.blue);
    }));
    return item;
  }

  Future<DragItem?> virtualFileDragItem(DragItemRequest request) async {
    // For multi drag on iOS check if this item is already in the session
    if (await request.session.hasLocalData('virtual-file-item')) {
      return null;
    }
    final item = DragItem(
      localData: 'virtual-file-item',
      suggestedName: 'VirtualFile.txt',
    );
    if (!item.virtualFileSupported) {
      return null;
    }
    item.addVirtualFile(
      format: Formats.plainTextFile,
      provider: (sinkProvider, progress) {
        showMessage('Requesting virtual file content.');
        final line = utf8.encode('Line in virtual file\n');
        const lines = 10;
        final sink = sinkProvider(fileSize: line.length * lines);
        for (var i = 0; i < lines; ++i) {
          sink.add(line);
        }
        sink.close();
      },
    );
    return item;
  }

  Future<DragItem?> multipleRepresentationsDragItem(
      DragItemRequest request) async {
    // For multi drag on iOS check if this item is already in the session
    if (await request.session.hasLocalData('multiple-representations-item')) {
      return null;
    }
    final item = DragItem(
      localData: 'multiple-representations-item',
    );
    item.add(Formats.png(await createImageData(Colors.pink)));
    item.add(Formats.plainText("Hello World"));
    item.add(Formats.uri(
        NamedUri(Uri.parse('https://flutter.dev'), name: 'Flutter')));
    return item;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: HomeLayout(
        draggable: [
          DragableWidget(
            name: 'Text',
            color: Colors.red,
            dragItemProvider: textDragItem,
          ),
          DragableWidget(
            name: 'Image',
            color: Colors.green,
            dragItemProvider: imageDragItem,
          ),
          DragableWidget(
            name: 'Image 2',
            color: Colors.blue,
            dragItemProvider: lazyImageDragItem,
          ),
          DragableWidget(
            name: 'Virtual',
            color: Colors.amber.shade700,
            dragItemProvider: virtualFileDragItem,
          ),
          DragableWidget(
            name: 'Multiple',
            color: Colors.pink,
            dragItemProvider: multipleRepresentationsDragItem,
          ),
        ],
        dropZone: Container(
          decoration: BoxDecoration(
            border: Border.all(color: Colors.blueGrey.shade200),
            borderRadius: BorderRadius.circular(14),
          ),
          child: _DropZone(),
        ),
      ),
    );
  }
}

class _DropZone extends StatefulWidget {
  @override
  State<StatefulWidget> createState() => _DropZoneState();
}

class _DropZoneState extends State<_DropZone> {
  @override
  Widget build(BuildContext context) {
    return DropRegion(
      formats: const [
        ...Formats.standardFormats,
        formatCustom,
      ],
      hitTestBehavior: HitTestBehavior.opaque,
      onDropOver: _onDropOver,
      onPerformDrop: _onPerformDrop,
      onDropLeave: _onDropLeave,
      child: Stack(
        children: [
          Positioned.fill(child: _content),
          Positioned.fill(
            child: IgnorePointer(
              child: AnimatedOpacity(
                opacity: _isDragOver ? 1.0 : 0.0,
                duration: const Duration(milliseconds: 200),
                child: _preview,
              ),
            ),
          ),
        ],
      ),
    );
  }

  DropOperation _onDropOver(DropOverEvent event) {
    setState(() {
      _isDragOver = true;
      _preview = Container(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(13),
          color: Colors.black.withOpacity(0.2),
        ),
        child: Padding(
          padding: const EdgeInsets.all(50),
          child: Center(
            child: ConstrainedBox(
              constraints: const BoxConstraints(maxWidth: 400),
              child: ClipRRect(
                borderRadius: BorderRadius.circular(10),
                child: ListView(
                  shrinkWrap: true,
                  children: event.session.items
                      .map<Widget>((e) => _DropItemInfo(dropItem: e))
                      .intersperse(Container(
                        height: 2,
                        color: Colors.white.withOpacity(0.7),
                      ))
                      .toList(growable: false),
                ),
              ),
            ),
          ),
        ),
      );
    });
    return event.session.allowedOperations.firstOrNull ?? DropOperation.none;
  }

  Future<void> _onPerformDrop(PerformDropEvent event) async {
    // Obtain additional reader information first
    final readers = await Future.wait(
      event.session.items.map(
        (e) => ReaderInfo.fromReader(
          e.dataReader!,
          localData: e.localData,
        ),
      ),
    );

    if (!mounted) {
      return;
    }

    buildWidgetsForReaders(context, readers, (value) {
      setState(() {
        // Use super_sliver_list to get around bad sliver list performance
        // with large amount if items.
        final delegate = SliverChildListDelegate(value
            .intersperse(const SizedBox(height: 10))
            .toList(growable: false));
        _content = CustomScrollView(
          slivers: [
            SliverPadding(
              padding: const EdgeInsets.all(10),
              sliver: SuperSliverList(delegate: delegate),
            )
          ],
        );
      });
    });
  }

  void _onDropLeave(DropEvent event) {
    setState(() {
      _isDragOver = false;
    });
  }

  bool _isDragOver = false;

  Widget _preview = const SizedBox();
  Widget _content = const Center(
    child: Text(
      'Drop here',
      style: TextStyle(
        color: Colors.grey,
        fontSize: 16,
      ),
    ),
  );
}

class _DropItemInfo extends StatelessWidget {
  const _DropItemInfo({
    required this.dropItem,
  });

  final DropItem dropItem;

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.white,
      padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 10),
      child: DefaultTextStyle.merge(
        style: const TextStyle(fontSize: 11.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            if (dropItem.localData != null)
              Text.rich(TextSpan(children: [
                const TextSpan(
                  text: 'Local data: ',
                  style: TextStyle(fontWeight: FontWeight.bold),
                ),
                TextSpan(text: '${dropItem.localData}'),
              ])),
            const SizedBox(
              height: 4,
            ),
            Text.rich(TextSpan(children: [
              const TextSpan(
                text: 'Native formats: ',
                style: TextStyle(fontWeight: FontWeight.bold),
              ),
              TextSpan(text: dropItem.platformFormats.join(', ')),
            ])),
          ],
        ),
      ),
    );
  }
}
435
likes
160
pub points
95%
popularity

Publisher

verified publishernativeshell.dev

Native Drag and Drop support for Flutter. Allows dragging contents across applications.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

collection, flutter, super_clipboard, super_native_extensions

More

Packages that depend on super_drag_and_drop