Merge branch 'singerdmx:master' into master

pull/1270/head
Cierra_Runis 2 years ago committed by GitHub
commit ddf14121aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 39
      CHANGELOG.md
  2. 3
      flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
  3. 3
      flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart
  4. 3
      flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
  5. 3
      flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
  6. 8
      flutter_quill_extensions/lib/flutter_quill_extensions.dart
  7. 8
      flutter_quill_extensions/pubspec.yaml
  8. 1
      lib/flutter_quill.dart
  9. 14
      lib/src/models/rules/insert.dart
  10. 9
      lib/src/models/themes/quill_custom_button.dart
  11. 52
      lib/src/translations/toolbar.i18n.dart
  12. 3
      lib/src/utils/font.dart
  13. 21
      lib/src/utils/widgets.dart
  14. 20
      lib/src/widgets/editor.dart
  15. 2
      lib/src/widgets/embeds.dart
  16. 48
      lib/src/widgets/raw_editor.dart
  17. 7
      lib/src/widgets/raw_editor/raw_editor_state_text_input_client_mixin.dart
  18. 1
      lib/src/widgets/style_widgets/number_point.dart
  19. 14
      lib/src/widgets/text_block.dart
  20. 4
      lib/src/widgets/text_line.dart
  21. 215
      lib/src/widgets/toolbar.dart
  22. 3
      lib/src/widgets/toolbar/clear_format_button.dart
  23. 3
      lib/src/widgets/toolbar/color_button.dart
  24. 30
      lib/src/widgets/toolbar/enum.dart
  25. 3
      lib/src/widgets/toolbar/history_button.dart
  26. 3
      lib/src/widgets/toolbar/indent_button.dart
  27. 3
      lib/src/widgets/toolbar/link_style_button.dart
  28. 100
      lib/src/widgets/toolbar/quill_font_family_button.dart
  29. 78
      lib/src/widgets/toolbar/quill_font_size_button.dart
  30. 7
      lib/src/widgets/toolbar/quill_icon_button.dart
  31. 3
      lib/src/widgets/toolbar/search_button.dart
  32. 33
      lib/src/widgets/toolbar/select_alignment_button.dart
  33. 10
      lib/src/widgets/toolbar/select_header_style_button.dart
  34. 8
      lib/src/widgets/toolbar/toggle_check_list_button.dart
  35. 8
      lib/src/widgets/toolbar/toggle_style_button.dart
  36. 2
      pubspec.yaml

@ -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.

@ -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,

@ -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,

@ -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,

@ -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,

@ -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,

@ -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

@ -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';

@ -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;

@ -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;
}

@ -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': 'نسخ الرابط',

@ -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;
}

@ -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;
}

@ -182,6 +182,8 @@ class QuillEditor extends StatefulWidget {
this.customShortcuts,
this.customActions,
this.detectWordBoundary = true,
this.enableUnfocusOnTapOutside = true,
this.customLinkPrefixes = const <String>[],
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<String> customLinkPrefixes;
@override
QuillEditorState createState() => QuillEditorState();
}
@ -498,6 +509,8 @@ class QuillEditorState extends State<QuillEditor>
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

@ -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;

@ -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 <String>[]})
: 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(
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<String> customLinkPrefixes;
@override
State<StatefulWidget> 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<Widget> _buildChildren(Document doc, BuildContext context) {
final result = <Widget>[];
final indentLevelCounts = <int, int>{};
// 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,

@ -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);
});
SchedulerBinding.instance
.addPostFrameCallback((_) => _updateSizeAndTransform());
}
}
}

@ -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,

@ -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 <String>[],
Key? key});
final Block block;
@ -91,8 +93,10 @@ class EditableTextBlock extends StatelessWidget {
final CustomStyleBuilder? customStyleBuilder;
final CursorCont cursorCont;
final Map<int, int> indentLevelCounts;
final bool clearIndents;
final Function(int, bool) onCheckboxTap;
final bool readOnly;
final List<String> 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<Widget> _buildChildren(
BuildContext context, Map<int, int> indentLevelCounts) {
List<Widget> _buildChildren(BuildContext context,
Map<int, int> indentLevelCounts, bool clearIndents) {
final defaultStyles = QuillStyles.getStyles(context, false);
final count = block.children.length;
final children = <Widget>[];
if (clearIndents) {
indentLevelCounts.clear();
}
var index = 0;
for (final line in Iterable.castFrom<dynamic, Line>(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),

@ -41,6 +41,7 @@ class TextLine extends StatefulWidget {
required this.linkActionPicker,
this.textDirection,
this.customStyleBuilder,
this.customLinkPrefixes = const <String>[],
Key? key,
}) : super(key: key);
@ -53,6 +54,7 @@ class TextLine extends StatefulWidget {
final CustomStyleBuilder? customStyleBuilder;
final ValueChanged<String>? onLaunchUrl;
final LinkActionPicker linkActionPicker;
final List<String> customLinkPrefixes;
@override
State<TextLine> createState() => _TextLineState();
@ -430,7 +432,7 @@ class _TextLineState extends State<TextLine> {
launchUrl ??= _launchUrl;
link = link.trim();
if (!linkPrefixes
if (!(widget.customLinkPrefixes + linkPrefixes)
.any((linkPrefix) => link!.toLowerCase().startsWith(linkPrefix))) {
link = 'https://$link';
}

@ -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<QuillCustomButton> 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, String>{
/// ToolbarButtons.undo: 'Undo',
/// ToolbarButtons.redo: 'Redo',
/// }
///
///```
///
/// To disable tooltips just pass empty map as well.
Map<ToolbarButtons, String>? tooltips,
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
Locale? locale,
/// 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, String>{
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<String, String> fontFamily in fontFamilies.entries)
PopupMenuItem<String>(
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<String, String> fontSize in fontSizes.entries)
PopupMenuItem<String>(
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<Widget> children;
final Axis axis;
final double toolbarSize;
@ -522,6 +576,15 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// List of custom buttons
final List<QuillCustomButton> customButtons;
/// The color to use when painting the toolbar section divider.
///
/// If this is null, then the [DividerThemeData.color] is used. If that is
/// also null, then [ThemeData.dividerColor] is used.
final Color? sectionDividerColor;
/// The space occupied by toolbar section divider.
final double? sectionDividerSpace;
@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,
);
}
}

@ -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<ClearFormatButton> {
final fillColor =
widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor;
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * kIconButtonFactor,

@ -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<ColorButton> {
: (widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor);
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * kIconButtonFactor,

@ -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,
}

@ -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<HistoryButton> {
_setIconColor();
});
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * 1.77,

@ -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<IndentButton> {
final iconFillColor =
widget.iconTheme?.iconUnselectedFillColor ?? theme.canvasColor;
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * 1.77,

@ -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<LinkStyleButton> {
final isToggled = _getLinkAttributeValue() != null;
final pressedHandler = () => _openLinkDialog(context);
return QuillIconButton(
tooltip: widget.tooltip,
highlightElevation: 0,
hoverElevation: 0,
size: widget.iconSize * kIconButtonFactor,

@ -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<PopupMenuEntry<String>> items;
@Deprecated('It is not required because of `rawItemsMap`')
final List<PopupMenuEntry<String>>? items;
final Map<String, String> rawItemsMap;
final ValueChanged<String> onSelected;
final ValueChanged<String>? onSelected;
final QuillIconTheme? iconTheme;
final Attribute attribute;
final QuillController controller;
final VoidCallback? afterButtonPressed;
final String? tooltip;
final EdgeInsetsGeometry? padding;
final TextStyle? style;
final double? width;
final bool renderFontFamilies;
final String? initialValue;
final TextOverflow labelOverflow;
final bool overrideTooltipByFontFamily;
final double? itemHeight;
final EdgeInsets? itemPadding;
final Color? defaultItemColor;
@override
_QuillFontFamilyButtonState createState() => _QuillFontFamilyButtonState();
@ -46,7 +72,7 @@ class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
@override
void initState() {
super.initState();
_currentValue = _defaultDisplayText = 'Font'.i18n;
_currentValue = _defaultDisplayText = widget.initialValue ?? 'Font'.i18n;
widget.controller.addListener(_didChangeEditingValue);
}
@ -87,7 +113,22 @@ class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81),
constraints: BoxConstraints.tightFor(
height: widget.iconSize * 1.81,
width: widget.width,
),
child: UtilityWidgets.maybeWidget(
enabled: (widget.tooltip ?? '').isNotEmpty ||
widget.overrideTooltipByFontFamily,
wrapper: (child) {
var effectiveTooltip = widget.tooltip ?? '';
if (widget.overrideTooltipByFontFamily) {
effectiveTooltip = effectiveTooltip.isNotEmpty
? '$effectiveTooltip: $_currentValue'
: '${'Font'.i18n}: $_currentValue';
}
return Tooltip(message: effectiveTooltip, child: child);
},
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
@ -103,6 +144,7 @@ class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
},
child: _buildContent(context),
),
),
);
}
@ -121,7 +163,24 @@ class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
showMenu<String>(
context: context,
elevation: 4,
items: widget.items,
items: [
for (MapEntry<String, String> fontFamily in widget.rawItemsMap.entries)
PopupMenuItem<String>(
key: ValueKey(fontFamily.key),
value: fontFamily.value,
height: widget.itemHeight ?? kMinInteractiveDimension,
padding: widget.itemPadding,
child: Text(
fontFamily.key.toString(),
style: TextStyle(
fontFamily: widget.renderFontFamilies ? fontFamily.value : null,
color: fontFamily.value == 'Clear'
? widget.defaultItemColor
: null,
),
),
),
],
position: position,
shape: popupMenuTheme.shape,
color: popupMenuTheme.color,
@ -134,7 +193,9 @@ class _QuillFontFamilyButtonState extends State<QuillFontFamilyButton> {
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<QuillFontFamilyButton> {
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(
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)),
theme.iconTheme.color),
),
),
const SizedBox(width: 3),
Icon(Icons.arrow_drop_down,
size: widget.iconSize / 1.15,

@ -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<PopupMenuEntry<String>> items;
@Deprecated('It is not required because of `rawItemsMap`')
final List<PopupMenuEntry<String>>? items;
final Map<String, String> rawItemsMap;
final ValueChanged<String> onSelected;
final ValueChanged<String>? onSelected;
final QuillIconTheme? iconTheme;
final Attribute attribute;
final QuillController controller;
final VoidCallback? afterButtonPressed;
final String? tooltip;
final EdgeInsetsGeometry? padding;
final TextStyle? style;
final double? width;
final 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<QuillFontSizeButton> {
@override
void initState() {
super.initState();
_currentValue = _defaultDisplayText = 'Size'.i18n;
_currentValue = _defaultDisplayText = widget.initialValue ?? 'Size'.i18n;
widget.controller.addListener(_didChangeEditingValue);
}
@ -88,7 +110,12 @@ class _QuillFontSizeButtonState extends State<QuillFontSizeButton> {
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints.tightFor(height: widget.iconSize * 1.81),
constraints: BoxConstraints.tightFor(
height: widget.iconSize * 1.81,
width: widget.width,
),
child: UtilityWidgets.maybeTooltip(
message: widget.tooltip,
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
@ -104,6 +131,7 @@ class _QuillFontSizeButtonState extends State<QuillFontSizeButton> {
},
child: _buildContent(context),
),
),
);
}
@ -122,7 +150,21 @@ class _QuillFontSizeButtonState extends State<QuillFontSizeButton> {
showMenu<String>(
context: context,
elevation: 4,
items: widget.items,
items: [
for (MapEntry<String, String> fontSize in widget.rawItemsMap.entries)
PopupMenuItem<String>(
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<QuillFontSizeButton> {
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<QuillFontSizeButton> {
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(
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,

@ -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,11 +24,14 @@ 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: UtilityWidgets.maybeTooltip(
message: tooltip,
child: RawMaterialButton(
visualDensity: VisualDensity.compact,
shape: RoundedRectangleBorder(
@ -40,6 +46,7 @@ class QuillIconButton extends StatelessWidget {
},
child: icon,
),
),
);
}
}

@ -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,

@ -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 <ToolbarButtons, String>{},
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<ToolbarButtons, String> tooltips;
final EdgeInsetsGeometry? padding;
@override
_SelectAlignmentButtonState createState() => _SelectAlignmentButtonState();
@ -74,6 +80,16 @@ class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
if (widget.showRightAlignment!) Attribute.rightAlignment.value!,
if (widget.showJustifyAlignment!) Attribute.justifyAlignment.value!,
];
final _valueToButtons = <Attribute, ToolbarButtons>{
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,13 +102,15 @@ class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
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: UtilityWidgets.maybeTooltip(
message: widget.tooltips[_valueToButtons[_valueAttribute[index]]],
child: RawMaterialButton(
hoverElevation: 0,
highlightElevation: 0,
@ -108,9 +126,10 @@ class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
theme.canvasColor),
onPressed: () {
_valueAttribute[index] == Attribute.leftAlignment
? widget.controller
.formatSelection(Attribute.clone(Attribute.align, null))
: widget.controller.formatSelection(_valueAttribute[index]);
? widget.controller.formatSelection(
Attribute.clone(Attribute.align, null))
: widget.controller
.formatSelection(_valueAttribute[index]);
widget.afterButtonPressed?.call();
},
child: Icon(
@ -118,7 +137,8 @@ class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
? Icons.format_align_left
: _valueString[index] == Attribute.centerAlignment.value
? Icons.format_align_center
: _valueString[index] == Attribute.rightAlignment.value
: _valueString[index] ==
Attribute.rightAlignment.value
? Icons.format_align_right
: Icons.format_align_justify,
size: widget.iconSize,
@ -130,6 +150,7 @@ class _SelectAlignmentButtonState extends State<SelectAlignmentButton> {
),
),
),
),
);
}),
);

@ -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<Attribute> attributes;
final VoidCallback? afterButtonPressed;
final String? tooltip;
@override
_SelectHeaderStyleButtonState createState() =>
@ -79,14 +82,16 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
width: widget.iconSize * kIconButtonFactor,
height: widget.iconSize * kIconButtonFactor,
),
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)),
borderRadius: BorderRadius.circular(
widget.iconTheme?.borderRadius ?? 2)),
fillColor: isSelected
? (widget.iconTheme?.iconSelectedFillColor ??
Theme.of(context).primaryColor)
@ -111,6 +116,7 @@ class _SelectHeaderStyleButtonState extends State<SelectHeaderStyleButton> {
),
),
),
),
);
}).toList();

@ -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,7 +94,9 @@ class _ToggleCheckListButtonState extends State<ToggleCheckListButton> {
@override
Widget build(BuildContext context) {
return widget.childBuilder(
return UtilityWidgets.maybeTooltip(
message: widget.tooltip,
child: widget.childBuilder(
context,
Attribute.unchecked,
widget.icon,
@ -101,6 +106,7 @@ class _ToggleCheckListButtonState extends State<ToggleCheckListButton> {
widget.afterButtonPressed,
widget.iconSize,
widget.iconTheme,
),
);
}

@ -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,7 +68,9 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
@override
Widget build(BuildContext context) {
return widget.childBuilder(
return UtilityWidgets.maybeTooltip(
message: widget.tooltip,
child: widget.childBuilder(
context,
widget.attribute,
widget.icon,
@ -75,6 +80,7 @@ class _ToggleStyleButtonState extends State<ToggleStyleButton> {
widget.afterButtonPressed,
widget.iconSize,
widget.iconTheme,
),
);
}

@ -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

Loading…
Cancel
Save