Major update 2 (#1446)

pull/1448/head
Ahmed Hnewa 1 year ago committed by GitHub
parent a73fca1f76
commit 3a82930bbe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      .github/PULL_REQUEST_TEMPLATE.md
  2. 4
      CHANGELOG.md
  3. 23
      before-push.sh
  4. 19
      example/lib/pages/home_page.dart
  5. 20
      example/lib/pages/read_only_page.dart
  6. 2
      example/lib/widgets/demo_scaffold.dart
  7. 2
      example/lib/widgets/time_stamp_embed_widget.dart
  8. 17
      flutter_quill_extensions/lib/embeds/widgets/video_app.dart
  9. 2
      flutter_quill_extensions/pubspec.yaml
  10. 2
      lib/extensions.dart
  11. 2
      lib/flutter_quill.dart
  12. 21
      lib/src/models/config/editor/configurations.dart
  13. 35
      lib/src/models/config/quill_configurations.dart
  14. 21
      lib/src/models/config/shared_configurations.dart
  15. 48
      lib/src/models/config/toolbar/buttons/base.dart
  16. 79
      lib/src/models/config/toolbar/buttons/font_family.dart
  17. 39
      lib/src/models/config/toolbar/buttons/history.dart
  18. 15
      lib/src/models/config/toolbar/buttons/toggle_style.dart
  19. 93
      lib/src/models/config/toolbar/configurations.dart
  20. 3
      lib/src/models/documents/nodes/leaf.dart
  21. 84
      lib/src/utils/extensions/build_context.dart
  22. 17
      lib/src/utils/extensions/quill_controller.dart
  23. 7
      lib/src/utils/platform.dart
  24. 32
      lib/src/widgets/editor.dart
  25. 5
      lib/src/widgets/raw_editor/raw_editor.dart
  26. 169
      lib/src/widgets/toolbar.dart
  27. 0
      lib/src/widgets/toolbar/buttons/arrow_indicated_list.dart
  28. 8
      lib/src/widgets/toolbar/buttons/clear_format.dart
  29. 14
      lib/src/widgets/toolbar/buttons/color.dart
  30. 4
      lib/src/widgets/toolbar/buttons/custom_button.dart
  31. 276
      lib/src/widgets/toolbar/buttons/font_family.dart
  32. 143
      lib/src/widgets/toolbar/buttons/history.dart
  33. 6
      lib/src/widgets/toolbar/buttons/indent.dart
  34. 18
      lib/src/widgets/toolbar/buttons/link_style.dart
  35. 16
      lib/src/widgets/toolbar/buttons/link_style2.dart
  36. 14
      lib/src/widgets/toolbar/buttons/quill_font_size.dart
  37. 2
      lib/src/widgets/toolbar/buttons/quill_icon.dart
  38. 10
      lib/src/widgets/toolbar/buttons/search.dart
  39. 14
      lib/src/widgets/toolbar/buttons/select_alignment.dart
  40. 12
      lib/src/widgets/toolbar/buttons/select_header_style.dart
  41. 12
      lib/src/widgets/toolbar/buttons/toggle_check_list.dart
  42. 12
      lib/src/widgets/toolbar/buttons/toggle_style.dart
  43. 2
      lib/src/widgets/toolbar/enum.dart
  44. 89
      lib/src/widgets/toolbar/history_button.dart
  45. 238
      lib/src/widgets/toolbar/quill_font_family_button.dart
  46. 17
      lib/src/widgets/utils/provider.dart
  47. 2
      pubspec.yaml
  48. 2
      test/bug_fix_test.dart
  49. 20
      test/widgets/editor_test.dart

@ -43,3 +43,4 @@ Closes #IssueNumber
- [ ] I have run `dart format .`` on the project <!-- REQUIRED -->
- [ ] I have run `dart fix --apply` on the project <!-- REQUIRED -->
- [ ] I have run `flutter test` and `flutter analyze` and it passed successfully <!-- REQUIRED -->
- [ ] I have run `./before-push.sh` and everything is fine <!-- Optional >

@ -1,3 +1,7 @@
## [7.6.0]
- **Breaking change**: To customize the buttons in the toolbar, you can do that in the `QuillProvider`
# [7.5.0]
- **Breaking change**: The widgets `QuillEditor` and `QuillToolbar` are no longer have controller parameter, instead you need to make sure in the widget tree you have wrapped them with `QuillProvider` widget and provide the controller and the require configurations

@ -0,0 +1,23 @@
#!/bin/bash
# Run Flutter analyze
echo "Running 'flutter analyze'..."
flutter analyze
# Run Flutter test
echo "Running 'flutter test'..."
flutter test
# Check if package is ready for publishing
echo "Running 'flutter pub publish --dry-run'..."
flutter pub publish --dry-run
# Apply Dart fixes
echo "Running 'dart fix --apply'..."
dart fix --apply
# Format Dart code
echo "Running 'dart format .'"
dart format .
echo "Script completed."

@ -9,7 +9,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart';
@ -190,8 +190,6 @@ class _HomePageState extends State<HomePage> {
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
readOnly: false,
placeholder: 'Add content',
expands: false,
padding: EdgeInsets.zero,
onTapUp: (details, p1) {
@ -221,8 +219,6 @@ class _HomePageState extends State<HomePage> {
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
readOnly: false,
placeholder: 'Add content',
enableSelectionToolbar: isMobile(),
expands: false,
padding: EdgeInsets.zero,
@ -324,10 +320,11 @@ class _HomePageState extends State<HomePage> {
return SafeArea(
child: QuillProvider(
configurations: QuillConfigurations(
// (throw ArgumentError.checkNotNull(
// _controller,
// 'Quill controller',
// ))
editorConfigurations: const QuillEditorConfigurations(
placeholder: 'Add content',
// ignore: avoid_redundant_argument_values
readOnly: false,
),
controller: _controller,
),
child: Column(
@ -348,7 +345,9 @@ class _HomePageState extends State<HomePage> {
const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: quillToolbar,
))
: Container(child: quillToolbar)
: Container(
child: quillToolbar,
)
],
),
),

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import '../universal_ui/universal_ui.dart';
@ -39,21 +39,21 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
readOnly: !_edit,
// readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: FlutterQuillEmbeds.builders(),
);
if (kIsWeb) {
quillEditor = QuillEditor(
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb);
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: true,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuildersWeb,
);
}
return Padding(
padding: const EdgeInsets.all(8),

@ -5,7 +5,7 @@ import 'package:filesystem_picker/filesystem_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path_provider/path_provider.dart';

@ -1,7 +1,7 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/flutter_quill.dart';
class TimeStampEmbed extends Embeddable {
const TimeStampEmbed(

@ -55,15 +55,22 @@ class _VideoAppState extends State<VideoApp> {
if (widget.readOnly) {
return RichText(
text: TextSpan(
text: widget.videoUrl,
style: defaultStyles.link,
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(Uri.parse(widget.videoUrl))),
text: widget.videoUrl,
style: defaultStyles.link,
recognizer: TapGestureRecognizer()
..onTap = () => launchUrl(
Uri.parse(widget.videoUrl),
),
),
);
}
return RichText(
text: TextSpan(text: widget.videoUrl, style: defaultStyles.link));
text: TextSpan(
text: widget.videoUrl,
style: defaultStyles.link,
),
);
} else if (!_controller.value.isInitialized) {
return VideoProgressIndicator(
_controller,

@ -19,7 +19,7 @@ dependencies:
flutter:
sdk: flutter
flutter_quill: ^7.4.16
flutter_quill: ^7.5.0
# In case you are working on changes for both libraries,
# flutter_quill:
# path: ~/development/playground/framework_based/flutter/flutter-quill

@ -1,6 +1,6 @@
library flutter_quill.extensions;
export 'src/models/documents/nodes/leaf.dart' hide Text;
export 'src/models/documents/nodes/leaf.dart';
export 'src/models/rules/insert.dart';
export 'src/utils/platform.dart';
export 'src/utils/string.dart';

@ -1,6 +1,6 @@
library flutter_quill;
export 'src/core/quill_configurations.dart';
export 'src/models/config/quill_configurations.dart';
export 'src/models/documents/attribute.dart';
export 'src/models/documents/document.dart';
export 'src/models/documents/nodes/block.dart';

@ -0,0 +1,21 @@
import 'package:flutter/foundation.dart' show immutable;
/// The configurations for the quill editor widget of flutter quill
@immutable
class QuillEditorConfigurations {
const QuillEditorConfigurations({
this.placeholder,
this.readOnly = false,
});
/// The text placeholder in the quill editor
final String? placeholder;
/// Whether the text can be changed.
///
/// When this is set to `true`, the text cannot be modified
/// by any shortcut or keyboard operation. The text is still selectable.
///
/// Defaults to `false`. Must not be `null`.
final bool readOnly;
}

@ -1,33 +1,10 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/material.dart' show Color, Colors;
import '../../flutter_quill.dart';
import '../../../flutter_quill.dart';
// I will start on this in the major-update-2
@immutable
class QuillToolbarConfigurations {
const QuillToolbarConfigurations();
}
///
@immutable
class QuillEditorConfigurations {
const QuillEditorConfigurations();
}
/// The shared configurations between [QuillEditorConfigurations] and
/// [QuillToolbarConfigurations] so we don't duplicate things
class QuillSharedConfigurations {
const QuillSharedConfigurations({
this.dialogBarrierColor = Colors.black54,
});
// This is just example or showcase of this major update to make the library
// more maintanable, flexible, and customizable
/// The barrier color of the shown dialogs
final Color dialogBarrierColor;
}
export './editor/configurations.dart';
export './shared_configurations.dart';
export './toolbar/configurations.dart';
@immutable
class QuillConfigurations {
@ -46,9 +23,13 @@ class QuillConfigurations {
/// 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;
}

@ -0,0 +1,21 @@
import 'package:flutter/material.dart' show Color, Colors, Locale;
import './editor/configurations.dart' show QuillEditorConfigurations;
import './toolbar/configurations.dart' show QuillToolbarConfigurations;
/// The shared configurations between [QuillEditorConfigurations] and
/// [QuillToolbarConfigurations] so we don't duplicate things
class QuillSharedConfigurations {
const QuillSharedConfigurations({
this.dialogBarrierColor = Colors.black54,
this.locale,
});
// This is just example or showcase of this major update to make the library
// more maintanable, flexible, and customizable
/// The barrier color of the shown dialogs
final Color dialogBarrierColor;
/// The locale to use for the editor and toolbar, defaults to system locale
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
}

@ -0,0 +1,48 @@
import 'package:flutter/foundation.dart' show VoidCallback, immutable;
import 'package:flutter/widgets.dart' show IconData, Widget;
import '../../../../../flutter_quill.dart' show QuillController, QuillProvider;
import '../../../themes/quill_icon_theme.dart' show QuillIconTheme;
import '../../quill_configurations.dart' show kDefaultIconSize;
/// The [T] is the options for the button, usually should refresnce itself
/// it's used in [childBuilder] so the developer can custmize this when using it
/// The [I] is extra options for the button, usually for it's state
@immutable
class QuillToolbarBaseButtonOptions<T, I> {
const QuillToolbarBaseButtonOptions({
this.iconData,
this.globalIconSize = kDefaultIconSize,
this.afterButtonPressed,
this.tooltip,
this.iconTheme,
this.childBuilder,
this.controller,
});
/// By default it will use a Icon data from Icons which comes from material
/// library, to change this, please pass a different value
/// If there is no Icon in this button then pass null in the child class
final IconData? iconData;
/// To change the the icon size pass a different value, by default will be
/// [kDefaultIconSize]
/// this will be used for all the buttons but you can override this
final double globalIconSize;
/// To do extra logic after pressing the button
final VoidCallback? afterButtonPressed;
/// By default it will use the default tooltip which already localized
final String? tooltip;
/// Use custom theme
final QuillIconTheme? iconTheme;
/// If you want to dispaly a differnet widget based using a builder
final Widget Function(T options, I extraOptions)? childBuilder;
/// By default it will be from the one in [QuillProvider]
/// To override it you must pass not null controller
final QuillController? controller;
}

@ -0,0 +1,79 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/material.dart' show Colors, PopupMenuEntry;
import 'package:flutter/widgets.dart'
show
Color,
ValueChanged,
EdgeInsetsGeometry,
TextStyle,
EdgeInsets,
TextOverflow;
import '../../../../../flutter_quill.dart';
@immutable
class QuillToolbarFontFamilyButtonExtraOptions {
const QuillToolbarFontFamilyButtonExtraOptions({
required this.defaultDisplayText,
required this.currentValue,
});
final String defaultDisplayText;
final String currentValue;
}
class QuillToolbarFontFamilyButtonOptions extends QuillToolbarBaseButtonOptions<
QuillToolbarFontFamilyButtonOptions,
QuillToolbarFontFamilyButtonExtraOptions> {
const QuillToolbarFontFamilyButtonOptions({
required this.attribute,
this.rawItemsMap,
super.controller,
super.iconData,
super.afterButtonPressed,
super.tooltip,
super.iconTheme,
super.childBuilder,
this.onSelected,
this.padding,
this.style,
this.width,
this.initialValue,
this.labelOverflow = TextOverflow.visible,
this.overrideTooltipByFontFamily = false,
this.itemHeight,
this.itemPadding,
this.defaultItemColor = Colors.red,
this.renderFontFamilies = true,
@Deprecated('It is not required because of `rawItemsMap`') this.items,
this.highlightElevation = 1,
this.hoverElevation = 1,
this.fillColor,
this.iconSize,
});
final Color? fillColor;
final double hoverElevation;
final double highlightElevation;
@Deprecated('It is not required because of `rawItemsMap`')
final List<PopupMenuEntry<String>>? items;
/// By default it will be [fontFamilyValues] from [QuillToolbarConfigurations]
/// You can override this if you want
final Map<String, String>? rawItemsMap;
final ValueChanged<String>? onSelected;
final Attribute attribute;
final EdgeInsetsGeometry? padding;
final TextStyle? style;
final double? width;
final bool renderFontFamilies;
final String? initialValue;
final TextOverflow labelOverflow;
final bool overrideTooltipByFontFamily;
final double? itemHeight;
final EdgeInsets? itemPadding;
final Color? defaultItemColor;
/// By default will use [globalIconSize]
final double? iconSize;
}

@ -0,0 +1,39 @@
import 'package:flutter/foundation.dart' show VoidCallback, immutable;
import '../../../../../flutter_quill.dart';
@immutable
class HistoryButtonExtraOptions {
const HistoryButtonExtraOptions({
required this.onPressed,
required this.canPressed,
});
/// When the button pressed
final VoidCallback onPressed;
/// If it can redo or undo
final bool canPressed;
}
@immutable
class QuillToolbarHistoryButtonOptions extends QuillToolbarBaseButtonOptions<
QuillToolbarHistoryButtonOptions, HistoryButtonExtraOptions> {
const QuillToolbarHistoryButtonOptions({
required this.isUndo,
super.iconData,
super.controller,
super.iconTheme,
super.afterButtonPressed,
super.tooltip,
super.childBuilder,
this.iconSize,
});
/// If this true then it will be the undo button
/// otherwise it will be redo
final bool isUndo;
/// By default will use [globalIconSize]
final double? iconSize;
}

@ -0,0 +1,15 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/widgets.dart' show IconData;
import 'base.dart';
@immutable
class QuillToolbarToggleStyleButtonOptions
extends QuillToolbarBaseButtonOptions {
const QuillToolbarToggleStyleButtonOptions({
required this.iconData,
});
@override
final IconData iconData;
}

@ -0,0 +1,93 @@
import 'package:flutter/foundation.dart' show immutable;
import '../../documents/attribute.dart';
import 'buttons/base.dart';
import 'buttons/font_family.dart';
import 'buttons/history.dart';
export './buttons/base.dart';
export './buttons/history.dart';
export './buttons/toggle_style.dart';
/// The default size of the icon of a button.
const double kDefaultIconSize = 18;
/// The default size for the toolbar (width, height)
const double defaultToolbarSize = kDefaultIconSize * 2;
/// The factor of how much larger the button is in relation to the icon.
const double kIconButtonFactor = 1.77;
/// The horizontal margin between the contents of each toolbar section.
const double kToolbarSectionSpacing = 4;
/// The configurations for the toolbar widget of flutter quill
@immutable
class QuillToolbarConfigurations {
const QuillToolbarConfigurations({
this.buttonOptions = const QuillToolbarButtonOptions(),
this.multiRowsDisplay = true,
this.fontFamilyValues,
/// By default it will calculated based on the [baseOptions] iconSize
/// You can change it but the the change only apply if
/// the [multiRowsDisplay] is false, if [multiRowsDisplay] then the value
/// will be [kDefaultIconSize] * 2
double? toolbarSize,
}) : _toolbarSize = toolbarSize;
final double? _toolbarSize;
/// The toolbar size, by default it will be `baseButtonOptions.iconSize * 2`
double get toolbarSize {
final alternativeToolbarSize = _toolbarSize;
if (alternativeToolbarSize != null) {
return alternativeToolbarSize;
}
return buttonOptions.baseButtonOptions.globalIconSize * 2;
}
/// If you want change spesefic buttons or all of them
/// then you came to the right place
final QuillToolbarButtonOptions buttonOptions;
final bool multiRowsDisplay;
/// By default will be final
/// ```
/// {
/// 'Sans Serif': 'sans-serif',
/// 'Serif': 'serif',
/// 'Monospace': 'monospace',
/// 'Ibarra Real Nova': 'ibarra-real-nova',
/// 'SquarePeg': 'square-peg',
/// 'Nunito': 'nunito',
/// 'Pacifico': 'pacifico',
/// 'Roboto Mono': 'roboto-mono',
/// 'Clear'.i18n: 'Clear'
/// };
/// ```
final Map<String, String>? fontFamilyValues;
}
/// The configurations for the buttons of the toolbar widget of flutter quill
@immutable
class QuillToolbarButtonOptions {
const QuillToolbarButtonOptions({
this.baseButtonOptions = const QuillToolbarBaseButtonOptions(),
this.undoHistoryButtonOptions = const QuillToolbarHistoryButtonOptions(
isUndo: true,
),
this.redoHistoryButtonOptions = const QuillToolbarHistoryButtonOptions(
isUndo: false,
),
this.fontFamilyButtonOptions = const QuillToolbarFontFamilyButtonOptions(
attribute: Attribute.font,
),
});
/// The base configurations for all the buttons
final QuillToolbarBaseButtonOptions baseButtonOptions;
final QuillToolbarHistoryButtonOptions undoHistoryButtonOptions;
final QuillToolbarHistoryButtonOptions redoHistoryButtonOptions;
final QuillToolbarFontFamilyButtonOptions fontFamilyButtonOptions;
}

@ -200,9 +200,6 @@ abstract class Leaf extends Node {
}
}
@Deprecated('Please use [QuillText] instead')
class Text extends QuillText {}
/// A span of formatted text within a line in a Quill document.
///
/// Text is a leaf node of a document tree.

@ -3,35 +3,113 @@ import 'package:flutter/widgets.dart' show BuildContext;
import '../../../flutter_quill.dart';
extension BuildContextExt on BuildContext {
/// return [QuillProvider] as not null
/// throw exception if it's not in the widget tree
QuillProvider get requireQuillProvider {
return QuillProvider.ofNotNull(this);
}
/// return nullable [QuillProvider]
/// don't throw exception if it's not in the widget tree
/// instead it will be null
QuillProvider? get quillProvider {
return QuillProvider.of(this);
}
/// return nullable [QuillController]
/// since the quill controller is in the [QuillProvider] then we need to get
/// the provider widget first and then we will return the controller
/// don't throw exception if [QuillProvider] is not in the widget tree
/// instead it will be null
QuillController? get quilController {
return quillProvider?.configurations.controller;
}
/// return [QuillController] as not null
/// since the quill controller is in the [QuillProvider] then we need to get
/// the provider widget first and then we will return the controller
/// throw exception if [QuillProvider] is not in the widget tree
QuillController get requireQuillController {
return requireQuillProvider.configurations.controller;
}
/// return [QuillConfigurations] as not null
/// since the quill configurations is in the [QuillProvider] then we need to
/// get the provider widget first and then we will return quill configurations
/// throw exception if [QuillProvider] is not in the widget tree
QuillConfigurations get requireQuillConfigurations {
return requireQuillProvider.configurations;
}
/// return nullable [QuillConfigurations]
/// since the quill configurations is in the [QuillProvider] then we need to
/// get the provider widget first and then we will return quill configurations
/// don't throw exception if [QuillProvider] is not in the widget tree
QuillConfigurations? get quillConfigurations {
return quillProvider?.configurations;
}
QuillSharedConfigurations? get sharedQuillConfigurations {
/// return [QuillSharedConfigurations] as not null. Since the quill
/// shared configurations is in the [QuillProvider] then we need to get the
/// provider widget first and then we will return shared configurations
/// throw exception if [QuillProvider] is not in the widget tree
QuillSharedConfigurations get requireQuillSharedConfigurations {
return requireQuillConfigurations.sharedConfigurations;
}
/// return nullable [QuillSharedConfigurations] . Since the quill
/// shared configurations is in the [QuillProvider] then we need to get the
/// provider widget first and then we will return shared configurations
/// don't throw exception if [QuillProvider] is not in the widget tree
QuillSharedConfigurations? get quillSharedConfigurations {
return quillConfigurations?.sharedConfigurations;
}
QuillSharedConfigurations get requireSharedQuillConfigurations {
return requireQuillConfigurations.sharedConfigurations;
/// return [QuillEditorConfigurations] as not null . Since the quill
/// editor configurations is in the [QuillProvider] then we need to get the
/// 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 nullable [QuillEditorConfigurations]. Since the quill
/// editor configurations is in the [QuillProvider] then we need to get the
/// 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 [QuillToolbarConfigurations] as not null . Since the quill
/// toolbar configurations is in the [QuillProvider] then we need to get the
/// 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 nullable [QuillToolbarConfigurations]. Since the quill
/// toolbar configurations is in the [QuillProvider] then we need to get the
/// 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 nullable [QuillToolbarBaseButtonOptions]. Since the quill
/// toolbar base button options is in the [QuillProvider] then we need to
/// get the provider widget first and then we will return base button
/// don't throw exception if [QuillProvider] is not in the widget tree
QuillToolbarBaseButtonOptions? get quillToolbarBaseButtonOptions {
return quillToolbarConfigurations?.buttonOptions.baseButtonOptions;
}
/// return [QuillToolbarBaseButtonOptions] as not null. Since the quill
/// toolbar base button options is in the [QuillProvider] then we need to
/// get the provider widget first and then we will return base button
/// throw exception if [QuillProvider] is not in the widget tree
QuillToolbarBaseButtonOptions get requireQuillToolbarBaseButtonOptions {
return requireQuillToolbarConfigurations.buttonOptions.baseButtonOptions;
}
}

@ -0,0 +1,17 @@
import 'package:flutter/widgets.dart' show BuildContext;
import '../../../flutter_quill.dart' show QuillController, QuillProvider;
import 'build_context.dart';
extension QuillControllerExt on QuillController? {
/// Simple logic to use the current passed controller if not null
/// if null then we will have to use the default one from [QuillProvider]
/// using the [context]
QuillController notNull(BuildContext context) {
final controller = this;
if (controller != null) {
return controller;
}
return context.requireQuillController;
}
}

@ -1,3 +1,5 @@
import 'dart:io' show Platform;
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart'
show kIsWeb, TargetPlatform, defaultTargetPlatform;
@ -39,6 +41,11 @@ bool isMacOS([TargetPlatform? targetPlatform]) {
return TargetPlatform.macOS == targetPlatform;
}
bool isFlutterTest() {
if (isWeb()) return false;
return Platform.environment.containsKey('FLUTTER_TEST');
}
Future<bool> isIOSSimulator() async {
if (!isAppleOS()) {
return false;

@ -148,12 +148,10 @@ class QuillEditor extends StatefulWidget {
required this.scrollable,
required this.padding,
required this.autoFocus,
required this.readOnly,
required this.expands,
this.textSelectionThemeData,
this.showCursor,
this.paintCursorAboveText,
this.placeholder,
this.enableInteractiveSelection = true,
this.enableSelectionToolbar = true,
this.scrollBottomInset = 0,
@ -175,7 +173,6 @@ class QuillEditor extends StatefulWidget {
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.customRecognizerBuilder,
this.locale,
this.floatingCursorDisabled = false,
this.textSelectionControls,
this.onImagePaste,
@ -202,10 +199,6 @@ class QuillEditor extends StatefulWidget {
FocusNode? focusNode,
String? placeholder,
GlobalKey<EditorState>? editorKey,
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
Locale? locale,
}) {
return QuillEditor(
scrollController: ScrollController(),
@ -213,13 +206,10 @@ class QuillEditor extends StatefulWidget {
focusNode: focusNode ?? FocusNode(),
textSelectionThemeData: textSelectionThemeData,
autoFocus: autoFocus,
readOnly: readOnly,
expands: expands,
padding: padding,
keyboardAppearance: keyboardAppearance ?? Brightness.light,
locale: locale,
embedBuilders: embedBuilders,
placeholder: placeholder,
editorKey: editorKey,
);
}
@ -264,15 +254,6 @@ class QuillEditor extends StatefulWidget {
final bool? showCursor;
final bool? paintCursorAboveText;
/// Whether the text can be changed.
///
/// When this is set to `true`, the text cannot be modified
/// by any shortcut or keyboard operation. The text is still selectable.
///
/// Defaults to `false`. Must not be `null`.
final bool readOnly;
final String? placeholder;
/// Whether to enable user interface affordances for changing the
/// text selection.
///
@ -381,10 +362,6 @@ class QuillEditor extends StatefulWidget {
final CustomStyleBuilder? customStyleBuilder;
final CustomRecognizerBuilder? customRecognizerBuilder;
/// The locale to use for the editor toolbar, defaults to system locale
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
/// Delegate function responsible for showing menu with link actions on
/// mobile platforms (iOS, Android).
///
@ -509,6 +486,9 @@ class QuillEditorState extends State<QuillEditor>
final showSelectionToolbar =
widget.enableInteractiveSelection && widget.enableSelectionToolbar;
final editorConfigurations =
context.requireQuillConfigurations.editorConfigurations;
final child = RawEditor(
key: _editorKey,
controller: context.requireQuillController,
@ -517,8 +497,8 @@ class QuillEditorState extends State<QuillEditor>
scrollable: widget.scrollable,
scrollBottomInset: widget.scrollBottomInset,
padding: widget.padding,
readOnly: widget.readOnly,
placeholder: widget.placeholder,
readOnly: editorConfigurations.readOnly,
placeholder: editorConfigurations.placeholder,
onLaunchUrl: widget.onLaunchUrl,
contextMenuBuilder: showSelectionToolbar
? (widget.contextMenuBuilder ?? RawEditor.defaultContextMenuBuilder)
@ -561,7 +541,7 @@ class QuillEditorState extends State<QuillEditor>
);
final editor = I18n(
initialLocale: widget.locale,
initialLocale: context.quillSharedConfigurations?.locale,
child: selectionEnabled
? _selectionGestureDetectorBuilder.build(
behavior: HitTestBehavior.translucent,

@ -1,6 +1,5 @@
import 'dart:async' show StreamSubscription;
import 'dart:convert' show jsonDecode;
import 'dart:io' show Platform;
import 'dart:math' as math;
import 'dart:ui' as ui hide TextStyle;
@ -50,7 +49,7 @@ import '../quill_single_child_scroll_view.dart';
import '../text_block.dart';
import '../text_line.dart';
import '../text_selection.dart';
import '../toolbar/link_style_button2.dart';
import '../toolbar/buttons/link_style2.dart';
import '../toolbar/search_dialog.dart';
import 'raw_editor_state_selection_delegate_mixin.dart';
import 'raw_editor_state_text_input_client_mixin.dart';
@ -1074,7 +1073,7 @@ class RawEditorState extends EditorState
if (isKeyboardOS()) {
_keyboardVisible = true;
} else if (!isWeb() && Platform.environment.containsKey('FLUTTER_TEST')) {
} else if (!isWeb() && isFlutterTest()) {
// treat tests like a keyboard OS
_keyboardVisible = true;
} else {

@ -4,32 +4,25 @@ import 'package:i18n_extension/i18n_widget.dart';
import '../../flutter_quill.dart';
import '../translations/toolbar.i18n.dart';
import '../utils/extensions/build_context.dart';
import 'toolbar/arrow_indicated_button_list.dart';
export 'toolbar/clear_format_button.dart';
export 'toolbar/color_button.dart';
export 'toolbar/custom_button.dart';
export 'toolbar/history_button.dart';
export 'toolbar/indent_button.dart';
export 'toolbar/link_style_button.dart';
export 'toolbar/link_style_button2.dart';
export 'toolbar/quill_font_family_button.dart';
export 'toolbar/quill_font_size_button.dart';
export 'toolbar/quill_icon_button.dart';
export 'toolbar/search_button.dart';
export 'toolbar/select_alignment_button.dart';
export 'toolbar/select_header_style_button.dart';
export 'toolbar/toggle_check_list_button.dart';
export 'toolbar/toggle_style_button.dart';
/// The default size of the icon of a button.
const double kDefaultIconSize = 18;
/// The factor of how much larger the button is in relation to the icon.
const double kIconButtonFactor = 1.77;
/// The horizontal margin between the contents of each toolbar section.
const double kToolbarSectionSpacing = 4;
import 'toolbar/buttons/arrow_indicated_list.dart';
export '../models/config/toolbar/buttons/base.dart';
export '../models/config/toolbar/configurations.dart';
export 'toolbar/buttons/clear_format.dart';
export 'toolbar/buttons/color.dart';
export 'toolbar/buttons/custom_button.dart';
export 'toolbar/buttons/font_family.dart';
export 'toolbar/buttons/history.dart';
export 'toolbar/buttons/indent.dart';
export 'toolbar/buttons/link_style.dart';
export 'toolbar/buttons/link_style2.dart';
export 'toolbar/buttons/quill_font_size.dart';
export 'toolbar/buttons/quill_icon.dart';
export 'toolbar/buttons/search.dart';
export 'toolbar/buttons/select_alignment.dart';
export 'toolbar/buttons/select_header_style.dart';
export 'toolbar/buttons/toggle_check_list.dart';
export 'toolbar/buttons/toggle_style.dart';
typedef QuillToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
@ -39,14 +32,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({
required this.childrenBuilder,
this.axis = Axis.horizontal,
this.toolbarSize = kDefaultIconSize * 2,
// this.toolbarSize = kDefaultIconSize * 2,
this.toolbarSectionSpacing = kToolbarSectionSpacing,
this.toolbarIconAlignment = WrapAlignment.center,
this.toolbarIconCrossAlignment = WrapCrossAlignment.center,
this.multiRowsDisplay = true,
this.color,
this.customButtons = const [],
this.locale,
VoidCallback? afterButtonPressed,
this.sectionDividerColor,
this.sectionDividerSpace,
@ -57,11 +48,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
factory QuillToolbar.basic({
Axis axis = Axis.horizontal,
double toolbarIconSize = kDefaultIconSize,
// double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = kToolbarSectionSpacing,
WrapAlignment toolbarIconAlignment = WrapAlignment.center,
WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center,
bool multiRowsDisplay = true,
bool showDividers = true,
bool showFontFamily = true,
bool showFontSize = true,
@ -101,9 +91,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
///Map of font sizes in string
Map<String, String>? fontSizeValues,
///Map of font families in string
Map<String, String>? fontFamilyValues,
/// Toolbar items to display for controls of embed blocks
List<EmbedButtonBuilder>? embedButtons,
@ -183,25 +170,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
'Clear'.i18n: '0'
};
//default font family values
final fontFamilies = fontFamilyValues ??
{
'Sans Serif': 'sans-serif',
'Serif': 'serif',
'Monospace': 'monospace',
'Ibarra Real Nova': 'ibarra-real-nova',
'SquarePeg': 'square-peg',
'Nunito': 'nunito',
'Pacifico': 'pacifico',
'Roboto Mono': 'roboto-mono',
'Clear'.i18n: 'Clear'
};
//default button tooltips
final buttonTooltips = tooltips ??
<ToolbarButtons, String>{
ToolbarButtons.undo: 'Undo'.i18n,
ToolbarButtons.redo: 'Redo'.i18n,
// ToolbarButtons.undo: 'Undo'.i18n,
// ToolbarButtons.redo: 'Redo'.i18n,
ToolbarButtons.fontFamily: 'Font family'.i18n,
ToolbarButtons.fontSize: 'Font size'.i18n,
ToolbarButtons.bold: 'Bold'.i18n,
@ -237,48 +210,62 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
axis: axis,
color: color,
decoration: decoration,
toolbarSize: toolbarIconSize * 2,
toolbarSectionSpacing: toolbarSectionSpacing,
toolbarIconAlignment: toolbarIconAlignment,
toolbarIconCrossAlignment: toolbarIconCrossAlignment,
multiRowsDisplay: multiRowsDisplay,
customButtons: customButtons,
locale: locale,
afterButtonPressed: afterButtonPressed,
childrenBuilder: (context) {
final controller = context.requireQuillController;
final toolbarConfigurations = context.requireQuillToolbarConfigurations;
final toolbarIconSize = toolbarConfigurations
.buttonOptions.baseButtonOptions.globalIconSize;
return [
if (showUndo)
HistoryButton(
icon: Icons.undo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.undo],
controller: controller,
undo: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
QuillToolbarHistoryButton(
options:
toolbarConfigurations.buttonOptions.undoHistoryButtonOptions,
),
// QuillToolbarHistoryButton(
// icon: Icons.undo_outlined,
// iconSize: toolbarIconSize,
// tooltip: buttonTooltips[ToolbarButtons.undo],
// controller: controller,
// undo: true,
// iconTheme: iconTheme,
// afterButtonPressed: afterButtonPressed,
// ),
if (showRedo)
HistoryButton(
icon: Icons.redo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.redo],
controller: controller,
undo: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
QuillToolbarHistoryButton(
options:
toolbarConfigurations.buttonOptions.redoHistoryButtonOptions,
),
// QuillToolbarHistoryButton(
// icon: Icons.redo_outlined,
// iconSize: toolbarIconSize,
// tooltip: buttonTooltips[ToolbarButtons.redo],
// controller: controller,
// undo: false,
// iconTheme: iconTheme,
// afterButtonPressed: afterButtonPressed,
// ),
if (showFontFamily)
QuillFontFamilyButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.fontFamily],
attribute: Attribute.font,
controller: controller,
rawItemsMap: fontFamilies,
afterButtonPressed: afterButtonPressed,
QuillToolbarFontFamilyButton(
options:
toolbarConfigurations.buttonOptions.fontFamilyButtonOptions,
),
// QuillFontFamilyButton(
// iconTheme: iconTheme,
// iconSize: toolbarIconSize,
// tooltip: buttonTooltips[ToolbarButtons.fontFamily],
// attribute: Attribute.font,
// controller: controller,
// rawItemsMap: {},
// afterButtonPressed: afterButtonPressed,
// ),
if (showFontSize)
QuillFontSizeButton(
iconTheme: iconTheme,
@ -379,7 +366,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
context.requireQuillSharedConfigurations.dialogBarrierColor,
),
if (showBackgroundColorButton)
ColorButton(
@ -391,7 +378,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
context.requireQuillSharedConfigurations.dialogBarrierColor,
),
if (showClearFormat)
ClearFormatButton(
@ -565,14 +552,14 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
linkRegExp: linkRegExp,
linkDialogAction: linkDialogAction,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
context.requireQuillSharedConfigurations.dialogBarrierColor,
),
if (showSearchButton)
SearchButton(
icon: Icons.search,
iconSize: toolbarIconSize,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
context.requireQuillSharedConfigurations.dialogBarrierColor,
tooltip: buttonTooltips[ToolbarButtons.search],
controller: controller,
iconTheme: iconTheme,
@ -610,11 +597,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
final QuillToolbarChildrenBuilder childrenBuilder;
final Axis axis;
final double toolbarSize;
final double toolbarSectionSpacing;
final WrapAlignment toolbarIconAlignment;
final WrapCrossAlignment toolbarIconCrossAlignment;
final bool multiRowsDisplay;
// Overrides the action in the _LinkDialog widget
final LinkDialogAction? linkDialogAction;
@ -625,10 +610,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// is given.
final Color? color;
/// The locale to use for the editor toolbar, defaults to system locale
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
/// List of custom buttons
final List<QuillCustomButton> customButtons;
@ -644,16 +625,22 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// 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
? Size.fromHeight(toolbarSize)
: Size.fromWidth(toolbarSize);
? const Size.fromHeight(defaultToolbarSize)
: const Size.fromWidth(defaultToolbarSize);
@override
Widget build(BuildContext context) {
final toolbarConfigurations = context.requireQuillToolbarConfigurations;
final toolbarSize = toolbarConfigurations.toolbarSize;
return I18n(
initialLocale: locale,
child: multiRowsDisplay
initialLocale: context.quillSharedConfigurations?.locale,
child: (toolbarConfigurations.multiRowsDisplay)
? Wrap(
direction: axis,
alignment: toolbarIconAlignment,

@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../controller.dart';
import '../../toolbar.dart';
class ClearFormatButton extends StatefulWidget {
const ClearFormatButton({

@ -1,13 +1,13 @@
import 'package:flutter/material.dart';
import 'package:flutter_colorpicker/flutter_colorpicker.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../utils/color.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../translations/toolbar.i18n.dart';
import '../../../utils/color.dart';
import '../../controller.dart';
import '../../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 '../../../models/themes/quill_icon_theme.dart';
import '../../toolbar.dart';
class CustomButton extends StatelessWidget {
const CustomButton({

@ -0,0 +1,276 @@
import 'package:flutter/material.dart';
import '../../../../extensions.dart';
import '../../../models/config/toolbar/buttons/font_family.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../translations/toolbar.i18n.dart';
import '../../../utils/extensions/build_context.dart';
import '../../../utils/extensions/quill_controller.dart';
import '../../controller.dart';
class QuillToolbarFontFamilyButton extends StatefulWidget {
QuillToolbarFontFamilyButton({
required this.options,
super.key,
}) : assert(options.rawItemsMap?.isNotEmpty ?? (true)),
assert(
options.initialValue == null || options.initialValue!.isNotEmpty,
);
final QuillToolbarFontFamilyButtonOptions options;
@override
_QuillToolbarFontFamilyButtonState createState() =>
_QuillToolbarFontFamilyButtonState();
}
class _QuillToolbarFontFamilyButtonState
extends State<QuillToolbarFontFamilyButton> {
late String _defaultDisplayText;
String _currentValue = '';
QuillToolbarFontFamilyButtonOptions get options {
return widget.options;
}
/// Since t's not safe to call anything related to the context in dispose
/// then we will save a reference to the [controller]
/// and update it in [didChangeDependencies]
/// and use it in dispose method
late QuillController _controller;
QuillController get controller {
return options.controller.notNull(context);
}
Style get _selectionStyle => controller.getSelectionStyle();
@override
void initState() {
super.initState();
_initState();
}
Future<void> _initState() async {
if (isFlutterTest()) {
// We don't need to listen for changes in the tests
return;
}
await Future.delayed(Duration.zero);
setState(() {
_currentValue = _defaultDisplayText = options.initialValue ?? 'Font'.i18n;
});
controller.addListener(_didChangeEditingValue);
}
@override
void didChangeDependencies() {
super.didChangeDependencies();
_controller = controller;
}
@override
void dispose() {
_controller.removeListener(_didChangeEditingValue);
super.dispose();
}
@override
void didUpdateWidget(covariant QuillToolbarFontFamilyButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (controller != controller) {
controller
..removeListener(_didChangeEditingValue)
..addListener(_didChangeEditingValue);
}
}
void _didChangeEditingValue() {
final attribute = _selectionStyle.attributes[options.attribute.key];
if (attribute == null) {
setState(() => _currentValue = _defaultDisplayText);
return;
}
final keyName = _getKeyName(attribute.value);
setState(() => _currentValue = keyName ?? _defaultDisplayText);
}
Map<String, String> get rawItemsMap {
final rawItemsMap = options.rawItemsMap ??
{
'Sans Serif': 'sans-serif',
'Serif': 'serif',
'Monospace': 'monospace',
'Ibarra Real Nova': 'ibarra-real-nova',
'SquarePeg': 'square-peg',
'Nunito': 'nunito',
'Pacifico': 'pacifico',
'Roboto Mono': 'roboto-mono',
'Clear'.i18n: 'Clear'
};
return rawItemsMap;
}
String? _getKeyName(String value) {
for (final entry in rawItemsMap.entries) {
if (entry.value == value) {
return entry.key;
}
}
return null;
}
double get iconSize {
final iconSize = options.iconSize;
return iconSize ?? 40;
// final baseFontSize =
// context.requireQuillToolbarBaseButtonOptions.globalIconSize;
// if (baseFontSize != iconSize) {
// return 40;
// }
// return iconSize ?? baseFontSize;
}
@override
Widget build(BuildContext context) {
final baseButtonConfigurations =
context.requireQuillToolbarBaseButtonOptions;
final childBuilder =
options.childBuilder ?? baseButtonConfigurations.childBuilder;
if (childBuilder != null) {
return childBuilder(
options,
QuillToolbarFontFamilyButtonExtraOptions(
currentValue: _currentValue,
defaultDisplayText: _defaultDisplayText,
),
);
}
return ConstrainedBox(
constraints: BoxConstraints.tightFor(
height: iconSize * 1.81,
width: options.width,
),
child: UtilityWidgets.maybeWidget(
enabled: (options.tooltip ?? '').isNotEmpty ||
options.overrideTooltipByFontFamily,
wrapper: (child) {
var effectiveTooltip = options.tooltip ?? '';
if (options.overrideTooltipByFontFamily) {
effectiveTooltip = effectiveTooltip.isNotEmpty
? '$effectiveTooltip: $_currentValue'
: '${'Font'.i18n}: $_currentValue';
}
return Tooltip(message: effectiveTooltip, child: child);
},
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(options.iconTheme?.borderRadius ?? 2),
),
fillColor: options.fillColor,
elevation: 0,
hoverElevation: options.hoverElevation,
highlightElevation: options.hoverElevation,
onPressed: () {
_showMenu();
options.afterButtonPressed?.call();
},
child: _buildContent(context),
),
),
);
}
Future<void> _showMenu() async {
final popupMenuTheme = PopupMenuTheme.of(context);
final button = context.findRenderObject() as RenderBox;
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset.zero, ancestor: overlay),
button.localToGlobal(button.size.bottomLeft(Offset.zero),
ancestor: overlay),
),
Offset.zero & overlay.size,
);
final newValue = await showMenu<String>(
context: context,
elevation: 4,
items: [
for (final MapEntry<String, String> fontFamily in rawItemsMap.entries)
PopupMenuItem<String>(
key: ValueKey(fontFamily.key),
value: fontFamily.value,
height: options.itemHeight ?? kMinInteractiveDimension,
padding: options.itemPadding,
child: Text(
fontFamily.key.toString(),
style: TextStyle(
fontFamily:
options.renderFontFamilies ? fontFamily.value : null,
color: fontFamily.value == 'Clear'
? options.defaultItemColor
: null,
),
),
),
],
position: position,
shape: popupMenuTheme.shape,
color: popupMenuTheme.color,
);
if (!mounted) return;
if (newValue == null) {
return;
}
final keyName = _getKeyName(newValue);
setState(() {
_currentValue = keyName ?? _defaultDisplayText;
if (keyName != null) {
controller.formatSelection(
Attribute.fromKeyValue('font', newValue == 'Clear' ? null : newValue),
);
options.onSelected?.call(newValue);
}
});
}
Widget _buildContent(BuildContext context) {
final theme = Theme.of(context);
final hasFinalWidth = options.width != null;
return Padding(
padding: options.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
UtilityWidgets.maybeWidget(
enabled: hasFinalWidth,
wrapper: (child) => Expanded(child: child),
child: Text(
_currentValue,
maxLines: 1,
overflow: options.labelOverflow,
style: options.style ??
TextStyle(
fontSize: iconSize / 1.15,
color: options.iconTheme?.iconUnselectedColor ??
theme.iconTheme.color,
),
),
),
const SizedBox(width: 3),
Icon(
Icons.arrow_drop_down,
size: iconSize / 1.15,
color:
options.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color,
)
],
),
);
}
}

@ -0,0 +1,143 @@
import 'package:flutter/material.dart';
import '../../../../extensions.dart';
import '../../../../translations.dart';
import '../../../utils/extensions/build_context.dart';
import '../../../utils/extensions/quill_controller.dart';
import '../../controller.dart';
import '../../toolbar.dart';
class QuillToolbarHistoryButton extends StatefulWidget {
const QuillToolbarHistoryButton({
required this.options,
super.key,
});
final QuillToolbarHistoryButtonOptions options;
@override
_QuillToolbarHistoryButtonState createState() =>
_QuillToolbarHistoryButtonState();
}
class _QuillToolbarHistoryButtonState extends State<QuillToolbarHistoryButton> {
late ThemeData theme;
var _canPressed = false;
QuillToolbarHistoryButtonOptions get options {
return widget.options;
}
QuillController get controller {
return options.controller.notNull(context);
}
@override
void initState() {
super.initState();
_listenForChanges(); // Listen for changes and change it
}
Future<void> _listenForChanges() async {
if (isFlutterTest()) {
// We don't need to listen for changes in the tests
return;
}
await Future.delayed(Duration.zero); // Wait for the widget to built
_updateCanPressed(); // Set the init state
// Listen for changes and change it
controller.changes.listen((event) async {
_updateCanPressed();
});
}
@override
Widget build(BuildContext context) {
theme = Theme.of(context);
final baseButtonConfigurations =
context.requireQuillToolbarBaseButtonOptions;
final tooltip = options.tooltip ??
baseButtonConfigurations.tooltip ??
(options.isUndo ? 'Undo'.i18n : 'Redo'.i18n);
final iconData = options.iconData ??
baseButtonConfigurations.iconData ??
(options.isUndo ? Icons.undo_outlined : Icons.redo_outlined);
final childBuilder =
options.childBuilder ?? baseButtonConfigurations.childBuilder;
final iconSize = options.iconSize ??
context.requireQuillToolbarBaseButtonOptions.globalIconSize;
final iconTheme = options.iconTheme ?? baseButtonConfigurations.iconTheme;
final fillColor = iconTheme?.iconUnselectedFillColor ?? theme.canvasColor;
final afterButtonPressed = options.afterButtonPressed ??
baseButtonConfigurations.afterButtonPressed;
if (childBuilder != null) {
return childBuilder(
QuillToolbarHistoryButtonOptions(
isUndo: options.isUndo,
afterButtonPressed: afterButtonPressed,
controller: controller,
iconData: iconData,
iconSize: iconSize,
iconTheme: iconTheme,
tooltip: tooltip,
),
HistoryButtonExtraOptions(
onPressed: () {
_updateHistory();
afterButtonPressed?.call();
},
canPressed: _canPressed,
),
);
}
return QuillIconButton(
tooltip: tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: iconSize * kIconButtonFactor,
icon: Icon(
iconData,
size: iconSize,
color: _canPressed
? iconTheme?.iconUnselectedColor ?? theme.iconTheme.color
: iconTheme?.disabledIconColor ?? theme.disabledColor,
),
fillColor: fillColor,
borderRadius: iconTheme?.borderRadius ?? 2,
onPressed: _updateHistory,
afterPressed: afterButtonPressed,
);
}
void _updateCanPressed() {
if (!mounted) return;
setState(() {
if (options.isUndo) {
_canPressed = controller.hasUndo;
return;
}
_canPressed = controller.hasRedo;
});
}
void _updateHistory() {
if (options.isUndo) {
if (controller.hasUndo) {
controller.undo();
}
// _updateCanPressed(); // We are already listeneting for the changes
return;
}
if (controller.hasRedo) {
controller.redo();
// _updateCanPressed(); // We are already listeneting for the changes
}
}
}

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../controller.dart';
import '../../toolbar.dart';
class IndentButton extends StatefulWidget {
const IndentButton({

@ -1,14 +1,14 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/rules/insert.dart';
import '../../models/structs/link_dialog_action.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../controller.dart';
import '../link.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/rules/insert.dart';
import '../../../models/structs/link_dialog_action.dart';
import '../../../models/themes/quill_dialog_theme.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../translations/toolbar.i18n.dart';
import '../../controller.dart';
import '../../link.dart';
import '../../toolbar.dart';
class LinkStyleButton extends StatefulWidget {
const LinkStyleButton({

@ -2,15 +2,15 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/link.dart';
import '../../../extensions.dart'
import '../../../../extensions.dart'
show UtilityWidgets, AutoFormatMultipleLinksRule;
import '../../../translations.dart';
import '../../models/documents/attribute.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../controller.dart';
import '../link.dart';
import '../toolbar.dart';
import '../../../../translations.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/themes/quill_dialog_theme.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../controller.dart';
import '../../link.dart';
import '../../toolbar.dart';
/// Alternative version of [LinkStyleButton]. This widget has more customization
/// and uses dialog similar to one which is used on [http://quilljs.com].

@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../utils/font.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../translations/toolbar.i18n.dart';
import '../../../utils/font.dart';
import '../../../utils/widgets.dart';
import '../../controller.dart';
class QuillFontSizeButton extends StatefulWidget {
const QuillFontSizeButton({

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import '../../utils/widgets.dart';
import '../../../utils/widgets.dart';
class QuillIconButton extends StatelessWidget {
const QuillIconButton({

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../controller.dart';
import '../toolbar.dart';
import 'search_dialog.dart';
import '../../../models/themes/quill_dialog_theme.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../controller.dart';
import '../../toolbar.dart';
import '../search_dialog.dart';
class SearchButton extends StatelessWidget {
const SearchButton({

@ -1,13 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
import '../toolbar.dart';
import 'enum.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/widgets.dart';
import '../../controller.dart';
import '../../toolbar.dart';
import '../enum.dart';
class SelectAlignmentButton extends StatefulWidget {
const SelectAlignmentButton({

@ -1,12 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/widgets.dart';
import '../../controller.dart';
import '../../toolbar.dart';
class SelectHeaderStyleButton extends StatefulWidget {
const SelectHeaderStyleButton({

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/widgets.dart';
import '../../controller.dart';
import '../../toolbar.dart';
class ToggleCheckListButton extends StatefulWidget {
const ToggleCheckListButton({

@ -1,11 +1,11 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
import '../toolbar.dart';
import '../../../models/documents/attribute.dart';
import '../../../models/documents/style.dart';
import '../../../models/themes/quill_icon_theme.dart';
import '../../../utils/widgets.dart';
import '../../controller.dart';
import '../../toolbar.dart';
typedef ToggleStyleButtonBuilder = Widget Function(
BuildContext context,

@ -1,4 +1,6 @@
enum ToolbarButtons {
// Not needed anymore, the dev can customize this much easier now
// in the toolbarConfigurations of the QuillProvider
undo,
redo,
fontFamily,

@ -1,89 +0,0 @@
import 'package:flutter/material.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../controller.dart';
import '../toolbar.dart';
class HistoryButton extends StatefulWidget {
const HistoryButton({
required this.icon,
required this.controller,
required this.undo,
this.iconSize = kDefaultIconSize,
this.iconTheme,
this.afterButtonPressed,
this.tooltip,
Key? key,
}) : super(key: key);
final IconData icon;
final double iconSize;
final bool undo;
final QuillController controller;
final QuillIconTheme? iconTheme;
final VoidCallback? afterButtonPressed;
final String? tooltip;
@override
_HistoryButtonState createState() => _HistoryButtonState();
}
class _HistoryButtonState extends State<HistoryButton> {
Color? _iconColor;
late ThemeData theme;
@override
Widget build(BuildContext context) {
theme = Theme.of(context);
_setIconColor();
final fillColor =
widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor;
widget.controller.changes.listen((event) async {
_setIconColor();
});
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * kIconButtonFactor,
icon: Icon(widget.icon, size: widget.iconSize, color: _iconColor),
fillColor: fillColor,
borderRadius: widget.iconTheme?.borderRadius ?? 2,
onPressed: _changeHistory,
afterPressed: widget.afterButtonPressed,
);
}
void _setIconColor() {
if (!mounted) return;
if (widget.undo) {
setState(() {
_iconColor = widget.controller.hasUndo
? widget.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color
: widget.iconTheme?.disabledIconColor ?? theme.disabledColor;
});
} else {
setState(() {
_iconColor = widget.controller.hasRedo
? widget.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color
: widget.iconTheme?.disabledIconColor ?? theme.disabledColor;
});
}
}
void _changeHistory() {
if (widget.undo) {
if (widget.controller.hasUndo) {
widget.controller.undo();
}
} else {
if (widget.controller.hasRedo) {
widget.controller.redo();
}
}
_setIconColor();
}
}

@ -1,238 +0,0 @@
import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../utils/widgets.dart';
import '../controller.dart';
class QuillFontFamilyButton extends StatefulWidget {
const QuillFontFamilyButton({
required this.rawItemsMap,
required this.attribute,
required this.controller,
@Deprecated('It is not required because of `rawItemsMap`') this.items,
this.onSelected,
this.iconSize = 40,
this.fillColor,
this.hoverElevation = 1,
this.highlightElevation = 1,
this.iconTheme,
this.afterButtonPressed,
this.tooltip,
this.padding,
this.style,
this.width,
this.renderFontFamilies = true,
this.initialValue,
this.labelOverflow = TextOverflow.visible,
this.overrideTooltipByFontFamily = false,
this.itemHeight,
this.itemPadding,
this.defaultItemColor = Colors.red,
Key? key,
}) : assert(rawItemsMap.length > 0),
assert(initialValue == null || initialValue.length > 0),
super(key: key);
final double iconSize;
final Color? fillColor;
final double hoverElevation;
final double highlightElevation;
@Deprecated('It is not required because of `rawItemsMap`')
final List<PopupMenuEntry<String>>? items;
final Map<String, String> rawItemsMap;
final ValueChanged<String>? onSelected;
final QuillIconTheme? iconTheme;
final Attribute attribute;
final QuillController controller;
final VoidCallback? afterButtonPressed;
final String? tooltip;
final EdgeInsetsGeometry? padding;
final TextStyle? style;
final double? width;
final bool renderFontFamilies;
final String? initialValue;
final TextOverflow labelOverflow;
final bool overrideTooltipByFontFamily;
final double? itemHeight;
final EdgeInsets? itemPadding;
final Color? defaultItemColor;
@override
_QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState();
}
class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
late String _defaultDisplayText;
late String _currentValue;
Style get _selectionStyle => widget.controller.getSelectionStyle();
@override
void initState() {
super.initState();
_currentValue = _defaultDisplayText = widget.initialValue ?? 'Font'.i18n;
widget.controller.addListener(_didChangeEditingValue);
}
@override
void dispose() {
widget.controller.removeListener(_didChangeEditingValue);
super.dispose();
}
@override
void didUpdateWidget(covariant QuillFontFamilyButton oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.controller != widget.controller) {
oldWidget.controller.removeListener(_didChangeEditingValue);
widget.controller.addListener(_didChangeEditingValue);
}
}
void _didChangeEditingValue() {
final attribute = _selectionStyle.attributes[widget.attribute.key];
if (attribute == null) {
setState(() => _currentValue = _defaultDisplayText);
return;
}
final keyName = _getKeyName(attribute.value);
setState(() => _currentValue = keyName ?? _defaultDisplayText);
}
String? _getKeyName(String value) {
for (final entry in widget.rawItemsMap.entries) {
if (entry.value == value) {
return entry.key;
}
}
return null;
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints.tightFor(
height: widget.iconSize * 1.81,
width: widget.width,
),
child: UtilityWidgets.maybeWidget(
enabled: (widget.tooltip ?? '').isNotEmpty ||
widget.overrideTooltipByFontFamily,
wrapper: (child) {
var effectiveTooltip = widget.tooltip ?? '';
if (widget.overrideTooltipByFontFamily) {
effectiveTooltip = effectiveTooltip.isNotEmpty
? '$effectiveTooltip: $_currentValue'
: '${'Font'.i18n}: $_currentValue';
}
return Tooltip(message: effectiveTooltip, child: child);
},
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)),
fillColor: widget.fillColor,
elevation: 0,
hoverElevation: widget.hoverElevation,
highlightElevation: widget.hoverElevation,
onPressed: () {
_showMenu();
widget.afterButtonPressed?.call();
},
child: _buildContent(context),
),
),
);
}
void _showMenu() {
final popupMenuTheme = PopupMenuTheme.of(context);
final button = context.findRenderObject() as RenderBox;
final overlay = Overlay.of(context).context.findRenderObject() as RenderBox;
final position = RelativeRect.fromRect(
Rect.fromPoints(
button.localToGlobal(Offset.zero, ancestor: overlay),
button.localToGlobal(button.size.bottomLeft(Offset.zero),
ancestor: overlay),
),
Offset.zero & overlay.size,
);
showMenu<String>(
context: context,
elevation: 4,
items: [
for (final MapEntry<String, String> fontFamily
in widget.rawItemsMap.entries)
PopupMenuItem<String>(
key: ValueKey(fontFamily.key),
value: fontFamily.value,
height: widget.itemHeight ?? kMinInteractiveDimension,
padding: widget.itemPadding,
child: Text(
fontFamily.key.toString(),
style: TextStyle(
fontFamily: widget.renderFontFamilies ? fontFamily.value : null,
color: fontFamily.value == 'Clear'
? widget.defaultItemColor
: null,
),
),
),
],
position: position,
shape: popupMenuTheme.shape,
color: popupMenuTheme.color,
).then((newValue) {
if (!mounted) return;
if (newValue == null) {
return;
}
final keyName = _getKeyName(newValue);
setState(() {
_currentValue = keyName ?? _defaultDisplayText;
if (keyName != null) {
widget.controller.formatSelection(Attribute.fromKeyValue(
'font', newValue == 'Clear' ? null : newValue));
widget.onSelected?.call(newValue);
}
});
});
}
Widget _buildContent(BuildContext context) {
final theme = Theme.of(context);
final hasFinalWidth = widget.width != null;
return Padding(
padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0),
child: Row(
mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
UtilityWidgets.maybeWidget(
enabled: hasFinalWidth,
wrapper: (child) => Expanded(child: child),
child: Text(
_currentValue,
maxLines: 1,
overflow: widget.labelOverflow,
style: widget.style ??
TextStyle(
fontSize: widget.iconSize / 1.15,
color: widget.iconTheme?.iconUnselectedColor ??
theme.iconTheme.color),
),
),
const SizedBox(width: 3),
Icon(Icons.arrow_drop_down,
size: widget.iconSize / 1.15,
color: widget.iconTheme?.iconUnselectedColor ??
theme.iconTheme.color)
],
),
);
}
}

@ -1,7 +1,8 @@
import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
import 'package:flutter/widgets.dart' show InheritedWidget, BuildContext;
import 'package:flutter/widgets.dart'
show BuildContext, InheritedWidget, Widget;
import '../../core/quill_configurations.dart';
import '../../models/config/quill_configurations.dart';
class QuillProvider extends InheritedWidget {
const QuillProvider({
@ -47,4 +48,16 @@ class QuillProvider extends InheritedWidget {
}
return provider;
}
/// To pass the [QuillProvider] instance as value instead of creating new
/// widget
static QuillProvider value({
required QuillProvider value,
required Widget child,
}) {
return QuillProvider(
configurations: value.configurations,
child: child,
);
}
}

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

@ -29,7 +29,7 @@ void main() {
);
final builtinFinder = find.descendant(
of: find.byType(HistoryButton),
of: find.byType(QuillToolbarHistoryButton),
matching: find.byType(QuillIconButton),
matchRoot: true,
);

@ -39,14 +39,20 @@ void main() {
await tester.pumpWidget(
MaterialApp(
home: QuillProvider(
configurations: QuillConfigurations(controller: controller),
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,
readOnly: false,
expands: true,
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (content) {
@ -113,14 +119,20 @@ void main() {
await tester.pumpWidget(
MaterialApp(
home: QuillProvider(
configurations: QuillConfigurations(controller: controller),
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,
readOnly: false,
expands: true,
contextMenuBuilder: customBuilder,
),

Loading…
Cancel
Save