From cacea0074dfc2a85a16449b88887af2c8ac9a823 Mon Sep 17 00:00:00 2001 From: Till Friebe Date: Thu, 15 Sep 2022 13:31:21 +0200 Subject: [PATCH] Add ability to paste images (#950) --- .../flutter/generated_plugin_registrant.cc | 4 ++ example/linux/flutter/generated_plugins.cmake | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 + .../flutter/generated_plugin_registrant.cc | 3 + .../windows/flutter/generated_plugins.cmake | 1 + lib/src/widgets/editor.dart | 8 +++ lib/src/widgets/raw_editor.dart | 55 ++++++++++++++----- pubspec.yaml | 1 + 8 files changed, 60 insertions(+), 15 deletions(-) diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index f6f23bfe..1560fc80 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -6,9 +6,13 @@ #include "generated_plugin_registrant.h" +#include #include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) pasteboard_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); + pasteboard_plugin_register_with_registrar(pasteboard_registrar); g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index f16b4c34..5a3c3882 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + pasteboard url_launcher_linux ) diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 49dbe0da..886c66ec 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,11 +6,13 @@ import FlutterMacOS import Foundation import device_info_plus_macos +import pasteboard import path_provider_macos import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) + PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index 4f788487..02129f58 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,12 @@ #include "generated_plugin_registrant.h" +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + PasteboardPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("PasteboardPlugin")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index 88b22e5c..82731c9d 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + pasteboard url_launcher_windows ) diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 04c42edc..96e858b3 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -1,4 +1,5 @@ import 'dart:math' as math; +import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -175,6 +176,7 @@ class QuillEditor extends StatefulWidget { this.locale, this.floatingCursorDisabled = false, this.textSelectionControls, + this.onImagePaste, Key? key}) : super(key: key); @@ -377,6 +379,11 @@ class QuillEditor extends StatefulWidget { /// will be used final TextSelectionControls? textSelectionControls; + /// Callback when the user pastes the given image. + /// + /// Returns the url of the image if the image should be inserted. + final Future Function(Uint8List imageBytes)? onImagePaste; + @override QuillEditorState createState() => QuillEditorState(); } @@ -499,6 +506,7 @@ class QuillEditorState extends State linkActionPickerDelegate: widget.linkActionPickerDelegate, customStyleBuilder: widget.customStyleBuilder, floatingCursorDisabled: widget.floatingCursorDisabled, + onImagePaste: widget.onImagePaste, ); final editor = I18n( diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index ac2678c9..47a3d990 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:math' as math; +import 'dart:typed_data'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -9,6 +10,7 @@ import 'package:flutter/rendering.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter/services.dart'; import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; +import 'package:pasteboard/pasteboard.dart'; import 'package:tuple/tuple.dart'; import '../models/documents/attribute.dart'; @@ -72,7 +74,8 @@ class RawEditor extends StatefulWidget { this.scrollPhysics, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, - this.floatingCursorDisabled = false}) + this.floatingCursorDisabled = false, + this.onImagePaste}) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'), assert(maxHeight == null || minHeight == null || maxHeight >= minHeight, @@ -219,6 +222,8 @@ class RawEditor extends StatefulWidget { /// See [Scrollable.physics]. final ScrollPhysics? scrollPhysics; + final Future Function(Uint8List imageBytes)? onImagePaste; + /// Builder function for embeddable objects. final EmbedsBuilder embedBuilder; final LinkActionPickerDelegate linkActionPickerDelegate; @@ -1022,25 +1027,45 @@ class RawEditorState extends EditorState } // Snapshot the input before using `await`. // See https://github.com/flutter/flutter/issues/11427 - final data = await Clipboard.getData(Clipboard.kTextPlain); - if (data == null) { + final text = await Clipboard.getData(Clipboard.kTextPlain); + if (text != null) { + _replaceText( + ReplaceTextIntent(textEditingValue, text.text!, selection, cause)); + + bringIntoView(textEditingValue.selection.extent); + + // Collapse the selection and hide the toolbar and handles. + userUpdateTextEditingValue( + TextEditingValue( + text: textEditingValue.text, + selection: + TextSelection.collapsed(offset: textEditingValue.selection.end), + ), + cause, + ); + return; } - _replaceText( - ReplaceTextIntent(textEditingValue, data.text!, selection, cause)); + if (widget.onImagePaste != null) { + final image = await Pasteboard.image; - bringIntoView(textEditingValue.selection.extent); + if (image == null) { + return; + } - // Collapse the selection and hide the toolbar and handles. - userUpdateTextEditingValue( - TextEditingValue( - text: textEditingValue.text, - selection: - TextSelection.collapsed(offset: textEditingValue.selection.end), - ), - cause, - ); + final imageUrl = await widget.onImagePaste!(image); + if (imageUrl == null) { + return; + } + + controller.replaceText( + textEditingValue.selection.end, + 0, + BlockEmbed.image(imageUrl), + null, + ); + } } /// Select the entire text value. diff --git a/pubspec.yaml b/pubspec.yaml index 587912ac..f2c3fa63 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -24,6 +24,7 @@ dependencies: i18n_extension: ^5.0.1 device_info_plus: ^4.0.0 platform: ^3.1.0 + pasteboard: ^0.2.0 dev_dependencies: flutter_test: