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. 164
      example/lib/pages/home_page.dart
  3. 26
      example/lib/pages/read_only_page.dart
  4. 14
      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. 490
      lib/src/widgets/editor/editor.dart
  17. 8
      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. 47
      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. 920
      lib/src/widgets/toolbar/toolbar.dart
  34. 112
      lib/src/widgets/utils/provider.dart
  35. 3
      pubspec.yaml
  36. 20
      test/bug_fix_test.dart
  37. 51
      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,12 +188,47 @@ class _HomePageState extends State<HomePage> {
QuillEditor get quillEditor {
if (kIsWeb) {
return QuillEditor(
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
scrollController: ScrollController(),
configurations: QuillEditorConfigurations(
placeholder: 'Add content',
readOnly: false,
scrollable: true,
autoFocus: false,
expands: false,
padding: EdgeInsets.zero,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
),
embedBuilders: [
...defaultEmbedBuildersWeb,
TimeStampEmbedBuilderWidget()
],
),
);
}
return QuillEditor(
configurations: QuillEditorConfigurations(
placeholder: 'Add content',
readOnly: false,
autoFocus: false,
enableSelectionToolbar: isMobile(),
expands: false,
padding: EdgeInsets.zero,
onImagePaste: _onImagePaste,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
@ -207,87 +244,79 @@ class _HomePageState extends State<HomePage> {
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
subscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.subscripts()],
),
superscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.superscripts()],
),
),
embedBuilders: [
...defaultEmbedBuildersWeb,
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
],
);
}
return QuillEditor(
),
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
enableSelectionToolbar: isMobile(),
expands: false,
padding: EdgeInsets.zero,
onImagePaste: _onImagePaste,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
subscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.subscripts()],
),
superscript: TextStyle(
fontFamily: 'SF-UI-Display',
fontFeatures: [FontFeature.superscripts()],
),
),
embedBuilders: [
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
],
);
}
QuillToolbar get quillToolbar {
if (kIsWeb) {
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
),
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
showAlignmentButtons: true,
// afterButtonPressed: _focusNode.requestFocus,
);
}
if (_isDesktop()) {
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
),
showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
showAlignmentButtons: true,
// afterButtonPressed: _focusNode.requestFocus,
);
}
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
// provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url.
// same goes for videos.
onImagePickCallback: _onImagePickCallback,
onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
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.
// same goes for videos.
onImagePickCallback: _onImagePickCallback,
onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
),
showAlignmentButtons: true,
buttonOptions: QuillToolbarButtonOptions(
base: QuillToolbarBaseButtonOptions(
afterButtonPressed: _focusNode.requestFocus,
),
),
),
showAlignmentButtons: true,
// 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(
configurations: QuillEditorConfigurations(
expands: false,
padding: EdgeInsets.zero,
embedBuilders: FlutterQuillEmbeds.builders(),
scrollable: true,
autoFocus: true,
),
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
// readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: FlutterQuillEmbeds.builders(),
);
if (kIsWeb) {
quillEditor = QuillEditor(
configurations: QuillEditorConfigurations(
autoFocus: true,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb,
scrollable: true,
),
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb,
);
}
return Padding(

@ -86,14 +86,18 @@ class _DemoScaffoldState extends State<DemoScaffold> {
QuillToolbar get quillToolbar {
if (_isDesktop()) {
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop,
return QuillToolbar(
configurations: QuillToolbarConfigurations(
embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop,
),
),
);
}
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(),
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,24 +161,25 @@ 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(),
textSelectionThemeData: textSelectionThemeData,
autoFocus: autoFocus,
expands: expands,
padding: padding,
keyboardAppearance: keyboardAppearance ?? Brightness.light,
embedBuilders: embedBuilders,
editorKey: editorKey,
configurations: configurations.copyWith(
textSelectionThemeData: textSelectionThemeData,
autoFocus: autoFocus,
expands: expands,
padding: padding,
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 editorConfigurations =
context.requireQuillConfigurations.editorConfigurations;
final 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,
contextMenuBuilder: showSelectionToolbar
? (widget.contextMenuBuilder ?? RawEditor.defaultContextMenuBuilder)
: null,
showSelectionHandles: isMobile(theme.platform),
showCursor: widget.showCursor,
cursorStyle: CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText: widget.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
final showSelectionToolbar = configurations.enableInteractiveSelection &&
configurations.enableSelectionToolbar;
final child = QuillEditorProvider(
editorConfigurations: configurations,
child: RawEditor(
key: _editorKey,
controller: context.requireQuillController,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: configurations.scrollable,
scrollBottomInset: configurations.scrollBottomInset,
padding: configurations.padding,
readOnly: configurations.readOnly,
placeholder: configurations.placeholder,
onLaunchUrl: configurations.onLaunchUrl,
contextMenuBuilder: showSelectionToolbar
? (configurations.contextMenuBuilder ??
RawEditor.defaultContextMenuBuilder)
: null,
showSelectionHandles: isMobile(theme.platform),
showCursor: configurations.showCursor,
cursorStyle: CursorStyle(
color: cursorColor,
backgroundColor: Colors.grey,
width: 2,
radius: cursorRadius,
offset: cursorOffset,
paintAboveText:
configurations.paintCursorAboveText ?? paintCursorAboveText,
opacityAnimates: cursorOpacityAnimates,
),
textCapitalization: configurations.textCapitalization,
minHeight: configurations.minHeight,
maxHeight: configurations.maxHeight,
maxContentWidth: configurations.maxContentWidth,
customStyles: configurations.customStyles,
expands: configurations.expands,
autoFocus: configurations.autoFocus,
selectionColor: selectionColor,
selectionCtrls:
configurations.textSelectionControls ?? textSelectionControls,
keyboardAppearance: configurations.keyboardAppearance,
enableInteractiveSelection: configurations.enableInteractiveSelection,
scrollPhysics: configurations.scrollPhysics,
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: configurations.linkActionPickerDelegate,
customStyleBuilder: configurations.customStyleBuilder,
customRecognizerBuilder: configurations.customRecognizerBuilder,
floatingCursorDisabled: configurations.floatingCursorDisabled,
onImagePaste: configurations.onImagePaste,
customShortcuts: configurations.customShortcuts,
customActions: configurations.customActions,
customLinkPrefixes: configurations.customLinkPrefixes,
enableUnfocusOnTapOutside: configurations.enableUnfocusOnTapOutside,
dialogTheme: configurations.dialogTheme,
contentInsertionConfiguration:
configurations.contentInsertionConfiguration,
),
textCapitalization: widget.textCapitalization,
minHeight: widget.minHeight,
maxHeight: widget.maxHeight,
maxContentWidth: widget.maxContentWidth,
customStyles: widget.customStyles,
expands: widget.expands,
autoFocus: widget.autoFocus,
selectionColor: selectionColor,
selectionCtrls: widget.textSelectionControls ?? textSelectionControls,
keyboardAppearance: widget.keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
scrollPhysics: widget.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,
);
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;
}

@ -53,9 +53,11 @@ Future<LinkMenuAction> defaultLinkActionPickerDelegate(
return _showMaterialMenu(context, link);
default:
assert(
false,
'defaultShowLinkActionsMenu not supposed to '
'be invoked for $defaultTargetPlatform');
false,
'defaultShowLinkActionsMenu not supposed to '
'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,29 +52,30 @@ const List<String> romanNumbers = [
];
class EditableTextBlock extends StatelessWidget {
const EditableTextBlock(
{required this.block,
required this.controller,
required this.textDirection,
required this.scrollBottomInset,
required this.verticalSpacing,
required this.textSelection,
required this.color,
required this.styles,
required this.enableInteractiveSelection,
required this.hasFocus,
required this.contentPadding,
required this.embedBuilder,
required this.linkActionPicker,
required this.cursorCont,
required this.indentLevelCounts,
required this.clearIndents,
required this.onCheckboxTap,
required this.readOnly,
this.onLaunchUrl,
this.customStyleBuilder,
this.customLinkPrefixes = const <String>[],
Key? key});
const EditableTextBlock({
required this.block,
required this.controller,
required this.textDirection,
required this.scrollBottomInset,
required this.verticalSpacing,
required this.textSelection,
required this.color,
required this.styles,
required this.enableInteractiveSelection,
required this.hasFocus,
required this.contentPadding,
required this.embedBuilder,
required this.linkActionPicker,
required this.cursorCont,
required this.indentLevelCounts,
required this.clearIndents,
required this.onCheckboxTap,
required this.readOnly,
this.onLaunchUrl,
this.customStyleBuilder,
this.customLinkPrefixes = const <String>[],
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,584 +1,370 @@
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,
childrenBuilder: (context) {
final controller = context.requireQuillController;
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',
);
}
return [
if (showUndo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.undoHistory,
controller:
toolbarConfigurations.buttonOptions.undoHistory.controller ??
context.requireQuillController,
),
if (showRedo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.redoHistory,
controller:
toolbarConfigurations.buttonOptions.redoHistory.controller ??
context.requireQuillController,
),
if (showFontFamily)
QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily,
controller:
toolbarConfigurations.buttonOptions.fontFamily.controller ??
context.requireQuillController,
),
if (showFontSize)
QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize,
controller:
toolbarConfigurations.buttonOptions.fontFamily.controller ??
context.requireQuillController,
),
if (showBoldButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
options: toolbarConfigurations.buttonOptions.bold,
controller: toolbarConfigurations.buttonOptions.bold.controller ??
context.requireQuillController,
),
if (showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller:
toolbarConfigurations.buttonOptions.subscript.controller ??
context.requireQuillController,
),
if (showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller:
toolbarConfigurations.buttonOptions.superscript.controller ??
context.requireQuillController,
),
if (showItalicButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic,
controller:
toolbarConfigurations.buttonOptions.italic.controller ??
context.requireQuillController,
),
if (showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller:
toolbarConfigurations.buttonOptions.small.controller ??
context.requireQuillController,
),
if (showUnderLineButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine,
controller:
toolbarConfigurations.buttonOptions.underLine.controller ??
context.requireQuillController,
),
if (showStrikeThrough)
QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: toolbarConfigurations
.buttonOptions.strikeThrough.controller ??
context.requireQuillController,
),
if (showInlineCode)
QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode,
controller:
toolbarConfigurations.buttonOptions.inlineCode.controller ??
context.requireQuillController,
),
if (showColorButton)
QuillToolbarColorButton(
controller: controller,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: controller,
isBackground: true,
),
if (showClearFormat)
QuillToolbarClearFormatButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (embedButtons != null)
for (final builder in embedButtons)
builder(controller, globalIconSize, iconTheme, dialogTheme),
if (showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
if (showAlignmentButtons)
QuillToolbarSelectAlignmentButton(
controller: controller,
options:
toolbarConfigurations.buttonOptions.selectAlignmentButtons,
// tooltips: Map.of(buttonTooltips)
// ..removeWhere((key, value) => ![
// ToolbarButtons.leftAlignment,
// ToolbarButtons.centerAlignment,
// ToolbarButtons.rightAlignment,
// ToolbarButtons.justifyAlignment,
// ].contains(key)),
showLeftAlignment: showLeftAlignment,
showCenterAlignment: showCenterAlignment,
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
),
if (showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller:
toolbarConfigurations.buttonOptions.direction.controller ??
context.requireQuillController,
),
if (showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
if (showHeaderStyle)
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options:
toolbarConfigurations.buttonOptions.selectHeaderStyleButtons,
// tooltip: buttonTooltips[ToolbarButtons.headerStyle],
// axis: axis,
// iconSize: toolbarIconSize,
// iconTheme: iconTheme,
// afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
if (showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers,
controller:
toolbarConfigurations.buttonOptions.listNumbers.controller ??
context.requireQuillController,
),
if (showListBullets)
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets,
controller:
toolbarConfigurations.buttonOptions.listBullets.controller ??
context.requireQuillController,
),
if (showListCheck)
QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: toolbarConfigurations
.buttonOptions.toggleCheckList.controller ??
context.requireQuillController,
),
if (showCodeBlock)
QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock,
controller:
toolbarConfigurations.buttonOptions.codeBlock.controller ??
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 globalIconSize =
toolbarConfigurations.buttonOptions.base.globalIconSize;
final axis = toolbarConfigurations.axis;
final globalController = context.requireQuillController;
return [
if (configurations.showUndo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.undoHistory,
controller: toolbarConfigurations
.buttonOptions.undoHistory.controller ??
globalController,
),
if (configurations.showRedo)
QuillToolbarHistoryButton(
options: toolbarConfigurations.buttonOptions.redoHistory,
controller: toolbarConfigurations
.buttonOptions.redoHistory.controller ??
globalController,
),
if (configurations.showFontFamily)
QuillToolbarFontFamilyButton(
options: toolbarConfigurations.buttonOptions.fontFamily,
controller: toolbarConfigurations
.buttonOptions.fontFamily.controller ??
globalController,
),
if (configurations.showFontSize)
QuillToolbarFontSizeButton(
options: toolbarConfigurations.buttonOptions.fontSize,
controller: toolbarConfigurations
.buttonOptions.fontFamily.controller ??
globalController,
),
if (configurations.showBoldButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.bold,
options: toolbarConfigurations.buttonOptions.bold,
controller:
toolbarConfigurations.buttonOptions.bold.controller ??
globalController,
),
if (configurations.showSubscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.subscript,
options: toolbarConfigurations.buttonOptions.subscript,
controller: toolbarConfigurations
.buttonOptions.subscript.controller ??
globalController,
),
if (configurations.showSuperscript)
QuillToolbarToggleStyleButton(
attribute: Attribute.superscript,
options: toolbarConfigurations.buttonOptions.superscript,
controller: toolbarConfigurations
.buttonOptions.superscript.controller ??
globalController,
),
if (configurations.showItalicButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.italic,
options: toolbarConfigurations.buttonOptions.italic,
controller:
toolbarConfigurations.buttonOptions.italic.controller ??
globalController,
),
if (configurations.showSmallButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.small,
options: toolbarConfigurations.buttonOptions.small,
controller:
toolbarConfigurations.buttonOptions.small.controller ??
globalController,
),
if (configurations.showUnderLineButton)
QuillToolbarToggleStyleButton(
attribute: Attribute.underline,
options: toolbarConfigurations.buttonOptions.underLine,
controller: toolbarConfigurations
.buttonOptions.underLine.controller ??
globalController,
),
if (configurations.showStrikeThrough)
QuillToolbarToggleStyleButton(
attribute: Attribute.strikeThrough,
options: toolbarConfigurations.buttonOptions.strikeThrough,
controller: toolbarConfigurations
.buttonOptions.strikeThrough.controller ??
globalController,
),
if (configurations.showInlineCode)
QuillToolbarToggleStyleButton(
attribute: Attribute.inlineCode,
options: toolbarConfigurations.buttonOptions.inlineCode,
controller: toolbarConfigurations
.buttonOptions.inlineCode.controller ??
globalController,
),
if (configurations.showColorButton)
QuillToolbarColorButton(
controller: controller,
isBackground: false,
options: toolbarConfigurations.buttonOptions.color,
),
if (configurations.showBackgroundColorButton)
QuillToolbarColorButton(
options: toolbarConfigurations.buttonOptions.backgroundColor,
controller: controller,
isBackground: true,
),
if (configurations.showClearFormat)
QuillToolbarClearFormatButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.clearFormat,
),
if (theEmbedButtons != null)
for (final builder in theEmbedButtons)
builder(controller, globalIconSize, configurations.iconTheme,
configurations.dialogTheme),
if (configurations.showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showAlignmentButtons)
QuillToolbarSelectAlignmentButton(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectAlignmentButtons,
// tooltips: Map.of(buttonTooltips)
// ..removeWhere((key, value) => ![
// ToolbarButtons.leftAlignment,
// ToolbarButtons.centerAlignment,
// ToolbarButtons.rightAlignment,
// ToolbarButtons.justifyAlignment,
// ].contains(key)),
showLeftAlignment: configurations.showLeftAlignment,
showCenterAlignment: configurations.showCenterAlignment,
showRightAlignment: configurations.showRightAlignment,
showJustifyAlignment: configurations.showJustifyAlignment,
),
if (configurations.showDirection)
QuillToolbarToggleStyleButton(
attribute: Attribute.rtl,
options: toolbarConfigurations.buttonOptions.direction,
controller: toolbarConfigurations
.buttonOptions.direction.controller ??
context.requireQuillController,
),
if (showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillToolbarDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showQuote)
QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote,
controller:
toolbarConfigurations.buttonOptions.quote.controller ??
context.requireQuillController,
attribute: Attribute.blockQuote,
),
if (showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentIncrease.controller ??
context.requireQuillController,
isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease,
),
if (showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentDecrease.controller ??
context.requireQuillController,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
QuillToolbarDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showLink)
QuillToolbarLinkStyleButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.linkStyle,
),
if (showSearchButton)
QuillToolbarSearchButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.search,
),
if (customButtons.isNotEmpty)
if (showDividers)
QuillToolbarDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
for (final customButton in customButtons)
if (customButton.child != null) ...[
InkWell(
onTap: customButton.onTap,
child: customButton.child,
),
] else ...[
CustomButton(
onPressed: customButton.onTap,
icon: customButton.iconData ??
context.quillToolbarBaseButtonOptions?.iconData,
iconColor: customButton.iconColor,
iconSize: customButton.iconSize ?? globalIconSize,
iconTheme: iconTheme ??
context.quillToolbarBaseButtonOptions?.iconTheme,
afterButtonPressed: customButton.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,
),
if (configurations.showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showHeaderStyle)
QuillToolbarSelectHeaderStyleButtons(
controller: controller,
options: toolbarConfigurations
.buttonOptions.selectHeaderStyleButtons,
),
if (configurations.showDividers &&
configurations.showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showListNumbers)
QuillToolbarToggleStyleButton(
attribute: Attribute.ol,
options: toolbarConfigurations.buttonOptions.listNumbers,
controller: toolbarConfigurations
.buttonOptions.listNumbers.controller ??
globalController,
),
if (configurations.showListBullets)
QuillToolbarToggleStyleButton(
attribute: Attribute.ul,
options: toolbarConfigurations.buttonOptions.listBullets,
controller: toolbarConfigurations
.buttonOptions.listBullets.controller ??
globalController,
),
if (configurations.showListCheck)
QuillToolbarToggleCheckListButton(
options: toolbarConfigurations.buttonOptions.toggleCheckList,
controller: toolbarConfigurations
.buttonOptions.toggleCheckList.controller ??
globalController,
),
if (configurations.showCodeBlock)
QuillToolbarToggleStyleButton(
attribute: Attribute.codeBlock,
options: toolbarConfigurations.buttonOptions.codeBlock,
controller: toolbarConfigurations
.buttonOptions.codeBlock.controller ??
globalController,
),
if (configurations.showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
if (configurations.showQuote)
QuillToolbarToggleStyleButton(
options: toolbarConfigurations.buttonOptions.quote,
controller:
toolbarConfigurations.buttonOptions.quote.controller ??
globalController,
attribute: Attribute.blockQuote,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentIncrease.controller ??
globalController,
isIncrease: true,
options: toolbarConfigurations.buttonOptions.indentIncrease,
),
if (configurations.showIndent)
QuillToolbarIndentButton(
controller: toolbarConfigurations
.buttonOptions.indentDecrease.controller ??
globalController,
isIncrease: false,
options: toolbarConfigurations.buttonOptions.indentDecrease,
),
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 (configurations.showSearchButton)
QuillToolbarSearchButton(
controller: controller,
options: toolbarConfigurations.buttonOptions.search,
),
if (configurations.customButtons.isNotEmpty)
if (configurations.showDividers)
QuillToolbarDivider(
axis,
color: configurations.sectionDividerColor,
space: configurations.sectionDividerSpace,
),
constraints: BoxConstraints.tightFor(
height: axis == Axis.horizontal ? toolbarSize : null,
width: axis == Axis.vertical ? toolbarSize : null,
),
child: QuillToolbarArrowIndicatedButtonList(
axis: axis,
buttons: childrenBuilder(context),
),
),
for (final customButton in configurations.customButtons)
if (customButton.child != null) ...[
InkWell(
onTap: customButton.onTap,
child: customButton.child,
),
] else ...[
CustomButton(
onPressed: customButton.onTap,
icon: customButton.iconData ??
context.quillToolbarBaseButtonOptions?.iconData,
iconColor: customButton.iconColor,
iconSize: customButton.iconSize ?? globalIconSize,
iconTheme: configurations.iconTheme ??
context.quillToolbarBaseButtonOptions?.iconTheme,
afterButtonPressed: customButton.afterButtonPressed ??
context
.quillToolbarBaseButtonOptions?.afterButtonPressed,
tooltip: customButton.tooltip ??
context.quillToolbarBaseButtonOptions?.tooltip,
),
],
];
},
),
),
);
}
}
/// 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,11 +18,13 @@ void main() {
configurations: QuillConfigurations(
controller: QuillController.basic(),
),
child: QuillToolbar.basic(
showRedo: false,
customButtons: [
const QuillCustomButton(tooltip: tooltip),
],
child: const QuillToolbar(
configurations: QuillToolbarConfigurations(
showRedo: false,
customButtons: [
QuillCustomButton(tooltip: tooltip),
],
),
),
),
),
@ -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(
readOnly: false,
// 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),
autoFocus: true,
expands: true,
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (content) {
latestUri = content.uri;
},
allowedMimeTypes: const <String>['image/gif'],
configurations: QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
autoFocus: true,
expands: true,
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (content) {
latestUri = content.uri;
},
allowedMimeTypes: <String>['image/gif'],
),
),
),
),
@ -121,20 +124,18 @@ 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,
autoFocus: true,
expands: true,
contextMenuBuilder: customBuilder,
// ignore: avoid_redundant_argument_values
configurations: QuillEditorConfigurations(
// ignore: avoid_redundant_argument_values
readOnly: false,
autoFocus: true,
expands: true,
contextMenuBuilder: customBuilder,
),
),
),
),

Loading…
Cancel
Save