Major update 6

pull/1461/head
Ahmed Hnewa 2 years ago
parent 0f6a5051dd
commit abde110da0
No known key found for this signature in database
GPG Key ID: C488CC70BBCEF0D1
  1. 56
      example/lib/pages/home_page.dart
  2. 22
      example/lib/pages/read_only_page.dart
  3. 8
      example/lib/widgets/demo_scaffold.dart
  4. 1
      lib/flutter_quill.dart
  5. 363
      lib/src/models/config/editor/configurations.dart
  6. 10
      lib/src/models/config/quill_configurations.dart
  7. 1
      lib/src/models/config/shared_configurations.dart
  8. 63
      lib/src/models/config/toolbar/base_configurations.dart
  9. 2
      lib/src/models/config/toolbar/buttons/link_style.dart
  10. 2
      lib/src/models/config/toolbar/buttons/select_header_style.dart
  11. 119
      lib/src/models/config/toolbar/configurations.dart
  12. 2
      lib/src/models/themes/quill_custom_button.dart
  13. 8
      lib/src/utils/extensions/build_context.dart
  14. 12
      lib/src/utils/platform.dart
  15. 442
      lib/src/widgets/editor/editor.dart
  16. 4
      lib/src/widgets/link.dart
  17. 6
      lib/src/widgets/raw_editor/raw_editor.dart
  18. 9
      lib/src/widgets/style_widgets/checkbox_point.dart
  19. 7
      lib/src/widgets/text_block.dart
  20. 132
      lib/src/widgets/toolbar/base_toolbar.dart
  21. 2
      lib/src/widgets/toolbar/buttons/clear_format.dart
  22. 2
      lib/src/widgets/toolbar/buttons/color.dart
  23. 2
      lib/src/widgets/toolbar/buttons/custom_button.dart
  24. 2
      lib/src/widgets/toolbar/buttons/history.dart
  25. 6
      lib/src/widgets/toolbar/buttons/indent.dart
  26. 2
      lib/src/widgets/toolbar/buttons/link_style.dart
  27. 2
      lib/src/widgets/toolbar/buttons/link_style2.dart
  28. 2
      lib/src/widgets/toolbar/buttons/search/search.dart
  29. 2
      lib/src/widgets/toolbar/buttons/select_alignment.dart
  30. 2
      lib/src/widgets/toolbar/buttons/select_header_style.dart
  31. 2
      lib/src/widgets/toolbar/buttons/toggle_style.dart
  32. 554
      lib/src/widgets/toolbar/toolbar.dart
  33. 112
      lib/src/widgets/utils/provider.dart
  34. 12
      test/bug_fix_test.dart
  35. 33
      test/widgets/editor_test.dart

@ -1,3 +1,5 @@
// ignore_for_file: avoid_redundant_argument_values
import 'dart:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io' show File, Platform; import 'dart:io' show File, Platform;
@ -186,9 +188,12 @@ class _HomePageState extends State<HomePage> {
QuillEditor get quillEditor { QuillEditor get quillEditor {
if (kIsWeb) { if (kIsWeb) {
return QuillEditor( return QuillEditor(
focusNode: _focusNode,
scrollController: ScrollController(), scrollController: ScrollController(),
configurations: QuillEditorConfigurations(
placeholder: 'Add content',
readOnly: false,
scrollable: true, scrollable: true,
focusNode: _focusNode,
autoFocus: false, autoFocus: false,
expands: false, expands: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
@ -212,12 +217,13 @@ class _HomePageState extends State<HomePage> {
...defaultEmbedBuildersWeb, ...defaultEmbedBuildersWeb,
TimeStampEmbedBuilderWidget() TimeStampEmbedBuilderWidget()
], ],
),
); );
} }
return QuillEditor( return QuillEditor(
scrollController: ScrollController(), configurations: QuillEditorConfigurations(
scrollable: true, placeholder: 'Add content',
focusNode: _focusNode, readOnly: false,
autoFocus: false, autoFocus: false,
enableSelectionToolbar: isMobile(), enableSelectionToolbar: isMobile(),
expands: false, expands: false,
@ -251,31 +257,48 @@ class _HomePageState extends State<HomePage> {
...FlutterQuillEmbeds.builders(), ...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget() TimeStampEmbedBuilderWidget()
], ],
),
scrollController: ScrollController(),
focusNode: _focusNode,
); );
} }
QuillToolbar get quillToolbar { QuillToolbar get quillToolbar {
if (kIsWeb) { if (kIsWeb) {
return QuillToolbar.basic( return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl, webImagePickImpl: _webImagePickImpl,
), ),
showAlignmentButtons: true, buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus, // afterButtonPressed: _focusNode.requestFocus,
); );
} }
if (_isDesktop()) { if (_isDesktop()) {
return QuillToolbar.basic( return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop, filePickImpl: openFileSystemPickerForDesktop,
), ),
showAlignmentButtons: true, showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus, // afterButtonPressed: _focusNode.requestFocus,
); );
} }
return QuillToolbar.basic( return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
// provide a callback to enable picking images from device. // provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url. // if omit, "image" button only allows adding images from url.
@ -288,6 +311,12 @@ class _HomePageState extends State<HomePage> {
// cameraPickSettingSelector: _selectCameraPickSetting, // cameraPickSettingSelector: _selectCameraPickSetting,
), ),
showAlignmentButtons: true, showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
// afterButtonPressed: _focusNode.requestFocus, // afterButtonPressed: _focusNode.requestFocus,
); );
} }
@ -320,17 +349,6 @@ class _HomePageState extends State<HomePage> {
return SafeArea( return SafeArea(
child: QuillProvider( child: QuillProvider(
configurations: QuillConfigurations( configurations: QuillConfigurations(
toolbarConfigurations: QuillToolbarConfigurations(
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
)),
editorConfigurations: const QuillEditorConfigurations(
placeholder: 'Add content',
// ignore: avoid_redundant_argument_values
readOnly: false,
),
controller: _controller, controller: _controller,
), ),
child: Column( child: Column(

@ -1,3 +1,5 @@
// ignore_for_file: avoid_redundant_argument_values
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
@ -35,24 +37,28 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
Widget _buildContent(BuildContext context, QuillController? controller) { Widget _buildContent(BuildContext context, QuillController? controller) {
var quillEditor = QuillEditor( var quillEditor = QuillEditor(
scrollController: ScrollController(), configurations: QuillEditorConfigurations(
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
// readOnly: !_edit,
expands: false, expands: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
embedBuilders: FlutterQuillEmbeds.builders(), embedBuilders: FlutterQuillEmbeds.builders(),
scrollable: true,
autoFocus: true,
),
scrollController: ScrollController(),
focusNode: _focusNode,
// readOnly: !_edit,
); );
if (kIsWeb) { if (kIsWeb) {
quillEditor = QuillEditor( quillEditor = QuillEditor(
scrollController: ScrollController(), configurations: QuillEditorConfigurations(
scrollable: true,
focusNode: _focusNode,
autoFocus: true, autoFocus: true,
expands: false, expands: false,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb, embedBuilders: defaultEmbedBuildersWeb,
scrollable: true,
),
scrollController: ScrollController(),
focusNode: _focusNode,
); );
} }
return Padding( return Padding(

@ -86,14 +86,18 @@ class _DemoScaffoldState extends State<DemoScaffold> {
QuillToolbar get quillToolbar { QuillToolbar get quillToolbar {
if (_isDesktop()) { if (_isDesktop()) {
return QuillToolbar.basic( return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop, filePickImpl: openFileSystemPickerForDesktop,
), ),
),
); );
} }
return QuillToolbar.basic( return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(), embedButtons: FlutterQuillEmbeds.buttons(),
),
); );
} }

@ -26,6 +26,7 @@ export 'src/widgets/editor/editor.dart';
export 'src/widgets/embeds.dart'; export 'src/widgets/embeds.dart';
export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
export 'src/widgets/style_widgets/style_widgets.dart'; export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar/base_toolbar.dart';
export 'src/widgets/toolbar/enum.dart'; export 'src/widgets/toolbar/enum.dart';
export 'src/widgets/toolbar/toolbar.dart'; export 'src/widgets/toolbar/toolbar.dart';
export 'src/widgets/utils/provider.dart'; export 'src/widgets/utils/provider.dart';

@ -1,12 +1,67 @@
// ignore_for_file: public_member_api_docs, sort_constructors_first
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/foundation.dart' show Brightness, Uint8List, immutable;
import 'package:flutter/material.dart'
show TextCapitalization, TextSelectionThemeData;
import 'package:flutter/widgets.dart';
import '../../../widgets/default_styles.dart';
import '../../../widgets/delegate.dart';
import '../../../widgets/editor/editor.dart';
import '../../../widgets/embeds.dart';
import '../../../widgets/link.dart';
import '../../../widgets/raw_editor/raw_editor.dart';
import '../../themes/quill_dialog_theme.dart';
/// The configurations for the quill editor widget of flutter quill /// The configurations for the quill editor widget of flutter quill
@immutable @immutable
class QuillEditorConfigurations extends Equatable { class QuillEditorConfigurations extends Equatable {
/// Important note for the maintainers
/// When editing this class please update the [copyWith] function too.
const QuillEditorConfigurations({ const QuillEditorConfigurations({
this.scrollable = true,
this.padding = EdgeInsets.zero,
this.autoFocus = false,
this.expands = false,
this.placeholder, this.placeholder,
this.readOnly = false, this.readOnly = false,
this.textSelectionThemeData,
this.showCursor,
this.paintCursorAboveText,
this.enableInteractiveSelection = true,
this.enableSelectionToolbar = true,
this.scrollBottomInset = 0,
this.minHeight,
this.maxHeight,
this.maxContentWidth,
this.customStyles,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardAppearance = Brightness.light,
this.scrollPhysics,
this.onLaunchUrl,
this.onTapDown,
this.onTapUp,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilders,
this.unknownEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.customRecognizerBuilder,
this.floatingCursorDisabled = false,
this.textSelectionControls,
this.onImagePaste,
this.customShortcuts,
this.customActions,
this.detectWordBoundary = true,
this.enableUnfocusOnTapOutside = true,
this.customLinkPrefixes = const <String>[],
this.dialogTheme,
this.contentInsertionConfiguration,
this.contextMenuBuilder,
this.editorKey,
this.requestKeyboardFocusOnCheckListChanged = false,
}); });
/// The text placeholder in the quill editor /// The text placeholder in the quill editor
@ -20,9 +75,315 @@ class QuillEditorConfigurations extends Equatable {
/// Defaults to `false`. Must not be `null`. /// Defaults to `false`. Must not be `null`.
final bool readOnly; final bool readOnly;
/// Whether this editor should create a scrollable container for its content.
///
/// When set to `true` the editor's height can be controlled by [minHeight],
/// [maxHeight] and [expands] properties.
///
/// When set to `false` the editor always expands to fit the entire content
/// of the document and should normally be placed as a child of another
/// scrollable widget, otherwise the content may be clipped.
/// by default it will by true
final bool scrollable;
final double scrollBottomInset;
/// Additional space around the content of this editor.
/// by default will be [EdgeInsets.zero]
final EdgeInsetsGeometry padding;
/// Whether this editor should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this editor obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the editor.
///
/// Defaults to `false`. Cannot be `null`.
final bool autoFocus;
/// Whether focus should be revoked on tap outside the editor.
final bool enableUnfocusOnTapOutside;
/// Whether to show cursor.
///
/// The cursor refers to the blinking caret when the editor is focused.
final bool? showCursor;
final bool? paintCursorAboveText;
/// Whether to enable user interface affordances for changing the
/// text selection.
///
/// For example, setting this to true will enable features such as
/// long-pressing the editor to select text and show the
/// cut/copy/paste menu, and tapping to move the text cursor.
///
/// When this is false, the text selection cannot be adjusted by
/// the user, text cannot be copied, and the user cannot paste into
/// the text field from the clipboard.
///
/// To disable just the selection toolbar, set enableSelectionToolbar
/// to false.
final bool enableInteractiveSelection;
/// Whether to show the cut/copy/paste menu when selecting text.
final bool enableSelectionToolbar;
/// The minimum height to be occupied by this editor.
///
/// This only has effect if [scrollable] is set to `true` and [expands] is
/// set to `false`.
final double? minHeight;
/// The maximum height to be occupied by this editor.
///
/// This only has effect if [scrollable] is set to `true` and [expands] is
/// set to `false`.
final double? maxHeight;
/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;
/// Allows to override [DefaultStyles].
final DefaultStyles? customStyles;
/// Whether this editor's height will be sized to fill its parent.
///
/// This only has effect if [scrollable] is set to `true`.
///
/// If expands is set to true and wrapped in a parent widget like [Expanded]
/// or [SizedBox], the editor will expand to fill the parent.
///
/// [maxHeight] and [minHeight] must both be `null` when this is set to
/// `true`.
///
/// Defaults to `false`.
final bool expands;
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
/// Only supports text keyboards, other keyboard types will ignore this
/// configuration. Capitalization is locale-aware.
///
/// Defaults to [TextCapitalization.sentences]. Must not be `null`.
final TextCapitalization textCapitalization;
/// The appearance of the keyboard.
///
/// This setting is only honored on iOS devices.
///
/// Defaults to [Brightness.light].
final Brightness keyboardAppearance;
/// The [ScrollPhysics] to use when vertically scrolling the input.
///
/// This only has effect if [scrollable] is set to `true`.
///
/// If not specified, it will behave according to the current platform.
///
/// See [Scrollable.physics].
final ScrollPhysics? scrollPhysics;
/// Callback to invoke when user wants to launch a URL.
final ValueChanged<String>? onLaunchUrl;
// Returns whether gesture is handled
final bool Function(
TapDownDetails details, TextPosition Function(Offset offset))? onTapDown;
// Returns whether gesture is handled
final bool Function(
TapUpDetails details, TextPosition Function(Offset offset))? onTapUp;
// Returns whether gesture is handled
final bool Function(
LongPressStartDetails details, TextPosition Function(Offset offset))?
onSingleLongTapStart;
// Returns whether gesture is handled
final bool Function(LongPressMoveUpdateDetails details,
TextPosition Function(Offset offset))? onSingleLongTapMoveUpdate;
// Returns whether gesture is handled
final bool Function(
LongPressEndDetails details, TextPosition Function(Offset offset))?
onSingleLongTapEnd;
final Iterable<EmbedBuilder>? embedBuilders;
final EmbedBuilder? unknownEmbedBuilder;
final CustomStyleBuilder? customStyleBuilder;
final CustomRecognizerBuilder? customRecognizerBuilder;
/// Delegate function responsible for showing menu with link actions on
/// mobile platforms (iOS, Android).
///
/// The menu is triggered in editing mode ([readOnly] is set to `false`)
/// when the user long-presses a link-styled text segment.
///
/// FlutterQuill provides default implementation which can be overridden by
/// this field to customize the user experience.
///
/// By default on iOS the menu is displayed with [showCupertinoModalPopup]
/// which constructs an instance of [CupertinoActionSheet]. For Android,
/// the menu is displayed with [showModalBottomSheet] and a list of
/// Material [ListTile]s.
final LinkActionPickerDelegate linkActionPickerDelegate;
final bool floatingCursorDisabled;
/// allows to create a custom textSelectionControls,
/// if this is null a default textSelectionControls based on the app's theme
/// 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<String?> Function(Uint8List imageBytes)? onImagePaste;
/// Contains user-defined shortcuts map.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts]
final Map<ShortcutActivator, Intent>? customShortcuts;
/// Contains user-defined actions.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions]
final Map<Type, Action<Intent>>? customActions;
final bool detectWordBoundary;
/// Additional list if links prefixes, which must not be prepended
/// with "https://" when [LinkMenuAction.launch] happened
///
/// Useful for deeplinks
final List<String> customLinkPrefixes;
/// Configures the dialog theme.
final QuillDialogTheme? dialogTheme;
// Allows for creating a custom context menu
final QuillEditorContextMenuBuilder? contextMenuBuilder;
/// Configuration of handler for media content inserted via the system input
/// method.
///
/// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html]
final ContentInsertionConfiguration? contentInsertionConfiguration;
/// Using the editorKey for get getLocalRectForCaret
/// editorKey.currentState?.renderEditor.getLocalRectForCaret
final GlobalKey<EditorState>? editorKey;
/// By default we will use
/// ```
/// TextSelectionTheme.of(context)
/// ```
/// to change it please pass a different value
final TextSelectionThemeData? textSelectionThemeData;
/// When there is a change the check list values
/// should we request keyboard focus??
final bool requestKeyboardFocusOnCheckListChanged;
@override @override
List<Object?> get props => [ List<Object?> get props => [
placeholder, placeholder,
readOnly, readOnly,
]; ];
// We might use code generator like freezed but sometimes it can be limitied
// instead whatever there is a change to the parameters in this class please
// regenerate this function using extension in vs code or plugin in intellij
QuillEditorConfigurations copyWith({
String? placeholder,
bool? readOnly,
bool? scrollable,
double? scrollBottomInset,
EdgeInsetsGeometry? padding,
bool? autoFocus,
bool? enableUnfocusOnTapOutside,
bool? showCursor,
bool? paintCursorAboveText,
bool? enableInteractiveSelection,
bool? enableSelectionToolbar,
double? minHeight,
double? maxHeight,
double? maxContentWidth,
DefaultStyles? customStyles,
bool? expands,
TextCapitalization? textCapitalization,
Brightness? keyboardAppearance,
ScrollPhysics? scrollPhysics,
ValueChanged<String>? onLaunchUrl,
Iterable<EmbedBuilder>? embedBuilders,
EmbedBuilder? unknownEmbedBuilder,
CustomStyleBuilder? customStyleBuilder,
CustomRecognizerBuilder? customRecognizerBuilder,
LinkActionPickerDelegate? linkActionPickerDelegate,
bool? floatingCursorDisabled,
TextSelectionControls? textSelectionControls,
Future<String?> Function(Uint8List imageBytes)? onImagePaste,
Map<ShortcutActivator, Intent>? customShortcuts,
Map<Type, Action<Intent>>? customActions,
bool? detectWordBoundary,
List<String>? customLinkPrefixes,
QuillDialogTheme? dialogTheme,
QuillEditorContextMenuBuilder? contextMenuBuilder,
ContentInsertionConfiguration? contentInsertionConfiguration,
GlobalKey<EditorState>? editorKey,
TextSelectionThemeData? textSelectionThemeData,
}) {
return QuillEditorConfigurations(
placeholder: placeholder ?? this.placeholder,
readOnly: readOnly ?? this.readOnly,
scrollable: scrollable ?? this.scrollable,
scrollBottomInset: scrollBottomInset ?? this.scrollBottomInset,
padding: padding ?? this.padding,
autoFocus: autoFocus ?? this.autoFocus,
enableUnfocusOnTapOutside:
enableUnfocusOnTapOutside ?? this.enableUnfocusOnTapOutside,
showCursor: showCursor ?? this.showCursor,
paintCursorAboveText: paintCursorAboveText ?? this.paintCursorAboveText,
enableInteractiveSelection:
enableInteractiveSelection ?? this.enableInteractiveSelection,
enableSelectionToolbar:
enableSelectionToolbar ?? this.enableSelectionToolbar,
minHeight: minHeight ?? this.minHeight,
maxHeight: maxHeight ?? this.maxHeight,
maxContentWidth: maxContentWidth ?? this.maxContentWidth,
customStyles: customStyles ?? this.customStyles,
expands: expands ?? this.expands,
textCapitalization: textCapitalization ?? this.textCapitalization,
keyboardAppearance: keyboardAppearance ?? this.keyboardAppearance,
scrollPhysics: scrollPhysics ?? this.scrollPhysics,
onLaunchUrl: onLaunchUrl ?? this.onLaunchUrl,
embedBuilders: embedBuilders ?? this.embedBuilders,
unknownEmbedBuilder: unknownEmbedBuilder ?? this.unknownEmbedBuilder,
customStyleBuilder: customStyleBuilder ?? this.customStyleBuilder,
customRecognizerBuilder:
customRecognizerBuilder ?? this.customRecognizerBuilder,
linkActionPickerDelegate:
linkActionPickerDelegate ?? this.linkActionPickerDelegate,
floatingCursorDisabled:
floatingCursorDisabled ?? this.floatingCursorDisabled,
textSelectionControls:
textSelectionControls ?? this.textSelectionControls,
onImagePaste: onImagePaste ?? this.onImagePaste,
customShortcuts: customShortcuts ?? this.customShortcuts,
customActions: customActions ?? this.customActions,
detectWordBoundary: detectWordBoundary ?? this.detectWordBoundary,
customLinkPrefixes: customLinkPrefixes ?? this.customLinkPrefixes,
dialogTheme: dialogTheme ?? this.dialogTheme,
contextMenuBuilder: contextMenuBuilder ?? this.contextMenuBuilder,
contentInsertionConfiguration:
contentInsertionConfiguration ?? this.contentInsertionConfiguration,
editorKey: editorKey ?? this.editorKey,
textSelectionThemeData:
textSelectionThemeData ?? this.textSelectionThemeData,
);
}
} }

@ -11,8 +11,6 @@ export './toolbar/configurations.dart';
class QuillConfigurations extends Equatable { class QuillConfigurations extends Equatable {
const QuillConfigurations({ const QuillConfigurations({
required this.controller, required this.controller,
this.editorConfigurations = const QuillEditorConfigurations(),
this.toolbarConfigurations = const QuillToolbarConfigurations(),
this.sharedConfigurations = const QuillSharedConfigurations(), this.sharedConfigurations = const QuillSharedConfigurations(),
}); });
@ -24,20 +22,12 @@ class QuillConfigurations extends Equatable {
/// here, it should not be null /// here, it should not be null
final QuillController controller; final QuillController controller;
/// The configurations for the quill editor widget of flutter quill
final QuillEditorConfigurations editorConfigurations;
/// The configurations for the toolbar widget of flutter quill
final QuillToolbarConfigurations toolbarConfigurations;
/// The shared configurations between [QuillEditorConfigurations] and /// The shared configurations between [QuillEditorConfigurations] and
/// [QuillToolbarConfigurations] so we don't duplicate things /// [QuillToolbarConfigurations] so we don't duplicate things
final QuillSharedConfigurations sharedConfigurations; final QuillSharedConfigurations sharedConfigurations;
@override @override
List<Object?> get props => [ List<Object?> get props => [
editorConfigurations,
toolbarConfigurations,
sharedConfigurations, sharedConfigurations,
]; ];
} }

@ -39,6 +39,7 @@ class QuillSharedConfigurations extends Equatable {
@override @override
List<Object?> get props => [ List<Object?> get props => [
dialogBarrierColor, dialogBarrierColor,
dialogTheme,
locale, locale,
animationConfigurations, animationConfigurations,
]; ];

@ -0,0 +1,63 @@
import 'package:equatable/equatable.dart';
import 'package:flutter/widgets.dart'
show Axis, Color, Decoration, WrapAlignment, WrapCrossAlignment, immutable;
import '../../../widgets/toolbar/base_toolbar.dart';
import '../../structs/link_dialog_action.dart';
import '../../themes/quill_custom_button.dart';
@immutable
class QuillBaseToolbarConfigurations extends Equatable {
const QuillBaseToolbarConfigurations({
required this.childrenBuilder,
this.axis = Axis.horizontal,
this.toolbarSize = kDefaultIconSize * 2,
this.toolbarSectionSpacing = kToolbarSectionSpacing,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.color,
this.customButtons = const [],
this.sectionDividerColor,
this.sectionDividerSpace,
this.linkDialogAction,
this.multiRowsDisplay = true,
this.decoration,
});
final QuillBaseToolbarChildrenBuilder childrenBuilder;
final Axis axis;
final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
final double toolbarSize;
// Overrides the action in the _LinkDialog widget
final LinkDialogAction? linkDialogAction;
/// The color of the toolbar.
///
/// Defaults to [ThemeData.canvasColor] of the current [Theme] if no color
/// is given.
final Color? color;
/// List of custom buttons
final List<QuillCustomButton> customButtons;
/// The color to use when painting the toolbar section divider.
///
/// If this is null, then the [DividerThemeData.color] is used. If that is
/// also null, then [ThemeData.dividerColor] is used.
final Color? sectionDividerColor;
/// The space occupied by toolbar section divider.
final double? sectionDividerSpace;
/// If you want the toolbar to not be a multiple rows pass false
final bool multiRowsDisplay;
/// The decoration to use for the toolbar.
final Decoration? decoration;
@override
List<Object?> get props => throw UnimplementedError();
}

@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart' show Color; import 'package:flutter/widgets.dart' show Color;
import '../../../../widgets/toolbar/toolbar.dart'; import '../../../../widgets/toolbar/base_toolbar.dart';
import '../../../structs/link_dialog_action.dart'; import '../../../structs/link_dialog_action.dart';
import '../../../themes/quill_dialog_theme.dart'; import '../../../themes/quill_dialog_theme.dart';

@ -1,6 +1,6 @@
import 'package:flutter/widgets.dart' show Axis; import 'package:flutter/widgets.dart' show Axis;
import '../../../../widgets/toolbar/toolbar.dart'; import '../../../../widgets/toolbar/base_toolbar.dart';
import '../../../documents/attribute.dart'; import '../../../documents/attribute.dart';
class QuillToolbarSelectHeaderStyleButtonExtraOptions class QuillToolbarSelectHeaderStyleButtonExtraOptions

@ -1,7 +1,13 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/widgets.dart' show Axis; import 'package:flutter/widgets.dart'
show Axis, Color, Decoration, WrapAlignment, WrapCrossAlignment;
import '../../../widgets/embeds.dart';
import '../../structs/link_dialog_action.dart';
import '../../themes/quill_custom_button.dart';
import '../../themes/quill_dialog_theme.dart';
import '../../themes/quill_icon_theme.dart';
import 'buttons/base.dart'; import 'buttons/base.dart';
import 'buttons/clear_format.dart'; import 'buttons/clear_format.dart';
import 'buttons/color.dart'; import 'buttons/color.dart';
@ -23,6 +29,7 @@ export './buttons/color.dart';
export './buttons/font_family.dart'; export './buttons/font_family.dart';
export './buttons/font_size.dart'; export './buttons/font_size.dart';
export './buttons/history.dart'; export './buttons/history.dart';
export './buttons/indent.dart';
export './buttons/link_style.dart'; export './buttons/link_style.dart';
export './buttons/search.dart'; export './buttons/search.dart';
export './buttons/select_alignment.dart'; export './buttons/select_alignment.dart';
@ -46,11 +53,60 @@ const double kToolbarSectionSpacing = 4;
@immutable @immutable
class QuillToolbarConfigurations extends Equatable { class QuillToolbarConfigurations extends Equatable {
const QuillToolbarConfigurations({ const QuillToolbarConfigurations({
this.toolbarSectionSpacing = kToolbarSectionSpacing,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.buttonOptions = const QuillToolbarButtonOptions(), this.buttonOptions = const QuillToolbarButtonOptions(),
this.multiRowsDisplay = true, this.multiRowsDisplay = true,
this.fontFamilyValues, this.fontFamilyValues,
this.fontSizesValues, this.fontSizesValues,
this.showDividers = true,
this.showFontFamily = true,
this.showFontSize = true,
this.showBoldButton = true,
this.showItalicButton = true,
this.showSmallButton = false,
this.showUnderLineButton = true,
this.showStrikeThrough = true,
this.showInlineCode = true,
this.showColorButton = true,
this.showBackgroundColorButton = true,
this.showClearFormat = true,
this.showAlignmentButtons = false,
this.showLeftAlignment = true,
this.showCenterAlignment = true,
this.showRightAlignment = true,
this.showJustifyAlignment = true,
this.showHeaderStyle = true,
this.showListNumbers = true,
this.showListBullets = true,
this.showListCheck = true,
this.showCodeBlock = true,
this.showQuote = true,
this.showIndent = true,
this.showLink = true,
this.showUndo = true,
this.showRedo = true,
this.showDirection = false,
this.showSearchButton = true,
this.showSubscript = true,
this.showSuperscript = true,
this.customButtons = const [],
/// The decoration to use for the toolbar.
this.decoration,
/// Toolbar items to display for controls of embed blocks
this.embedButtons,
this.linkDialogAction,
///The theme to use for the icons in the toolbar, uses type [QuillIconTheme]
this.iconTheme,
this.dialogTheme,
this.axis = Axis.horizontal, this.axis = Axis.horizontal,
this.color,
this.sectionDividerColor,
this.sectionDividerSpace,
/// By default it will calculated based on the [globalIconSize] from /// By default it will calculated based on the [globalIconSize] from
/// [base] in [QuillToolbarButtonOptions] /// [base] in [QuillToolbarButtonOptions]
@ -108,6 +164,67 @@ class QuillToolbarConfigurations extends Equatable {
/// we will update that logic soon /// we will update that logic soon
final Axis axis; final Axis axis;
// Overrides the action in the _LinkDialog widget
final LinkDialogAction? linkDialogAction;
final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
final bool showDividers;
final bool showFontFamily;
final bool showFontSize;
final bool showBoldButton;
final bool showItalicButton;
final bool showSmallButton;
final bool showUnderLineButton;
final bool showStrikeThrough;
final bool showInlineCode;
final bool showColorButton;
final bool showBackgroundColorButton;
final bool showClearFormat;
final bool showAlignmentButtons;
final bool showLeftAlignment;
final bool showCenterAlignment;
final bool showRightAlignment;
final bool showJustifyAlignment;
final bool showHeaderStyle;
final bool showListNumbers;
final bool showListBullets;
final bool showListCheck;
final bool showCodeBlock;
final bool showQuote;
final bool showIndent;
final bool showLink;
final bool showUndo;
final bool showRedo;
final bool showDirection;
final bool showSearchButton;
final bool showSubscript;
final bool showSuperscript;
final List<QuillCustomButton> customButtons;
/// The decoration to use for the toolbar.
final Decoration? decoration;
/// Toolbar items to display for controls of embed blocks
final List<EmbedButtonBuilder>? embedButtons;
///The theme to use for the icons in the toolbar, uses type [QuillIconTheme]
final QuillIconTheme? iconTheme;
///The theme to use for the theming of the [LinkDialog()],
///shown when embedding an image, for example
final QuillDialogTheme? dialogTheme;
/// The color of the toolbar
final Color? color;
/// The color of the toolbar section divider
final Color? sectionDividerColor;
/// The space occupied by toolbar divider
final double? sectionDividerSpace;
@override @override
List<Object?> get props => [ List<Object?> get props => [
buttonOptions, buttonOptions,

@ -1,6 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../widgets/toolbar/toolbar.dart'; import '../../widgets/toolbar/base_toolbar.dart';
class QuillCustomButton extends QuillToolbarBaseButtonOptions { class QuillCustomButton extends QuillToolbarBaseButtonOptions {
const QuillCustomButton({ const QuillCustomButton({

@ -70,7 +70,7 @@ extension BuildContextExt on BuildContext {
/// provider widget first and then we will return editor configurations /// provider widget first and then we will return editor configurations
/// throw exception if [QuillProvider] is not in the widget tree /// throw exception if [QuillProvider] is not in the widget tree
QuillEditorConfigurations get requireQuillEditorConfigurations { QuillEditorConfigurations get requireQuillEditorConfigurations {
return requireQuillConfigurations.editorConfigurations; return QuillEditorProvider.ofNotNull(this).editorConfigurations;
} }
/// return nullable [QuillEditorConfigurations]. Since the quill /// return nullable [QuillEditorConfigurations]. Since the quill
@ -78,7 +78,7 @@ extension BuildContextExt on BuildContext {
/// provider widget first and then we will return editor configurations /// provider widget first and then we will return editor configurations
/// don't throw exception if [QuillProvider] is not in the widget tree /// don't throw exception if [QuillProvider] is not in the widget tree
QuillEditorConfigurations? get quillEditorConfigurations { QuillEditorConfigurations? get quillEditorConfigurations {
return quillConfigurations?.editorConfigurations; return QuillEditorProvider.of(this)?.editorConfigurations;
} }
/// return [QuillToolbarConfigurations] as not null . Since the quill /// return [QuillToolbarConfigurations] as not null . Since the quill
@ -86,7 +86,7 @@ extension BuildContextExt on BuildContext {
/// provider widget first and then we will return toolbar configurations /// provider widget first and then we will return toolbar configurations
/// throw exception if [QuillProvider] is not in the widget tree /// throw exception if [QuillProvider] is not in the widget tree
QuillToolbarConfigurations get requireQuillToolbarConfigurations { QuillToolbarConfigurations get requireQuillToolbarConfigurations {
return requireQuillConfigurations.toolbarConfigurations; return QuillToolbarProvider.ofNotNull(this).toolbarConfigurations;
} }
/// return nullable [QuillToolbarConfigurations]. Since the quill /// return nullable [QuillToolbarConfigurations]. Since the quill
@ -94,7 +94,7 @@ extension BuildContextExt on BuildContext {
/// provider widget first and then we will return toolbar configurations /// provider widget first and then we will return toolbar configurations
/// don't throw exception if [QuillProvider] is not in the widget tree /// don't throw exception if [QuillProvider] is not in the widget tree
QuillToolbarConfigurations? get quillToolbarConfigurations { QuillToolbarConfigurations? get quillToolbarConfigurations {
return quillConfigurations?.toolbarConfigurations; return QuillToolbarProvider.of(this)?.toolbarConfigurations;
} }
/// return nullable [QuillToolbarBaseButtonOptions]. Since the quill /// return nullable [QuillToolbarBaseButtonOptions]. Since the quill

@ -41,6 +41,18 @@ bool isMacOS([TargetPlatform? targetPlatform]) {
return TargetPlatform.macOS == targetPlatform; return TargetPlatform.macOS == targetPlatform;
} }
bool isIOS([TargetPlatform? targetPlatform]) {
if (isWeb()) return false;
targetPlatform ??= defaultTargetPlatform;
return TargetPlatform.iOS == targetPlatform;
}
bool isAndroid([TargetPlatform? targetPlatform]) {
if (isWeb()) return false;
targetPlatform ??= defaultTargetPlatform;
return TargetPlatform.android == targetPlatform;
}
bool isFlutterTest() { bool isFlutterTest() {
if (isWeb()) return false; if (isWeb()) return false;
return Platform.environment.containsKey('FLUTTER_TEST'); return Platform.environment.containsKey('FLUTTER_TEST');

@ -1,7 +1,9 @@
import 'dart:math' as math; import 'dart:math' as math;
// ignore: unnecessary_import // ignore: unnecessary_import
import 'dart:typed_data'; // import 'dart:typed_data';
// The project maanged to compiled successfully without the import
// do we still need this import (dart:typed_data)??
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -19,7 +21,6 @@ import '../box.dart';
import '../cursor.dart'; import '../cursor.dart';
import '../delegate.dart'; import '../delegate.dart';
import '../float_cursor.dart'; import '../float_cursor.dart';
import '../link.dart';
import '../raw_editor/raw_editor.dart'; import '../raw_editor/raw_editor.dart';
import '../text_selection.dart'; import '../text_selection.dart';
@ -143,53 +144,16 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics {
class QuillEditor extends StatefulWidget { class QuillEditor extends StatefulWidget {
const QuillEditor({ const QuillEditor({
required this.configurations,
required this.focusNode, required this.focusNode,
required this.scrollController, required this.scrollController,
required this.scrollable, super.key,
required this.padding, });
required this.autoFocus,
required this.expands,
this.textSelectionThemeData,
this.showCursor,
this.paintCursorAboveText,
this.enableInteractiveSelection = true,
this.enableSelectionToolbar = true,
this.scrollBottomInset = 0,
this.minHeight,
this.maxHeight,
this.maxContentWidth,
this.customStyles,
this.textCapitalization = TextCapitalization.sentences,
this.keyboardAppearance = Brightness.light,
this.scrollPhysics,
this.onLaunchUrl,
this.onTapDown,
this.onTapUp,
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilders,
this.unknownEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.customRecognizerBuilder,
this.floatingCursorDisabled = false,
this.textSelectionControls,
this.onImagePaste,
this.customShortcuts,
this.customActions,
this.detectWordBoundary = true,
this.enableUnfocusOnTapOutside = true,
this.customLinkPrefixes = const <String>[],
this.dialogTheme,
this.contentInsertionConfiguration,
this.contextMenuBuilder,
this.editorKey,
Key? key,
}) : super(key: key);
factory QuillEditor.basic({ factory QuillEditor.basic({
required bool readOnly, /// The configurations for the quill editor widget of flutter quill
QuillEditorConfigurations configurations =
const QuillEditorConfigurations(),
TextSelectionThemeData? textSelectionThemeData, TextSelectionThemeData? textSelectionThemeData,
Brightness? keyboardAppearance, Brightness? keyboardAppearance,
Iterable<EmbedBuilder>? embedBuilders, Iterable<EmbedBuilder>? embedBuilders,
@ -197,13 +161,12 @@ class QuillEditor extends StatefulWidget {
bool autoFocus = true, bool autoFocus = true,
bool expands = false, bool expands = false,
FocusNode? focusNode, FocusNode? focusNode,
String? placeholder,
GlobalKey<EditorState>? editorKey, GlobalKey<EditorState>? editorKey,
}) { }) {
return QuillEditor( return QuillEditor(
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true,
focusNode: focusNode ?? FocusNode(), focusNode: focusNode ?? FocusNode(),
configurations: configurations.copyWith(
textSelectionThemeData: textSelectionThemeData, textSelectionThemeData: textSelectionThemeData,
autoFocus: autoFocus, autoFocus: autoFocus,
expands: expands, expands: expands,
@ -211,10 +174,12 @@ class QuillEditor extends StatefulWidget {
keyboardAppearance: keyboardAppearance ?? Brightness.light, keyboardAppearance: keyboardAppearance ?? Brightness.light,
embedBuilders: embedBuilders, embedBuilders: embedBuilders,
editorKey: editorKey, editorKey: editorKey,
),
); );
} }
// final QuillController controller; /// The configurations for the quill editor widget of flutter quill
final QuillEditorConfigurations configurations;
/// Controls whether this editor has keyboard focus. /// Controls whether this editor has keyboard focus.
final FocusNode focusNode; final FocusNode focusNode;
@ -222,214 +187,6 @@ class QuillEditor extends StatefulWidget {
/// The [ScrollController] to use when vertically scrolling the contents. /// The [ScrollController] to use when vertically scrolling the contents.
final ScrollController scrollController; final ScrollController scrollController;
/// Whether this editor should create a scrollable container for its content.
///
/// When set to `true` the editor's height can be controlled by [minHeight],
/// [maxHeight] and [expands] properties.
///
/// When set to `false` the editor always expands to fit the entire content
/// of the document and should normally be placed as a child of another
/// scrollable widget, otherwise the content may be clipped.
final bool scrollable;
final double scrollBottomInset;
/// Additional space around the content of this editor.
final EdgeInsetsGeometry padding;
/// Whether this editor should focus itself if nothing else is already
/// focused.
///
/// If true, the keyboard will open as soon as this editor obtains focus.
/// Otherwise, the keyboard is only shown after the user taps the editor.
///
/// Defaults to `false`. Cannot be `null`.
final bool autoFocus;
/// Whether focus should be revoked on tap outside the editor.
final bool enableUnfocusOnTapOutside;
/// Whether to show cursor.
///
/// The cursor refers to the blinking caret when the editor is focused.
final bool? showCursor;
final bool? paintCursorAboveText;
/// Whether to enable user interface affordances for changing the
/// text selection.
///
/// For example, setting this to true will enable features such as
/// long-pressing the editor to select text and show the
/// cut/copy/paste menu, and tapping to move the text cursor.
///
/// When this is false, the text selection cannot be adjusted by
/// the user, text cannot be copied, and the user cannot paste into
/// the text field from the clipboard.
///
/// To disable just the selection toolbar, set enableSelectionToolbar
/// to false.
final bool enableInteractiveSelection;
/// Whether to show the cut/copy/paste menu when selecting text.
final bool enableSelectionToolbar;
/// The minimum height to be occupied by this editor.
///
/// This only has effect if [scrollable] is set to `true` and [expands] is
/// set to `false`.
final double? minHeight;
/// The maximum height to be occupied by this editor.
///
/// This only has effect if [scrollable] is set to `true` and [expands] is
/// set to `false`.
final double? maxHeight;
/// The maximum width to be occupied by the content of this editor.
///
/// If this is not null and and this editor's width is larger than this value
/// then the contents will be constrained to the provided maximum width and
/// horizontally centered. This is mostly useful on devices with wide screens.
final double? maxContentWidth;
/// Allows to override [DefaultStyles].
final DefaultStyles? customStyles;
/// Whether this editor's height will be sized to fill its parent.
///
/// This only has effect if [scrollable] is set to `true`.
///
/// If expands is set to true and wrapped in a parent widget like [Expanded]
/// or [SizedBox], the editor will expand to fill the parent.
///
/// [maxHeight] and [minHeight] must both be `null` when this is set to
/// `true`.
///
/// Defaults to `false`.
final bool expands;
/// Configures how the platform keyboard will select an uppercase or
/// lowercase keyboard.
///
/// Only supports text keyboards, other keyboard types will ignore this
/// configuration. Capitalization is locale-aware.
///
/// Defaults to [TextCapitalization.sentences]. Must not be `null`.
final TextCapitalization textCapitalization;
/// The appearance of the keyboard.
///
/// This setting is only honored on iOS devices.
///
/// Defaults to [Brightness.light].
final Brightness keyboardAppearance;
/// The [ScrollPhysics] to use when vertically scrolling the input.
///
/// This only has effect if [scrollable] is set to `true`.
///
/// If not specified, it will behave according to the current platform.
///
/// See [Scrollable.physics].
final ScrollPhysics? scrollPhysics;
/// Callback to invoke when user wants to launch a URL.
final ValueChanged<String>? onLaunchUrl;
// Returns whether gesture is handled
final bool Function(
TapDownDetails details, TextPosition Function(Offset offset))? onTapDown;
// Returns whether gesture is handled
final bool Function(
TapUpDetails details, TextPosition Function(Offset offset))? onTapUp;
// Returns whether gesture is handled
final bool Function(
LongPressStartDetails details, TextPosition Function(Offset offset))?
onSingleLongTapStart;
// Returns whether gesture is handled
final bool Function(LongPressMoveUpdateDetails details,
TextPosition Function(Offset offset))? onSingleLongTapMoveUpdate;
// Returns whether gesture is handled
final bool Function(
LongPressEndDetails details, TextPosition Function(Offset offset))?
onSingleLongTapEnd;
final Iterable<EmbedBuilder>? embedBuilders;
final EmbedBuilder? unknownEmbedBuilder;
final CustomStyleBuilder? customStyleBuilder;
final CustomRecognizerBuilder? customRecognizerBuilder;
/// Delegate function responsible for showing menu with link actions on
/// mobile platforms (iOS, Android).
///
/// The menu is triggered in editing mode ([readOnly] is set to `false`)
/// when the user long-presses a link-styled text segment.
///
/// FlutterQuill provides default implementation which can be overridden by
/// this field to customize the user experience.
///
/// By default on iOS the menu is displayed with [showCupertinoModalPopup]
/// which constructs an instance of [CupertinoActionSheet]. For Android,
/// the menu is displayed with [showModalBottomSheet] and a list of
/// Material [ListTile]s.
final LinkActionPickerDelegate linkActionPickerDelegate;
final bool floatingCursorDisabled;
/// allows to create a custom textSelectionControls,
/// if this is null a default textSelectionControls based on the app's theme
/// 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<String?> Function(Uint8List imageBytes)? onImagePaste;
/// Contains user-defined shortcuts map.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#shortcuts]
final Map<ShortcutActivator, Intent>? customShortcuts;
/// Contains user-defined actions.
///
/// [https://docs.flutter.dev/development/ui/advanced/actions-and-shortcuts#actions]
final Map<Type, Action<Intent>>? customActions;
final bool detectWordBoundary;
/// Additional list if links prefixes, which must not be prepended
/// with "https://" when [LinkMenuAction.launch] happened
///
/// Useful for deeplinks
final List<String> customLinkPrefixes;
/// Configures the dialog theme.
final QuillDialogTheme? dialogTheme;
// Allows for creating a custom context menu
final QuillEditorContextMenuBuilder? contextMenuBuilder;
/// Configuration of handler for media content inserted via the system input
/// method.
///
/// See [https://api.flutter.dev/flutter/widgets/EditableText/contentInsertionConfiguration.html]
final ContentInsertionConfiguration? contentInsertionConfiguration;
/// Using the editorKey for get getLocalRectForCaret
/// editorKey.currentState?.renderEditor.getLocalRectForCaret
final GlobalKey<EditorState>? editorKey;
/// By default we will use
/// ```
/// TextSelectionTheme.of(context)
/// ```
/// to change it please pass a different value
final TextSelectionThemeData? textSelectionThemeData;
@override @override
QuillEditorState createState() => QuillEditorState(); QuillEditorState createState() => QuillEditorState();
} }
@ -440,20 +197,26 @@ class QuillEditorState extends State<QuillEditor>
late EditorTextSelectionGestureDetectorBuilder late EditorTextSelectionGestureDetectorBuilder
_selectionGestureDetectorBuilder; _selectionGestureDetectorBuilder;
QuillEditorConfigurations get configurations {
return widget.configurations;
}
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_editorKey = widget.editorKey ?? GlobalKey<EditorState>(); _editorKey = configurations.editorKey ?? GlobalKey<EditorState>();
_selectionGestureDetectorBuilder = _selectionGestureDetectorBuilder =
_QuillEditorSelectionGestureDetectorBuilder( _QuillEditorSelectionGestureDetectorBuilder(
this, widget.detectWordBoundary); this,
configurations.detectWordBoundary,
);
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final selectionTheme = final selectionTheme =
widget.textSelectionThemeData ?? TextSelectionTheme.of(context); configurations.textSelectionThemeData ?? TextSelectionTheme.of(context);
TextSelectionControls textSelectionControls; TextSelectionControls textSelectionControls;
bool paintCursorAboveText; bool paintCursorAboveText;
@ -483,61 +246,65 @@ class QuillEditorState extends State<QuillEditor>
theme.colorScheme.primary.withOpacity(0.40); theme.colorScheme.primary.withOpacity(0.40);
} }
final showSelectionToolbar = final showSelectionToolbar = configurations.enableInteractiveSelection &&
widget.enableInteractiveSelection && widget.enableSelectionToolbar; configurations.enableSelectionToolbar;
final editorConfigurations = final child = QuillEditorProvider(
context.requireQuillConfigurations.editorConfigurations; editorConfigurations: configurations,
child: RawEditor(
final child = RawEditor(
key: _editorKey, key: _editorKey,
controller: context.requireQuillController, controller: context.requireQuillController,
focusNode: widget.focusNode, focusNode: widget.focusNode,
scrollController: widget.scrollController, scrollController: widget.scrollController,
scrollable: widget.scrollable, scrollable: configurations.scrollable,
scrollBottomInset: widget.scrollBottomInset, scrollBottomInset: configurations.scrollBottomInset,
padding: widget.padding, padding: configurations.padding,
readOnly: editorConfigurations.readOnly, readOnly: configurations.readOnly,
placeholder: editorConfigurations.placeholder, placeholder: configurations.placeholder,
onLaunchUrl: widget.onLaunchUrl, onLaunchUrl: configurations.onLaunchUrl,
contextMenuBuilder: showSelectionToolbar contextMenuBuilder: showSelectionToolbar
? (widget.contextMenuBuilder ?? RawEditor.defaultContextMenuBuilder) ? (configurations.contextMenuBuilder ??
RawEditor.defaultContextMenuBuilder)
: null, : null,
showSelectionHandles: isMobile(theme.platform), showSelectionHandles: isMobile(theme.platform),
showCursor: widget.showCursor, showCursor: configurations.showCursor,
cursorStyle: CursorStyle( cursorStyle: CursorStyle(
color: cursorColor, color: cursorColor,
backgroundColor: Colors.grey, backgroundColor: Colors.grey,
width: 2, width: 2,
radius: cursorRadius, radius: cursorRadius,
offset: cursorOffset, offset: cursorOffset,
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText, paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates, opacityAnimates: cursorOpacityAnimates,
), ),
textCapitalization: widget.textCapitalization, textCapitalization: configurations.textCapitalization,
minHeight: widget.minHeight, minHeight: configurations.minHeight,
maxHeight: widget.maxHeight, maxHeight: configurations.maxHeight,
maxContentWidth: widget.maxContentWidth, maxContentWidth: configurations.maxContentWidth,
customStyles: widget.customStyles, customStyles: configurations.customStyles,
expands: widget.expands, expands: configurations.expands,
autoFocus: widget.autoFocus, autoFocus: configurations.autoFocus,
selectionColor: selectionColor, selectionColor: selectionColor,
selectionCtrls: widget.textSelectionControls ?? textSelectionControls, selectionCtrls:
keyboardAppearance: widget.keyboardAppearance, configurations.textSelectionControls ?? textSelectionControls,
enableInteractiveSelection: widget.enableInteractiveSelection, keyboardAppearance: configurations.keyboardAppearance,
scrollPhysics: widget.scrollPhysics, enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder, embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: widget.linkActionPickerDelegate, linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: widget.customStyleBuilder, customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: widget.customRecognizerBuilder, customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: widget.floatingCursorDisabled, floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: widget.onImagePaste, onImagePaste: configurations.onImagePaste,
customShortcuts: widget.customShortcuts, customShortcuts: configurations.customShortcuts,
customActions: widget.customActions, customActions: configurations.customActions,
customLinkPrefixes: widget.customLinkPrefixes, customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside, enableUnfocusOnTapOutside: configurations.enableUnfocusOnTapOutside,
dialogTheme: widget.dialogTheme, dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration: widget.contentInsertionConfiguration, contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
),
); );
final editor = I18n( final editor = I18n(
@ -545,7 +312,7 @@ class QuillEditorState extends State<QuillEditor>
child: selectionEnabled child: selectionEnabled
? _selectionGestureDetectorBuilder.build( ? _selectionGestureDetectorBuilder.build(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
detectWordBoundary: widget.detectWordBoundary, detectWordBoundary: configurations.detectWordBoundary,
child: child, child: child,
) )
: child, : child,
@ -569,7 +336,7 @@ class QuillEditorState extends State<QuillEditor>
} }
EmbedBuilder _getEmbedBuilder(Embed node) { EmbedBuilder _getEmbedBuilder(Embed node) {
final builders = widget.embedBuilders; final builders = configurations.embedBuilders;
if (builders != null) { if (builders != null) {
for (final builder in builders) { for (final builder in builders) {
@ -579,8 +346,9 @@ class QuillEditorState extends State<QuillEditor>
} }
} }
if (widget.unknownEmbedBuilder != null) { final unknownEmbedBuilder = configurations.unknownEmbedBuilder;
return widget.unknownEmbedBuilder!; if (unknownEmbedBuilder != null) {
return unknownEmbedBuilder;
} }
throw UnimplementedError( throw UnimplementedError(
@ -598,7 +366,7 @@ class QuillEditorState extends State<QuillEditor>
bool get forcePressEnabled => false; bool get forcePressEnabled => false;
@override @override
bool get selectionEnabled => widget.enableInteractiveSelection; bool get selectionEnabled => configurations.enableInteractiveSelection;
void _requestKeyboard() { void _requestKeyboard() {
final editorCurrentState = _editorKey.currentState; final editorCurrentState = _editorKey.currentState;
@ -633,10 +401,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) { void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
if (_state.widget.onSingleLongTapMoveUpdate != null) { if (_state.configurations.onSingleLongTapMoveUpdate != null) {
if (renderEditor != null && if (renderEditor != null &&
_state.widget.onSingleLongTapMoveUpdate!( _state.configurations.onSingleLongTapMoveUpdate!(
details, renderEditor!.getPositionForOffset)) { details,
renderEditor!.getPositionForOffset,
)) {
return; return;
} }
} }
@ -683,10 +453,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
void onTapDown(TapDownDetails details) { void onTapDown(TapDownDetails details) {
if (_state.widget.onTapDown != null) { if (_state.configurations.onTapDown != null) {
if (renderEditor != null && if (renderEditor != null &&
_state.widget.onTapDown!( _state.configurations.onTapDown!(
details, renderEditor!.getPositionForOffset)) { details,
renderEditor!.getPositionForOffset,
)) {
return; return;
} }
} }
@ -702,9 +474,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
void onSingleTapUp(TapUpDetails details) { void onSingleTapUp(TapUpDetails details) {
if (_state.widget.onTapUp != null && if (_state.configurations.onTapUp != null &&
renderEditor != null && renderEditor != null &&
_state.widget.onTapUp!(details, renderEditor!.getPositionForOffset)) { _state.configurations.onTapUp!(
details,
renderEditor!.getPositionForOffset,
)) {
return; return;
} }
@ -765,10 +540,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
void onSingleLongTapStart(LongPressStartDetails details) { void onSingleLongTapStart(LongPressStartDetails details) {
if (_state.widget.onSingleLongTapStart != null) { if (_state.configurations.onSingleLongTapStart != null) {
if (renderEditor != null && if (renderEditor != null &&
_state.widget.onSingleLongTapStart!( _state.configurations.onSingleLongTapStart!(
details, renderEditor!.getPositionForOffset)) { details,
renderEditor!.getPositionForOffset,
)) {
return; return;
} }
} }
@ -789,10 +566,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override @override
void onSingleLongTapEnd(LongPressEndDetails details) { void onSingleLongTapEnd(LongPressEndDetails details) {
if (_state.widget.onSingleLongTapEnd != null) { if (_state.configurations.onSingleLongTapEnd != null) {
if (renderEditor != null) { if (renderEditor != null) {
if (_state.widget.onSingleLongTapEnd!( if (_state.configurations.onSingleLongTapEnd!(
details, renderEditor!.getPositionForOffset)) { details,
renderEditor!.getPositionForOffset,
)) {
return; return;
} }
@ -1447,6 +1226,28 @@ class RenderEditor extends RenderEditableContainerBox
@override @override
Rect getLocalRectForCaret(TextPosition position) { Rect getLocalRectForCaret(TextPosition position) {
final targetChild = childAtPosition(position); final targetChild = childAtPosition(position);
// TODO: There is a bug here
// The provided text position is not in the current node
// 'package:flutter_quill/src/widgets/text_block.dart':
// text_block.dart:1
// Failed assertion: line 604 pos 12:
// 'container.containsOffset(position.offset)'
// When the exception was thrown, this was the stack
// #2 RenderEditableTextBlock.globalToLocalPosition
// text_block.dart:604
// #3 RenderEditor.getLocalRectForCaret
// editor.dart:1230
// #4 RawEditorStateTextInputClientMixin._updateComposingRectIfNeeded
// raw_editor_state_text_input_client_mixin.dart:85
// #5 RawEditorStateTextInputClientMixin.openConnectionIfNeeded
// raw_editor_state_text_input_client_mixin.dart:70
// #6 RawEditorState.requestKeyboard
// raw_editor.dart:1428
// #7 QuillEditorState._requestKeyboard
// editor.dart:379
// #8 _QuillEditorSelectionGestureDetectorBuilder.onSingleTapUp
// editor.dart:538
// #9 _EditorTextSelectionGestureDetectorState._handleTapUp
final localPosition = targetChild.globalToLocalPosition(position); final localPosition = targetChild.globalToLocalPosition(position);
final childLocalRect = targetChild.getLocalRectForCaret(localPosition); final childLocalRect = targetChild.getLocalRectForCaret(localPosition);
@ -1652,8 +1453,9 @@ class RenderEditor extends RenderEditableContainerBox
@override @override
TextPosition getTextPositionBelow(TextPosition position) { TextPosition getTextPositionBelow(TextPosition position) {
final child = childAtPosition(position); final child = childAtPosition(position);
final localPosition = final localPosition = TextPosition(
TextPosition(offset: position.offset - child.container.documentOffset); offset: position.offset - child.container.documentOffset,
);
var newPosition = child.getPositionBelow(localPosition); var newPosition = child.getPositionBelow(localPosition);
@ -1672,11 +1474,13 @@ class RenderEditor extends RenderEditableContainerBox
final finalOffset = Offset(caretOffset.dx, testOffset.dy); final finalOffset = Offset(caretOffset.dx, testOffset.dy);
final siblingPosition = sibling.getPositionForOffset(finalOffset); final siblingPosition = sibling.getPositionForOffset(finalOffset);
newPosition = TextPosition( newPosition = TextPosition(
offset: sibling.container.documentOffset + siblingPosition.offset); offset: sibling.container.documentOffset + siblingPosition.offset,
);
} }
} else { } else {
newPosition = TextPosition( newPosition = TextPosition(
offset: child.container.documentOffset + newPosition.offset); offset: child.container.documentOffset + newPosition.offset,
);
} }
return newPosition; return newPosition;
} }

@ -55,7 +55,9 @@ Future<LinkMenuAction> defaultLinkActionPickerDelegate(
assert( assert(
false, false,
'defaultShowLinkActionsMenu not supposed to ' 'defaultShowLinkActionsMenu not supposed to '
'be invoked for $defaultTargetPlatform'); 'be invoked for $defaultTargetPlatform. '
"it's only supported for iOS and Android.",
);
return LinkMenuAction.none; return LinkMenuAction.none;
} }
} }

@ -36,6 +36,7 @@ import '../../models/themes/quill_dialog_theme.dart';
import '../../utils/cast.dart'; import '../../utils/cast.dart';
import '../../utils/delta.dart'; import '../../utils/delta.dart';
import '../../utils/embeds.dart'; import '../../utils/embeds.dart';
import '../../utils/extensions/build_context.dart';
import '../../utils/platform.dart'; import '../../utils/platform.dart';
import '../controller.dart'; import '../controller.dart';
import '../cursor.dart'; import '../cursor.dart';
@ -880,6 +881,9 @@ class RawEditorState extends EditorState
/// Updates the checkbox positioned at [offset] in document /// Updates the checkbox positioned at [offset] in document
/// by changing its attribute according to [value]. /// by changing its attribute according to [value].
void _handleCheckboxTap(int offset, bool value) { void _handleCheckboxTap(int offset, bool value) {
final requestKeyboardFocusOnCheckListChanged = context
.requireQuillEditorConfigurations
.requestKeyboardFocusOnCheckListChanged;
if (!widget.readOnly) { if (!widget.readOnly) {
_disableScrollControllerAnimateOnce = true; _disableScrollControllerAnimateOnce = true;
final currentSelection = controller.selection.copyWith(); final currentSelection = controller.selection.copyWith();
@ -888,6 +892,7 @@ class RawEditorState extends EditorState
_markNeedsBuild(); _markNeedsBuild();
controller controller
..ignoreFocusOnTextChange = true ..ignoreFocusOnTextChange = true
..skipRequestKeyboard = !requestKeyboardFocusOnCheckListChanged
..formatText(offset, 0, attribute) ..formatText(offset, 0, attribute)
// Checkbox tapping causes controller.selection to go to offset 0 // Checkbox tapping causes controller.selection to go to offset 0
@ -901,6 +906,7 @@ class RawEditorState extends EditorState
SchedulerBinding.instance.addPostFrameCallback((_) { SchedulerBinding.instance.addPostFrameCallback((_) {
controller controller
..ignoreFocusOnTextChange = false ..ignoreFocusOnTextChange = false
..skipRequestKeyboard = !requestKeyboardFocusOnCheckListChanged
..updateSelection(currentSelection, ChangeSource.LOCAL); ..updateSelection(currentSelection, ChangeSource.LOCAL);
}); });
} }

@ -10,8 +10,8 @@ class CheckboxPoint extends StatefulWidget {
required this.enabled, required this.enabled,
required this.onChanged, required this.onChanged,
this.uiBuilder, this.uiBuilder,
Key? key, super.key,
}) : super(key: key); });
final double size; final double size;
final bool value; final bool value;
@ -26,8 +26,9 @@ class CheckboxPoint extends StatefulWidget {
class _CheckboxPointState extends State<CheckboxPoint> { class _CheckboxPointState extends State<CheckboxPoint> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (widget.uiBuilder != null) { final uiBuilder = widget.uiBuilder;
return widget.uiBuilder!.build( if (uiBuilder != null) {
return uiBuilder.build(
context: context, context: context,
isChecked: widget.value, isChecked: widget.value,
onChanged: widget.onChanged, onChanged: widget.onChanged,

@ -52,8 +52,8 @@ const List<String> romanNumbers = [
]; ];
class EditableTextBlock extends StatelessWidget { class EditableTextBlock extends StatelessWidget {
const EditableTextBlock( const EditableTextBlock({
{required this.block, required this.block,
required this.controller, required this.controller,
required this.textDirection, required this.textDirection,
required this.scrollBottomInset, required this.scrollBottomInset,
@ -74,7 +74,8 @@ class EditableTextBlock extends StatelessWidget {
this.onLaunchUrl, this.onLaunchUrl,
this.customStyleBuilder, this.customStyleBuilder,
this.customLinkPrefixes = const <String>[], this.customLinkPrefixes = const <String>[],
Key? key}); super.key,
});
final Block block; final Block block;
final QuillController controller; final QuillController controller;

@ -0,0 +1,132 @@
import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.dart';
import '../../../flutter_quill.dart';
import '../../models/config/toolbar/base_configurations.dart';
import '../../utils/extensions/build_context.dart';
import 'buttons/arrow_indicated_list.dart';
export '../../models/config/toolbar/buttons/base.dart';
export '../../models/config/toolbar/configurations.dart';
export 'buttons/clear_format.dart';
export 'buttons/color.dart';
export 'buttons/custom_button.dart';
export 'buttons/font_family.dart';
export 'buttons/font_size.dart';
export 'buttons/history.dart';
export 'buttons/indent.dart';
export 'buttons/link_style.dart';
export 'buttons/link_style2.dart';
export 'buttons/quill_icon.dart';
export 'buttons/search/search.dart';
export 'buttons/select_alignment.dart';
export 'buttons/select_header_style.dart';
export 'buttons/toggle_check_list.dart';
export 'buttons/toggle_style.dart';
typedef QuillBaseToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
);
class QuillBaseToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillBaseToolbar({
required this.configurations,
super.key,
});
final QuillBaseToolbarConfigurations configurations;
// We can't get the modified [toolbarSize] by the developer
// but we tested the [QuillToolbar] on the [appBar] and I didn't notice
// a difference no matter what the value is so I will leave it to the
// default
@override
Size get preferredSize => configurations.axis == Axis.horizontal
? const Size.fromHeight(defaultToolbarSize)
: const Size.fromWidth(defaultToolbarSize);
@override
Widget build(BuildContext context) {
final toolbarSize = configurations.toolbarSize;
return I18n(
initialLocale: context.quillSharedConfigurations?.locale,
child: Builder(
builder: (context) {
if (configurations.multiRowsDisplay) {
return Wrap(
direction: configurations.axis,
alignment: configurations.toolbarIconAlignment,
crossAxisAlignment: configurations.toolbarIconCrossAlignment,
runSpacing: 4,
spacing: configurations.toolbarSectionSpacing,
children: configurations.childrenBuilder(context),
);
}
return Container(
decoration: configurations.decoration ??
BoxDecoration(
color: configurations.color ?? Theme.of(context).canvasColor,
),
constraints: BoxConstraints.tightFor(
height:
configurations.axis == Axis.horizontal ? toolbarSize : null,
width: configurations.axis == Axis.vertical ? toolbarSize : null,
),
child: QuillToolbarArrowIndicatedButtonList(
axis: configurations.axis,
buttons: configurations.childrenBuilder(context),
),
);
},
),
);
}
}
/// The divider which is used for separation of buttons in the toolbar.
///
/// It can be used outside of this package, for example when user does not use
/// [QuillBaseToolbar.basic] and compose toolbar's children on its own.
class QuillToolbarDivider extends StatelessWidget {
const QuillToolbarDivider(
this.axis, {
super.key,
this.color,
this.space,
});
/// Provides a horizontal divider for vertical toolbar.
const QuillToolbarDivider.horizontal({Color? color, double? space})
: this(Axis.horizontal, color: color, space: space);
/// Provides a horizontal divider for horizontal toolbar.
const QuillToolbarDivider.vertical({Color? color, double? space})
: this(Axis.vertical, color: color, space: space);
/// The axis along which the toolbar is.
final Axis axis;
/// The color to use when painting this divider's line.
final Color? color;
/// The divider's space (width or height) depending of [axis].
final double? space;
@override
Widget build(BuildContext context) {
// Vertical toolbar requires horizontal divider, and vice versa
return axis == Axis.vertical
? Divider(
height: space,
color: color,
indent: 12,
endIndent: 12,
)
: VerticalDivider(
width: space,
color: color,
indent: 12,
endIndent: 12,
);
}
}

@ -5,7 +5,7 @@ import '../../../models/documents/attribute.dart';
import '../../../models/themes/quill_icon_theme.dart'; import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class QuillToolbarClearFormatButton extends StatelessWidget { class QuillToolbarClearFormatButton extends StatelessWidget {
const QuillToolbarClearFormatButton({ const QuillToolbarClearFormatButton({

@ -8,7 +8,7 @@ import '../../../translations/toolbar.i18n.dart';
import '../../../utils/color.dart'; import '../../../utils/color.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
/// Controls color styles. /// Controls color styles.
/// ///

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import '../../../models/themes/quill_icon_theme.dart'; import '../../../models/themes/quill_icon_theme.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class CustomButton extends StatelessWidget { class CustomButton extends StatelessWidget {
const CustomButton({ const CustomButton({

@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
import '../../../../translations.dart'; import '../../../../translations.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class QuillToolbarHistoryButton extends StatefulWidget { class QuillToolbarHistoryButton extends StatefulWidget {
const QuillToolbarHistoryButton({ const QuillToolbarHistoryButton({

@ -5,7 +5,11 @@ import '../../../models/themes/quill_icon_theme.dart';
import '../../../translations/toolbar.i18n.dart'; import '../../../translations/toolbar.i18n.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart'
show
QuillToolbarBaseButtonOptions,
QuillToolbarIconButton,
kIconButtonFactor;
class QuillToolbarIndentButton extends StatefulWidget { class QuillToolbarIndentButton extends StatefulWidget {
const QuillToolbarIndentButton({ const QuillToolbarIndentButton({

@ -9,7 +9,7 @@ import '../../../translations/toolbar.i18n.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../../link.dart'; import '../../link.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class QuillToolbarLinkStyleButton extends StatefulWidget { class QuillToolbarLinkStyleButton extends StatefulWidget {
const QuillToolbarLinkStyleButton({ const QuillToolbarLinkStyleButton({

@ -10,7 +10,7 @@ import '../../../models/themes/quill_dialog_theme.dart';
import '../../../models/themes/quill_icon_theme.dart'; import '../../../models/themes/quill_icon_theme.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../../link.dart'; import '../../link.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
/// Alternative version of [QuillToolbarLinkStyleButton]. This widget has more /// Alternative version of [QuillToolbarLinkStyleButton]. This widget has more
/// customization /// customization

@ -5,7 +5,7 @@ import '../../../../models/themes/quill_dialog_theme.dart';
import '../../../../models/themes/quill_icon_theme.dart'; import '../../../../models/themes/quill_icon_theme.dart';
import '../../../../utils/extensions/build_context.dart'; import '../../../../utils/extensions/build_context.dart';
import '../../../controller.dart'; import '../../../controller.dart';
import '../../toolbar.dart'; import '../../base_toolbar.dart';
class QuillToolbarSearchButton extends StatelessWidget { class QuillToolbarSearchButton extends StatelessWidget {
const QuillToolbarSearchButton({ const QuillToolbarSearchButton({

@ -8,7 +8,7 @@ import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../../utils/widgets.dart'; import '../../../utils/widgets.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class QuillToolbarSelectAlignmentButton extends StatefulWidget { class QuillToolbarSelectAlignmentButton extends StatefulWidget {
const QuillToolbarSelectAlignmentButton({ const QuillToolbarSelectAlignmentButton({

@ -7,7 +7,7 @@ import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart'; import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
class QuillToolbarSelectHeaderStyleButtons extends StatefulWidget { class QuillToolbarSelectHeaderStyleButtons extends StatefulWidget {
const QuillToolbarSelectHeaderStyleButtons({ const QuillToolbarSelectHeaderStyleButtons({

@ -7,7 +7,7 @@ import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/extensions/build_context.dart'; import '../../../utils/extensions/build_context.dart';
import '../../../utils/widgets.dart'; import '../../../utils/widgets.dart';
import '../../controller.dart'; import '../../controller.dart';
import '../toolbar.dart'; import '../base_toolbar.dart';
typedef ToggleStyleButtonBuilder = Widget Function( typedef ToggleStyleButtonBuilder = Widget Function(
BuildContext context, BuildContext context,

@ -1,293 +1,192 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.dart';
import '../../../flutter_quill.dart'; import '../../../flutter_quill.dart';
import '../../models/config/toolbar/base_configurations.dart';
import '../../utils/extensions/build_context.dart'; import '../../utils/extensions/build_context.dart';
import 'buttons/arrow_indicated_list.dart';
export '../../models/config/toolbar/buttons/base.dart'; class QuillToolbar extends StatelessWidget {
export '../../models/config/toolbar/configurations.dart';
export 'buttons/clear_format.dart';
export 'buttons/color.dart';
export 'buttons/custom_button.dart';
export 'buttons/font_family.dart';
export 'buttons/font_size.dart';
export 'buttons/history.dart';
export 'buttons/indent.dart';
export 'buttons/link_style.dart';
export 'buttons/link_style2.dart';
export 'buttons/quill_icon.dart';
export 'buttons/search/search.dart';
export 'buttons/select_alignment.dart';
export 'buttons/select_header_style.dart';
export 'buttons/toggle_check_list.dart';
export 'buttons/toggle_style.dart';
typedef QuillToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
);
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({ const QuillToolbar({
required this.childrenBuilder, super.key,
this.axis = Axis.horizontal, this.configurations = const QuillToolbarConfigurations(),
// this.toolbarSize = kDefaultIconSize * 2, });
this.toolbarSectionSpacing = kToolbarSectionSpacing,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.color,
this.customButtons = const [],
this.sectionDividerColor,
this.sectionDividerSpace,
this.linkDialogAction,
this.decoration,
Key? key,
}) : super(key: key);
factory QuillToolbar.basic({
double toolbarSectionSpacing = kToolbarSectionSpacing,
WrapAlignment toolbarIconAlignment = WrapAlignment.center,
WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center,
bool showDividers = true,
bool showFontFamily = true,
bool showFontSize = true,
bool showBoldButton = true,
bool showItalicButton = true,
bool showSmallButton = false,
bool showUnderLineButton = true,
bool showStrikeThrough = true,
bool showInlineCode = true,
bool showColorButton = true,
bool showBackgroundColorButton = true,
bool showClearFormat = true,
bool showAlignmentButtons = false,
bool showLeftAlignment = true,
bool showCenterAlignment = true,
bool showRightAlignment = true,
bool showJustifyAlignment = true,
bool showHeaderStyle = true,
bool showListNumbers = true,
bool showListBullets = true,
bool showListCheck = true,
bool showCodeBlock = true,
bool showQuote = true,
bool showIndent = true,
bool showLink = true,
bool showUndo = true,
bool showRedo = true,
bool showDirection = false,
bool showSearchButton = true,
bool showSubscript = true,
bool showSuperscript = true,
List<QuillCustomButton> customButtons = const [],
/// The decoration to use for the toolbar.
Decoration? decoration,
/// Toolbar items to display for controls of embed blocks
List<EmbedButtonBuilder>? embedButtons,
///The theme to use for the icons in the toolbar, uses type [QuillIconTheme]
QuillIconTheme? iconTheme,
///The theme to use for the theming of the [LinkDialog()],
///shown when embedding an image, for example
QuillDialogTheme? dialogTheme,
///Map of tooltips for toolbar buttons
///
///The example is:
///```dart
/// tooltips = <ToolbarButtons, String>{
/// ToolbarButtons.undo: 'Undo',
/// ToolbarButtons.redo: 'Redo',
/// }
///
///```
///
/// To disable tooltips just pass empty map as well.
@Deprecated('This is deprecated and will no longer used. '
'to change the tooltips please pass them in the Quill toolbar button'
' configurations which exists in in the QuillProvider')
Map<ToolbarButtons, String>? tooltips,
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
Locale? locale,
/// The color of the toolbar /// The configurations for the toolbar widget of flutter quill
Color? color, final QuillToolbarConfigurations configurations;
/// The color of the toolbar section divider @override
Color? sectionDividerColor, Widget build(BuildContext context) {
final theEmbedButtons = configurations.embedButtons;
/// The space occupied by toolbar divider
double? sectionDividerSpace,
Key? key,
}) {
final isButtonGroupShown = [ final isButtonGroupShown = [
showFontFamily || configurations.showFontFamily ||
showFontSize || configurations.showFontSize ||
showBoldButton || configurations.showBoldButton ||
showItalicButton || configurations.showItalicButton ||
showSmallButton || configurations.showSmallButton ||
showUnderLineButton || configurations.showUnderLineButton ||
showStrikeThrough || configurations.showStrikeThrough ||
showInlineCode || configurations.showInlineCode ||
showColorButton || configurations.showColorButton ||
showBackgroundColorButton || configurations.showBackgroundColorButton ||
showClearFormat || configurations.showClearFormat ||
embedButtons?.isNotEmpty == true, theEmbedButtons?.isNotEmpty == true,
showLeftAlignment || configurations.showLeftAlignment ||
showCenterAlignment || configurations.showCenterAlignment ||
showRightAlignment || configurations.showRightAlignment ||
showJustifyAlignment || configurations.showJustifyAlignment ||
showDirection, configurations.showDirection,
showHeaderStyle, configurations.showHeaderStyle,
showListNumbers || showListBullets || showListCheck || showCodeBlock, configurations.showListNumbers ||
showQuote || showIndent, configurations.showListBullets ||
showLink || showSearchButton configurations.showListCheck ||
configurations.showCodeBlock,
configurations.showQuote || configurations.showIndent,
configurations.showLink || configurations.showSearchButton
]; ];
return QuillToolbar( return QuillToolbarProvider(
key: key, toolbarConfigurations: configurations,
color: color, child: QuillBaseToolbar(
decoration: decoration, configurations: QuillBaseToolbarConfigurations(
toolbarSectionSpacing: toolbarSectionSpacing, color: configurations.color,
toolbarIconAlignment: toolbarIconAlignment, decoration: configurations.decoration,
toolbarIconCrossAlignment: toolbarIconCrossAlignment, toolbarSectionSpacing: configurations.toolbarSectionSpacing,
customButtons: customButtons, toolbarIconAlignment: configurations.toolbarIconAlignment,
toolbarIconCrossAlignment: configurations.toolbarIconCrossAlignment,
customButtons: configurations.customButtons,
linkDialogAction: configurations.linkDialogAction,
multiRowsDisplay: configurations.multiRowsDisplay,
sectionDividerColor: configurations.sectionDividerColor,
axis: configurations.axis,
sectionDividerSpace: configurations.sectionDividerSpace,
toolbarSize: configurations.toolbarSize,
childrenBuilder: (context) { childrenBuilder: (context) {
final controller = context.requireQuillController; final controller = context.requireQuillController;
final toolbarConfigurations = context.requireQuillToolbarConfigurations; final toolbarConfigurations =
context.requireQuillToolbarConfigurations;
final globalIconSize = final globalIconSize =
toolbarConfigurations.buttonOptions.base.globalIconSize; toolbarConfigurations.buttonOptions.base.globalIconSize;
final axis = toolbarConfigurations.axis; final axis = toolbarConfigurations.axis;
final globalController = context.requireQuillController;
if (tooltips != null) {
throw UnsupportedError(
'This is deprecated and will no longer used. to change '
'the tooltips please pass them in the Quill toolbar button'
'configurations which exists in in the QuillProvider',
);
}
return [ return [
if (showUndo) if (configurations.showUndo)
QuillToolbarHistoryButton( QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.undoHistory, options: toolbarConfigurations.buttonOptions.undoHistory,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.undoHistory.controller ?? .buttonOptions.undoHistory.controller ??
context.requireQuillController, globalController,
), ),
if (showRedo) if (configurations.showRedo)
QuillToolbarHistoryButton( QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.redoHistory, options: toolbarConfigurations.buttonOptions.redoHistory,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.redoHistory.controller ?? .buttonOptions.redoHistory.controller ??
context.requireQuillController, globalController,
), ),
if (showFontFamily) if (configurations.showFontFamily)
QuillToolbarFontFamilyButton( QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily, options: toolbarConfigurations.buttonOptions.fontFamily,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.fontFamily.controller ?? .buttonOptions.fontFamily.controller ??
context.requireQuillController, globalController,
), ),
if (showFontSize) if (configurations.showFontSize)
QuillToolbarFontSizeButton( QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize, options: toolbarConfigurations.buttonOptions.fontSize,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.fontFamily.controller ?? .buttonOptions.fontFamily.controller ??
context.requireQuillController, globalController,
), ),
if (showBoldButton) if (configurations.showBoldButton)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.bold, attribute: Attribute.bold,
options: toolbarConfigurations.buttonOptions.bold, options: toolbarConfigurations.buttonOptions.bold,
controller: toolbarConfigurations.buttonOptions.bold.controller ?? controller:
context.requireQuillController, toolbarConfigurations.buttonOptions.bold.controller ??
globalController,
), ),
if (showSubscript) if (configurations.showSubscript)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.subscript, attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript, options: toolbarConfigurations.buttonOptions.subscript,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.subscript.controller ?? .buttonOptions.subscript.controller ??
context.requireQuillController, globalController,
), ),
if (showSuperscript) if (configurations.showSuperscript)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.superscript, attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript, options: toolbarConfigurations.buttonOptions.superscript,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.superscript.controller ?? .buttonOptions.superscript.controller ??
context.requireQuillController, globalController,
), ),
if (showItalicButton) if (configurations.showItalicButton)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.italic, attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic, options: toolbarConfigurations.buttonOptions.italic,
controller: controller:
toolbarConfigurations.buttonOptions.italic.controller ?? toolbarConfigurations.buttonOptions.italic.controller ??
context.requireQuillController, globalController,
), ),
if (showSmallButton) if (configurations.showSmallButton)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.small, attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small, options: toolbarConfigurations.buttonOptions.small,
controller: controller:
toolbarConfigurations.buttonOptions.small.controller ?? toolbarConfigurations.buttonOptions.small.controller ??
context.requireQuillController, globalController,
), ),
if (showUnderLineButton) if (configurations.showUnderLineButton)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.underline, attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine, options: toolbarConfigurations.buttonOptions.underLine,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.underLine.controller ?? .buttonOptions.underLine.controller ??
context.requireQuillController, globalController,
), ),
if (showStrikeThrough) if (configurations.showStrikeThrough)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough, attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough, options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: toolbarConfigurations controller: toolbarConfigurations
.buttonOptions.strikeThrough.controller ?? .buttonOptions.strikeThrough.controller ??
context.requireQuillController, globalController,
), ),
if (showInlineCode) if (configurations.showInlineCode)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode, attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode, options: toolbarConfigurations.buttonOptions.inlineCode,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.inlineCode.controller ?? .buttonOptions.inlineCode.controller ??
context.requireQuillController, globalController,
), ),
if (showColorButton) if (configurations.showColorButton)
QuillToolbarColorButton( QuillToolbarColorButton(
controller: controller, controller: controller,
isBackground: false, isBackground: false,
options: toolbarConfigurations.buttonOptions.color, options: toolbarConfigurations.buttonOptions.color,
), ),
if (showBackgroundColorButton) if (configurations.showBackgroundColorButton)
QuillToolbarColorButton( QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor, options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: controller, controller: controller,
isBackground: true, isBackground: true,
), ),
if (showClearFormat) if (configurations.showClearFormat)
QuillToolbarClearFormatButton( QuillToolbarClearFormatButton(
controller: controller, controller: controller,
options: toolbarConfigurations.buttonOptions.clearFormat, options: toolbarConfigurations.buttonOptions.clearFormat,
), ),
if (embedButtons != null) if (theEmbedButtons != null)
for (final builder in embedButtons) for (final builder in theEmbedButtons)
builder(controller, globalIconSize, iconTheme, dialogTheme), builder(controller, globalIconSize, configurations.iconTheme,
if (showDividers && configurations.dialogTheme),
if (configurations.showDividers &&
isButtonGroupShown[0] && isButtonGroupShown[0] &&
(isButtonGroupShown[1] || (isButtonGroupShown[1] ||
isButtonGroupShown[2] || isButtonGroupShown[2] ||
@ -296,14 +195,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[5])) isButtonGroupShown[5]))
QuillToolbarDivider( QuillToolbarDivider(
axis, axis,
color: sectionDividerColor, color: configurations.sectionDividerColor,
space: sectionDividerSpace, space: configurations.sectionDividerSpace,
), ),
if (showAlignmentButtons) if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButton( QuillToolbarSelectAlignmentButton(
controller: controller, controller: controller,
options: options: toolbarConfigurations
toolbarConfigurations.buttonOptions.selectAlignmentButtons, .buttonOptions.selectAlignmentButtons,
// tooltips: Map.of(buttonTooltips) // tooltips: Map.of(buttonTooltips)
// ..removeWhere((key, value) => ![ // ..removeWhere((key, value) => ![
// ToolbarButtons.leftAlignment, // ToolbarButtons.leftAlignment,
@ -311,20 +210,20 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
// ToolbarButtons.rightAlignment, // ToolbarButtons.rightAlignment,
// ToolbarButtons.justifyAlignment, // ToolbarButtons.justifyAlignment,
// ].contains(key)), // ].contains(key)),
showLeftAlignment: showLeftAlignment, showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: showCenterAlignment, showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: showRightAlignment, showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: showJustifyAlignment, showJustifyAlignment: configurations.showJustifyAlignment,
), ),
if (showDirection) if (configurations.showDirection)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.rtl, attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction, options: toolbarConfigurations.buttonOptions.direction,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.direction.controller ?? .buttonOptions.direction.controller ??
context.requireQuillController, context.requireQuillController,
), ),
if (showDividers && if (configurations.showDividers &&
isButtonGroupShown[1] && isButtonGroupShown[1] &&
(isButtonGroupShown[2] || (isButtonGroupShown[2] ||
isButtonGroupShown[3] || isButtonGroupShown[3] ||
@ -332,112 +231,115 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[5])) isButtonGroupShown[5]))
QuillToolbarDivider( QuillToolbarDivider(
axis, axis,
color: sectionDividerColor, color: configurations.sectionDividerColor,
space: sectionDividerSpace, space: configurations.sectionDividerSpace,
), ),
if (showHeaderStyle) if (configurations.showHeaderStyle)
QuillToolbarSelectHeaderStyleButtons( QuillToolbarSelectHeaderStyleButtons(
controller: controller, controller: controller,
options: options: toolbarConfigurations
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons, .buttonOptions.selectHeaderStyleButtons,
// tooltip: buttonTooltips[ToolbarButtons.headerStyle], ),
// axis: axis, if (configurations.showDividers &&
// iconSize: toolbarIconSize, configurations.showHeaderStyle &&
// iconTheme: iconTheme,
// afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
showHeaderStyle &&
isButtonGroupShown[2] && isButtonGroupShown[2] &&
(isButtonGroupShown[3] || (isButtonGroupShown[3] ||
isButtonGroupShown[4] || isButtonGroupShown[4] ||
isButtonGroupShown[5])) isButtonGroupShown[5]))
QuillToolbarDivider( QuillToolbarDivider(
axis, axis,
color: sectionDividerColor, color: configurations.sectionDividerColor,
space: sectionDividerSpace, space: configurations.sectionDividerSpace,
), ),
if (showListNumbers) if (configurations.showListNumbers)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.ol, attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers, options: toolbarConfigurations.buttonOptions.listNumbers,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.listNumbers.controller ?? .buttonOptions.listNumbers.controller ??
context.requireQuillController, globalController,
), ),
if (showListBullets) if (configurations.showListBullets)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.ul, attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets, options: toolbarConfigurations.buttonOptions.listBullets,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.listBullets.controller ?? .buttonOptions.listBullets.controller ??
context.requireQuillController, globalController,
), ),
if (showListCheck) if (configurations.showListCheck)
QuillToolbarToggleCheckListButton( QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList, options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: toolbarConfigurations controller: toolbarConfigurations
.buttonOptions.toggleCheckList.controller ?? .buttonOptions.toggleCheckList.controller ??
context.requireQuillController, globalController,
), ),
if (showCodeBlock) if (configurations.showCodeBlock)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock, attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock, options: toolbarConfigurations.buttonOptions.codeBlock,
controller: controller: toolbarConfigurations
toolbarConfigurations.buttonOptions.codeBlock.controller ?? .buttonOptions.codeBlock.controller ??
context.requireQuillController, globalController,
), ),
if (showDividers && if (configurations.showDividers &&
isButtonGroupShown[3] && isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5])) (isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillToolbarDivider(axis, QuillToolbarDivider(
color: sectionDividerColor, space: sectionDividerSpace), axis,
if (showQuote) color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showQuote)
QuillToolbarToggleStyleButton( QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote, options: toolbarConfigurations.buttonOptions.quote,
controller: controller:
toolbarConfigurations.buttonOptions.quote.controller ?? toolbarConfigurations.buttonOptions.quote.controller ??
context.requireQuillController, globalController,
attribute: Attribute.blockQuote, attribute: Attribute.blockQuote,
), ),
if (showIndent) if (configurations.showIndent)
QuillToolbarIndentButton( QuillToolbarIndentButton(
controller: toolbarConfigurations controller: toolbarConfigurations
.buttonOptions.indentIncrease.controller ?? .buttonOptions.indentIncrease.controller ??
context.requireQuillController, globalController,
isIncrease: true, isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease, options: toolbarConfigurations.buttonOptions.indentIncrease,
), ),
if (showIndent) if (configurations.showIndent)
QuillToolbarIndentButton( QuillToolbarIndentButton(
controller: toolbarConfigurations controller: toolbarConfigurations
.buttonOptions.indentDecrease.controller ?? .buttonOptions.indentDecrease.controller ??
context.requireQuillController, globalController,
isIncrease: false, isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease, options: toolbarConfigurations.buttonOptions.indentDecrease,
), ),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) if (configurations.showDividers &&
QuillToolbarDivider(axis, isButtonGroupShown[4] &&
color: sectionDividerColor, space: sectionDividerSpace), isButtonGroupShown[5])
if (showLink) QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showLink)
QuillToolbarLinkStyleButton( QuillToolbarLinkStyleButton(
controller: controller, controller: controller,
options: toolbarConfigurations.buttonOptions.linkStyle, options: toolbarConfigurations.buttonOptions.linkStyle,
), ),
if (showSearchButton) if (configurations.showSearchButton)
QuillToolbarSearchButton( QuillToolbarSearchButton(
controller: controller, controller: controller,
options: toolbarConfigurations.buttonOptions.search, options: toolbarConfigurations.buttonOptions.search,
), ),
if (customButtons.isNotEmpty) if (configurations.customButtons.isNotEmpty)
if (showDividers) if (configurations.showDividers)
QuillToolbarDivider( QuillToolbarDivider(
axis, axis,
color: sectionDividerColor, color: configurations.sectionDividerColor,
space: sectionDividerSpace, space: configurations.sectionDividerSpace,
), ),
for (final customButton in customButtons) for (final customButton in configurations.customButtons)
if (customButton.child != null) ...[ if (customButton.child != null) ...[
InkWell( InkWell(
onTap: customButton.onTap, onTap: customButton.onTap,
@ -450,135 +352,19 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
context.quillToolbarBaseButtonOptions?.iconData, context.quillToolbarBaseButtonOptions?.iconData,
iconColor: customButton.iconColor, iconColor: customButton.iconColor,
iconSize: customButton.iconSize ?? globalIconSize, iconSize: customButton.iconSize ?? globalIconSize,
iconTheme: iconTheme ?? iconTheme: configurations.iconTheme ??
context.quillToolbarBaseButtonOptions?.iconTheme, context.quillToolbarBaseButtonOptions?.iconTheme,
afterButtonPressed: customButton.afterButtonPressed ?? afterButtonPressed: customButton.afterButtonPressed ??
context.quillToolbarBaseButtonOptions?.afterButtonPressed, context
.quillToolbarBaseButtonOptions?.afterButtonPressed,
tooltip: customButton.tooltip ?? tooltip: customButton.tooltip ??
context.quillToolbarBaseButtonOptions?.tooltip, context.quillToolbarBaseButtonOptions?.tooltip,
), ),
], ],
]; ];
}, },
);
}
final QuillToolbarChildrenBuilder childrenBuilder;
final Axis axis;
final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
// Overrides the action in the _LinkDialog widget
final LinkDialogAction? linkDialogAction;
/// The color of the toolbar.
///
/// Defaults to [ThemeData.canvasColor] of the current [Theme] if no color
/// is given.
final Color? color;
/// List of custom buttons
final List<QuillCustomButton> customButtons;
/// The color to use when painting the toolbar section divider.
///
/// If this is null, then the [DividerThemeData.color] is used. If that is
/// also null, then [ThemeData.dividerColor] is used.
final Color? sectionDividerColor;
/// The space occupied by toolbar section divider.
final double? sectionDividerSpace;
/// The decoration to use for the toolbar.
final Decoration? decoration;
// We can't get the modified [toolbarSize] by the developer
// but I tested the [QuillToolbar] on the [appBar] and I didn't notice
// a difference no matter what the value is so I will leave it to the
// default
@override
Size get preferredSize => axis == Axis.horizontal
? const Size.fromHeight(defaultToolbarSize)
: const Size.fromWidth(defaultToolbarSize);
@override
Widget build(BuildContext context) {
final toolbarConfigurations = context.requireQuillToolbarConfigurations;
final toolbarSize = toolbarConfigurations.toolbarSize;
return I18n(
initialLocale: context.quillSharedConfigurations?.locale,
child: (toolbarConfigurations.multiRowsDisplay)
? Wrap(
direction: axis,
alignment: toolbarIconAlignment,
crossAxisAlignment: toolbarIconCrossAlignment,
runSpacing: 4,
spacing: toolbarSectionSpacing,
children: childrenBuilder(context),
)
: Container(
decoration: decoration ??
BoxDecoration(
color: color ?? Theme.of(context).canvasColor,
),
constraints: BoxConstraints.tightFor(
height: axis == Axis.horizontal ? toolbarSize : null,
width: axis == Axis.vertical ? toolbarSize : null,
),
child: QuillToolbarArrowIndicatedButtonList(
axis: axis,
buttons: childrenBuilder(context),
), ),
), ),
); );
} }
} }
/// The divider which is used for separation of buttons in the toolbar.
///
/// It can be used outside of this package, for example when user does not use
/// [QuillToolbar.basic] and compose toolbar's children on its own.
class QuillToolbarDivider extends StatelessWidget {
const QuillToolbarDivider(
this.axis, {
super.key,
this.color,
this.space,
});
/// Provides a horizontal divider for vertical toolbar.
const QuillToolbarDivider.horizontal({Color? color, double? space})
: this(Axis.horizontal, color: color, space: space);
/// Provides a horizontal divider for horizontal toolbar.
const QuillToolbarDivider.vertical({Color? color, double? space})
: this(Axis.vertical, color: color, space: space);
/// The axis along which the toolbar is.
final Axis axis;
/// The color to use when painting this divider's line.
final Color? color;
/// The divider's space (width or height) depending of [axis].
final double? space;
@override
Widget build(BuildContext context) {
// Vertical toolbar requires horizontal divider, and vice versa
return axis == Axis.vertical
? Divider(
height: space,
color: color,
indent: 12,
endIndent: 12,
)
: VerticalDivider(
width: space,
color: color,
indent: 12,
endIndent: 12,
);
}
}

@ -61,3 +61,115 @@ class QuillProvider extends InheritedWidget {
); );
} }
} }
class QuillToolbarProvider extends InheritedWidget {
const QuillToolbarProvider({
required super.child,
required this.toolbarConfigurations,
});
/// The configurations for the toolbar widget of flutter quill
final QuillToolbarConfigurations toolbarConfigurations;
@override
bool updateShouldNotify(covariant QuillToolbarProvider oldWidget) {
return oldWidget.toolbarConfigurations != toolbarConfigurations;
}
static QuillToolbarProvider? of(BuildContext context) {
/// The configurations for the quill editor widget of flutter quill
return context.dependOnInheritedWidgetOfExactType<QuillToolbarProvider>();
}
static QuillToolbarProvider ofNotNull(BuildContext context) {
final provider = of(context);
if (provider == null) {
if (kDebugMode) {
debugPrint(
'The quill toolbar provider must be provided in the widget tree.',
);
}
throw ArgumentError.checkNotNull(
'You are using a widget in the Flutter quill library that require '
'The Quill toolbar provider widget to be in the parent widget tree '
'because '
'The provider is $provider. Please make sure to wrap this widget'
' with'
' QuillProvider widget. '
'You might using QuillToolbar so make sure to'
' wrap them with the quill provider widget and setup the required '
'configurations',
'QuillProvider',
);
}
return provider;
}
/// To pass the [QuillToolbarProvider] instance as value instead of creating
/// new widget
static QuillToolbarProvider value({
required QuillToolbarProvider value,
required Widget child,
}) {
return QuillToolbarProvider(
toolbarConfigurations: value.toolbarConfigurations,
child: child,
);
}
}
class QuillEditorProvider extends InheritedWidget {
const QuillEditorProvider({
required super.child,
required this.editorConfigurations,
});
/// The configurations for the quill editor widget of flutter quill
final QuillEditorConfigurations editorConfigurations;
@override
bool updateShouldNotify(covariant QuillEditorProvider oldWidget) {
return oldWidget.editorConfigurations != editorConfigurations;
}
static QuillEditorProvider? of(BuildContext context) {
/// The configurations for the quill editor widget of flutter quill
return context.dependOnInheritedWidgetOfExactType<QuillEditorProvider>();
}
static QuillEditorProvider ofNotNull(BuildContext context) {
final provider = of(context);
if (provider == null) {
if (kDebugMode) {
debugPrint(
'The quill editor provider must be provided in the widget tree.',
);
}
throw ArgumentError.checkNotNull(
'You are using a widget in the Flutter quill library that require '
'The Quill editor provider widget to be in the parent widget tree '
'because '
'The provider is $provider. Please make sure to wrap this widget'
' with'
' QuillProvider widget. '
'You might using QuillEditor so make sure to'
' wrap them with the quill provider widget and setup the required '
'configurations',
'QuillProvider',
);
}
return provider;
}
/// To pass the [QuillEditorProvider] instance as value instead of creating
/// new widget
static QuillEditorProvider value({
required QuillEditorProvider value,
required Widget child,
}) {
return QuillEditorProvider(
editorConfigurations: value.editorConfigurations,
child: child,
);
}
}

@ -18,14 +18,16 @@ void main() {
configurations: QuillConfigurations( configurations: QuillConfigurations(
controller: QuillController.basic(), controller: QuillController.basic(),
), ),
child: QuillToolbar.basic( child: const QuillToolbar(
configurations: QuillToolbarConfigurations(
showRedo: false, showRedo: false,
customButtons: [ customButtons: [
const QuillCustomButton(tooltip: tooltip), QuillCustomButton(tooltip: tooltip),
], ],
), ),
), ),
), ),
),
); );
final builtinFinder = find.descendant( final builtinFinder = find.descendant(
@ -38,7 +40,7 @@ void main() {
builtinFinder.evaluate().first.widget as QuillToolbarIconButton; builtinFinder.evaluate().first.widget as QuillToolbarIconButton;
final customFinder = find.descendant( final customFinder = find.descendant(
of: find.byType(QuillToolbar), of: find.byType(QuillBaseToolbar),
matching: find.byWidgetPredicate((widget) => matching: find.byWidgetPredicate((widget) =>
widget is QuillToolbarIconButton && widget.tooltip == tooltip), widget is QuillToolbarIconButton && widget.tooltip == tooltip),
matchRoot: true); matchRoot: true);
@ -57,7 +59,11 @@ void main() {
setUp(() { setUp(() {
controller = QuillController.basic(); controller = QuillController.basic();
editor = QuillEditor.basic( editor = QuillEditor.basic(
// ignore: avoid_redundant_argument_values
configurations: const QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false, readOnly: false,
),
); );
}); });

@ -25,7 +25,13 @@ void main() {
QuillProvider( QuillProvider(
configurations: QuillConfigurations(controller: controller), configurations: QuillConfigurations(controller: controller),
child: MaterialApp( child: MaterialApp(
home: QuillEditor.basic(readOnly: false), home: QuillEditor.basic(
// ignore: avoid_redundant_argument_values
configurations: const QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
),
),
), ),
), ),
); );
@ -41,24 +47,21 @@ void main() {
home: QuillProvider( home: QuillProvider(
configurations: QuillConfigurations( configurations: QuillConfigurations(
controller: controller, controller: controller,
// ignore: avoid_redundant_argument_values
editorConfigurations: const QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
),
), ),
child: QuillEditor( child: QuillEditor(
focusNode: FocusNode(), focusNode: FocusNode(),
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, configurations: QuillEditorConfigurations(
padding: const EdgeInsets.all(0), // ignore: avoid_redundant_argument_values
readOnly: false,
autoFocus: true, autoFocus: true,
expands: true, expands: true,
contentInsertionConfiguration: ContentInsertionConfiguration( contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (content) { onContentInserted: (content) {
latestUri = content.uri; latestUri = content.uri;
}, },
allowedMimeTypes: const <String>['image/gif'], allowedMimeTypes: <String>['image/gif'],
),
), ),
), ),
), ),
@ -121,23 +124,21 @@ void main() {
home: QuillProvider( home: QuillProvider(
configurations: QuillConfigurations( configurations: QuillConfigurations(
controller: controller, controller: controller,
// ignore: avoid_redundant_argument_values
editorConfigurations: const QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
),
), ),
child: QuillEditor( child: QuillEditor(
focusNode: FocusNode(), focusNode: FocusNode(),
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, // ignore: avoid_redundant_argument_values
padding: EdgeInsets.zero, configurations: QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
autoFocus: true, autoFocus: true,
expands: true, expands: true,
contextMenuBuilder: customBuilder, contextMenuBuilder: customBuilder,
), ),
), ),
), ),
),
); );
// Long press to show menu // Long press to show menu

Loading…
Cancel
Save