diff --git a/CHANGELOG.md b/CHANGELOG.md index a79192b7..eac64dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,6 @@ +## [8.2.5] +- Add `builder` property in the `QuillEditorConfigurations` + ## [8.2.4] - Follow flutter best practices - Auto focus bug fix diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 9a84d9ab..b2236b12 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -5,6 +5,7 @@ import 'dart:convert'; import 'dart:io' show File; import 'dart:ui'; +import 'package:desktop_drop/desktop_drop.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.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_extensions/flutter_quill_extensions.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_provider/path_provider.dart'; @@ -333,12 +335,45 @@ class _HomePageState extends State { }); } + 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 { if (kIsWeb) { return QuillEditor( focusNode: _focusNode, scrollController: ScrollController(), configurations: QuillEditorConfigurations( + builder: (context, rawEditor) { + return DropTarget( + onDragDone: _onDragDone, + child: rawEditor, + ); + }, placeholder: 'Add content', readOnly: false, scrollable: true, @@ -370,6 +405,12 @@ class _HomePageState extends State { } return QuillEditor( configurations: QuillEditorConfigurations( + builder: (context, rawEditor) { + return DropTarget( + onDragDone: _onDragDone, + child: rawEditor, + ); + }, placeholder: 'Add content', readOnly: _isReadOnly, autoFocus: false, diff --git a/example/linux/flutter/generated_plugin_registrant.cc b/example/linux/flutter/generated_plugin_registrant.cc index 158f759a..fe311fa2 100644 --- a/example/linux/flutter/generated_plugin_registrant.cc +++ b/example/linux/flutter/generated_plugin_registrant.cc @@ -6,11 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include 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 = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); diff --git a/example/linux/flutter/generated_plugins.cmake b/example/linux/flutter/generated_plugins.cmake index 93c755ee..3f7f250e 100644 --- a/example/linux/flutter/generated_plugins.cmake +++ b/example/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop file_selector_linux pasteboard url_launcher_linux diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 9245196c..ac927005 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import desktop_drop import device_info_plus import file_selector_macos import gal @@ -14,6 +15,7 @@ import url_launcher_macos import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + DesktopDropPlugin.register(with: registry.registrar(forPlugin: "DesktopDropPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) diff --git a/example/pubspec.yaml b/example/pubspec.yaml index c9db1b46..509b1013 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: flutter_quill: ^8.2.3 flutter_quill_extensions: ^0.6.1 path: ^1.8.3 + desktop_drop: ^0.4.4 dependency_overrides: flutter_quill: diff --git a/example/windows/flutter/generated_plugin_registrant.cc b/example/windows/flutter/generated_plugin_registrant.cc index bc268640..9e89e3c8 100644 --- a/example/windows/flutter/generated_plugin_registrant.cc +++ b/example/windows/flutter/generated_plugin_registrant.cc @@ -6,12 +6,15 @@ #include "generated_plugin_registrant.h" +#include #include #include #include #include void RegisterPlugins(flutter::PluginRegistry* registry) { + DesktopDropPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("DesktopDropPlugin")); FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); GalPluginCApiRegisterWithRegistrar( diff --git a/example/windows/flutter/generated_plugins.cmake b/example/windows/flutter/generated_plugins.cmake index 029549d7..f3449e49 100644 --- a/example/windows/flutter/generated_plugins.cmake +++ b/example/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + desktop_drop file_selector_windows gal pasteboard diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index 3754c611..c1f1996b 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.6.3 +- Update `README.md` + ## 0.6.2 - Add more default exports diff --git a/flutter_quill_extensions/README.md b/flutter_quill_extensions/README.md index c59ae175..bbb75671 100644 --- a/flutter_quill_extensions/README.md +++ b/flutter_quill_extensions/README.md @@ -18,6 +18,7 @@ Currently the support for **Web** is limitied. - [Embed Blocks](#embed-blocks) - [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) + - [Drag and drop feature](#drag-and-drop-feature) - [Features](#features) - [Contributing](#contributing) - [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` 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 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 ```markdown diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index ef21c857..f68ffa34 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_extensions 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 repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions diff --git a/lib/src/models/config/editor/configurations.dart b/lib/src/models/config/editor/configurations.dart index 464689de..c54edc56 100644 --- a/lib/src/models/config/editor/configurations.dart +++ b/lib/src/models/config/editor/configurations.dart @@ -8,6 +8,7 @@ import 'package:flutter/widgets.dart'; import '../../../widgets/default_styles.dart'; import '../../../widgets/delegate.dart'; import '../../../widgets/editor/editor.dart'; +import '../../../widgets/editor/editor_builder.dart'; import '../../../widgets/embeds.dart'; import '../../../widgets/link.dart'; import '../../../widgets/raw_editor/raw_editor.dart'; @@ -67,6 +68,7 @@ class QuillEditorConfigurations extends Equatable { this.editorKey, this.requestKeyboardFocusOnCheckListChanged = false, this.elementOptions = const QuillEditorElementOptions(), + this.builder, }); /// The text placeholder in the quill editor @@ -306,6 +308,8 @@ class QuillEditorConfigurations extends Equatable { /// This is not complete yet and might changed final QuillEditorElementOptions elementOptions; + final QuillEditorBuilder? builder; + @override List get props => [ placeholder, @@ -323,7 +327,7 @@ class QuillEditorConfigurations extends Equatable { double? scrollBottomInset, EdgeInsetsGeometry? padding, bool? autoFocus, - bool? enableUnfocusOnTapOutside, + bool? isOnTapOutsideEnabled, Function(PointerDownEvent event, FocusNode focusNode)? onTapOutside, bool? showCursor, bool? paintCursorAboveText, @@ -357,6 +361,7 @@ class QuillEditorConfigurations extends Equatable { TextSelectionThemeData? textSelectionThemeData, bool? requestKeyboardFocusOnCheckListChanged, QuillEditorElementOptions? elementOptions, + QuillEditorBuilder? builder, }) { return QuillEditorConfigurations( placeholder: placeholder ?? this.placeholder, @@ -365,7 +370,8 @@ class QuillEditorConfigurations extends Equatable { scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset, padding: padding ?? this.padding, autoFocus: autoFocus ?? this.autoFocus, - isOnTapOutsideEnabled: enableUnfocusOnTapOutside ?? isOnTapOutsideEnabled, + isOnTapOutsideEnabled: + isOnTapOutsideEnabled ?? this.isOnTapOutsideEnabled, onTapOutside: onTapOutside ?? this.onTapOutside, showCursor: showCursor ?? this.showCursor, paintCursorAboveText: paintCursorAboveText ?? this.paintCursorAboveText, @@ -409,6 +415,7 @@ class QuillEditorConfigurations extends Equatable { requestKeyboardFocusOnCheckListChanged ?? this.requestKeyboardFocusOnCheckListChanged, elementOptions: elementOptions ?? this.elementOptions, + builder: builder ?? this.builder, ); } } diff --git a/lib/src/widgets/editor/editor.dart b/lib/src/widgets/editor/editor.dart index b4e35811..c598fec7 100644 --- a/lib/src/widgets/editor/editor.dart +++ b/lib/src/widgets/editor/editor.dart @@ -16,6 +16,7 @@ import '../cursor.dart'; import '../delegate.dart'; import '../float_cursor.dart'; import '../text_selection.dart'; +import 'editor_builder.dart'; /// Base interface for the editor state which defines contract used by /// various mixins. @@ -238,59 +239,62 @@ class QuillEditorState extends State final child = QuillEditorProvider( editorConfigurations: configurations, - child: QuillRawEditor( - key: _editorKey, - controller: context.requireQuillController, - focusNode: widget.focusNode, - scrollController: widget.scrollController, - scrollable: configurations.scrollable, - scrollBottomInset: configurations.scrollBottomInset, - padding: configurations.padding, - readOnly: configurations.readOnly, - placeholder: configurations.placeholder, - onLaunchUrl: configurations.onLaunchUrl, - contextMenuBuilder: showSelectionToolbar - ? (configurations.contextMenuBuilder ?? - QuillRawEditor.defaultContextMenuBuilder) - : null, - showSelectionHandles: isMobile(theme.platform), - showCursor: configurations.showCursor, - cursorStyle: CursorStyle( - color: cursorColor, - backgroundColor: Colors.grey, - width: 2, - radius: cursorRadius, - offset: cursorOffset, - paintAboveText: - configurations.paintCursorAboveText ?? paintCursorAboveText, - opacityAnimates: cursorOpacityAnimates, + child: QuillEditorBuilderWidget( + builder: configurations.builder, + child: QuillRawEditor( + key: _editorKey, + controller: context.requireQuillController, + focusNode: widget.focusNode, + scrollController: widget.scrollController, + scrollable: configurations.scrollable, + scrollBottomInset: configurations.scrollBottomInset, + padding: configurations.padding, + readOnly: configurations.readOnly, + placeholder: configurations.placeholder, + onLaunchUrl: configurations.onLaunchUrl, + contextMenuBuilder: showSelectionToolbar + ? (configurations.contextMenuBuilder ?? + QuillRawEditor.defaultContextMenuBuilder) + : null, + showSelectionHandles: isMobile(theme.platform), + showCursor: configurations.showCursor, + cursorStyle: CursorStyle( + color: cursorColor, + backgroundColor: Colors.grey, + width: 2, + radius: cursorRadius, + offset: cursorOffset, + 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, ), ); diff --git a/lib/src/widgets/editor/editor_builder.dart b/lib/src/widgets/editor/editor_builder.dart new file mode 100644 index 00000000..926af254 --- /dev/null +++ b/lib/src/widgets/editor/editor_builder.dart @@ -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; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 22ba0056..b714f2a7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ 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. -version: 8.2.4 +version: 8.2.5 homepage: https://1o24bbs.com/c/bulletjournal/108 repository: https://github.com/singerdmx/flutter-quill