Drag and drop feature in the example and builder for QuillEditor (#1508)

* Drag and drop feature in the example

* Update README.md of `flutter_quill_extensions`

* Add builder method in `QuillEditorConfigurations`
pull/1511/head
Ellet 1 year ago committed by GitHub
parent 7fdadc051f
commit b020dd6b7d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      CHANGELOG.md
  2. 41
      example/lib/pages/home_page.dart
  3. 4
      example/linux/flutter/generated_plugin_registrant.cc
  4. 1
      example/linux/flutter/generated_plugins.cmake
  5. 2
      example/macos/Flutter/GeneratedPluginRegistrant.swift
  6. 1
      example/pubspec.yaml
  7. 3
      example/windows/flutter/generated_plugin_registrant.cc
  8. 1
      example/windows/flutter/generated_plugins.cmake
  9. 3
      flutter_quill_extensions/CHANGELOG.md
  10. 73
      flutter_quill_extensions/README.md
  11. 2
      flutter_quill_extensions/pubspec.yaml
  12. 11
      lib/src/models/config/editor/configurations.dart
  13. 108
      lib/src/widgets/editor/editor.dart
  14. 31
      lib/src/widgets/editor/editor_builder.dart
  15. 2
      pubspec.yaml

@ -1,3 +1,6 @@
## [8.2.5]
- Add `builder` property in the `QuillEditorConfigurations`
## [8.2.4] ## [8.2.4]
- Follow flutter best practices - Follow flutter best practices
- Auto focus bug fix - Auto focus bug fix

@ -5,6 +5,7 @@ import 'dart:convert';
import 'dart:io' show File; import 'dart:io' show File;
import 'dart:ui'; import 'dart:ui';
import 'package:desktop_drop/desktop_drop.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -13,6 +14,7 @@ import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart'; import 'package:flutter_quill_extensions/logic/services/image_picker/image_picker.dart';
import 'package:flutter_quill_extensions/presentation/embeds/widgets/image.dart';
import 'package:path/path.dart' as path; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -333,12 +335,45 @@ class _HomePageState extends State<HomePage> {
}); });
} }
OnDragDoneCallback get _onDragDone {
return (details) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final file = details.files.first;
final isSupported =
imageFileExtensions.any((ext) => file.name.endsWith(ext));
if (!isSupported) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
'Only images are supported right now: ${file.mimeType}, ${file.name}, ${file.path}, $imageFileExtensions',
),
),
);
return;
}
_controller.insertImageBlock(
imageSource: file.path,
);
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('Image is inserted.'),
),
);
};
}
QuillEditor get quillEditor { QuillEditor get quillEditor {
if (kIsWeb) { if (kIsWeb) {
return QuillEditor( return QuillEditor(
focusNode: _focusNode, focusNode: _focusNode,
scrollController: ScrollController(), scrollController: ScrollController(),
configurations: QuillEditorConfigurations( configurations: QuillEditorConfigurations(
builder: (context, rawEditor) {
return DropTarget(
onDragDone: _onDragDone,
child: rawEditor,
);
},
placeholder: 'Add content', placeholder: 'Add content',
readOnly: false, readOnly: false,
scrollable: true, scrollable: true,
@ -370,6 +405,12 @@ class _HomePageState extends State<HomePage> {
} }
return QuillEditor( return QuillEditor(
configurations: QuillEditorConfigurations( configurations: QuillEditorConfigurations(
builder: (context, rawEditor) {
return DropTarget(
onDragDone: _onDragDone,
child: rawEditor,
);
},
placeholder: 'Add content', placeholder: 'Add content',
readOnly: _isReadOnly, readOnly: _isReadOnly,
autoFocus: false, autoFocus: false,

@ -6,11 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h> #include <file_selector_linux/file_selector_plugin.h>
#include <pasteboard/pasteboard_plugin.h> #include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar); file_selector_plugin_register_with_registrar(file_selector_linux_registrar);

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_linux file_selector_linux
pasteboard pasteboard
url_launcher_linux url_launcher_linux

@ -5,6 +5,7 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import desktop_drop
import device_info_plus import device_info_plus
import file_selector_macos import file_selector_macos
import gal import gal
@ -14,6 +15,7 @@ import url_launcher_macos
import video_player_avfoundation import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin"))

@ -20,6 +20,7 @@ dependencies:
flutter_quill: ^8.2.3 flutter_quill: ^8.2.3
flutter_quill_extensions: ^0.6.1 flutter_quill_extensions: ^0.6.1
path: ^1.8.3 path: ^1.8.3
desktop_drop: ^0.4.4
dependency_overrides: dependency_overrides:
flutter_quill: flutter_quill:

@ -6,12 +6,15 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_windows/file_selector_windows.h> #include <file_selector_windows/file_selector_windows.h>
#include <gal/gal_plugin_c_api.h> #include <gal/gal_plugin_c_api.h>
#include <pasteboard/pasteboard_plugin.h> #include <pasteboard/pasteboard_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
DesktopDropPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FileSelectorWindowsRegisterWithRegistrar( FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows")); registry->GetRegistrarForPlugin("FileSelectorWindows"));
GalPluginCApiRegisterWithRegistrar( GalPluginCApiRegisterWithRegistrar(

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
desktop_drop
file_selector_windows file_selector_windows
gal gal
pasteboard pasteboard

@ -1,3 +1,6 @@
## 0.6.3
- Update `README.md`
## 0.6.2 ## 0.6.2
- Add more default exports - Add more default exports

@ -18,6 +18,7 @@ Currently the support for **Web** is limitied.
- [Embed Blocks](#embed-blocks) - [Embed Blocks](#embed-blocks)
- [Custom Size Image for Mobile](#custom-size-image-for-mobile) - [Custom Size Image for Mobile](#custom-size-image-for-mobile)
- [Custom Size Image for other platforms (excluding web)](#custom-size-image-for-other-platforms-excluding-web) - [Custom Size Image for other platforms (excluding web)](#custom-size-image-for-other-platforms-excluding-web)
- [Drag and drop feature](#drag-and-drop-feature)
- [Features](#features) - [Features](#features)
- [Contributing](#contributing) - [Contributing](#contributing)
- [License](#license) - [License](#license)
@ -153,6 +154,78 @@ On mobile we will use `mobileWidth`, `mobileHeight`, on desktop will use `width`
on Web we will use the `width` and the `height` but the ones in the `attributes` on Web we will use the `width` and the `height` but the ones in the `attributes`
This may not clear but don't worry we will update it soon. This may not clear but don't worry we will update it soon.
### Drag and drop feature
Currently the drag and drop feature is not offically supported but you can achieve this very easily in the following steps:
1. Drag and drop require native code, you can use any flutter plugin you like, if you want a suggestion we recommend [desktop_drop](https://pub.dev/packages/desktop_drop), it was origanlly developed for desktop but it has support for web as well mobile platforms
2. Add the dependency in your `pubspec.yaml` using the following command:
```yaml
flutter pub add desktop_drop
```
and import it with
```dart
import 'package:desktop_drop/desktop_drop.dart';
```
3. in the configurations of `QuillEditor`, use the `builder` to wrap the editor with `DropTarget` which comes from `desktop_drop`
```dart
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
QuillEditor.basic(
configurations: QuillEditorConfigurations(
padding: const EdgeInsets.all(16),
builder: (context, rawEditor) {
return DropTarget(
onDragDone: _onDragDone,
child: rawEditor,
);
},
embedBuilders: kIsWeb
? FlutterQuillEmbeds.editorsWebBuilders()
: FlutterQuillEmbeds.editorBuilders(),
),
)
```
4. Implement the `_onDragDone`, it depend on your use case but this just a simple example
```dart
const List<String> imageFileExtensions = [
'.jpeg',
'.png',
'.jpg',
'.gif',
'.webp',
'.tif',
'.heic'
];
OnDragDoneCallback get _onDragDone {
return (details) {
final scaffoldMessenger = ScaffoldMessenger.of(context);
final file = details.files.first;
final isSupported =
imageFileExtensions.any((ext) => file.name.endsWith(ext));
if (!isSupported) {
scaffoldMessenger.showSnackBar(
SnackBar(
content: Text(
'Only images are supported right now: ${file.mimeType}, ${file.name}, ${file.path}, $imageFileExtensions',
),
),
);
return;
}
// To get this extension function please import flutter_quill_extensions
_controller.insertImageBlock(
imageSource: file.path,
);
scaffoldMessenger.showSnackBar(
const SnackBar(
content: Text('Image is inserted.'),
),
);
};
}
```
## Features ## Features
```markdown ```markdown

@ -1,6 +1,6 @@
name: flutter_quill_extensions name: flutter_quill_extensions
description: Embed extensions for flutter_quill including image, video, formula and etc. description: Embed extensions for flutter_quill including image, video, formula and etc.
version: 0.6.2 version: 0.6.3
homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions

@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart';
import '../../../widgets/default_styles.dart'; import '../../../widgets/default_styles.dart';
import '../../../widgets/delegate.dart'; import '../../../widgets/delegate.dart';
import '../../../widgets/editor/editor.dart'; import '../../../widgets/editor/editor.dart';
import '../../../widgets/editor/editor_builder.dart';
import '../../../widgets/embeds.dart'; import '../../../widgets/embeds.dart';
import '../../../widgets/link.dart'; import '../../../widgets/link.dart';
import '../../../widgets/raw_editor/raw_editor.dart'; import '../../../widgets/raw_editor/raw_editor.dart';
@ -67,6 +68,7 @@ class QuillEditorConfigurations extends Equatable {
this.editorKey, this.editorKey,
this.requestKeyboardFocusOnCheckListChanged = false, this.requestKeyboardFocusOnCheckListChanged = false,
this.elementOptions = const QuillEditorElementOptions(), this.elementOptions = const QuillEditorElementOptions(),
this.builder,
}); });
/// The text placeholder in the quill editor /// The text placeholder in the quill editor
@ -306,6 +308,8 @@ class QuillEditorConfigurations extends Equatable {
/// This is not complete yet and might changed /// This is not complete yet and might changed
final QuillEditorElementOptions elementOptions; final QuillEditorElementOptions elementOptions;
final QuillEditorBuilder? builder;
@override @override
List<Object?> get props => [ List<Object?> get props => [
placeholder, placeholder,
@ -323,7 +327,7 @@ class QuillEditorConfigurations extends Equatable {
double? scrollBottomInset, double? scrollBottomInset,
EdgeInsetsGeometry? padding, EdgeInsetsGeometry? padding,
bool? autoFocus, bool? autoFocus,
bool? enableUnfocusOnTapOutside, bool? isOnTapOutsideEnabled,
Function(PointerDownEvent event, FocusNode focusNode)? onTapOutside, Function(PointerDownEvent event, FocusNode focusNode)? onTapOutside,
bool? showCursor, bool? showCursor,
bool? paintCursorAboveText, bool? paintCursorAboveText,
@ -357,6 +361,7 @@ class QuillEditorConfigurations extends Equatable {
TextSelectionThemeData? textSelectionThemeData, TextSelectionThemeData? textSelectionThemeData,
bool? requestKeyboardFocusOnCheckListChanged, bool? requestKeyboardFocusOnCheckListChanged,
QuillEditorElementOptions? elementOptions, QuillEditorElementOptions? elementOptions,
QuillEditorBuilder? builder,
}) { }) {
return QuillEditorConfigurations( return QuillEditorConfigurations(
placeholder: placeholder ?? this.placeholder, placeholder: placeholder ?? this.placeholder,
@ -365,7 +370,8 @@ class QuillEditorConfigurations extends Equatable {
scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset, scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset,
padding: padding ?? this.padding, padding: padding ?? this.padding,
autoFocus: autoFocus ?? this.autoFocus, autoFocus: autoFocus ?? this.autoFocus,
isOnTapOutsideEnabled: enableUnfocusOnTapOutside ?? isOnTapOutsideEnabled, isOnTapOutsideEnabled:
isOnTapOutsideEnabled ?? this.isOnTapOutsideEnabled,
onTapOutside: onTapOutside ?? this.onTapOutside, onTapOutside: onTapOutside ?? this.onTapOutside,
showCursor: showCursor ?? this.showCursor, showCursor: showCursor ?? this.showCursor,
paintCursorAboveText: paintCursorAboveText ?? this.paintCursorAboveText, paintCursorAboveText: paintCursorAboveText ?? this.paintCursorAboveText,
@ -409,6 +415,7 @@ class QuillEditorConfigurations extends Equatable {
requestKeyboardFocusOnCheckListChanged ?? requestKeyboardFocusOnCheckListChanged ??
this.requestKeyboardFocusOnCheckListChanged, this.requestKeyboardFocusOnCheckListChanged,
elementOptions: elementOptions ?? this.elementOptions, elementOptions: elementOptions ?? this.elementOptions,
builder: builder ?? this.builder,
); );
} }
} }

@ -16,6 +16,7 @@ import '../cursor.dart';
import '../delegate.dart'; import '../delegate.dart';
import '../float_cursor.dart'; import '../float_cursor.dart';
import '../text_selection.dart'; import '../text_selection.dart';
import 'editor_builder.dart';
/// Base interface for the editor state which defines contract used by /// Base interface for the editor state which defines contract used by
/// various mixins. /// various mixins.
@ -238,59 +239,62 @@ class QuillEditorState extends State<QuillEditor>
final child = QuillEditorProvider( final child = QuillEditorProvider(
editorConfigurations: configurations, editorConfigurations: configurations,
child: QuillRawEditor( child: QuillEditorBuilderWidget(
key: _editorKey, builder: configurations.builder,
controller: context.requireQuillController, child: QuillRawEditor(
focusNode: widget.focusNode, key: _editorKey,
scrollController: widget.scrollController, controller: context.requireQuillController,
scrollable: configurations.scrollable, focusNode: widget.focusNode,
scrollBottomInset: configurations.scrollBottomInset, scrollController: widget.scrollController,
padding: configurations.padding, scrollable: configurations.scrollable,
readOnly: configurations.readOnly, scrollBottomInset: configurations.scrollBottomInset,
placeholder: configurations.placeholder, padding: configurations.padding,
onLaunchUrl: configurations.onLaunchUrl, readOnly: configurations.readOnly,
contextMenuBuilder: showSelectionToolbar placeholder: configurations.placeholder,
? (configurations.contextMenuBuilder ?? onLaunchUrl: configurations.onLaunchUrl,
QuillRawEditor.defaultContextMenuBuilder) contextMenuBuilder: showSelectionToolbar
: null, ? (configurations.contextMenuBuilder ??
showSelectionHandles: isMobile(theme.platform), QuillRawEditor.defaultContextMenuBuilder)
showCursor: configurations.showCursor, : null,
cursorStyle: CursorStyle( showSelectionHandles: isMobile(theme.platform),
color: cursorColor, showCursor: configurations.showCursor,
backgroundColor: Colors.grey, cursorStyle: CursorStyle(
width: 2, color: cursorColor,
radius: cursorRadius, backgroundColor: Colors.grey,
offset: cursorOffset, width: 2,
paintAboveText: radius: cursorRadius,
configurations.paintCursorAboveText ?? paintCursorAboveText, offset: cursorOffset,
opacityAnimates: cursorOpacityAnimates, paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: configurations.isOnTapOutsideEnabled,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
), ),
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: configurations.isOnTapOutsideEnabled,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
), ),
); );

@ -0,0 +1,31 @@
import 'package:flutter/material.dart';
import '../raw_editor/raw_editor.dart';
typedef QuillEditorBuilder = Widget Function(
BuildContext context,
QuillRawEditor rawEditor,
);
class QuillEditorBuilderWidget extends StatelessWidget {
const QuillEditorBuilderWidget({
required this.child,
this.builder,
super.key,
});
final QuillRawEditor child;
final QuillEditorBuilder? builder;
@override
Widget build(BuildContext context) {
final builderCallback = builder;
if (builderCallback != null) {
return builderCallback(
context,
child,
);
}
return child;
}
}

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter.
version: 8.2.4 version: 8.2.5
homepage: https://1o24bbs.com/c/bulletjournal/108 homepage: https://1o24bbs.com/c/bulletjournal/108
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill

Loading…
Cancel
Save