Major update 6 (#1461)

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

@ -1,3 +1,10 @@
## [7.10.0]
- **Breaking change**: `QuillToolbar.basic()` can be accessed from `QuillToolbar()` directly and the old `QuillToolbar` can be accessed from `QuillBaseToolbar`
- The Quill editor and toolbar configurations are now refactored in one single class for each one
- After changing one of the checkbox list values the controller will not request the keyboard focus by default
- We have moved the configurations of the toolbar and the editor directly into the widget but we are still using inherited widgets internally
- Fixes to some of the code after the refactoring
## [7.9.0]
- Buttons Improvemenets
- Refactor all the button configurations that used in `QuillToolbar.basic()` but there are still few lefts

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

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

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

@ -26,6 +26,7 @@ export 'src/widgets/editor/editor.dart';
export 'src/widgets/embeds.dart';
export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
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/toolbar.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: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
@immutable
class QuillEditorConfigurations extends Equatable {
/// Important note for the maintainers
/// When editing this class please update the [copyWith] function too.
const QuillEditorConfigurations({
this.scrollable = true,
this.padding = EdgeInsets.zero,
this.autoFocus = false,
this.expands = false,
this.placeholder,
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
@ -20,9 +75,315 @@ class QuillEditorConfigurations extends Equatable {
/// Defaults to `false`. Must not be `null`.
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
List<Object?> get props => [
placeholder,
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 {
const QuillConfigurations({
required this.controller,
this.editorConfigurations = const QuillEditorConfigurations(),
this.toolbarConfigurations = const QuillToolbarConfigurations(),
this.sharedConfigurations = const QuillSharedConfigurations(),
});
@ -24,20 +22,12 @@ class QuillConfigurations extends Equatable {
/// here, it should not be null
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
/// [QuillToolbarConfigurations] so we don't duplicate things
final QuillSharedConfigurations sharedConfigurations;
@override
List<Object?> get props => [
editorConfigurations,
toolbarConfigurations,
sharedConfigurations,
];
}

@ -39,6 +39,7 @@ class QuillSharedConfigurations extends Equatable {
@override
List<Object?> get props => [
dialogBarrierColor,
dialogTheme,
locale,
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 '../../../../widgets/toolbar/toolbar.dart';
import '../../../../widgets/toolbar/base_toolbar.dart';
import '../../../structs/link_dialog_action.dart';
import '../../../themes/quill_dialog_theme.dart';

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

@ -1,7 +1,13 @@
import 'package:equatable/equatable.dart';
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/clear_format.dart';
import 'buttons/color.dart';
@ -47,11 +53,60 @@ const double kToolbarSectionSpacing = 4;
@immutable
class QuillToolbarConfigurations extends Equatable {
const QuillToolbarConfigurations({
this.toolbarSectionSpacing = kToolbarSectionSpacing,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.buttonOptions = const QuillToolbarButtonOptions(),
this.multiRowsDisplay = true,
this.fontFamilyValues,
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.color,
this.sectionDividerColor,
this.sectionDividerSpace,
/// By default it will calculated based on the [globalIconSize] from
/// [base] in [QuillToolbarButtonOptions]
@ -109,6 +164,67 @@ class QuillToolbarConfigurations extends Equatable {
/// we will update that logic soon
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
List<Object?> get props => [
buttonOptions,

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

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

@ -41,6 +41,18 @@ bool isMacOS([TargetPlatform? 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() {
if (isWeb()) return false;
return Platform.environment.containsKey('FLUTTER_TEST');

@ -1,7 +1,9 @@
import 'dart:math' as math;
// 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/foundation.dart';
@ -19,7 +21,6 @@ import '../box.dart';
import '../cursor.dart';
import '../delegate.dart';
import '../float_cursor.dart';
import '../link.dart';
import '../raw_editor/raw_editor.dart';
import '../text_selection.dart';
@ -143,53 +144,16 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics {
class QuillEditor extends StatefulWidget {
const QuillEditor({
required this.configurations,
required this.focusNode,
required this.scrollController,
required this.scrollable,
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);
super.key,
});
factory QuillEditor.basic({
required bool readOnly,
/// The configurations for the quill editor widget of flutter quill
QuillEditorConfigurations configurations =
const QuillEditorConfigurations(),
TextSelectionThemeData? textSelectionThemeData,
Brightness? keyboardAppearance,
Iterable<EmbedBuilder>? embedBuilders,
@ -197,13 +161,12 @@ class QuillEditor extends StatefulWidget {
bool autoFocus = true,
bool expands = false,
FocusNode? focusNode,
String? placeholder,
GlobalKey<EditorState>? editorKey,
}) {
return QuillEditor(
scrollController: ScrollController(),
scrollable: true,
focusNode: focusNode ?? FocusNode(),
configurations: configurations.copyWith(
textSelectionThemeData: textSelectionThemeData,
autoFocus: autoFocus,
expands: expands,
@ -211,10 +174,12 @@ class QuillEditor extends StatefulWidget {
keyboardAppearance: keyboardAppearance ?? Brightness.light,
embedBuilders: embedBuilders,
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.
final FocusNode focusNode;
@ -222,214 +187,6 @@ class QuillEditor extends StatefulWidget {
/// The [ScrollController] to use when vertically scrolling the contents.
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
QuillEditorState createState() => QuillEditorState();
}
@ -440,20 +197,26 @@ class QuillEditorState extends State<QuillEditor>
late EditorTextSelectionGestureDetectorBuilder
_selectionGestureDetectorBuilder;
QuillEditorConfigurations get configurations {
return widget.configurations;
}
@override
void initState() {
super.initState();
_editorKey = widget.editorKey ?? GlobalKey<EditorState>();
_editorKey = configurations.editorKey ?? GlobalKey<EditorState>();
_selectionGestureDetectorBuilder =
_QuillEditorSelectionGestureDetectorBuilder(
this, widget.detectWordBoundary);
this,
configurations.detectWordBoundary,
);
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final selectionTheme =
widget.textSelectionThemeData ?? TextSelectionTheme.of(context);
configurations.textSelectionThemeData ?? TextSelectionTheme.of(context);
TextSelectionControls textSelectionControls;
bool paintCursorAboveText;
@ -483,61 +246,65 @@ class QuillEditorState extends State<QuillEditor>
theme.colorScheme.primary.withOpacity(0.40);
}
final showSelectionToolbar =
widget.enableInteractiveSelection && widget.enableSelectionToolbar;
final showSelectionToolbar = configurations.enableInteractiveSelection &&
configurations.enableSelectionToolbar;
final editorConfigurations =
context.requireQuillConfigurations.editorConfigurations;
final child = RawEditor(
final child = QuillEditorProvider(
editorConfigurations: configurations,
child: RawEditor(
key: _editorKey,
controller: context.requireQuillController,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: widget.scrollable,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
readOnly: editorConfigurations.readOnly,
placeholder: editorConfigurations.placeholder,
onLaunchUrl: widget.onLaunchUrl,
scrollable: configurations.scrollable,
scrollBottomInset: configurations.scrollBottomInset,
padding: configurations.padding,
readOnly: configurations.readOnly,
placeholder: configurations.placeholder,
onLaunchUrl: configurations.onLaunchUrl,
contextMenuBuilder: showSelectionToolbar
? (widget.contextMenuBuilder ?? RawEditor.defaultContextMenuBuilder)
? (configurations.contextMenuBuilder ??
RawEditor.defaultContextMenuBuilder)
: null,
showSelectionHandles: isMobile(theme.platform),
showCursor: widget.showCursor,
showCursor: configurations.showCursor,
cursorStyle: CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
textCapitalization: widget.textCapitalization,
minHeight: widget.minHeight,
maxHeight: widget.maxHeight,
maxContentWidth: widget.maxContentWidth,
customStyles: widget.customStyles,
expands: widget.expands,
autoFocus: widget.autoFocus,
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls: widget.textSelectionControls ?? textSelectionControls,
keyboardAppearance: widget.keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
scrollPhysics: widget.scrollPhysics,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: widget.linkActionPickerDelegate,
customStyleBuilder: widget.customStyleBuilder,
customRecognizerBuilder: widget.customRecognizerBuilder,
floatingCursorDisabled: widget.floatingCursorDisabled,
onImagePaste: widget.onImagePaste,
customShortcuts: widget.customShortcuts,
customActions: widget.customActions,
customLinkPrefixes: widget.customLinkPrefixes,
enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside,
dialogTheme: widget.dialogTheme,
contentInsertionConfiguration: widget.contentInsertionConfiguration,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: configurations.enableUnfocusOnTapOutside,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
),
);
final editor = I18n(
@ -545,7 +312,7 @@ class QuillEditorState extends State<QuillEditor>
child: selectionEnabled
? _selectionGestureDetectorBuilder.build(
behavior: HitTestBehavior.translucent,
detectWordBoundary: widget.detectWordBoundary,
detectWordBoundary: configurations.detectWordBoundary,
child: child,
)
: child,
@ -569,7 +336,7 @@ class QuillEditorState extends State<QuillEditor>
}
EmbedBuilder _getEmbedBuilder(Embed node) {
final builders = widget.embedBuilders;
final builders = configurations.embedBuilders;
if (builders != null) {
for (final builder in builders) {
@ -579,8 +346,9 @@ class QuillEditorState extends State<QuillEditor>
}
}
if (widget.unknownEmbedBuilder != null) {
return widget.unknownEmbedBuilder!;
final unknownEmbedBuilder = configurations.unknownEmbedBuilder;
if (unknownEmbedBuilder != null) {
return unknownEmbedBuilder;
}
throw UnimplementedError(
@ -598,7 +366,7 @@ class QuillEditorState extends State<QuillEditor>
bool get forcePressEnabled => false;
@override
bool get selectionEnabled => widget.enableInteractiveSelection;
bool get selectionEnabled => configurations.enableInteractiveSelection;
void _requestKeyboard() {
final editorCurrentState = _editorKey.currentState;
@ -633,10 +401,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleLongTapMoveUpdate(LongPressMoveUpdateDetails details) {
if (_state.widget.onSingleLongTapMoveUpdate != null) {
if (_state.configurations.onSingleLongTapMoveUpdate != null) {
if (renderEditor != null &&
_state.widget.onSingleLongTapMoveUpdate!(
details, renderEditor!.getPositionForOffset)) {
_state.configurations.onSingleLongTapMoveUpdate!(
details,
renderEditor!.getPositionForOffset,
)) {
return;
}
}
@ -683,10 +453,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onTapDown(TapDownDetails details) {
if (_state.widget.onTapDown != null) {
if (_state.configurations.onTapDown != null) {
if (renderEditor != null &&
_state.widget.onTapDown!(
details, renderEditor!.getPositionForOffset)) {
_state.configurations.onTapDown!(
details,
renderEditor!.getPositionForOffset,
)) {
return;
}
}
@ -702,9 +474,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleTapUp(TapUpDetails details) {
if (_state.widget.onTapUp != null &&
if (_state.configurations.onTapUp != null &&
renderEditor != null &&
_state.widget.onTapUp!(details, renderEditor!.getPositionForOffset)) {
_state.configurations.onTapUp!(
details,
renderEditor!.getPositionForOffset,
)) {
return;
}
@ -765,10 +540,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleLongTapStart(LongPressStartDetails details) {
if (_state.widget.onSingleLongTapStart != null) {
if (_state.configurations.onSingleLongTapStart != null) {
if (renderEditor != null &&
_state.widget.onSingleLongTapStart!(
details, renderEditor!.getPositionForOffset)) {
_state.configurations.onSingleLongTapStart!(
details,
renderEditor!.getPositionForOffset,
)) {
return;
}
}
@ -789,10 +566,12 @@ class _QuillEditorSelectionGestureDetectorBuilder
@override
void onSingleLongTapEnd(LongPressEndDetails details) {
if (_state.widget.onSingleLongTapEnd != null) {
if (_state.configurations.onSingleLongTapEnd != null) {
if (renderEditor != null) {
if (_state.widget.onSingleLongTapEnd!(
details, renderEditor!.getPositionForOffset)) {
if (_state.configurations.onSingleLongTapEnd!(
details,
renderEditor!.getPositionForOffset,
)) {
return;
}
@ -1447,6 +1226,28 @@ class RenderEditor extends RenderEditableContainerBox
@override
Rect getLocalRectForCaret(TextPosition 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 childLocalRect = targetChild.getLocalRectForCaret(localPosition);
@ -1652,8 +1453,9 @@ class RenderEditor extends RenderEditableContainerBox
@override
TextPosition getTextPositionBelow(TextPosition position) {
final child = childAtPosition(position);
final localPosition =
TextPosition(offset: position.offset - child.container.documentOffset);
final localPosition = TextPosition(
offset: position.offset - child.container.documentOffset,
);
var newPosition = child.getPositionBelow(localPosition);
@ -1672,11 +1474,13 @@ class RenderEditor extends RenderEditableContainerBox
final finalOffset = Offset(caretOffset.dx, testOffset.dy);
final siblingPosition = sibling.getPositionForOffset(finalOffset);
newPosition = TextPosition(
offset: sibling.container.documentOffset + siblingPosition.offset);
offset: sibling.container.documentOffset + siblingPosition.offset,
);
}
} else {
newPosition = TextPosition(
offset: child.container.documentOffset + newPosition.offset);
offset: child.container.documentOffset + newPosition.offset,
);
}
return newPosition;
}

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

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

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

@ -52,8 +52,8 @@ const List<String> romanNumbers = [
];
class EditableTextBlock extends StatelessWidget {
const EditableTextBlock(
{required this.block,
const EditableTextBlock({
required this.block,
required this.controller,
required this.textDirection,
required this.scrollBottomInset,
@ -74,7 +74,8 @@ class EditableTextBlock extends StatelessWidget {
this.onLaunchUrl,
this.customStyleBuilder,
this.customLinkPrefixes = const <String>[],
Key? key});
super.key,
});
final Block block;
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 '../../../utils/extensions/build_context.dart';
import '../../controller.dart';
import '../toolbar.dart';
import '../base_toolbar.dart';
class QuillToolbarClearFormatButton extends StatelessWidget {
const QuillToolbarClearFormatButton({

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

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

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

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

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

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

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

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

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

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

@ -1,293 +1,192 @@
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 QuillToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
);
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
class QuillToolbar extends StatelessWidget {
const QuillToolbar({
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.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,
super.key,
this.configurations = const QuillToolbarConfigurations(),
});
/// The color of the toolbar
Color? color,
/// The configurations for the toolbar widget of flutter quill
final QuillToolbarConfigurations configurations;
/// The color of the toolbar section divider
Color? sectionDividerColor,
@override
Widget build(BuildContext context) {
final theEmbedButtons = configurations.embedButtons;
/// The space occupied by toolbar divider
double? sectionDividerSpace,
Key? key,
}) {
final isButtonGroupShown = [
showFontFamily ||
showFontSize ||
showBoldButton ||
showItalicButton ||
showSmallButton ||
showUnderLineButton ||
showStrikeThrough ||
showInlineCode ||
showColorButton ||
showBackgroundColorButton ||
showClearFormat ||
embedButtons?.isNotEmpty == true,
showLeftAlignment ||
showCenterAlignment ||
showRightAlignment ||
showJustifyAlignment ||
showDirection,
showHeaderStyle,
showListNumbers || showListBullets || showListCheck || showCodeBlock,
showQuote || showIndent,
showLink || showSearchButton
configurations.showFontFamily ||
configurations.showFontSize ||
configurations.showBoldButton ||
configurations.showItalicButton ||
configurations.showSmallButton ||
configurations.showUnderLineButton ||
configurations.showStrikeThrough ||
configurations.showInlineCode ||
configurations.showColorButton ||
configurations.showBackgroundColorButton ||
configurations.showClearFormat ||
theEmbedButtons?.isNotEmpty == true,
configurations.showLeftAlignment ||
configurations.showCenterAlignment ||
configurations.showRightAlignment ||
configurations.showJustifyAlignment ||
configurations.showDirection,
configurations.showHeaderStyle,
configurations.showListNumbers ||
configurations.showListBullets ||
configurations.showListCheck ||
configurations.showCodeBlock,
configurations.showQuote || configurations.showIndent,
configurations.showLink || configurations.showSearchButton
];
return QuillToolbar(
key: key,
color: color,
decoration: decoration,
toolbarSectionSpacing: toolbarSectionSpacing,
toolbarIconAlignment: toolbarIconAlignment,
toolbarIconCrossAlignment: toolbarIconCrossAlignment,
customButtons: customButtons,
return QuillToolbarProvider(
toolbarConfigurations: configurations,
child: QuillBaseToolbar(
configurations: QuillBaseToolbarConfigurations(
color: configurations.color,
decoration: configurations.decoration,
toolbarSectionSpacing: configurations.toolbarSectionSpacing,
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) {
final controller = context.requireQuillController;
final toolbarConfigurations = context.requireQuillToolbarConfigurations;
final toolbarConfigurations =
context.requireQuillToolbarConfigurations;
final globalIconSize =
toolbarConfigurations.buttonOptions.base.globalIconSize;
final axis = toolbarConfigurations.axis;
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',
);
}
final globalController = context.requireQuillController;
return [
if (showUndo)
if (configurations.showUndo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.undoHistory,
controller:
toolbarConfigurations.buttonOptions.undoHistory.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.undoHistory.controller ??
globalController,
),
if (showRedo)
if (configurations.showRedo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.redoHistory,
controller:
toolbarConfigurations.buttonOptions.redoHistory.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.redoHistory.controller ??
globalController,
),
if (showFontFamily)
if (configurations.showFontFamily)
QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily,
controller:
toolbarConfigurations.buttonOptions.fontFamily.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.fontFamily.controller ??
globalController,
),
if (showFontSize)
if (configurations.showFontSize)
QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize,
controller:
toolbarConfigurations.buttonOptions.fontFamily.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.fontFamily.controller ??
globalController,
),
if (showBoldButton)
if (configurations.showBoldButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
options: toolbarConfigurations.buttonOptions.bold,
controller: toolbarConfigurations.buttonOptions.bold.controller ??
context.requireQuillController,
controller:
toolbarConfigurations.buttonOptions.bold.controller ??
globalController,
),
if (showSubscript)
if (configurations.showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller:
toolbarConfigurations.buttonOptions.subscript.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.subscript.controller ??
globalController,
),
if (showSuperscript)
if (configurations.showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller:
toolbarConfigurations.buttonOptions.superscript.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.superscript.controller ??
globalController,
),
if (showItalicButton)
if (configurations.showItalicButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic,
controller:
toolbarConfigurations.buttonOptions.italic.controller ??
context.requireQuillController,
globalController,
),
if (showSmallButton)
if (configurations.showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller:
toolbarConfigurations.buttonOptions.small.controller ??
context.requireQuillController,
globalController,
),
if (showUnderLineButton)
if (configurations.showUnderLineButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine,
controller:
toolbarConfigurations.buttonOptions.underLine.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.underLine.controller ??
globalController,
),
if (showStrikeThrough)
if (configurations.showStrikeThrough)
QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: toolbarConfigurations
.buttonOptions.strikeThrough.controller ??
context.requireQuillController,
globalController,
),
if (showInlineCode)
if (configurations.showInlineCode)
QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode,
controller:
toolbarConfigurations.buttonOptions.inlineCode.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.inlineCode.controller ??
globalController,
),
if (showColorButton)
if (configurations.showColorButton)
QuillToolbarColorButton(
controller: controller,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (showBackgroundColorButton)
if (configurations.showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: controller,
isBackground: true,
),
if (showClearFormat)
if (configurations.showClearFormat)
QuillToolbarClearFormatButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (embedButtons != null)
for (final builder in embedButtons)
builder(controller, globalIconSize, iconTheme, dialogTheme),
if (showDividers &&
if (theEmbedButtons != null)
for (final builder in theEmbedButtons)
builder(controller, globalIconSize, configurations.iconTheme,
configurations.dialogTheme),
if (configurations.showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
@ -296,14 +195,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (showAlignmentButtons)
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButton(
controller: controller,
options:
toolbarConfigurations.buttonOptions.selectAlignmentButtons,
options: toolbarConfigurations
.buttonOptions.selectAlignmentButtons,
// tooltips: Map.of(buttonTooltips)
// ..removeWhere((key, value) => ![
// ToolbarButtons.leftAlignment,
@ -311,20 +210,20 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
// ToolbarButtons.rightAlignment,
// ToolbarButtons.justifyAlignment,
// ].contains(key)),
showLeftAlignment: showLeftAlignment,
showCenterAlignment: showCenterAlignment,
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: configurations.showJustifyAlignment,
),
if (showDirection)
if (configurations.showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller:
toolbarConfigurations.buttonOptions.direction.controller ??
controller: toolbarConfigurations
.buttonOptions.direction.controller ??
context.requireQuillController,
),
if (showDividers &&
if (configurations.showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
@ -332,112 +231,115 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (showHeaderStyle)
if (configurations.showHeaderStyle)
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options:
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
// tooltip: buttonTooltips[ToolbarButtons.headerStyle],
// axis: axis,
// iconSize: toolbarIconSize,
// iconTheme: iconTheme,
// afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
showHeaderStyle &&
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleButtons,
),
if (configurations.showDividers &&
configurations.showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (showListNumbers)
if (configurations.showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers,
controller:
toolbarConfigurations.buttonOptions.listNumbers.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.listNumbers.controller ??
globalController,
),
if (showListBullets)
if (configurations.showListBullets)
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets,
controller:
toolbarConfigurations.buttonOptions.listBullets.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.listBullets.controller ??
globalController,
),
if (showListCheck)
if (configurations.showListCheck)
QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: toolbarConfigurations
.buttonOptions.toggleCheckList.controller ??
context.requireQuillController,
globalController,
),
if (showCodeBlock)
if (configurations.showCodeBlock)
QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock,
controller:
toolbarConfigurations.buttonOptions.codeBlock.controller ??
context.requireQuillController,
controller: toolbarConfigurations
.buttonOptions.codeBlock.controller ??
globalController,
),
if (showDividers &&
if (configurations.showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillToolbarDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showQuote)
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showQuote)
QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote,
controller:
toolbarConfigurations.buttonOptions.quote.controller ??
context.requireQuillController,
globalController,
attribute: Attribute.blockQuote,
),
if (showIndent)
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentIncrease.controller ??
context.requireQuillController,
globalController,
isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease,
),
if (showIndent)
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentDecrease.controller ??
context.requireQuillController,
globalController,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
QuillToolbarDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showLink)
if (configurations.showDividers &&
isButtonGroupShown[4] &&
isButtonGroupShown[5])
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showLink)
QuillToolbarLinkStyleButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.linkStyle,
),
if (showSearchButton)
if (configurations.showSearchButton)
QuillToolbarSearchButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.search,
),
if (customButtons.isNotEmpty)
if (showDividers)
if (configurations.customButtons.isNotEmpty)
if (configurations.showDividers)
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
for (final customButton in customButtons)
for (final customButton in configurations.customButtons)
if (customButton.child != null) ...[
InkWell(
onTap: customButton.onTap,
@ -450,135 +352,19 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
context.quillToolbarBaseButtonOptions?.iconData,
iconColor: customButton.iconColor,
iconSize: customButton.iconSize ?? globalIconSize,
iconTheme: iconTheme ??
iconTheme: configurations.iconTheme ??
context.quillToolbarBaseButtonOptions?.iconTheme,
afterButtonPressed: customButton.afterButtonPressed ??
context.quillToolbarBaseButtonOptions?.afterButtonPressed,
context
.quillToolbarBaseButtonOptions?.afterButtonPressed,
tooltip: customButton.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,
);
}
}

@ -1,11 +1,10 @@
name: flutter_quill
description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter.
version: 7.9.0
version: 7.10.0
homepage: https://1o24bbs.com/c/bulletjournal/108
repository: https://github.com/singerdmx/flutter-quill
topics:
- ui
- effects
- widgets
- widget
- rich-text-editor

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

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

Loading…
Cancel
Save