diff --git a/CHANGELOG.md b/CHANGELOG.md index 10d679ad..a745d7b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,42 @@ +# [7.1.8] +* Dropdown tweaks. + +# [7.1.7] +* Toolbar tweaks. + +# [7.1.6] +* Add enableUnfocusOnTapOutside field to RawEditor and Editor widgets. + +# [7.1.5] +* Add tooltips for toolbar buttons. + +# [7.1.4] +* Fix inserting tab character in lists. + +# [7.1.3] +* Fix ios cursor bug when word.length==1. + +# [7.1.2] +* Fix non scrollable editor exception, when tapped under content. + +# [7.1.1] +* customLinkPrefixes parameter - makes possible to open links with custom protoco. + +# [7.1.0] +* Fix ordered list numeration with several lists in document. + +# [7.0.9] +* Use const constructor for EmbedBuilder. + +# [7.0.8] +* Fix IME position bug with scroller. + +# [7.0.7] +* Add TextFieldTapRegion for contextMenu. + +# [7.0.6] +* Fix line style loss on new line from non string. + # [7.0.5] * Fix IME position bug for Mac and Windows. * Unfocus when tap outside editor. fix the bug that cant refocus in afterButtonPressed after click ToggleStyleButton on Mac. diff --git a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart index bb906f30..5ec8e28e 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart @@ -19,6 +19,7 @@ class CameraButton extends StatelessWidget { this.webVideoPickImpl, this.cameraPickSettingSelector, this.iconTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -42,6 +43,7 @@ class CameraButton extends StatelessWidget { final MediaPickSettingSelector? cameraPickSettingSelector; final QuillIconTheme? iconTheme; + final String? tooltip; @override Widget build(BuildContext context) { @@ -53,6 +55,7 @@ class CameraButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart index fb0ab679..5c7c5684 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart @@ -9,6 +9,7 @@ class FormulaButton extends StatelessWidget { this.fillColor, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -23,6 +24,7 @@ class FormulaButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { @@ -34,6 +36,7 @@ class FormulaButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart index 5cc51aff..d05d851d 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/image_button.dart @@ -17,6 +17,7 @@ class ImageButton extends StatelessWidget { this.mediaPickSettingSelector, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -38,6 +39,7 @@ class ImageButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { @@ -49,6 +51,7 @@ class ImageButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart index e6193622..e5c1ab73 100644 --- a/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart +++ b/flutter_quill_extensions/lib/embeds/toolbar/video_button.dart @@ -17,6 +17,7 @@ class VideoButton extends StatelessWidget { this.mediaPickSettingSelector, this.iconTheme, this.dialogTheme, + this.tooltip, Key? key, }) : super(key: key); @@ -38,6 +39,7 @@ class VideoButton extends StatelessWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; + final String? tooltip; @override Widget build(BuildContext context) { @@ -49,6 +51,7 @@ class VideoButton extends StatelessWidget { return QuillIconButton( icon: Icon(icon, size: iconSize, color: iconColor), + tooltip: tooltip, highlightElevation: 0, hoverElevation: 0, size: iconSize * 1.77, diff --git a/flutter_quill_extensions/lib/flutter_quill_extensions.dart b/flutter_quill_extensions/lib/flutter_quill_extensions.dart index 7ef38328..12b3bd82 100644 --- a/flutter_quill_extensions/lib/flutter_quill_extensions.dart +++ b/flutter_quill_extensions/lib/flutter_quill_extensions.dart @@ -32,6 +32,10 @@ class FlutterQuillEmbeds { bool showVideoButton = true, bool showCameraButton = true, bool showFormulaButton = false, + String? imageButtonTooltip, + String? videoButtonTooltip, + String? cameraButtonTooltip, + String? formulaButtonTooltip, OnImagePickCallback? onImagePickCallback, OnVideoPickCallback? onVideoPickCallback, MediaPickSettingSelector? mediaPickSettingSelector, @@ -45,6 +49,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => ImageButton( icon: Icons.image, iconSize: toolbarIconSize, + tooltip: imageButtonTooltip, controller: controller, onImagePickCallback: onImagePickCallback, filePickImpl: filePickImpl, @@ -57,6 +62,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => VideoButton( icon: Icons.movie_creation, iconSize: toolbarIconSize, + tooltip: videoButtonTooltip, controller: controller, onVideoPickCallback: onVideoPickCallback, filePickImpl: filePickImpl, @@ -70,6 +76,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => CameraButton( icon: Icons.photo_camera, iconSize: toolbarIconSize, + tooltip: cameraButtonTooltip, controller: controller, onImagePickCallback: onImagePickCallback, onVideoPickCallback: onVideoPickCallback, @@ -83,6 +90,7 @@ class FlutterQuillEmbeds { (controller, toolbarIconSize, iconTheme, dialogTheme) => FormulaButton( icon: Icons.functions, iconSize: toolbarIconSize, + tooltip: formulaButtonTooltip, controller: controller, iconTheme: iconTheme, dialogTheme: dialogTheme, diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 5ca94b5c..2bb092ce 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_extensions description: Embed extensions for flutter_quill including image, video, formula and etc. -version: 0.2.0 +version: 0.2.1 homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions @@ -12,7 +12,7 @@ dependencies: flutter: sdk: flutter - flutter_quill: ^7.0.2 + flutter_quill: ^7.1.7 image_picker: ^0.8.5+3 photo_view: ^0.14.0 @@ -23,10 +23,6 @@ dependencies: string_validator: ^1.0.0 url_launcher: ^6.1.9 -# dependency_overrides: -# flutter_quill: -# path: ../ - dev_dependencies: flutter_test: sdk: flutter diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index aefc566c..bfc666f1 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -25,3 +25,4 @@ export 'src/widgets/embeds.dart'; export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; export 'src/widgets/style_widgets/style_widgets.dart'; export 'src/widgets/toolbar.dart'; +export 'src/widgets/toolbar/enum.dart'; diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 8ef6c196..44124fbc 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -35,20 +35,20 @@ class PreserveLineStyleOnSplitRule extends InsertRule { final itr = DeltaIterator(document); final before = itr.skip(index); - if (before == null || - before.data is! String || - (before.data as String).endsWith('\n')) { + if (before == null) { return null; } - final after = itr.next(); - if (after.data is! String || (after.data as String).startsWith('\n')) { + if (before.data is String && (before.data as String).endsWith('\n')) { return null; } - final text = after.data as String; + final after = itr.next(); + if (after.data is String && (after.data as String).startsWith('\n')) { + return null; + } final delta = Delta()..retain(index + (len ?? 0)); - if (text.contains('\n')) { + if (after.data is String && (after.data as String).contains('\n')) { assert(after.isPlain); delta.insert('\n'); return delta; diff --git a/lib/src/models/themes/quill_custom_button.dart b/lib/src/models/themes/quill_custom_button.dart index 0dbac618..4ea4e4f5 100644 --- a/lib/src/models/themes/quill_custom_button.dart +++ b/lib/src/models/themes/quill_custom_button.dart @@ -1,11 +1,18 @@ import 'package:flutter/material.dart'; class QuillCustomButton { - const QuillCustomButton({this.icon, this.onTap}); + const QuillCustomButton({ + this.icon, + this.onTap, + this.tooltip, + }); ///The icon widget final IconData? icon; ///The function when the icon is tapped final VoidCallback? onTap; + + /// The button tooltip. + final String? tooltip; } diff --git a/lib/src/translations/toolbar.i18n.dart b/lib/src/translations/toolbar.i18n.dart index 2abcfb21..cb6c7853 100644 --- a/lib/src/translations/toolbar.i18n.dart +++ b/lib/src/translations/toolbar.i18n.dart @@ -35,6 +35,32 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', }, 'en_us': { 'Paste a link': 'Paste a link', @@ -68,6 +94,32 @@ extension Localization on String { 'Next': 'Next', 'Camera': 'Camera', 'Video': 'Video', + 'Undo': 'Undo', + 'Redo': 'Redo', + 'Font family': 'Font family', + 'Font size': 'Font size', + 'Bold': 'Bold', + 'Italic': 'Italic', + 'Underline': 'Underline', + 'Strike through': 'Strike through', + 'Inline code': 'Inline code', + 'Font color': 'Font color', + 'Background color': 'Background color', + 'Clear format': 'Clear format', + 'Align left': 'Align left', + 'Align center': 'Align center', + 'Align right': 'Align right', + 'Justify win width': 'Justify win width', + 'Text direction': 'Text direction', + 'Header style': 'Header style', + 'Numbered list': 'Numbered list', + 'Bullet list': 'Bullet list', + 'Checked list': 'Checked list', + 'Code block': 'Code block', + 'Quote': 'Quote', + 'Increase indent': 'Increase indent', + 'Decrease indent': 'Decrease indent', + 'Insert URL': 'Insert URL', }, 'ar': { 'Paste a link': 'نسخ الرابط', diff --git a/lib/src/utils/font.dart b/lib/src/utils/font.dart index 4962a9a2..1e996e10 100644 --- a/lib/src/utils/font.dart +++ b/lib/src/utils/font.dart @@ -1,5 +1,6 @@ dynamic getFontSize(dynamic sizeValue) { - if (sizeValue is String && ['small', 'large', 'huge'].contains(sizeValue)) { + if (sizeValue is String && + ['small', 'normal', 'large', 'huge'].contains(sizeValue)) { return sizeValue; } diff --git a/lib/src/utils/widgets.dart b/lib/src/utils/widgets.dart new file mode 100644 index 00000000..c27777f0 --- /dev/null +++ b/lib/src/utils/widgets.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +typedef WidgetWrapper = Widget Function(Widget child); + +/// Provides utiulity widgets. +abstract class UtilityWidgets { + /// Conditionally wraps the [child] with [Tooltip] widget if [message] + /// is not null and not empty. + static Widget maybeTooltip({required Widget child, String? message}) => + (message ?? '').isNotEmpty + ? Tooltip(message: message!, child: child) + : child; + + /// Conditionally wraps the [child] with [wrapper] widget if [enabled] + /// is true. + static Widget maybeWidget( + {required WidgetWrapper wrapper, + required Widget child, + bool enabled = false}) => + enabled ? wrapper(child) : child; +} diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index e733c0e0..2a1bc5e3 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -182,6 +182,8 @@ class QuillEditor extends StatefulWidget { this.customShortcuts, this.customActions, this.detectWordBoundary = true, + this.enableUnfocusOnTapOutside = true, + this.customLinkPrefixes = const [], Key? key}) : super(key: key); @@ -245,6 +247,9 @@ class QuillEditor extends StatefulWidget { /// Defaults to `false`. Cannot be `null`. final bool autoFocus; + /// Whether focus should be revoked on tap outside the editor. + final bool enableUnfocusOnTapOutside; + /// Whether to show cursor. /// /// The cursor refers to the blinking caret when the editor is focused. @@ -401,6 +406,12 @@ class QuillEditor extends StatefulWidget { final bool detectWordBoundary; + /// Additional list if links prefixes, which must not be prepended + /// with "https://" when [LinkMenuAction.launch] happened + /// + /// Useful for deeplinks + final List customLinkPrefixes; + @override QuillEditorState createState() => QuillEditorState(); } @@ -498,6 +509,8 @@ class QuillEditorState extends State onImagePaste: widget.onImagePaste, customShortcuts: widget.customShortcuts, customActions: widget.customActions, + customLinkPrefixes: widget.customLinkPrefixes, + enableUnfocusOnTapOutside: widget.enableUnfocusOnTapOutside, ); final editor = I18n( @@ -1127,7 +1140,7 @@ class RenderEditor extends RenderEditableContainerBox start: localWord.start + nodeOffset, end: localWord.end + nodeOffset, ); - if (position.offset - word.start <= 1) { + if (position.offset - word.start <= 1 && word.end != position.offset) { _handleSelectionChange( TextSelection.collapsed(offset: word.start), cause, @@ -1786,7 +1799,10 @@ class RenderEditableContainerBox extends RenderBox dy += child.size.height; child = childAfter(child); } - throw StateError('No child at offset $offset.'); + + // this case possible, when editor not scrollable, + // but minHeight > content height and tap was under content + return lastChild!; } @override diff --git a/lib/src/widgets/embeds.dart b/lib/src/widgets/embeds.dart index 8cfe2657..8d72cd5b 100644 --- a/lib/src/widgets/embeds.dart +++ b/lib/src/widgets/embeds.dart @@ -6,6 +6,8 @@ import '../models/themes/quill_icon_theme.dart'; import 'controller.dart'; abstract class EmbedBuilder { + const EmbedBuilder(); + String get key; bool get expanded => true; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 474326d7..64059983 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -72,13 +72,15 @@ class RawEditor extends StatefulWidget { this.customActions, this.expands = false, this.autoFocus = false, + this.enableUnfocusOnTapOutside = true, this.keyboardAppearance = Brightness.light, this.enableInteractiveSelection = true, this.scrollPhysics, this.linkActionPickerDelegate = defaultLinkActionPickerDelegate, this.customStyleBuilder, this.floatingCursorDisabled = false, - this.onImagePaste}) + this.onImagePaste, + this.customLinkPrefixes = const []}) : assert(maxHeight == null || maxHeight > 0, 'maxHeight cannot be null'), assert(minHeight == null || minHeight >= 0, 'minHeight cannot be null'), assert(maxHeight == null || minHeight == null || maxHeight >= minHeight, @@ -94,6 +96,7 @@ class RawEditor extends StatefulWidget { final ScrollController scrollController; final bool scrollable; final double scrollBottomInset; + final bool enableUnfocusOnTapOutside; /// Additional space around the editor contents. final EdgeInsetsGeometry padding; @@ -125,9 +128,11 @@ class RawEditor extends StatefulWidget { BuildContext context, RawEditorState state, ) { - return AdaptiveTextSelectionToolbar.buttonItems( - buttonItems: state.contextMenuButtonItems, - anchors: state.contextMenuAnchors, + return TextFieldTapRegion( + child: AdaptiveTextSelectionToolbar.buttonItems( + buttonItems: state.contextMenuButtonItems, + anchors: state.contextMenuAnchors, + ), ); } @@ -248,6 +253,7 @@ class RawEditor extends StatefulWidget { final LinkActionPickerDelegate linkActionPickerDelegate; final CustomStyleBuilder? customStyleBuilder; final bool floatingCursorDisabled; + final List customLinkPrefixes; @override State createState() => RawEditorState(); @@ -490,6 +496,7 @@ class RawEditorState extends EditorState maxHeight: widget.maxHeight ?? double.infinity); return TextFieldTapRegion( + enabled: widget.enableUnfocusOnTapOutside, onTapOutside: _defaultOnTapOutside, child: QuillStyles( data: _styles!, @@ -661,18 +668,23 @@ class RawEditorState extends EditorState return insertTabCharacter(); } - if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { - return insertTabCharacter(); - } - final parentBlock = parent; if (parentBlock.style.containsKey(Attribute.ol.key) || parentBlock.style.containsKey(Attribute.ul.key) || parentBlock.style.containsKey(Attribute.checked.key)) { + if (node.isNotEmpty && + (node.first as leaf.Text).value.isNotEmpty && + controller.selection.base.offset > node.documentOffset) { + return insertTabCharacter(); + } controller.indentSelection(!event.isShiftPressed); return KeyEventResult.handled; } + if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { + return insertTabCharacter(); + } + return insertTabCharacter(); } @@ -754,13 +766,28 @@ class RawEditorState extends EditorState List _buildChildren(Document doc, BuildContext context) { final result = []; final indentLevelCounts = {}; + // this need for several ordered list in document + // we need to reset indents Map, if list finished + // List finished when there is node without Attribute.ol in styles + // So in this case we set clearIndents=true and send it + // to the next EditableTextBlock + var prevNodeOl = false; + var clearIndents = false; + for (final node in doc.root.children) { + final attrs = node.style.attributes; + + if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol) { + clearIndents = true; + } + + prevNodeOl = attrs[Attribute.list.key] == Attribute.ol; + if (node is Line) { final editableTextLine = _getEditableTextLineFromNode(node, context); result.add(Directionality( textDirection: getDirectionOfNode(node), child: editableTextLine)); } else if (node is Block) { - final attrs = node.style.attributes; final editableTextBlock = EditableTextBlock( block: node, controller: controller, @@ -780,11 +807,15 @@ class RawEditorState extends EditorState onLaunchUrl: widget.onLaunchUrl, cursorCont: _cursorCont, indentLevelCounts: indentLevelCounts, + clearIndents: clearIndents, onCheckboxTap: _handleCheckboxTap, readOnly: widget.readOnly, - customStyleBuilder: widget.customStyleBuilder); + customStyleBuilder: widget.customStyleBuilder, + customLinkPrefixes: widget.customLinkPrefixes); result.add(Directionality( textDirection: getDirectionOfNode(node), child: editableTextBlock)); + + clearIndents = false; } else { throw StateError('Unreachable.'); } @@ -804,6 +835,7 @@ class RawEditorState extends EditorState controller: controller, linkActionPicker: _linkActionPicker, onLaunchUrl: widget.onLaunchUrl, + customLinkPrefixes: widget.customLinkPrefixes, ); final editableTextLine = EditableTextLine( node, diff --git a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart index 5f98d56a..e3b9ff5f 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart @@ -333,14 +333,11 @@ mixin RawEditorStateTextInputClientMixin on EditorState if (hasConnection) { // Asking for renderEditor.size here can cause errors if layout hasn't // occurred yet. So we schedule a post frame callback instead. - SchedulerBinding.instance.addPostFrameCallback((_) { - if (!mounted) { - return; - } - final size = renderEditor.size; - final transform = renderEditor.getTransformTo(null); - _textInputConnection?.setEditableSizeAndTransform(size, transform); - }); + final size = renderEditor.size; + final transform = renderEditor.getTransformTo(null); + _textInputConnection?.setEditableSizeAndTransform(size, transform); + SchedulerBinding.instance + .addPostFrameCallback((_) => _updateSizeAndTransform()); } } } diff --git a/lib/src/widgets/style_widgets/number_point.dart b/lib/src/widgets/style_widgets/number_point.dart index 1dbf2261..54d5ebc9 100644 --- a/lib/src/widgets/style_widgets/number_point.dart +++ b/lib/src/widgets/style_widgets/number_point.dart @@ -31,6 +31,7 @@ class QuillNumberPoint extends StatelessWidget { int? level = 0; if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { indentLevelCounts.clear(); + indentLevelCounts[0] = 1; return Container( alignment: AlignmentDirectional.topEnd, width: width, diff --git a/lib/src/widgets/text_block.dart b/lib/src/widgets/text_block.dart index 8ece7cf5..b61ad9cf 100644 --- a/lib/src/widgets/text_block.dart +++ b/lib/src/widgets/text_block.dart @@ -68,10 +68,12 @@ class EditableTextBlock extends StatelessWidget { required this.linkActionPicker, required this.cursorCont, required this.indentLevelCounts, + required this.clearIndents, required this.onCheckboxTap, required this.readOnly, this.onLaunchUrl, this.customStyleBuilder, + this.customLinkPrefixes = const [], Key? key}); final Block block; @@ -91,8 +93,10 @@ class EditableTextBlock extends StatelessWidget { final CustomStyleBuilder? customStyleBuilder; final CursorCont cursorCont; final Map indentLevelCounts; + final bool clearIndents; final Function(int, bool) onCheckboxTap; final bool readOnly; + final List customLinkPrefixes; @override Widget build(BuildContext context) { @@ -107,7 +111,7 @@ class EditableTextBlock extends StatelessWidget { decoration: _getDecorationForBlock(block, defaultStyles) ?? const BoxDecoration(), contentPadding: contentPadding, - children: _buildChildren(context, indentLevelCounts)); + children: _buildChildren(context, indentLevelCounts, clearIndents)); } BoxDecoration? _getDecorationForBlock( @@ -122,11 +126,14 @@ class EditableTextBlock extends StatelessWidget { return null; } - List _buildChildren( - BuildContext context, Map indentLevelCounts) { + List _buildChildren(BuildContext context, + Map indentLevelCounts, bool clearIndents) { final defaultStyles = QuillStyles.getStyles(context, false); final count = block.children.length; final children = []; + if (clearIndents) { + indentLevelCounts.clear(); + } var index = 0; for (final line in Iterable.castFrom(block.children)) { index++; @@ -143,6 +150,7 @@ class EditableTextBlock extends StatelessWidget { controller: controller, linkActionPicker: linkActionPicker, onLaunchUrl: onLaunchUrl, + customLinkPrefixes: customLinkPrefixes, ), _getIndentWidth(), _getSpacingForLine(line, index, count, defaultStyles), diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index b7174a2f..22623f4d 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -41,6 +41,7 @@ class TextLine extends StatefulWidget { required this.linkActionPicker, this.textDirection, this.customStyleBuilder, + this.customLinkPrefixes = const [], Key? key, }) : super(key: key); @@ -53,6 +54,7 @@ class TextLine extends StatefulWidget { final CustomStyleBuilder? customStyleBuilder; final ValueChanged? onLaunchUrl; final LinkActionPicker linkActionPicker; + final List customLinkPrefixes; @override State createState() => _TextLineState(); @@ -430,7 +432,7 @@ class _TextLineState extends State { launchUrl ??= _launchUrl; link = link.trim(); - if (!linkPrefixes + if (!(widget.customLinkPrefixes + linkPrefixes) .any((linkPrefix) => link!.toLowerCase().startsWith(linkPrefix))) { link = 'https://$link'; } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 58171ad1..d5a5c93a 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -6,12 +6,12 @@ import '../models/themes/quill_custom_button.dart'; import '../models/themes/quill_dialog_theme.dart'; import '../models/themes/quill_icon_theme.dart'; import '../translations/toolbar.i18n.dart'; -import '../utils/font.dart'; import 'controller.dart'; import 'embeds.dart'; import 'toolbar/arrow_indicated_button_list.dart'; import 'toolbar/clear_format_button.dart'; import 'toolbar/color_button.dart'; +import 'toolbar/enum.dart'; import 'toolbar/history_button.dart'; import 'toolbar/indent_button.dart'; import 'toolbar/link_style_button.dart'; @@ -29,32 +29,39 @@ export 'toolbar/color_button.dart'; export 'toolbar/history_button.dart'; export 'toolbar/indent_button.dart'; export 'toolbar/link_style_button.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. +/// 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. +/// 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; + class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { const QuillToolbar({ required this.children, this.axis = Axis.horizontal, - this.toolbarSize = 36, + this.toolbarSize = kDefaultIconSize * 2, + this.toolbarSectionSpacing = kToolbarSectionSpacing, this.toolbarIconAlignment = WrapAlignment.center, this.toolbarIconCrossAlignment = WrapCrossAlignment.center, - this.toolbarSectionSpacing = 4, this.multiRowsDisplay = true, this.color, this.customButtons = const [], this.locale, VoidCallback? afterButtonPressed, + this.sectionDividerColor, + this.sectionDividerSpace, Key? key, }) : super(key: key); @@ -62,9 +69,10 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { required QuillController controller, Axis axis = Axis.horizontal, double toolbarIconSize = kDefaultIconSize, - double toolbarSectionSpacing = 4, + double toolbarSectionSpacing = kToolbarSectionSpacing, WrapAlignment toolbarIconAlignment = WrapAlignment.center, WrapCrossAlignment toolbarIconCrossAlignment = WrapCrossAlignment.center, + bool multiRowsDisplay = true, bool showDividers = true, bool showFontFamily = true, bool showFontSize = true, @@ -92,7 +100,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { bool showLink = true, bool showUndo = true, bool showRedo = true, - bool multiRowsDisplay = true, bool showDirection = false, bool showSearchButton = true, List customButtons = const [], @@ -117,12 +124,32 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// Is called after whatever logic the button performs has run. VoidCallback? afterButtonPressed, + ///Map of tooltips for toolbar buttons + /// + ///The example is: + ///```dart + /// tooltips = { + /// ToolbarButtons.undo: 'Undo', + /// ToolbarButtons.redo: 'Redo', + /// } + /// + ///``` + /// + /// To disable tooltips just pass empty map as well. + Map? tooltips, + /// The locale to use for the editor toolbar, defaults to system locale /// More at https://github.com/singerdmx/flutter-quill#translation Locale? locale, /// The color of the toolbar Color? color, + + /// The color of the toolbar section divider + Color? sectionDividerColor, + + /// The space occupied by toolbar divider + double? sectionDividerSpace, Key? key, }) { final isButtonGroupShown = [ @@ -172,6 +199,39 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { '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, + ToolbarButtons.italic: 'Italic'.i18n, + ToolbarButtons.small: 'Small'.i18n, + ToolbarButtons.underline: 'Underline'.i18n, + ToolbarButtons.strikeThrough: 'Strike through'.i18n, + ToolbarButtons.inlineCode: 'Inline code'.i18n, + ToolbarButtons.color: 'Font color'.i18n, + ToolbarButtons.backgroundColor: 'Background color'.i18n, + ToolbarButtons.clearFormat: 'Clear format'.i18n, + ToolbarButtons.leftAlignment: 'Align left'.i18n, + ToolbarButtons.centerAlignment: 'Align center'.i18n, + ToolbarButtons.rightAlignment: 'Align right'.i18n, + ToolbarButtons.justifyAlignment: 'Justify win width'.i18n, + ToolbarButtons.direction: 'Text direction'.i18n, + ToolbarButtons.headerStyle: 'Header style'.i18n, + ToolbarButtons.listNumbers: 'Numbered list'.i18n, + ToolbarButtons.listBullets: 'Bullet list'.i18n, + ToolbarButtons.listChecks: 'Checked list'.i18n, + ToolbarButtons.codeBlock: 'Code block'.i18n, + ToolbarButtons.quote: 'Quote'.i18n, + ToolbarButtons.indentIncrease: 'Increase indent'.i18n, + ToolbarButtons.indentDecrease: 'Decrease indent'.i18n, + ToolbarButtons.link: 'Insert URL'.i18n, + ToolbarButtons.search: 'Search'.i18n, + }; + return QuillToolbar( key: key, axis: axis, @@ -189,6 +249,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { HistoryButton( icon: Icons.undo_outlined, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.undo], controller: controller, undo: true, iconTheme: iconTheme, @@ -198,6 +259,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { HistoryButton( icon: Icons.redo_outlined, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.redo], controller: controller, undo: false, iconTheme: iconTheme, @@ -207,23 +269,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { QuillFontFamilyButton( iconTheme: iconTheme, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.fontFamily], attribute: Attribute.font, controller: controller, - items: [ - for (MapEntry fontFamily in fontFamilies.entries) - PopupMenuItem( - key: ValueKey(fontFamily.key), - value: fontFamily.value, - child: Text(fontFamily.key.toString(), - style: TextStyle( - color: - fontFamily.value == 'Clear' ? Colors.red : null)), - ), - ], - onSelected: (newFont) { - controller.formatSelection(Attribute.fromKeyValue( - 'font', newFont == 'Clear' ? null : newFont)); - }, rawItemsMap: fontFamilies, afterButtonPressed: afterButtonPressed, ), @@ -231,22 +279,9 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { QuillFontSizeButton( iconTheme: iconTheme, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.fontSize], attribute: Attribute.size, controller: controller, - items: [ - for (MapEntry fontSize in fontSizes.entries) - PopupMenuItem( - key: ValueKey(fontSize.key), - value: fontSize.value, - child: Text(fontSize.key.toString(), - style: TextStyle( - color: fontSize.value == '0' ? Colors.red : null)), - ), - ], - onSelected: (newSize) { - controller.formatSelection(Attribute.fromKeyValue( - 'size', newSize == '0' ? null : getFontSize(newSize))); - }, rawItemsMap: fontSizes, afterButtonPressed: afterButtonPressed, ), @@ -255,6 +290,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.bold, icon: Icons.format_bold, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.bold], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -264,6 +300,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.italic, icon: Icons.format_italic, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.italic], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -273,6 +310,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.small, icon: Icons.format_size, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.small], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -282,6 +320,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.underline, icon: Icons.format_underline, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.underline], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -291,6 +330,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.strikeThrough, icon: Icons.format_strikethrough, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.strikeThrough], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -300,6 +340,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { attribute: Attribute.inlineCode, icon: Icons.code, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.inlineCode], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -308,6 +349,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ColorButton( icon: Icons.color_lens, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.color], controller: controller, background: false, iconTheme: iconTheme, @@ -317,6 +359,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ColorButton( icon: Icons.format_color_fill, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.backgroundColor], controller: controller, background: true, iconTheme: iconTheme, @@ -326,6 +369,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ClearFormatButton( icon: Icons.format_clear, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.clearFormat], controller: controller, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, @@ -340,10 +384,18 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showAlignmentButtons) SelectAlignmentButton( controller: controller, + tooltips: Map.of(buttonTooltips) + ..removeWhere((key, value) => ![ + ToolbarButtons.leftAlignment, + ToolbarButtons.centerAlignment, + ToolbarButtons.rightAlignment, + ToolbarButtons.justifyAlignment, + ].contains(key)), iconSize: toolbarIconSize, iconTheme: iconTheme, showLeftAlignment: showLeftAlignment, @@ -355,6 +407,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDirection) ToggleStyleButton( attribute: Attribute.rtl, + tooltip: buttonTooltips[ToolbarButtons.direction], controller: controller, icon: Icons.format_textdirection_r_to_l, iconSize: toolbarIconSize, @@ -367,9 +420,11 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showHeaderStyle) SelectHeaderStyleButton( + tooltip: buttonTooltips[ToolbarButtons.headerStyle], controller: controller, axis: axis, iconSize: toolbarIconSize, @@ -382,10 +437,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { (isButtonGroupShown[3] || isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showListNumbers) ToggleStyleButton( attribute: Attribute.ol, + tooltip: buttonTooltips[ToolbarButtons.listNumbers], controller: controller, icon: Icons.format_list_numbered, iconSize: toolbarIconSize, @@ -395,6 +452,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showListBullets) ToggleStyleButton( attribute: Attribute.ul, + tooltip: buttonTooltips[ToolbarButtons.listBullets], controller: controller, icon: Icons.format_list_bulleted, iconSize: toolbarIconSize, @@ -404,6 +462,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showListCheck) ToggleCheckListButton( attribute: Attribute.unchecked, + tooltip: buttonTooltips[ToolbarButtons.listChecks], controller: controller, icon: Icons.check_box, iconSize: toolbarIconSize, @@ -413,6 +472,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showCodeBlock) ToggleStyleButton( attribute: Attribute.codeBlock, + tooltip: buttonTooltips[ToolbarButtons.codeBlock], controller: controller, icon: Icons.code, iconSize: toolbarIconSize, @@ -422,10 +482,12 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { if (showDividers && isButtonGroupShown[3] && (isButtonGroupShown[4] || isButtonGroupShown[5])) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showQuote) ToggleStyleButton( attribute: Attribute.blockQuote, + tooltip: buttonTooltips[ToolbarButtons.quote], controller: controller, icon: Icons.format_quote, iconSize: toolbarIconSize, @@ -436,6 +498,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { IndentButton( icon: Icons.format_indent_increase, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.indentIncrease], controller: controller, isIncrease: true, iconTheme: iconTheme, @@ -445,15 +508,18 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { IndentButton( icon: Icons.format_indent_decrease, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.indentDecrease], controller: controller, isIncrease: false, iconTheme: iconTheme, afterButtonPressed: afterButtonPressed, ), if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5]) - _dividerOnAxis(axis), + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), if (showLink) LinkStyleButton( + tooltip: buttonTooltips[ToolbarButtons.link], controller: controller, iconSize: toolbarIconSize, iconTheme: iconTheme, @@ -464,19 +530,23 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { SearchButton( icon: Icons.search, iconSize: toolbarIconSize, + tooltip: buttonTooltips[ToolbarButtons.search], controller: controller, iconTheme: iconTheme, dialogTheme: dialogTheme, afterButtonPressed: afterButtonPressed, ), if (customButtons.isNotEmpty) - if (showDividers) _dividerOnAxis(axis), + if (showDividers) + AxisDivider(axis, + color: sectionDividerColor, space: sectionDividerSpace), for (var customButton in customButtons) QuillIconButton( highlightElevation: 0, hoverElevation: 0, size: toolbarIconSize * kIconButtonFactor, icon: Icon(customButton.icon, size: toolbarIconSize), + tooltip: customButton.tooltip, borderRadius: iconTheme?.borderRadius ?? 2, onPressed: customButton.onTap, afterPressed: afterButtonPressed, @@ -485,22 +555,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } - static Widget _dividerOnAxis(Axis axis) { - if (axis == Axis.horizontal) { - return const VerticalDivider( - indent: 12, - endIndent: 12, - color: Colors.grey, - ); - } else { - return const Divider( - indent: 12, - endIndent: 12, - color: Colors.grey, - ); - } - } - final List children; final Axis axis; final double toolbarSize; @@ -522,6 +576,15 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { /// List of custom buttons final List customButtons; + /// The color to use when painting the toolbar section divider. + /// + /// If this is null, then the [DividerThemeData.color] is used. If that is + /// also null, then [ThemeData.dividerColor] is used. + final Color? sectionDividerColor; + + /// The space occupied by toolbar section divider. + final double? sectionDividerSpace; + @override Size get preferredSize => axis == Axis.horizontal ? Size.fromHeight(toolbarSize) @@ -554,3 +617,39 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { ); } } + +class AxisDivider extends StatelessWidget { + const AxisDivider( + this.axis, { + Key? key, + this.color, + this.space, + }) : super(key: key); + + const AxisDivider.horizontal({Color? color, double? space}) + : this(Axis.horizontal, color: color, space: space); + + const AxisDivider.vertical({Color? color, double? space}) + : this(Axis.vertical, color: color, space: space); + + final Axis axis; + final Color? color; + final double? space; + + @override + Widget build(BuildContext context) { + return axis == Axis.horizontal + ? Divider( + height: space, + color: color, + indent: 12, + endIndent: 12, + ) + : VerticalDivider( + width: space, + color: color, + indent: 12, + endIndent: 12, + ); + } +} diff --git a/lib/src/widgets/toolbar/clear_format_button.dart b/lib/src/widgets/toolbar/clear_format_button.dart index f601bd28..14610232 100644 --- a/lib/src/widgets/toolbar/clear_format_button.dart +++ b/lib/src/widgets/toolbar/clear_format_button.dart @@ -12,6 +12,7 @@ class ClearFormatButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -22,6 +23,7 @@ class ClearFormatButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ClearFormatButtonState createState() => _ClearFormatButtonState(); @@ -36,6 +38,7 @@ class _ClearFormatButtonState extends State { final fillColor = widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/color_button.dart b/lib/src/widgets/toolbar/color_button.dart index 087ae94f..60f0a591 100644 --- a/lib/src/widgets/toolbar/color_button.dart +++ b/lib/src/widgets/toolbar/color_button.dart @@ -21,6 +21,7 @@ class ColorButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -30,6 +31,7 @@ class ColorButton extends StatefulWidget { final QuillController controller; final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ColorButtonState createState() => _ColorButtonState(); @@ -119,6 +121,7 @@ class _ColorButtonState extends State { : (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/enum.dart b/lib/src/widgets/toolbar/enum.dart new file mode 100644 index 00000000..197bea56 --- /dev/null +++ b/lib/src/widgets/toolbar/enum.dart @@ -0,0 +1,30 @@ +enum ToolbarButtons { + undo, + redo, + fontFamily, + fontSize, + bold, + italic, + small, + underline, + strikeThrough, + inlineCode, + color, + backgroundColor, + clearFormat, + centerAlignment, + leftAlignment, + rightAlignment, + justifyAlignment, + direction, + headerStyle, + listNumbers, + listBullets, + listChecks, + codeBlock, + quote, + indentIncrease, + indentDecrease, + link, + search, +} diff --git a/lib/src/widgets/toolbar/history_button.dart b/lib/src/widgets/toolbar/history_button.dart index a3abc7f2..6d3c29ad 100644 --- a/lib/src/widgets/toolbar/history_button.dart +++ b/lib/src/widgets/toolbar/history_button.dart @@ -12,6 +12,7 @@ class HistoryButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -21,6 +22,7 @@ class HistoryButton extends StatefulWidget { final QuillController controller; final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _HistoryButtonState createState() => _HistoryButtonState(); @@ -41,6 +43,7 @@ class _HistoryButtonState extends State { _setIconColor(); }); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * 1.77, diff --git a/lib/src/widgets/toolbar/indent_button.dart b/lib/src/widgets/toolbar/indent_button.dart index 129ef8b1..1ce83e99 100644 --- a/lib/src/widgets/toolbar/indent_button.dart +++ b/lib/src/widgets/toolbar/indent_button.dart @@ -12,6 +12,7 @@ class IndentButton extends StatefulWidget { this.iconSize = kDefaultIconSize, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -22,6 +23,7 @@ class IndentButton extends StatefulWidget { final VoidCallback? afterButtonPressed; final QuillIconTheme? iconTheme; + final String? tooltip; @override _IndentButtonState createState() => _IndentButtonState(); @@ -37,6 +39,7 @@ class _IndentButtonState extends State { final iconFillColor = widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor; return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * 1.77, diff --git a/lib/src/widgets/toolbar/link_style_button.dart b/lib/src/widgets/toolbar/link_style_button.dart index f48a15e4..a0865941 100644 --- a/lib/src/widgets/toolbar/link_style_button.dart +++ b/lib/src/widgets/toolbar/link_style_button.dart @@ -17,6 +17,7 @@ class LinkStyleButton extends StatefulWidget { this.iconTheme, this.dialogTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -26,6 +27,7 @@ class LinkStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final QuillDialogTheme? dialogTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _LinkStyleButtonState createState() => _LinkStyleButtonState(); @@ -63,6 +65,7 @@ class _LinkStyleButtonState extends State { final isToggled = _getLinkAttributeValue() != null; final pressedHandler = () => _openLinkDialog(context); return QuillIconButton( + tooltip: widget.tooltip, highlightElevation: 0, hoverElevation: 0, size: widget.iconSize * kIconButtonFactor, diff --git a/lib/src/widgets/toolbar/quill_font_family_button.dart b/lib/src/widgets/toolbar/quill_font_family_button.dart index 88b3dda4..8108c200 100644 --- a/lib/src/widgets/toolbar/quill_font_family_button.dart +++ b/lib/src/widgets/toolbar/quill_font_family_button.dart @@ -4,35 +4,61 @@ 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.items, required this.rawItemsMap, required this.attribute, required this.controller, - required this.onSelected, + @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, - }) : super(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; - final List> items; + @Deprecated('It is not required because of `rawItemsMap`') + final List>? items; final Map rawItemsMap; - final ValueChanged onSelected; + 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; @override _QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState(); @@ -46,7 +72,7 @@ class _QuillFontFamilyButtonState extends State { @override void initState() { super.initState(); - _currentValue = _defaultDisplayText = 'Font'.i18n; + _currentValue = _defaultDisplayText = widget.initialValue ?? 'Font'.i18n; widget.controller.addListener(_didChangeEditingValue); } @@ -87,21 +113,37 @@ class _QuillFontFamilyButtonState extends State { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), - 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(); + 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: _buildContent(context), + 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), + ), ), ); } @@ -121,7 +163,24 @@ class _QuillFontFamilyButtonState extends State { showMenu( context: context, elevation: 4, - items: widget.items, + items: [ + for (MapEntry fontFamily in widget.rawItemsMap.entries) + PopupMenuItem( + 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, @@ -134,7 +193,9 @@ class _QuillFontFamilyButtonState extends State { setState(() { _currentValue = keyName ?? _defaultDisplayText; if (keyName != null) { - widget.onSelected(newValue); + widget.controller.formatSelection(Attribute.fromKeyValue( + 'font', newValue == 'Clear' ? null : newValue)); + widget.onSelected?.call(newValue); } }); }); @@ -142,16 +203,27 @@ class _QuillFontFamilyButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); + final hasFinalWidth = widget.width != null; return Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(_currentValue, - style: TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + 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, diff --git a/lib/src/widgets/toolbar/quill_font_size_button.dart b/lib/src/widgets/toolbar/quill_font_size_button.dart index da6fa187..e445c0ae 100644 --- a/lib/src/widgets/toolbar/quill_font_size_button.dart +++ b/lib/src/widgets/toolbar/quill_font_size_button.dart @@ -5,35 +5,57 @@ 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({ - required this.items, required this.rawItemsMap, required this.attribute, required this.controller, - required this.onSelected, + this.onSelected, + @Deprecated('It is not required because of `rawItemsMap`') this.items, this.iconSize = 40, this.fillColor, this.hoverElevation = 1, this.highlightElevation = 1, this.iconTheme, this.afterButtonPressed, + this.tooltip, + this.padding, + this.style, + this.width, + this.initialValue, + this.labelOverflow = TextOverflow.visible, + this.itemHeight, + this.itemPadding, + this.defaultItemColor = Colors.red, Key? key, - }) : super(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; - final List> items; + @Deprecated('It is not required because of `rawItemsMap`') + final List>? items; final Map rawItemsMap; - final ValueChanged onSelected; + 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 String? initialValue; + final TextOverflow labelOverflow; + final double? itemHeight; + final EdgeInsets? itemPadding; + final Color? defaultItemColor; @override _QuillFontSizeButtonState createState() => _QuillFontSizeButtonState(); @@ -47,7 +69,7 @@ class _QuillFontSizeButtonState extends State { @override void initState() { super.initState(); - _currentValue = _defaultDisplayText = 'Size'.i18n; + _currentValue = _defaultDisplayText = widget.initialValue ?? 'Size'.i18n; widget.controller.addListener(_didChangeEditingValue); } @@ -88,21 +110,27 @@ class _QuillFontSizeButtonState extends State { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81), - 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), + constraints: BoxConstraints.tightFor( + height: widget.iconSize * 1.81, + width: widget.width, + ), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltip, + 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), + ), ), ); } @@ -122,7 +150,21 @@ class _QuillFontSizeButtonState extends State { showMenu( context: context, elevation: 4, - items: widget.items, + items: [ + for (MapEntry fontSize in widget.rawItemsMap.entries) + PopupMenuItem( + key: ValueKey(fontSize.key), + value: fontSize.value, + height: widget.itemHeight ?? kMinInteractiveDimension, + padding: widget.itemPadding, + child: Text( + fontSize.key.toString(), + style: TextStyle( + color: fontSize.value == '0' ? widget.defaultItemColor : null, + ), + ), + ), + ], position: position, shape: popupMenuTheme.shape, color: popupMenuTheme.color, @@ -135,7 +177,9 @@ class _QuillFontSizeButtonState extends State { setState(() { _currentValue = keyName ?? _defaultDisplayText; if (keyName != null) { - widget.onSelected(newValue); + widget.controller.formatSelection(Attribute.fromKeyValue( + 'size', newValue == '0' ? null : getFontSize(newValue))); + widget.onSelected?.call(newValue); } }); }); @@ -143,16 +187,24 @@ class _QuillFontSizeButtonState extends State { Widget _buildContent(BuildContext context) { final theme = Theme.of(context); + final hasFinalWidth = widget.width != null; return Padding( - padding: const EdgeInsets.fromLTRB(10, 0, 0, 0), + padding: widget.padding ?? const EdgeInsets.fromLTRB(10, 0, 0, 0), child: Row( - mainAxisSize: MainAxisSize.min, + mainAxisSize: !hasFinalWidth ? MainAxisSize.min : MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Text(_currentValue, - style: TextStyle( - fontSize: widget.iconSize / 1.15, - color: widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color)), + UtilityWidgets.maybeWidget( + enabled: hasFinalWidth, + wrapper: (child) => Expanded(child: child), + child: Text(_currentValue, + 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, diff --git a/lib/src/widgets/toolbar/quill_icon_button.dart b/lib/src/widgets/toolbar/quill_icon_button.dart index 7714187c..86c5b30b 100644 --- a/lib/src/widgets/toolbar/quill_icon_button.dart +++ b/lib/src/widgets/toolbar/quill_icon_button.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; +import '../../utils/widgets.dart'; + class QuillIconButton extends StatelessWidget { const QuillIconButton({ required this.onPressed, @@ -10,6 +12,7 @@ class QuillIconButton extends StatelessWidget { this.hoverElevation = 1, this.highlightElevation = 1, this.borderRadius = 2, + this.tooltip, Key? key, }) : super(key: key); @@ -21,24 +24,28 @@ class QuillIconButton extends StatelessWidget { final double hoverElevation; final double highlightElevation; final double borderRadius; + final String? tooltip; @override Widget build(BuildContext context) { return ConstrainedBox( constraints: BoxConstraints.tightFor(width: size, height: size), - child: RawMaterialButton( - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(borderRadius)), - fillColor: fillColor, - elevation: 0, - hoverElevation: hoverElevation, - highlightElevation: hoverElevation, - onPressed: () { - onPressed?.call(); - afterPressed?.call(); - }, - child: icon, + child: UtilityWidgets.maybeTooltip( + message: tooltip, + child: RawMaterialButton( + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(borderRadius)), + fillColor: fillColor, + elevation: 0, + hoverElevation: hoverElevation, + highlightElevation: hoverElevation, + onPressed: () { + onPressed?.call(); + afterPressed?.call(); + }, + child: icon, + ), ), ); } diff --git a/lib/src/widgets/toolbar/search_button.dart b/lib/src/widgets/toolbar/search_button.dart index b9436bf1..9233cf45 100644 --- a/lib/src/widgets/toolbar/search_button.dart +++ b/lib/src/widgets/toolbar/search_button.dart @@ -15,6 +15,7 @@ class SearchButton extends StatelessWidget { this.iconTheme, this.dialogTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -27,6 +28,7 @@ class SearchButton extends StatelessWidget { final QuillDialogTheme? dialogTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override Widget build(BuildContext context) { @@ -37,6 +39,7 @@ class SearchButton extends StatelessWidget { iconTheme?.iconUnselectedFillColor ?? (fillColor ?? theme.canvasColor); return QuillIconButton( + tooltip: tooltip, icon: Icon(icon, size: iconSize, color: iconColor), highlightElevation: 0, hoverElevation: 0, diff --git a/lib/src/widgets/toolbar/select_alignment_button.dart b/lib/src/widgets/toolbar/select_alignment_button.dart index 596b095d..922b30fc 100644 --- a/lib/src/widgets/toolbar/select_alignment_button.dart +++ b/lib/src/widgets/toolbar/select_alignment_button.dart @@ -4,8 +4,10 @@ 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'; class SelectAlignmentButton extends StatefulWidget { const SelectAlignmentButton({ @@ -17,6 +19,8 @@ class SelectAlignmentButton extends StatefulWidget { this.showRightAlignment, this.showJustifyAlignment, this.afterButtonPressed, + this.tooltips = const {}, + this.padding, Key? key, }) : super(key: key); @@ -29,6 +33,8 @@ class SelectAlignmentButton extends StatefulWidget { final bool? showRightAlignment; final bool? showJustifyAlignment; final VoidCallback? afterButtonPressed; + final Map tooltips; + final EdgeInsetsGeometry? padding; @override _SelectAlignmentButtonState createState() => _SelectAlignmentButtonState(); @@ -74,6 +80,16 @@ class _SelectAlignmentButtonState extends State { if (widget.showRightAlignment!) Attribute.rightAlignment.value!, if (widget.showJustifyAlignment!) Attribute.justifyAlignment.value!, ]; + final _valueToButtons = { + if (widget.showLeftAlignment!) + Attribute.leftAlignment: ToolbarButtons.leftAlignment, + if (widget.showCenterAlignment!) + Attribute.centerAlignment: ToolbarButtons.centerAlignment, + if (widget.showRightAlignment!) + Attribute.rightAlignment: ToolbarButtons.rightAlignment, + if (widget.showJustifyAlignment!) + Attribute.justifyAlignment: ToolbarButtons.justifyAlignment, + }; final theme = Theme.of(context); @@ -86,47 +102,52 @@ class _SelectAlignmentButtonState extends State { mainAxisSize: MainAxisSize.min, children: List.generate(buttonCount, (index) { return Padding( - // ignore: prefer_const_constructors - padding: EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), + padding: widget.padding ?? + const EdgeInsets.symmetric(horizontal: !kIsWeb ? 1.0 : 5.0), child: ConstrainedBox( constraints: BoxConstraints.tightFor( width: widget.iconSize * kIconButtonFactor, height: widget.iconSize * kIconButtonFactor, ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular( - widget.iconTheme?.borderRadius ?? 2)), - fillColor: _valueToText[_value] == _valueString[index] - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - _valueAttribute[index] == Attribute.leftAlignment - ? widget.controller - .formatSelection(Attribute.clone(Attribute.align, null)) - : widget.controller.formatSelection(_valueAttribute[index]); - widget.afterButtonPressed?.call(); - }, - child: Icon( - _valueString[index] == Attribute.leftAlignment.value - ? Icons.format_align_left - : _valueString[index] == Attribute.centerAlignment.value - ? Icons.format_align_center - : _valueString[index] == Attribute.rightAlignment.value - ? Icons.format_align_right - : Icons.format_align_justify, - size: widget.iconSize, - color: _valueToText[_value] == _valueString[index] - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltips[_valueToButtons[_valueAttribute[index]]], + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: _valueToText[_value] == _valueString[index] + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + _valueAttribute[index] == Attribute.leftAlignment + ? widget.controller.formatSelection( + Attribute.clone(Attribute.align, null)) + : widget.controller + .formatSelection(_valueAttribute[index]); + widget.afterButtonPressed?.call(); + }, + child: Icon( + _valueString[index] == Attribute.leftAlignment.value + ? Icons.format_align_left + : _valueString[index] == Attribute.centerAlignment.value + ? Icons.format_align_center + : _valueString[index] == + Attribute.rightAlignment.value + ? Icons.format_align_right + : Icons.format_align_justify, + size: widget.iconSize, + color: _valueToText[_value] == _valueString[index] + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), + ), ), ), ), diff --git a/lib/src/widgets/toolbar/select_header_style_button.dart b/lib/src/widgets/toolbar/select_header_style_button.dart index f27998b8..986abc22 100644 --- a/lib/src/widgets/toolbar/select_header_style_button.dart +++ b/lib/src/widgets/toolbar/select_header_style_button.dart @@ -4,6 +4,7 @@ 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'; @@ -20,6 +21,7 @@ class SelectHeaderStyleButton extends StatefulWidget { Attribute.h3, ], this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -29,6 +31,7 @@ class SelectHeaderStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final List attributes; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _SelectHeaderStyleButtonState createState() => @@ -79,34 +82,37 @@ class _SelectHeaderStyleButtonState extends State { width: widget.iconSize * kIconButtonFactor, height: widget.iconSize * kIconButtonFactor, ), - child: RawMaterialButton( - hoverElevation: 0, - highlightElevation: 0, - elevation: 0, - visualDensity: VisualDensity.compact, - shape: RoundedRectangleBorder( - borderRadius: - BorderRadius.circular(widget.iconTheme?.borderRadius ?? 2)), - fillColor: isSelected - ? (widget.iconTheme?.iconSelectedFillColor ?? - Theme.of(context).primaryColor) - : (widget.iconTheme?.iconUnselectedFillColor ?? - theme.canvasColor), - onPressed: () { - final _attribute = _selectedAttribute == attribute - ? Attribute.header - : attribute; - widget.controller.formatSelection(_attribute); - widget.afterButtonPressed?.call(); - }, - child: Text( - _valueToText[attribute] ?? '', - style: style.copyWith( - color: isSelected - ? (widget.iconTheme?.iconSelectedColor ?? - theme.primaryIconTheme.color) - : (widget.iconTheme?.iconUnselectedColor ?? - theme.iconTheme.color), + child: UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: RawMaterialButton( + hoverElevation: 0, + highlightElevation: 0, + elevation: 0, + visualDensity: VisualDensity.compact, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular( + widget.iconTheme?.borderRadius ?? 2)), + fillColor: isSelected + ? (widget.iconTheme?.iconSelectedFillColor ?? + Theme.of(context).primaryColor) + : (widget.iconTheme?.iconUnselectedFillColor ?? + theme.canvasColor), + onPressed: () { + final _attribute = _selectedAttribute == attribute + ? Attribute.header + : attribute; + widget.controller.formatSelection(_attribute); + widget.afterButtonPressed?.call(); + }, + child: Text( + _valueToText[attribute] ?? '', + style: style.copyWith( + color: isSelected + ? (widget.iconTheme?.iconSelectedColor ?? + theme.primaryIconTheme.color) + : (widget.iconTheme?.iconUnselectedColor ?? + theme.iconTheme.color), + ), ), ), ), diff --git a/lib/src/widgets/toolbar/toggle_check_list_button.dart b/lib/src/widgets/toolbar/toggle_check_list_button.dart index 037c47cd..6912916b 100644 --- a/lib/src/widgets/toolbar/toggle_check_list_button.dart +++ b/lib/src/widgets/toolbar/toggle_check_list_button.dart @@ -3,6 +3,7 @@ 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'; @@ -16,6 +17,7 @@ class ToggleCheckListButton extends StatefulWidget { this.childBuilder = defaultToggleStyleButtonBuilder, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -32,6 +34,7 @@ class ToggleCheckListButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ToggleCheckListButtonState createState() => _ToggleCheckListButtonState(); @@ -91,16 +94,19 @@ class _ToggleCheckListButtonState extends State { @override Widget build(BuildContext context) { - return widget.childBuilder( - context, - Attribute.unchecked, - widget.icon, - widget.fillColor, - _isToggled, - _toggleAttribute, - widget.afterButtonPressed, - widget.iconSize, - widget.iconTheme, + return UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: widget.childBuilder( + context, + Attribute.unchecked, + widget.icon, + widget.fillColor, + _isToggled, + _toggleAttribute, + widget.afterButtonPressed, + widget.iconSize, + widget.iconTheme, + ), ); } diff --git a/lib/src/widgets/toolbar/toggle_style_button.dart b/lib/src/widgets/toolbar/toggle_style_button.dart index 176b96b0..1eb0eb57 100644 --- a/lib/src/widgets/toolbar/toggle_style_button.dart +++ b/lib/src/widgets/toolbar/toggle_style_button.dart @@ -3,6 +3,7 @@ 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'; @@ -28,6 +29,7 @@ class ToggleStyleButton extends StatefulWidget { this.childBuilder = defaultToggleStyleButtonBuilder, this.iconTheme, this.afterButtonPressed, + this.tooltip, Key? key, }) : super(key: key); @@ -46,6 +48,7 @@ class ToggleStyleButton extends StatefulWidget { final QuillIconTheme? iconTheme; final VoidCallback? afterButtonPressed; + final String? tooltip; @override _ToggleStyleButtonState createState() => _ToggleStyleButtonState(); @@ -65,16 +68,19 @@ class _ToggleStyleButtonState extends State { @override Widget build(BuildContext context) { - return widget.childBuilder( - context, - widget.attribute, - widget.icon, - widget.fillColor, - _isToggled, - _toggleAttribute, - widget.afterButtonPressed, - widget.iconSize, - widget.iconTheme, + return UtilityWidgets.maybeTooltip( + message: widget.tooltip, + child: widget.childBuilder( + context, + widget.attribute, + widget.icon, + widget.fillColor, + _isToggled, + _toggleAttribute, + widget.afterButtonPressed, + widget.iconSize, + widget.iconTheme, + ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index 6b051d89..38a3d504 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor supporting mobile and web (Demo App @ bulletjournal.us) -version: 7.0.5 +version: 7.1.8 #author: bulletjournal homepage: https://bulletjournal.us/home/index.html repository: https://github.com/singerdmx/flutter-quill