From fbc633f01e72731185ad9a765fa69e2de276840a Mon Sep 17 00:00:00 2001 From: Ahmed Hnewa <73608287+freshtechtips@users.noreply.github.com> Date: Sat, 21 Oct 2023 17:57:08 +0300 Subject: [PATCH] Major update step 2 --- example/lib/pages/home_page.dart | 17 +- example/lib/pages/read_only_page.dart | 18 +- .../lib/embeds/widgets/video_app.dart | 17 +- .../models/config/editor/configurations.dart | 21 ++ .../models/config/quill_configurations.dart | 37 +-- .../models/config/shared_configurations.dart | 21 ++ .../models/config/toolbar/buttons/base.dart | 48 ++++ .../config/toolbar/buttons/font_family.dart | 79 ++++++ .../config/toolbar/buttons/history.dart | 39 +++ .../config/toolbar/buttons/toggle_style.dart | 15 ++ .../models/config/toolbar/configurations.dart | 94 +++++++ lib/src/utils/extensions/build_context.dart | 84 +++++- .../utils/extensions/quill_controller.dart | 17 ++ lib/src/widgets/editor.dart | 26 +- lib/src/widgets/toolbar.dart | 104 +++----- lib/src/widgets/toolbar/enum.dart | 6 +- lib/src/widgets/toolbar/history_button.dart | 165 +++++++----- .../toolbar/quill_font_family_button.dart | 242 ++++++++++-------- lib/src/widgets/utils/provider.dart | 15 +- test/bug_fix_test.dart | 2 +- test/widgets/editor_test.dart | 21 +- 21 files changed, 767 insertions(+), 321 deletions(-) create mode 100644 lib/src/models/config/editor/configurations.dart create mode 100644 lib/src/models/config/shared_configurations.dart create mode 100644 lib/src/models/config/toolbar/buttons/base.dart create mode 100644 lib/src/models/config/toolbar/buttons/font_family.dart create mode 100644 lib/src/models/config/toolbar/buttons/history.dart create mode 100644 lib/src/models/config/toolbar/buttons/toggle_style.dart create mode 100644 lib/src/models/config/toolbar/configurations.dart create mode 100644 lib/src/utils/extensions/quill_controller.dart diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 2914f8e2..25e923df 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -190,8 +190,6 @@ class _HomePageState extends State { 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 { 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 { 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 { const EdgeInsets.symmetric(vertical: 16, horizontal: 8), child: quillToolbar, )) - : Container(child: quillToolbar) + : Container( + child: quillToolbar, + ) ], ), ), diff --git a/example/lib/pages/read_only_page.dart b/example/lib/pages/read_only_page.dart index fb14d475..3a700d86 100644 --- a/example/lib/pages/read_only_page.dart +++ b/example/lib/pages/read_only_page.dart @@ -39,21 +39,21 @@ class _ReadOnlyPageState extends State { 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), diff --git a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart index 7ca9cb6b..c65b9a78 100644 --- a/flutter_quill_extensions/lib/embeds/widgets/video_app.dart +++ b/flutter_quill_extensions/lib/embeds/widgets/video_app.dart @@ -55,15 +55,22 @@ class _VideoAppState extends State { 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, diff --git a/lib/src/models/config/editor/configurations.dart b/lib/src/models/config/editor/configurations.dart new file mode 100644 index 00000000..ce4e7ead --- /dev/null +++ b/lib/src/models/config/editor/configurations.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; +} diff --git a/lib/src/models/config/quill_configurations.dart b/lib/src/models/config/quill_configurations.dart index f5786222..25313dbf 100644 --- a/lib/src/models/config/quill_configurations.dart +++ b/lib/src/models/config/quill_configurations.dart @@ -1,37 +1,10 @@ import 'package:flutter/foundation.dart' show immutable; -import 'package:flutter/material.dart' show Color, Colors, Locale; import '../../../flutter_quill.dart'; -/// The configurations for the toolbar widget of flutter quill -@immutable -class QuillToolbarConfigurations { - const QuillToolbarConfigurations(); -} - -/// The configurations for the quill editor widget of flutter quill -@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.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; -} +export './editor/configurations.dart'; +export './shared_configurations.dart'; +export './toolbar/configurations.dart'; @immutable class QuillConfigurations { @@ -50,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; } diff --git a/lib/src/models/config/shared_configurations.dart b/lib/src/models/config/shared_configurations.dart new file mode 100644 index 00000000..434677a0 --- /dev/null +++ b/lib/src/models/config/shared_configurations.dart @@ -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; +} diff --git a/lib/src/models/config/toolbar/buttons/base.dart b/lib/src/models/config/toolbar/buttons/base.dart new file mode 100644 index 00000000..82514a27 --- /dev/null +++ b/lib/src/models/config/toolbar/buttons/base.dart @@ -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 { + 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; +} diff --git a/lib/src/models/config/toolbar/buttons/font_family.dart b/lib/src/models/config/toolbar/buttons/font_family.dart new file mode 100644 index 00000000..babdb56d --- /dev/null +++ b/lib/src/models/config/toolbar/buttons/font_family.dart @@ -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>? items; + + /// By default it will be [fontFamilyValues] from [QuillToolbarConfigurations] + /// You can override this if you want + final Map? rawItemsMap; + final ValueChanged? 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; +} diff --git a/lib/src/models/config/toolbar/buttons/history.dart b/lib/src/models/config/toolbar/buttons/history.dart new file mode 100644 index 00000000..06076fcd --- /dev/null +++ b/lib/src/models/config/toolbar/buttons/history.dart @@ -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; +} diff --git a/lib/src/models/config/toolbar/buttons/toggle_style.dart b/lib/src/models/config/toolbar/buttons/toggle_style.dart new file mode 100644 index 00000000..4f68693a --- /dev/null +++ b/lib/src/models/config/toolbar/buttons/toggle_style.dart @@ -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; +} diff --git a/lib/src/models/config/toolbar/configurations.dart b/lib/src/models/config/toolbar/configurations.dart new file mode 100644 index 00000000..0cfa52d4 --- /dev/null +++ b/lib/src/models/config/toolbar/configurations.dart @@ -0,0 +1,94 @@ +import 'package:flutter/foundation.dart' show immutable; +import 'package:flutter/material.dart' show Icons; + +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? 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; +} diff --git a/lib/src/utils/extensions/build_context.dart b/lib/src/utils/extensions/build_context.dart index 2df233ab..820919d9 100644 --- a/lib/src/utils/extensions/build_context.dart +++ b/lib/src/utils/extensions/build_context.dart @@ -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; } } diff --git a/lib/src/utils/extensions/quill_controller.dart b/lib/src/utils/extensions/quill_controller.dart new file mode 100644 index 00000000..0495daa2 --- /dev/null +++ b/lib/src/utils/extensions/quill_controller.dart @@ -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; + } +} diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 6b29fde5..2c154eb9 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -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, @@ -201,10 +199,6 @@ class QuillEditor extends StatefulWidget { FocusNode? focusNode, String? placeholder, GlobalKey? 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(), @@ -212,12 +206,10 @@ class QuillEditor extends StatefulWidget { focusNode: focusNode ?? FocusNode(), textSelectionThemeData: textSelectionThemeData, autoFocus: autoFocus, - readOnly: readOnly, expands: expands, padding: padding, keyboardAppearance: keyboardAppearance ?? Brightness.light, embedBuilders: embedBuilders, - placeholder: placeholder, editorKey: editorKey, ); } @@ -262,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. /// @@ -503,6 +486,9 @@ class QuillEditorState extends State final showSelectionToolbar = widget.enableInteractiveSelection && widget.enableSelectionToolbar; + final editorConfigurations = + context.requireQuillConfigurations.editorConfigurations; + final child = RawEditor( key: _editorKey, controller: context.requireQuillController, @@ -511,8 +497,8 @@ class QuillEditorState extends State 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) @@ -555,7 +541,7 @@ class QuillEditorState extends State ); final editor = I18n( - initialLocale: context.sharedQuillConfigurations?.locale, + initialLocale: context.quillSharedConfigurations?.locale, child: selectionEnabled ? _selectionGestureDetectorBuilder.build( behavior: HitTestBehavior.translucent, diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 75653510..68c42fec 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -6,6 +6,8 @@ import '../translations/toolbar.i18n.dart'; import '../utils/extensions/build_context.dart'; import 'toolbar/arrow_indicated_button_list.dart'; +export '../models/config/toolbar/buttons/base.dart'; +export '../models/config/toolbar/configurations.dart'; export 'toolbar/clear_format_button.dart'; export 'toolbar/color_button.dart'; export 'toolbar/custom_button.dart'; @@ -22,15 +24,6 @@ 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; - typedef QuillToolbarChildrenBuilder = List Function( BuildContext context, ); @@ -39,11 +32,10 @@ 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 [], VoidCallback? afterButtonPressed, @@ -56,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, @@ -100,9 +91,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ///Map of font sizes in string Map? fontSizeValues, - ///Map of font families in string - Map? fontFamilyValues, - /// Toolbar items to display for controls of embed blocks List? embedButtons, @@ -182,25 +170,9 @@ 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.undo: 'Undo'.i18n, - ToolbarButtons.redo: 'Redo'.i18n, ToolbarButtons.fontFamily: 'Font family'.i18n, ToolbarButtons.fontSize: 'Font size'.i18n, ToolbarButtons.bold: 'Bold'.i18n, @@ -236,46 +208,34 @@ 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, 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, ), 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, ), 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, ), if (showFontSize) QuillFontSizeButton( @@ -377,7 +337,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, dialogBarrierColor: - context.requireSharedQuillConfigurations.dialogBarrierColor, + context.requireQuillSharedConfigurations.dialogBarrierColor, ), if (showBackgroundColorButton) ColorButton( @@ -389,7 +349,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, dialogBarrierColor: - context.requireSharedQuillConfigurations.dialogBarrierColor, + context.requireQuillSharedConfigurations.dialogBarrierColor, ), if (showClearFormat) ClearFormatButton( @@ -563,14 +523,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, @@ -608,11 +568,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; @@ -639,15 +597,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { final Decoration? decoration; @override - Size get preferredSize => axis == Axis.horizontal - ? Size.fromHeight(toolbarSize) - : Size.fromWidth(toolbarSize); + Size get preferredSize { + // 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 + return 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.sharedQuillConfigurations?.locale, - child: multiRowsDisplay + initialLocale: context.quillSharedConfigurations?.locale, + child: (toolbarConfigurations.multiRowsDisplay) ? Wrap( direction: axis, alignment: toolbarIconAlignment, diff --git a/lib/src/widgets/toolbar/enum.dart b/lib/src/widgets/toolbar/enum.dart index e6719e50..993ee842 100644 --- a/lib/src/widgets/toolbar/enum.dart +++ b/lib/src/widgets/toolbar/enum.dart @@ -1,6 +1,8 @@ enum ToolbarButtons { - undo, - redo, + // Not needed anymore, the dev can customize this much easier now + // in the toolbarConfigurations of the QuillProvider + // undo, + // redo, fontFamily, fontSize, bold, diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index 909842c9..da2cb368 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -1,89 +1,138 @@ import 'package:flutter/material.dart'; -import '../../models/themes/quill_icon_theme.dart'; +import '../../../translations.dart'; +import '../../utils/extensions/build_context.dart'; +import '../../utils/extensions/quill_controller.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; +class QuillToolbarHistoryButton extends StatefulWidget { + const QuillToolbarHistoryButton({ + required this.options, + super.key, + }); + + final QuillToolbarHistoryButtonOptions options; @override - _HistoryButtonState createState() => _HistoryButtonState(); + _QuillToolbarHistoryButtonState createState() => + _QuillToolbarHistoryButtonState(); } -class _HistoryButtonState extends State { - Color? _iconColor; +class _QuillToolbarHistoryButtonState extends State { 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 _listenForChanges() async { + 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); - _setIconColor(); - final fillColor = - widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; - widget.controller.changes.listen((event) async { - _setIconColor(); - }); + 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: widget.tooltip, + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, - size: widget.iconSize * kIconButtonFactor, - icon: Icon(widget.icon, size: widget.iconSize, color: _iconColor), + size: iconSize * kIconButtonFactor, + icon: Icon( + iconData, + size: iconSize, + color: _canPressed + ? iconTheme?.iconUnselectedColor ?? theme.iconTheme.color + : iconTheme?.disabledIconColor ?? theme.disabledColor, + ), fillColor: fillColor, - borderRadius: widget.iconTheme?.borderRadius ?? 2, - onPressed: _changeHistory, - afterPressed: widget.afterButtonPressed, + borderRadius: iconTheme?.borderRadius ?? 2, + onPressed: _updateHistory, + afterPressed: afterButtonPressed, ); } - void _setIconColor() { + void _updateCanPressed() { 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; - }); - } + setState(() { + if (options.isUndo) { + _canPressed = controller.hasUndo; + return; + } + _canPressed = controller.hasRedo; + }); } - void _changeHistory() { - if (widget.undo) { - if (widget.controller.hasUndo) { - widget.controller.undo(); - } - } else { - if (widget.controller.hasRedo) { - widget.controller.redo(); + void _updateHistory() { + if (options.isUndo) { + if (controller.hasUndo) { + controller.undo(); } + // _updateCanPressed(); // We are already listeneting for the changes + return; } - _setIconColor(); + if (controller.hasRedo) { + controller.redo(); + // _updateCanPressed(); // We are already listeneting for the changes + } } } diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index d8c15fd5..e7ee24b7 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -1,99 +1,77 @@ import 'package:flutter/material.dart'; +import '../../models/config/toolbar/buttons/font_family.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/extensions/build_context.dart'; +import '../../utils/extensions/quill_controller.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>? items; - final Map rawItemsMap; - final ValueChanged? 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; +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 - _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); + _QuillToolbarFontFamilyButtonState createState() => + _QuillToolbarFontFamilyButtonState(); } -class _QuillFontFamilyButtonState extends State { +class _QuillToolbarFontFamilyButtonState + extends State { late String _defaultDisplayText; - late String _currentValue; + String _currentValue = ''; + + QuillToolbarFontFamilyButtonOptions get options { + return widget.options; + } + + QuillController get controller { + return options.controller.notNull(context); + } - Style get _selectionStyle => widget.controller.getSelectionStyle(); + Style get _selectionStyle => controller.getSelectionStyle(); @override void initState() { super.initState(); - _currentValue = _defaultDisplayText = widget.initialValue ?? 'Font'.i18n; - widget.controller.addListener(_didChangeEditingValue); + _initState(); + } + + Future _initState() async { + await Future.delayed(Duration.zero); + setState(() { + _currentValue = _defaultDisplayText = options.initialValue ?? 'Font'.i18n; + }); + controller.addListener(_didChangeEditingValue); } @override void dispose() { - widget.controller.removeListener(_didChangeEditingValue); + controller.removeListener(_didChangeEditingValue); super.dispose(); } @override - void didUpdateWidget(covariant QuillFontFamilyButton oldWidget) { + void didUpdateWidget(covariant QuillToolbarFontFamilyButton oldWidget) { super.didUpdateWidget(oldWidget); - if (oldWidget.controller != widget.controller) { - oldWidget.controller.removeListener(_didChangeEditingValue); - widget.controller.addListener(_didChangeEditingValue); + if (controller != controller) { + controller + ..removeListener(_didChangeEditingValue) + ..addListener(_didChangeEditingValue); } } void _didChangeEditingValue() { - final attribute = _selectionStyle.attributes[widget.attribute.key]; + final attribute = _selectionStyle.attributes[options.attribute.key]; if (attribute == null) { setState(() => _currentValue = _defaultDisplayText); return; @@ -102,8 +80,24 @@ class _QuillFontFamilyButtonState extends State { setState(() => _currentValue = keyName ?? _defaultDisplayText); } + Map 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 widget.rawItemsMap.entries) { + for (final entry in rawItemsMap.entries) { if (entry.value == value) { return entry.key; } @@ -111,19 +105,43 @@ class _QuillFontFamilyButtonState extends State { 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: widget.iconSize * 1.81, - width: widget.width, + height: iconSize * 1.81, + width: options.width, ), child: UtilityWidgets.maybeWidget( - enabled: (widget.tooltip ?? '').isNotEmpty || - widget.overrideTooltipByFontFamily, + enabled: (options.tooltip ?? '').isNotEmpty || + options.overrideTooltipByFontFamily, wrapper: (child) { - var effectiveTooltip = widget.tooltip ?? ''; - if (widget.overrideTooltipByFontFamily) { + var effectiveTooltip = options.tooltip ?? ''; + if (options.overrideTooltipByFontFamily) { effectiveTooltip = effectiveTooltip.isNotEmpty ? '$effectiveTooltip: $_currentValue' : '${'Font'.i18n}: $_currentValue'; @@ -133,15 +151,16 @@ class _QuillFontFamilyButtonState extends State { child: RawMaterialButton( visualDensity: VisualDensity.compact, shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), - fillColor: widget.fillColor, + borderRadius: + BorderRadius.circular(options.iconTheme?.borderRadius ?? 2), + ), + fillColor: options.fillColor, elevation: 0, - hoverElevation: widget.hoverElevation, - highlightElevation: widget.hoverElevation, + hoverElevation: options.hoverElevation, + highlightElevation: options.hoverElevation, onPressed: () { _showMenu(); - widget.afterButtonPressed?.call(); + options.afterButtonPressed?.call(); }, child: _buildContent(context), ), @@ -149,7 +168,7 @@ class _QuillFontFamilyButtonState extends State { ); } - void _showMenu() { + Future _showMenu() async { final popupMenuTheme = PopupMenuTheme.of(context); final button = context.findRenderObject() as RenderBox; final overlay = Overlay.of(context).context.findRenderObject() as RenderBox; @@ -161,23 +180,23 @@ class _QuillFontFamilyButtonState extends State { ), Offset.zero & overlay.size, ); - showMenu( + final newValue = await showMenu( context: context, elevation: 4, items: [ - for (final MapEntry fontFamily - in widget.rawItemsMap.entries) + for (final MapEntry fontFamily in rawItemsMap.entries) PopupMenuItem( key: ValueKey(fontFamily.key), value: fontFamily.value, - height: widget.itemHeight ?? kMinInteractiveDimension, - padding: widget.itemPadding, + height: options.itemHeight ?? kMinInteractiveDimension, + padding: options.itemPadding, child: Text( fontFamily.key.toString(), style: TextStyle( - fontFamily: widget.renderFontFamilies ? fontFamily.value : null, + fontFamily: + options.renderFontFamilies ? fontFamily.value : null, color: fontFamily.value == 'Clear' - ? widget.defaultItemColor + ? options.defaultItemColor : null, ), ), @@ -186,28 +205,28 @@ class _QuillFontFamilyButtonState extends State { position: position, shape: popupMenuTheme.shape, color: popupMenuTheme.color, - ).then((newValue) { - if (!mounted) return; - if (newValue == null) { - return; + ); + 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); } - 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; + final hasFinalWidth = options.width != null; return Padding( - padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), + padding: options.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -218,19 +237,22 @@ class _QuillFontFamilyButtonState extends State { child: Text( _currentValue, maxLines: 1, - overflow: widget.labelOverflow, - style: widget.style ?? + overflow: options.labelOverflow, + style: options.style ?? TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + fontSize: iconSize / 1.15, + color: options.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) + Icon( + Icons.arrow_drop_down, + size: iconSize / 1.15, + color: + options.iconTheme?.iconUnselectedColor ?? theme.iconTheme.color, + ) ], ), ); diff --git a/lib/src/widgets/utils/provider.dart b/lib/src/widgets/utils/provider.dart index 80ebed63..1835bc87 100644 --- a/lib/src/widgets/utils/provider.dart +++ b/lib/src/widgets/utils/provider.dart @@ -1,5 +1,6 @@ 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 '../../models/config/quill_configurations.dart'; @@ -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, + ); + } } diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart index ee53926c..b3c93c41 100644 --- a/test/bug_fix_test.dart +++ b/test/bug_fix_test.dart @@ -29,7 +29,7 @@ void main() { ); final builtinFinder = find.descendant( - of: find.byType(HistoryButton), + of: find.byType(QuillToolbarHistoryButton), matching: find.byType(QuillIconButton), matchRoot: true, ); diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart index d3453ed6..52fa5e42 100644 --- a/test/widgets/editor_test.dart +++ b/test/widgets/editor_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill_test.dart'; +import 'package:flutter_quill/src/models/config/editor/configurations.dart'; import 'package:flutter_quill/src/widgets/raw_editor/raw_editor.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -39,14 +40,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 +120,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, ),