From 43b752f4a2b034be559a299ef8105d9d74810d80 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Sat, 24 Aug 2024 22:10:21 -0400 Subject: [PATCH 01/13] Feat: custom node builders --- .../editor/config/editor_configurations.dart | 12 ++ lib/src/editor/editor.dart | 4 + .../builders/base_builder_configuration.dart | 25 +++ .../builders/builder_configuration.dart | 71 +++++++++ lib/src/editor/raw_editor/builders/utils.dart | 149 ++++++++++++++++++ .../config/raw_editor_configurations.dart | 5 + .../editor/raw_editor/raw_editor_state.dart | 38 +++-- lib/src/editor/widgets/text/text_block.dart | 72 ++++++++- 8 files changed, 362 insertions(+), 14 deletions(-) create mode 100644 lib/src/editor/raw_editor/builders/base_builder_configuration.dart create mode 100644 lib/src/editor/raw_editor/builders/builder_configuration.dart create mode 100644 lib/src/editor/raw_editor/builders/utils.dart diff --git a/lib/src/editor/config/editor_configurations.dart b/lib/src/editor/config/editor_configurations.dart index 6d5150d72..315454385 100644 --- a/lib/src/editor/config/editor_configurations.dart +++ b/lib/src/editor/config/editor_configurations.dart @@ -11,6 +11,7 @@ import '../../editor_toolbar_shared/config/quill_shared_configurations.dart'; import '../../toolbar/theme/quill_dialog_theme.dart'; import '../editor_builder.dart'; import '../embed/embed_editor_builder.dart'; +import '../raw_editor/builders/utils.dart'; import '../raw_editor/raw_editor.dart'; import '../widgets/default_styles.dart'; import '../widgets/delegate.dart'; @@ -87,10 +88,15 @@ class QuillEditorConfigurations extends Equatable { this.scribbleAreaInsets, this.readOnlyMouseCursor = SystemMouseCursors.text, this.onPerformAction, + this.customTextLineNodeBuilder, + this.customLeadingBlockBuilder, }); final QuillSharedConfigurations sharedConfigurations; + final TextLineNodeBuilder? customTextLineNodeBuilder; + final LeadingBlockNodeBuilder? customLeadingBlockBuilder; + @Deprecated('controller will be removed in future versions.') final QuillController? controller; @@ -445,6 +451,8 @@ class QuillEditorConfigurations extends Equatable { ContentInsertionConfiguration? contentInsertionConfiguration, GlobalKey? editorKey, TextSelectionThemeData? textSelectionThemeData, + TextLineNodeBuilder? customTextLineNodeBuilder, + LeadingBlockNodeBuilder? customLeadingBlockBuilder, bool? requestKeyboardFocusOnCheckListChanged, QuillEditorElementOptions? elementOptions, QuillEditorBuilder? builder, @@ -458,6 +466,10 @@ class QuillEditorConfigurations extends Equatable { return QuillEditorConfigurations( sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations, // ignore: deprecated_member_use_from_same_package + customTextLineNodeBuilder: + customTextLineNodeBuilder ?? this.customTextLineNodeBuilder, + customLeadingBlockBuilder: + customLeadingBlockBuilder ?? this.customLeadingBlockBuilder, controller: controller ?? this.controller, placeholder: placeholder ?? this.placeholder, checkBoxReadOnly: checkBoxReadOnly ?? this.checkBoxReadOnly, diff --git a/lib/src/editor/editor.dart b/lib/src/editor/editor.dart index 48acdc4be..02528cb10 100644 --- a/lib/src/editor/editor.dart +++ b/lib/src/editor/editor.dart @@ -298,6 +298,10 @@ class QuillEditorState extends State key: _editorKey, controller: controller, configurations: QuillRawEditorConfigurations( + customLeadingBuilder: + widget.configurations.customLeadingBlockBuilder, + customTextLineNodeBuilder: + widget.configurations.customTextLineNodeBuilder, focusNode: widget.focusNode, scrollController: widget.scrollController, scrollable: configurations.scrollable, diff --git a/lib/src/editor/raw_editor/builders/base_builder_configuration.dart b/lib/src/editor/raw_editor/builders/base_builder_configuration.dart new file mode 100644 index 000000000..d88a8067a --- /dev/null +++ b/lib/src/editor/raw_editor/builders/base_builder_configuration.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; +import '../../../document/nodes/node.dart'; +import '../../widgets/default_styles.dart'; +import '../../widgets/delegate.dart'; + +/// The base class for the configurations of the custom nodes builders +abstract class BuilderConfiguration { + BuilderConfiguration({ + required this.textDirection, + required this.node, + required this.customRecognizerBuilder, + required this.customStyleBuilder, + required this.customLinkPrefixes, + required this.readOnly, + required this.styles, + }); + + final TextDirection textDirection; + final T node; + final CustomRecognizerBuilder? customRecognizerBuilder; + final CustomStyleBuilder? customStyleBuilder; + final List customLinkPrefixes; + final bool readOnly; + final DefaultStyles? styles; +} diff --git a/lib/src/editor/raw_editor/builders/builder_configuration.dart b/lib/src/editor/raw_editor/builders/builder_configuration.dart new file mode 100644 index 000000000..a53a2f0c8 --- /dev/null +++ b/lib/src/editor/raw_editor/builders/builder_configuration.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +import '../../../common/structs/horizontal_spacing.dart'; +import '../../../common/structs/vertical_spacing.dart'; +import '../../../document/nodes/block.dart'; +import '../../../document/nodes/leaf.dart'; +import '../../../document/nodes/line.dart'; +import '../../../document/nodes/node.dart'; +import '../../embed/embed_editor_builder.dart'; +import '../../widgets/cursor.dart'; +import '../../widgets/link.dart'; +import 'base_builder_configuration.dart'; + +typedef CheckBoxTapHandler = Function(int offset, bool value); +typedef LaunchURL = void Function(String); +typedef LinkActionPicker = Future Function(Node); +/// TODO: implement this configurations for block lines +class BlockBuilderConfiguration extends BuilderConfiguration { + BlockBuilderConfiguration({ + required this.selectionColor, + required this.verticalSpacing, + required this.horizontalSpacing, + required this.cursorCont, + required this.indentLevelCounts, + required this.clearIndents, + required this.onCheckboxTap, + required this.selection, + required this.hasFocus, + required this.enableInteractiveSelection, + required this.checkBoxReadOnly, + required super.textDirection, + required super.node, + required super.customRecognizerBuilder, + required super.customStyleBuilder, + required super.customLinkPrefixes, + required super.readOnly, + required super.styles, + }); + final Color selectionColor; + + final VerticalSpacing verticalSpacing; + final HorizontalSpacing horizontalSpacing; + final CursorCont cursorCont; + final Map indentLevelCounts; + final bool clearIndents; + final CheckBoxTapHandler onCheckboxTap; + final TextSelection selection; + final bool hasFocus; + final bool enableInteractiveSelection; + final bool? checkBoxReadOnly; +} + +class InlineBuilderConfiguration extends BuilderConfiguration { + InlineBuilderConfiguration({ + required super.textDirection, + required this.onLaunchUrl, + required this.linkActionPicker, + required this.embedBuilder, + required super.node, + required super.customRecognizerBuilder, + required super.customStyleBuilder, + required super.customLinkPrefixes, + required super.readOnly, + required super.styles, + required this.devicePixelRatioOf, + }); + final double devicePixelRatioOf; + final LaunchURL? onLaunchUrl; + final LinkActionPicker linkActionPicker; + final EmbedBuilder Function(Embed) embedBuilder; +} diff --git a/lib/src/editor/raw_editor/builders/utils.dart b/lib/src/editor/raw_editor/builders/utils.dart new file mode 100644 index 000000000..a63584178 --- /dev/null +++ b/lib/src/editor/raw_editor/builders/utils.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import '../../../document/attribute.dart'; +import '../../../document/nodes/node.dart'; +import '../../style_widgets/style_widgets.dart'; +import '../../widgets/text/text_line.dart'; +import 'builder_configuration.dart'; + +// TODO: by now this builder doesn't work since the EditableTextLine always try to render a RenderContentProxyBox +typedef TextLineNodeBuilder = Widget? Function( + Node, TextLine, InlineBuilderConfiguration); +typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); + +class LeadingConfigurations { + LeadingConfigurations({ + required this.attribute, + required this.indentLevelCounts, + required this.count, + required this.style, + required this.width, + required this.padding, + required this.value, + required this.onCheckboxTap, + required this.attrs, + this.withDot = true, + this.index, + this.lineSize, + this.enabled, + this.uiBuilder, + }); + + final Attribute attribute; + final Map attrs; + final bool withDot; + final Map indentLevelCounts; + // if is a list that contains a number as its leading then this is non null + final int? index; + final int count; + final TextStyle? style; + final double? width; + final double? padding; + + /// these values are used if the leading is from a check list + final QuillCheckboxBuilder? uiBuilder; + final double? lineSize; + final bool? enabled; + final bool value; + final void Function(bool) onCheckboxTap; + + String? get getIndexNumberByIndent { + if (index == null) return null; + var s = index.toString(); + int? level = 0; + if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { + indentLevelCounts.clear(); + indentLevelCounts[0] = 1; + return s; + } + if (attrs.containsKey(Attribute.indent.key)) { + level = attrs[Attribute.indent.key]!.value; + } else if (!indentLevelCounts.containsKey(0)) { + // first level but is back from previous indent level + // supposed to be "2." + indentLevelCounts[0] = 1; + } + if (indentLevelCounts.containsKey(level! + 1)) { + // last visited level is done, going up + indentLevelCounts.remove(level + 1); + } + final count = (indentLevelCounts[level] ?? 0) + 1; + indentLevelCounts[level] = count; + + s = count.toString(); + if (level % 3 == 1) { + // a. b. c. d. e. ... + s = _toExcelSheetColumnTitle(count); + } else if (level % 3 == 2) { + // i. ii. iii. ... + s = _intToRoman(count); + } + return s; + } + + String _toExcelSheetColumnTitle(int n) { + final result = StringBuffer(); + while (n > 0) { + n--; + result.write(String.fromCharCode((n % 26).floor() + 97)); + n = (n / 26).floor(); + } + + return result.toString().split('').reversed.join(); + } + + String _intToRoman(int input) { + var num = input; + + if (num < 0) { + return ''; + } else if (num == 0) { + return 'nulla'; + } + + final builder = StringBuffer(); + for (var a = 0; a < _arabianRomanNumbers.length; a++) { + final times = (num / _arabianRomanNumbers[a]) + .truncate(); // equals 1 only when arabianRomanNumbers[a] = num + // executes n times where n is the number of times you have to add + // the current roman number value to reach current num. + builder.write(_romanNumbers[a] * times); + num -= times * + _arabianRomanNumbers[ + a]; // subtract previous roman number value from num + } + + return builder.toString().toLowerCase(); + } +} + +const _arabianRomanNumbers = [ + 1000, + 900, + 500, + 400, + 100, + 90, + 50, + 40, + 10, + 9, + 5, + 4, + 1 +]; + +const _romanNumbers = [ + 'M', + 'CM', + 'D', + 'CD', + 'C', + 'XC', + 'L', + 'XL', + 'X', + 'IX', + 'V', + 'IV', + 'I' +]; diff --git a/lib/src/editor/raw_editor/config/raw_editor_configurations.dart b/lib/src/editor/raw_editor/config/raw_editor_configurations.dart index 9d5e41dc0..30ef97b5d 100644 --- a/lib/src/editor/raw_editor/config/raw_editor_configurations.dart +++ b/lib/src/editor/raw_editor/config/raw_editor_configurations.dart @@ -36,6 +36,7 @@ import '../../../editor/widgets/default_styles.dart'; import '../../../editor/widgets/delegate.dart'; import '../../../editor/widgets/link.dart'; import '../../../toolbar/theme/quill_dialog_theme.dart'; +import '../builders/utils.dart'; @immutable class QuillRawEditorConfigurations extends Equatable { @@ -92,6 +93,8 @@ class QuillRawEditorConfigurations extends Equatable { this.readOnlyMouseCursor = SystemMouseCursors.text, this.magnifierConfiguration, this.onPerformAction, + this.customLeadingBuilder, + this.customTextLineNodeBuilder, }); /// Controls the document being edited. @@ -103,6 +106,8 @@ class QuillRawEditorConfigurations extends Equatable { final ScrollController scrollController; final bool scrollable; final double scrollBottomInset; + final TextLineNodeBuilder? customTextLineNodeBuilder; + final LeadingBlockNodeBuilder? customLeadingBuilder; /// Additional space around the editor contents. final EdgeInsetsGeometry padding; diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index dde3ba994..eef50b921 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -43,6 +43,7 @@ import '../widgets/proxy.dart'; import '../widgets/text/text_block.dart'; import '../widgets/text/text_line.dart'; import '../widgets/text/text_selection.dart'; +import 'builders/builder_configuration.dart'; import 'quill_single_child_scroll_view.dart'; import 'raw_editor.dart'; import 'raw_editor_actions.dart'; @@ -997,6 +998,7 @@ class QuillRawEditorState extends EditorState final editableTextBlock = EditableTextBlock( block: node, controller: controller, + customLeadingBlockBuilder: widget.configurations.customLeadingBuilder, textDirection: nodeTextDirection, scrollBottomInset: widget.configurations.scrollBottomInset, horizontalSpacing: _getHorizontalSpacingForBlock(node, _styles), @@ -1043,31 +1045,47 @@ class QuillRawEditorState extends EditorState EditableTextLine _getEditableTextLineFromNode( Line node, BuildContext context) { - final textLine = TextLine( - line: node, + final lineConfiguration = InlineBuilderConfiguration( textDirection: _textDirection, + onLaunchUrl: widget.configurations.onLaunchUrl, + linkActionPicker: _linkActionPicker, embedBuilder: widget.configurations.embedBuilder, - customStyleBuilder: widget.configurations.customStyleBuilder, + node: node, customRecognizerBuilder: widget.configurations.customRecognizerBuilder, - styles: _styles!, + customStyleBuilder: widget.configurations.customStyleBuilder, + customLinkPrefixes: widget.configurations.customLinkPrefixes, + devicePixelRatioOf: MediaQuery.devicePixelRatioOf(context), readOnly: widget.configurations.readOnly, + styles: _styles!, + ); + final textLine = TextLine( + line: node, + textDirection: lineConfiguration.textDirection, + embedBuilder: lineConfiguration.embedBuilder, + customStyleBuilder: lineConfiguration.customStyleBuilder, + customRecognizerBuilder: lineConfiguration.customRecognizerBuilder, + styles: lineConfiguration.styles!, + readOnly: lineConfiguration.readOnly, controller: controller, - linkActionPicker: _linkActionPicker, - onLaunchUrl: widget.configurations.onLaunchUrl, - customLinkPrefixes: widget.configurations.customLinkPrefixes, + onLaunchUrl: lineConfiguration.onLaunchUrl, + linkActionPicker: lineConfiguration.linkActionPicker, + customLinkPrefixes: lineConfiguration.customLinkPrefixes, ); + // TODO: we need to verify if the custom text line builder have its child [TextLine] + final customTextLine = widget.configurations.customTextLineNodeBuilder + ?.call(node, textLine, lineConfiguration); final editableTextLine = EditableTextLine( node, null, - textLine, + customTextLine ?? textLine, _getHorizontalSpacingForLine(node, _styles), _getVerticalSpacingForLine(node, _styles), - _textDirection, + lineConfiguration.textDirection, controller.selection, widget.configurations.selectionColor, widget.configurations.enableInteractiveSelection, _hasFocus, - MediaQuery.devicePixelRatioOf(context), + lineConfiguration.devicePixelRatioOf, _cursorCont); return editableTextLine; } diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index aa5944d9b..9e6422b5b 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -13,6 +13,7 @@ import '../../../toolbar/base_toolbar.dart'; import '../../editor.dart'; import '../../embed/embed_editor_builder.dart'; import '../../provider.dart'; +import '../../raw_editor/builders/utils.dart'; import '../../style_widgets/bullet_point.dart'; import '../../style_widgets/checkbox_point.dart'; import '../../style_widgets/number_point.dart'; @@ -82,6 +83,7 @@ class EditableTextBlock extends StatelessWidget { this.onLaunchUrl, this.customStyleBuilder, this.customLinkPrefixes = const [], + this.customLeadingBlockBuilder, super.key, }); @@ -94,6 +96,7 @@ class EditableTextBlock extends StatelessWidget { final TextSelection textSelection; final Color color; final DefaultStyles? styles; + final LeadingBlockNodeBuilder? customLeadingBlockBuilder; final bool enableInteractiveSelection; final bool hasFocus; final EdgeInsets? contentPadding; @@ -265,8 +268,70 @@ class EditableTextBlock extends StatelessWidget { // final textAlign = line.style.attributes[Attribute.align.key]?.value != null // ? getTextAlign(line.style.attributes[Attribute.align.key]?.value) // : null; + final attribute = + attrs[Attribute.list.key] ?? attrs[Attribute.codeBlock.key]; + if (attribute != null && customLeadingBlockBuilder != null) { + final isUnordered = attribute == Attribute.ul; + final isOrdered = attribute == Attribute.ol; + final isCheck = + attribute == Attribute.checked || attribute == Attribute.unchecked; + final isCodeBlock = attribute == Attribute.codeBlock; + final leadingBlockNodeBuilder = customLeadingBlockBuilder?.call( + line, + LeadingConfigurations( + attribute: attribute, + attrs: attrs, + indentLevelCounts: indentLevelCounts, + index: isOrdered || isCodeBlock ? index : null, + count: count, + enabled: !isCheck ? null : !(checkBoxReadOnly ?? readOnly), + style: isOrdered + ? defaultStyles.leading!.style.copyWith( + fontSize: size, + color: context.quillEditorElementOptions?.orderedList + .useTextColorForDot == + true + ? fontColor + : null, + ) + : isUnordered + ? defaultStyles.leading!.style.copyWith( + fontWeight: FontWeight.bold, + fontSize: size, + color: context.quillEditorElementOptions?.unorderedList + .useTextColorForDot == + true + ? fontColor + : null, + ) + : isCheck + ? null + : defaultStyles.code!.style.copyWith( + color: + defaultStyles.code!.style.color!.withOpacity(0.4), + ), + width: isOrdered || isCodeBlock + ? _numberPointWidth(fontSize, count) + : isUnordered + ? fontSize * 2 + : null, + padding: isOrdered || isUnordered + ? fontSize / 2 + : isCodeBlock + ? fontSize + : null, + lineSize: isCheck ? fontSize : null, + uiBuilder: isCheck ? defaultStyles.lists?.checkboxUIBuilder : null, + value: attribute == Attribute.checked, + onCheckboxTap: (value) => onCheckboxTap(line.documentOffset, value), + ), + ); + if (leadingBlockNodeBuilder != null) { + return leadingBlockNodeBuilder; + } + } - if (attrs[Attribute.list.key] == Attribute.ol) { + if (attribute == Attribute.ol) { return QuillEditorNumberPoint( index: index, indentLevelCounts: indentLevelCounts, @@ -285,7 +350,7 @@ class EditableTextBlock extends StatelessWidget { ); } - if (attrs[Attribute.list.key] == Attribute.ul) { + if (attribute == Attribute.ul) { return QuillEditorBulletPoint( style: defaultStyles.leading!.style.copyWith( fontWeight: FontWeight.bold, @@ -301,8 +366,7 @@ class EditableTextBlock extends StatelessWidget { ); } - if (attrs[Attribute.list.key] == Attribute.checked || - attrs[Attribute.list.key] == Attribute.unchecked) { + if (attribute == Attribute.checked || attribute == Attribute.unchecked) { return QuillEditorCheckboxPoint( size: fontSize, value: attrs[Attribute.list.key] == Attribute.checked, From cf154907338860ccd54a0b1da93f6a3262df7b86 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Mon, 26 Aug 2024 14:01:53 -0400 Subject: [PATCH 02/13] Fix: bad clear of indents and refactor on leading widgets --- ...dart => block_builder_configurations.dart} | 32 +--- .../base_builder_configuration.dart | 10 +- .../config/leading_configurations.dart | 110 ++++++++++++ .../inline_builder_configurations.dart | 29 ++++ lib/src/editor/raw_editor/builders/utils.dart | 146 +--------------- .../editor/raw_editor/raw_editor_state.dart | 4 +- .../editor/style_widgets/number_point.dart | 71 +------- .../bullet_point_leading.dart | 10 ++ .../check_box_leading.dart | 12 ++ .../codeblock_line_number_leading.dart | 14 ++ .../leading_components.dart | 4 + .../number_point_leading.dart | 14 ++ lib/src/editor/widgets/text/text_block.dart | 163 +++++++----------- 13 files changed, 270 insertions(+), 349 deletions(-) rename lib/src/editor/raw_editor/builders/{builder_configuration.dart => block_builder_configurations.dart} (56%) rename lib/src/editor/raw_editor/builders/{ => config}/base_builder_configuration.dart (73%) create mode 100644 lib/src/editor/raw_editor/builders/config/leading_configurations.dart create mode 100644 lib/src/editor/raw_editor/builders/inline_builder_configurations.dart create mode 100644 lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart create mode 100644 lib/src/editor/widgets/default_leading_components/check_box_leading.dart create mode 100644 lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart create mode 100644 lib/src/editor/widgets/default_leading_components/leading_components.dart create mode 100644 lib/src/editor/widgets/default_leading_components/number_point_leading.dart diff --git a/lib/src/editor/raw_editor/builders/builder_configuration.dart b/lib/src/editor/raw_editor/builders/block_builder_configurations.dart similarity index 56% rename from lib/src/editor/raw_editor/builders/builder_configuration.dart rename to lib/src/editor/raw_editor/builders/block_builder_configurations.dart index a53a2f0c8..443c49370 100644 --- a/lib/src/editor/raw_editor/builders/builder_configuration.dart +++ b/lib/src/editor/raw_editor/builders/block_builder_configurations.dart @@ -3,19 +3,13 @@ import 'package:flutter/material.dart'; import '../../../common/structs/horizontal_spacing.dart'; import '../../../common/structs/vertical_spacing.dart'; import '../../../document/nodes/block.dart'; -import '../../../document/nodes/leaf.dart'; -import '../../../document/nodes/line.dart'; -import '../../../document/nodes/node.dart'; -import '../../embed/embed_editor_builder.dart'; import '../../widgets/cursor.dart'; -import '../../widgets/link.dart'; -import 'base_builder_configuration.dart'; +import 'config/base_builder_configuration.dart'; typedef CheckBoxTapHandler = Function(int offset, bool value); -typedef LaunchURL = void Function(String); -typedef LinkActionPicker = Future Function(Node); + /// TODO: implement this configurations for block lines -class BlockBuilderConfiguration extends BuilderConfiguration { +class BlockBuilderConfiguration extends BaseBuilderConfiguration { BlockBuilderConfiguration({ required this.selectionColor, required this.verticalSpacing, @@ -49,23 +43,3 @@ class BlockBuilderConfiguration extends BuilderConfiguration { final bool enableInteractiveSelection; final bool? checkBoxReadOnly; } - -class InlineBuilderConfiguration extends BuilderConfiguration { - InlineBuilderConfiguration({ - required super.textDirection, - required this.onLaunchUrl, - required this.linkActionPicker, - required this.embedBuilder, - required super.node, - required super.customRecognizerBuilder, - required super.customStyleBuilder, - required super.customLinkPrefixes, - required super.readOnly, - required super.styles, - required this.devicePixelRatioOf, - }); - final double devicePixelRatioOf; - final LaunchURL? onLaunchUrl; - final LinkActionPicker linkActionPicker; - final EmbedBuilder Function(Embed) embedBuilder; -} diff --git a/lib/src/editor/raw_editor/builders/base_builder_configuration.dart b/lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart similarity index 73% rename from lib/src/editor/raw_editor/builders/base_builder_configuration.dart rename to lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart index d88a8067a..1015eb9c6 100644 --- a/lib/src/editor/raw_editor/builders/base_builder_configuration.dart +++ b/lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart @@ -1,11 +1,11 @@ import 'package:flutter/material.dart'; -import '../../../document/nodes/node.dart'; -import '../../widgets/default_styles.dart'; -import '../../widgets/delegate.dart'; +import '../../../../document/nodes/node.dart'; +import '../../../widgets/default_styles.dart'; +import '../../../widgets/delegate.dart'; /// The base class for the configurations of the custom nodes builders -abstract class BuilderConfiguration { - BuilderConfiguration({ +abstract class BaseBuilderConfiguration { + BaseBuilderConfiguration({ required this.textDirection, required this.node, required this.customRecognizerBuilder, diff --git a/lib/src/editor/raw_editor/builders/config/leading_configurations.dart b/lib/src/editor/raw_editor/builders/config/leading_configurations.dart new file mode 100644 index 000000000..f241abdd4 --- /dev/null +++ b/lib/src/editor/raw_editor/builders/config/leading_configurations.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import '../../../../document/attribute.dart'; +import '../../../style_widgets/checkbox_point.dart'; + +class LeadingConfigurations { + LeadingConfigurations({ + required this.attribute, + required this.indentLevelCounts, + required this.count, + required this.style, + required this.width, + required this.padding, + required this.value, + required this.onCheckboxTap, + required this.attrs, + this.withDot = true, + this.index, + this.lineSize, + this.enabled, + this.uiBuilder, + }); + + final Attribute attribute; + final Map attrs; + final bool withDot; + final Map indentLevelCounts; + // if is a list that contains a number as its leading then this is non null + final int? index; + final int count; + final TextStyle? style; + final double? width; + final double? padding; + + /// these values are used if the leading is from a check list + final QuillCheckboxBuilder? uiBuilder; + final double? lineSize; + final bool? enabled; + final bool value; + final void Function(bool) onCheckboxTap; + + String? get getIndexNumberByIndent { + if (index == null) return null; + var s = index.toString(); + var level = 0; + if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { + indentLevelCounts.clear(); + indentLevelCounts[0] = 1; + return s; + } + if (attrs.containsKey(Attribute.indent.key)) { + level = attrs[Attribute.indent.key]!.value; + } else if (!indentLevelCounts.containsKey(0)) { + // first level but is back from previous indent level + // supposed to be "2." + indentLevelCounts[0] = 1; + } + if (indentLevelCounts.containsKey(level + 1)) { + // last visited level is done, going up + indentLevelCounts.remove(level + 1); + } + final count = (indentLevelCounts[level] ?? 0) + 1; + indentLevelCounts[level] = count; + + s = count.toString(); + if (level % 3 == 1) { + // a. b. c. d. e. ... + s = _toExcelSheetColumnTitle(count); + } else if (level % 3 == 2) { + // i. ii. iii. ... + s = _intToRoman(count); + } + return s; + } + + String _toExcelSheetColumnTitle(int n) { + final result = StringBuffer(); + while (n > 0) { + n--; + result.write(String.fromCharCode((n % 26).floor() + 97)); + n = (n / 26).floor(); + } + + return result.toString().split('').reversed.join(); + } + + String _intToRoman(int input) { + var num = input; + + if (num < 0) { + return ''; + } else if (num == 0) { + return 'nulla'; + } + + final builder = StringBuffer(); + for (var a = 0; a < _arabianRomanNumbers.length; a++) { + final times = (num / _arabianRomanNumbers[a]).truncate(); // equals 1 only when arabianRomanNumbers[a] = num + // executes n times where n is the number of times you have to add + // the current roman number value to reach current num. + builder.write(_romanNumbers[a] * times); + num -= times * _arabianRomanNumbers[a]; // subtract previous roman number value from num + } + + return builder.toString().toLowerCase(); + } +} + +const _arabianRomanNumbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; + +const _romanNumbers = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']; diff --git a/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart b/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart new file mode 100644 index 000000000..c19eecc73 --- /dev/null +++ b/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart @@ -0,0 +1,29 @@ +import '../../../document/nodes/leaf.dart'; +import '../../../document/nodes/line.dart'; +import '../../../document/nodes/node.dart'; +import '../../embed/embed_editor_builder.dart'; +import '../../widgets/link.dart'; +import 'config/base_builder_configuration.dart'; + +typedef LaunchURL = void Function(String); +typedef LinkActionPicker = Future Function(Node); + +class InlineBuilderConfiguration extends BaseBuilderConfiguration { + InlineBuilderConfiguration({ + required super.textDirection, + required this.onLaunchUrl, + required this.linkActionPicker, + required this.embedBuilder, + required super.node, + required super.customRecognizerBuilder, + required super.customStyleBuilder, + required super.customLinkPrefixes, + required super.readOnly, + required super.styles, + required this.devicePixelRatioOf, + }); + final double devicePixelRatioOf; + final LaunchURL? onLaunchUrl; + final LinkActionPicker linkActionPicker; + final EmbedBuilder Function(Embed) embedBuilder; +} diff --git a/lib/src/editor/raw_editor/builders/utils.dart b/lib/src/editor/raw_editor/builders/utils.dart index a63584178..6b3637aba 100644 --- a/lib/src/editor/raw_editor/builders/utils.dart +++ b/lib/src/editor/raw_editor/builders/utils.dart @@ -1,149 +1,9 @@ import 'package:flutter/material.dart'; -import '../../../document/attribute.dart'; import '../../../document/nodes/node.dart'; -import '../../style_widgets/style_widgets.dart'; import '../../widgets/text/text_line.dart'; -import 'builder_configuration.dart'; +import 'config/leading_configurations.dart'; +import 'inline_builder_configurations.dart'; -// TODO: by now this builder doesn't work since the EditableTextLine always try to render a RenderContentProxyBox +typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); typedef TextLineNodeBuilder = Widget? Function( Node, TextLine, InlineBuilderConfiguration); -typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); - -class LeadingConfigurations { - LeadingConfigurations({ - required this.attribute, - required this.indentLevelCounts, - required this.count, - required this.style, - required this.width, - required this.padding, - required this.value, - required this.onCheckboxTap, - required this.attrs, - this.withDot = true, - this.index, - this.lineSize, - this.enabled, - this.uiBuilder, - }); - - final Attribute attribute; - final Map attrs; - final bool withDot; - final Map indentLevelCounts; - // if is a list that contains a number as its leading then this is non null - final int? index; - final int count; - final TextStyle? style; - final double? width; - final double? padding; - - /// these values are used if the leading is from a check list - final QuillCheckboxBuilder? uiBuilder; - final double? lineSize; - final bool? enabled; - final bool value; - final void Function(bool) onCheckboxTap; - - String? get getIndexNumberByIndent { - if (index == null) return null; - var s = index.toString(); - int? level = 0; - if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { - indentLevelCounts.clear(); - indentLevelCounts[0] = 1; - return s; - } - if (attrs.containsKey(Attribute.indent.key)) { - level = attrs[Attribute.indent.key]!.value; - } else if (!indentLevelCounts.containsKey(0)) { - // first level but is back from previous indent level - // supposed to be "2." - indentLevelCounts[0] = 1; - } - if (indentLevelCounts.containsKey(level! + 1)) { - // last visited level is done, going up - indentLevelCounts.remove(level + 1); - } - final count = (indentLevelCounts[level] ?? 0) + 1; - indentLevelCounts[level] = count; - - s = count.toString(); - if (level % 3 == 1) { - // a. b. c. d. e. ... - s = _toExcelSheetColumnTitle(count); - } else if (level % 3 == 2) { - // i. ii. iii. ... - s = _intToRoman(count); - } - return s; - } - - String _toExcelSheetColumnTitle(int n) { - final result = StringBuffer(); - while (n > 0) { - n--; - result.write(String.fromCharCode((n % 26).floor() + 97)); - n = (n / 26).floor(); - } - - return result.toString().split('').reversed.join(); - } - - String _intToRoman(int input) { - var num = input; - - if (num < 0) { - return ''; - } else if (num == 0) { - return 'nulla'; - } - - final builder = StringBuffer(); - for (var a = 0; a < _arabianRomanNumbers.length; a++) { - final times = (num / _arabianRomanNumbers[a]) - .truncate(); // equals 1 only when arabianRomanNumbers[a] = num - // executes n times where n is the number of times you have to add - // the current roman number value to reach current num. - builder.write(_romanNumbers[a] * times); - num -= times * - _arabianRomanNumbers[ - a]; // subtract previous roman number value from num - } - - return builder.toString().toLowerCase(); - } -} - -const _arabianRomanNumbers = [ - 1000, - 900, - 500, - 400, - 100, - 90, - 50, - 40, - 10, - 9, - 5, - 4, - 1 -]; - -const _romanNumbers = [ - 'M', - 'CM', - 'D', - 'CD', - 'C', - 'XC', - 'L', - 'XL', - 'X', - 'IX', - 'V', - 'IV', - 'I' -]; diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index eef50b921..8464c262b 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -43,7 +43,7 @@ import '../widgets/proxy.dart'; import '../widgets/text/text_block.dart'; import '../widgets/text/text_line.dart'; import '../widgets/text/text_selection.dart'; -import 'builders/builder_configuration.dart'; +import 'builders/inline_builder_configurations.dart'; import 'quill_single_child_scroll_view.dart'; import 'raw_editor.dart'; import 'raw_editor_actions.dart'; @@ -976,7 +976,7 @@ class QuillRawEditorState extends EditorState for (final node in doc.root.children) { final attrs = node.style.attributes; - if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol) { + if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol || attrs.isEmpty) { clearIndents = true; } diff --git a/lib/src/editor/style_widgets/number_point.dart b/lib/src/editor/style_widgets/number_point.dart index 7064da01d..c45b7cee1 100644 --- a/lib/src/editor/style_widgets/number_point.dart +++ b/lib/src/editor/style_widgets/number_point.dart @@ -1,7 +1,5 @@ import 'package:flutter/widgets.dart'; - import '../../../flutter_quill.dart'; -import '../widgets/text/text_block.dart'; class QuillEditorNumberPoint extends StatelessWidget { const QuillEditorNumberPoint({ @@ -18,7 +16,7 @@ class QuillEditorNumberPoint extends StatelessWidget { this.backgroundColor, }); - final int index; + final String index; final Map indentLevelCounts; final int count; final TextStyle style; @@ -31,11 +29,7 @@ class QuillEditorNumberPoint extends StatelessWidget { @override Widget build(BuildContext context) { - var s = index.toString(); - int? level = 0; if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { - indentLevelCounts.clear(); - indentLevelCounts[0] = 1; return Container( alignment: AlignmentDirectional.topEnd, width: width, @@ -44,36 +38,12 @@ class QuillEditorNumberPoint extends StatelessWidget { child: context.quillEditorConfigurations?.elementOptions.orderedList .customWidget ?? Text( - withDot ? '$s.' : s, + withDot ? '$index.' : index, style: style, textAlign: textAlign, ), ); } - if (attrs.containsKey(Attribute.indent.key)) { - level = attrs[Attribute.indent.key]!.value; - } else if (!indentLevelCounts.containsKey(0)) { - // first level but is back from previous indent level - // supposed to be "2." - indentLevelCounts[0] = 1; - } - if (indentLevelCounts.containsKey(level! + 1)) { - // last visited level is done, going up - indentLevelCounts.remove(level + 1); - } - final count = (indentLevelCounts[level] ?? 0) + 1; - indentLevelCounts[level] = count; - - s = count.toString(); - if (level % 3 == 1) { - // a. b. c. d. e. ... - s = _toExcelSheetColumnTitle(count); - } else if (level % 3 == 2) { - // i. ii. iii. ... - s = _intToRoman(count); - } - // level % 3 == 0 goes back to 1. 2. 3. - return Container( alignment: AlignmentDirectional.topEnd, width: width, @@ -82,45 +52,10 @@ class QuillEditorNumberPoint extends StatelessWidget { child: context.quillEditorConfigurations?.elementOptions.orderedList .customWidget ?? Text( - withDot ? '$s.' : s, + withDot ? '$index.' : index, style: style, textAlign: textAlign, ), ); } - - String _toExcelSheetColumnTitle(int n) { - final result = StringBuffer(); - while (n > 0) { - n--; - result.write(String.fromCharCode((n % 26).floor() + 97)); - n = (n / 26).floor(); - } - - return result.toString().split('').reversed.join(); - } - - String _intToRoman(int input) { - var num = input; - - if (num < 0) { - return ''; - } else if (num == 0) { - return 'nulla'; - } - - final builder = StringBuffer(); - for (var a = 0; a < arabianRomanNumbers.length; a++) { - final times = (num / arabianRomanNumbers[a]) - .truncate(); // equals 1 only when arabianRomanNumbers[a] = num - // executes n times where n is the number of times you have to add - // the current roman number value to reach current num. - builder.write(romanNumbers[a] * times); - num -= times * - arabianRomanNumbers[ - a]; // subtract previous roman number value from num - } - - return builder.toString().toLowerCase(); - } } diff --git a/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart new file mode 100644 index 000000000..306a1b549 --- /dev/null +++ b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart @@ -0,0 +1,10 @@ +import 'package:flutter/material.dart'; +import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../style_widgets/style_widgets.dart'; + +Widget bulletPointLeading(LeadingConfigurations config) => + QuillEditorBulletPoint( + style: config.style!, + width: config.width!, + padding: config.padding!, + ); diff --git a/lib/src/editor/widgets/default_leading_components/check_box_leading.dart b/lib/src/editor/widgets/default_leading_components/check_box_leading.dart new file mode 100644 index 000000000..5e9cb32d7 --- /dev/null +++ b/lib/src/editor/widgets/default_leading_components/check_box_leading.dart @@ -0,0 +1,12 @@ +import 'package:flutter/material.dart'; +import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../style_widgets/style_widgets.dart'; + +Widget checkboxLeading(LeadingConfigurations config) => + QuillEditorCheckboxPoint( + size: config.lineSize!, + value: config.value, + enabled: config.enabled!, + onChanged: config.onCheckboxTap, + uiBuilder: config.uiBuilder, + ); diff --git a/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart b/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart new file mode 100644 index 000000000..d9c799a65 --- /dev/null +++ b/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../style_widgets/style_widgets.dart'; + +Widget codeBlockLineNumberLeading(LeadingConfigurations config) => + QuillEditorNumberPoint( + index: config.getIndexNumberByIndent!, + indentLevelCounts: config.indentLevelCounts, + count: config.count, + style: config.style!, + attrs: config.attrs, + width: config.width!, + padding: config.padding!, + ); diff --git a/lib/src/editor/widgets/default_leading_components/leading_components.dart b/lib/src/editor/widgets/default_leading_components/leading_components.dart new file mode 100644 index 000000000..8e7906ee8 --- /dev/null +++ b/lib/src/editor/widgets/default_leading_components/leading_components.dart @@ -0,0 +1,4 @@ +export 'bullet_point_leading.dart'; +export 'check_box_leading.dart'; +export 'codeblock_line_number_leading.dart'; +export 'number_point_leading.dart'; diff --git a/lib/src/editor/widgets/default_leading_components/number_point_leading.dart b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart new file mode 100644 index 000000000..e6297cb4c --- /dev/null +++ b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart @@ -0,0 +1,14 @@ +import 'package:flutter/material.dart'; +import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../style_widgets/style_widgets.dart'; + +Widget numberPointLeading(LeadingConfigurations config) => + QuillEditorNumberPoint( + index: config.getIndexNumberByIndent!, + indentLevelCounts: config.indentLevelCounts, + count: config.count, + style: config.style!, + attrs: config.attrs, + width: config.width!, + padding: config.padding!, + ); diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 9e6422b5b..6f3287d45 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -13,12 +13,11 @@ import '../../../toolbar/base_toolbar.dart'; import '../../editor.dart'; import '../../embed/embed_editor_builder.dart'; import '../../provider.dart'; +import '../../raw_editor/builders/config/leading_configurations.dart'; import '../../raw_editor/builders/utils.dart'; -import '../../style_widgets/bullet_point.dart'; -import '../../style_widgets/checkbox_point.dart'; -import '../../style_widgets/number_point.dart'; import '../box.dart'; import '../cursor.dart'; +import '../default_leading_components/leading_components.dart'; import '../default_styles.dart'; import '../delegate.dart'; import '../link.dart'; @@ -270,124 +269,84 @@ class EditableTextBlock extends StatelessWidget { // : null; final attribute = attrs[Attribute.list.key] ?? attrs[Attribute.codeBlock.key]; - if (attribute != null && customLeadingBlockBuilder != null) { - final isUnordered = attribute == Attribute.ul; - final isOrdered = attribute == Attribute.ol; - final isCheck = - attribute == Attribute.checked || attribute == Attribute.unchecked; - final isCodeBlock = attribute == Attribute.codeBlock; - final leadingBlockNodeBuilder = customLeadingBlockBuilder?.call( - line, - LeadingConfigurations( - attribute: attribute, - attrs: attrs, - indentLevelCounts: indentLevelCounts, - index: isOrdered || isCodeBlock ? index : null, - count: count, - enabled: !isCheck ? null : !(checkBoxReadOnly ?? readOnly), - style: isOrdered + final isUnordered = attribute == Attribute.ul; + final isOrdered = attribute == Attribute.ol; + final isCheck = + attribute == Attribute.checked || attribute == Attribute.unchecked; + final isCodeBlock = attrs.containsKey(Attribute.codeBlock.key); + if (attribute == null) return null; + final leadingConfigurations = LeadingConfigurations( + attribute: attribute, + attrs: attrs, + indentLevelCounts: indentLevelCounts, + index: isOrdered || isCodeBlock ? index : null, + count: count, + enabled: !isCheck ? null : !(checkBoxReadOnly ?? readOnly), + style: isOrdered + ? defaultStyles.leading!.style.copyWith( + fontSize: size, + color: context.quillEditorElementOptions?.orderedList + .useTextColorForDot == + true + ? fontColor + : null, + ) + : isUnordered ? defaultStyles.leading!.style.copyWith( + fontWeight: FontWeight.bold, fontSize: size, - color: context.quillEditorElementOptions?.orderedList + color: context.quillEditorElementOptions?.unorderedList .useTextColorForDot == true ? fontColor : null, ) - : isUnordered - ? defaultStyles.leading!.style.copyWith( - fontWeight: FontWeight.bold, - fontSize: size, - color: context.quillEditorElementOptions?.unorderedList - .useTextColorForDot == - true - ? fontColor - : null, - ) - : isCheck - ? null - : defaultStyles.code!.style.copyWith( - color: - defaultStyles.code!.style.color!.withOpacity(0.4), - ), - width: isOrdered || isCodeBlock - ? _numberPointWidth(fontSize, count) - : isUnordered - ? fontSize * 2 - : null, - padding: isOrdered || isUnordered - ? fontSize / 2 - : isCodeBlock - ? fontSize - : null, - lineSize: isCheck ? fontSize : null, - uiBuilder: isCheck ? defaultStyles.lists?.checkboxUIBuilder : null, - value: attribute == Attribute.checked, - onCheckboxTap: (value) => onCheckboxTap(line.documentOffset, value), - ), + : isCheck + ? null + : defaultStyles.code!.style.copyWith( + color: defaultStyles.code!.style.color!.withOpacity(0.4), + ), + width: isOrdered || isCodeBlock + ? _numberPointWidth(fontSize, count) + : isUnordered + ? fontSize * 2 + : null, + padding: isOrdered || isUnordered + ? fontSize / 2 + : isCodeBlock + ? fontSize + : null, + lineSize: isCheck ? fontSize : null, + uiBuilder: isCheck ? defaultStyles.lists?.checkboxUIBuilder : null, + value: attribute == Attribute.checked, + onCheckboxTap: !isCheck + ? (value) {} + : (value) => onCheckboxTap(line.documentOffset, value), + ); + if (customLeadingBlockBuilder != null) { + final leadingBlockNodeBuilder = customLeadingBlockBuilder?.call( + line, + leadingConfigurations, ); if (leadingBlockNodeBuilder != null) { return leadingBlockNodeBuilder; } } - if (attribute == Attribute.ol) { - return QuillEditorNumberPoint( - index: index, - indentLevelCounts: indentLevelCounts, - count: count, - style: defaultStyles.leading!.style.copyWith( - fontSize: size, - color: context.quillEditorElementOptions?.orderedList - .useTextColorForDot == - true - ? fontColor - : null, - ), - attrs: attrs, - width: _numberPointWidth(fontSize, count), - padding: fontSize / 2, - ); + if (isOrdered) { + return numberPointLeading(leadingConfigurations); } - if (attribute == Attribute.ul) { - return QuillEditorBulletPoint( - style: defaultStyles.leading!.style.copyWith( - fontWeight: FontWeight.bold, - fontSize: size, - color: context.quillEditorElementOptions?.unorderedList - .useTextColorForDot == - true - ? fontColor - : null, - ), - width: fontSize * 2, - padding: fontSize / 2, - ); + if (isUnordered) { + return bulletPointLeading(leadingConfigurations); } - if (attribute == Attribute.checked || attribute == Attribute.unchecked) { - return QuillEditorCheckboxPoint( - size: fontSize, - value: attrs[Attribute.list.key] == Attribute.checked, - enabled: !(checkBoxReadOnly ?? readOnly), - onChanged: (checked) => onCheckboxTap(line.documentOffset, checked), - uiBuilder: defaultStyles.lists?.checkboxUIBuilder, - ); + if (isCheck) { + return checkboxLeading(leadingConfigurations); } - if (attrs.containsKey(Attribute.codeBlock.key) && + if (isCodeBlock && context.requireQuillEditorElementOptions.codeBlock.enableLineNumbers) { - return QuillEditorNumberPoint( - index: index, - indentLevelCounts: indentLevelCounts, - count: count, - style: defaultStyles.code!.style - .copyWith(color: defaultStyles.code!.style.color!.withOpacity(0.4)), - width: _numberPointWidth(fontSize, count), - attrs: attrs, - padding: fontSize, - withDot: false, - ); + return codeBlockLineNumberLeading(leadingConfigurations); } return null; } From c99b2c5af466ee49fe353f60976dc6ade2f0d842 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Mon, 26 Aug 2024 14:24:34 -0400 Subject: [PATCH 03/13] Chore: added ignore for deprecated controller in editor_configurations file --- lib/src/editor/config/editor_configurations.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/editor/config/editor_configurations.dart b/lib/src/editor/config/editor_configurations.dart index 315454385..8ba8ef53b 100644 --- a/lib/src/editor/config/editor_configurations.dart +++ b/lib/src/editor/config/editor_configurations.dart @@ -27,6 +27,7 @@ class QuillEditorConfigurations extends Equatable { /// Important note for the maintainers /// When editing this class please update the [copyWith] function too. const QuillEditorConfigurations({ + // ignore: deprecated_member_use_from_same_package @Deprecated( 'controller should be passed directly to the editor - this parameter will be removed in future versions.') this.controller, From 687cbbf1d159489eb0daff799c22e37b355397ff Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Mon, 26 Aug 2024 14:33:14 -0400 Subject: [PATCH 04/13] Chore: ignore deprecated operation in copyWith method from editor_configurations --- lib/src/editor/config/editor_configurations.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/editor/config/editor_configurations.dart b/lib/src/editor/config/editor_configurations.dart index 8ba8ef53b..f85836a5b 100644 --- a/lib/src/editor/config/editor_configurations.dart +++ b/lib/src/editor/config/editor_configurations.dart @@ -27,7 +27,6 @@ class QuillEditorConfigurations extends Equatable { /// Important note for the maintainers /// When editing this class please update the [copyWith] function too. const QuillEditorConfigurations({ - // ignore: deprecated_member_use_from_same_package @Deprecated( 'controller should be passed directly to the editor - this parameter will be removed in future versions.') this.controller, @@ -471,6 +470,7 @@ class QuillEditorConfigurations extends Equatable { customTextLineNodeBuilder ?? this.customTextLineNodeBuilder, customLeadingBlockBuilder: customLeadingBlockBuilder ?? this.customLeadingBlockBuilder, + // ignore: deprecated_member_use_from_same_package controller: controller ?? this.controller, placeholder: placeholder ?? this.placeholder, checkBoxReadOnly: checkBoxReadOnly ?? this.checkBoxReadOnly, From 7c8dbb6a5546803e33230cb29204bd2686ef9ffe Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Mon, 26 Aug 2024 14:38:21 -0400 Subject: [PATCH 05/13] Chore: dart formatting --- .../config/leading_configurations.dart | 39 +++++++++++++++++-- .../editor/raw_editor/raw_editor_state.dart | 3 +- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/lib/src/editor/raw_editor/builders/config/leading_configurations.dart b/lib/src/editor/raw_editor/builders/config/leading_configurations.dart index f241abdd4..6b3c163ec 100644 --- a/lib/src/editor/raw_editor/builders/config/leading_configurations.dart +++ b/lib/src/editor/raw_editor/builders/config/leading_configurations.dart @@ -94,17 +94,48 @@ class LeadingConfigurations { final builder = StringBuffer(); for (var a = 0; a < _arabianRomanNumbers.length; a++) { - final times = (num / _arabianRomanNumbers[a]).truncate(); // equals 1 only when arabianRomanNumbers[a] = num + final times = (num / _arabianRomanNumbers[a]) + .truncate(); // equals 1 only when arabianRomanNumbers[a] = num // executes n times where n is the number of times you have to add // the current roman number value to reach current num. builder.write(_romanNumbers[a] * times); - num -= times * _arabianRomanNumbers[a]; // subtract previous roman number value from num + num -= times * + _arabianRomanNumbers[ + a]; // subtract previous roman number value from num } return builder.toString().toLowerCase(); } } -const _arabianRomanNumbers = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]; +const _arabianRomanNumbers = [ + 1000, + 900, + 500, + 400, + 100, + 90, + 50, + 40, + 10, + 9, + 5, + 4, + 1 +]; -const _romanNumbers = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']; +const _romanNumbers = [ + 'M', + 'CM', + 'D', + 'CD', + 'C', + 'XC', + 'L', + 'XL', + 'X', + 'IX', + 'V', + 'IV', + 'I' +]; diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 8464c262b..f9328127d 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -976,7 +976,8 @@ class QuillRawEditorState extends EditorState for (final node in doc.root.children) { final attrs = node.style.attributes; - if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol || attrs.isEmpty) { + if (prevNodeOl && attrs[Attribute.list.key] != Attribute.ol || + attrs.isEmpty) { clearIndents = true; } From 0b014b97edd5b1f5433f4ac0eb16c2b28f63db43 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Mon, 26 Aug 2024 21:47:17 -0400 Subject: [PATCH 06/13] Chore: remove unused import --- lib/src/editor/raw_editor/raw_editor_state.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 36cac16c6..dff29c16f 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -44,7 +44,6 @@ import '../widgets/text/text_block.dart'; import '../widgets/text/text_line.dart'; import '../widgets/text/text_selection.dart'; import 'builders/inline_builder_configurations.dart'; -import 'quill_single_child_scroll_view.dart'; import 'raw_editor.dart'; import 'raw_editor_actions.dart'; import 'raw_editor_render_object.dart'; From bd11120efb0d2180442eabd7372cadbf5edf298c Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Tue, 27 Aug 2024 01:18:00 -0400 Subject: [PATCH 07/13] Feat: partial support for custom text line widgets --- .../editor/raw_editor/raw_editor_state.dart | 5 +- lib/src/editor/widgets/proxy.dart | 87 +++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index dff29c16f..2b9c14bb2 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -1068,8 +1068,11 @@ class QuillRawEditorState extends EditorState customLinkPrefixes: lineConfiguration.customLinkPrefixes, ); // TODO: we need to verify if the custom text line builder have its child [TextLine] - final customTextLine = widget.configurations.customTextLineNodeBuilder + var customTextLine = widget.configurations.customTextLineNodeBuilder ?.call(node, textLine, lineConfiguration); + if (customTextLine != null) { + customTextLine = CustomTextProxy(customTextLine); + } final editableTextLine = EditableTextLine( node, null, diff --git a/lib/src/editor/widgets/proxy.dart b/lib/src/editor/widgets/proxy.dart index 9504a11f6..206d2030e 100644 --- a/lib/src/editor/widgets/proxy.dart +++ b/lib/src/editor/widgets/proxy.dart @@ -133,6 +133,93 @@ class RenderEmbedProxy extends RenderProxyBox implements RenderContentProxyBox { double get preferredLineHeight => size.height; } +class CustomTextProxy extends SingleChildRenderObjectWidget { + const CustomTextProxy(Widget child, {super.key}) : super(child: child); + + @override + RenderCustomTextProxy createRenderObject(BuildContext context) { + return RenderCustomTextProxy(null); + } +} + +class RenderCustomTextProxy extends RenderProxyBox + implements RenderContentProxyBox { + RenderCustomTextProxy(super.child); + + RenderParagraphProxy? richTextProxyChild; + + @override + List getBoxesForSelection(TextSelection selection) { + _searchChild(); + if (richTextProxyChild == null) { + throw Exception( + "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); + } + return richTextProxyChild!.getBoxesForSelection(selection); + } + + void _searchChild() { + final box = child; + if (richTextProxyChild != null) return; + box!.visitChildren((renderObject) { + if (renderObject is RenderParagraphProxy) { + richTextProxyChild = renderObject; + return; + } + }); + } + + @override + double? getFullHeightForCaret(TextPosition position) { + _searchChild(); + if (richTextProxyChild == null) { + throw Exception( + "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); + } + return richTextProxyChild!.getFullHeightForCaret(position); + } + + @override + Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) { + //TODO: here we will need to search the padding or container widgets that can contains EdgeInsets param + // to adjust correctly the offset of the caret + _searchChild(); + if (richTextProxyChild == null) { + throw Exception( + "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); + } + return richTextProxyChild!.getOffsetForCaret(position, caretPrototype); + } + + @override + TextPosition getPositionForOffset(Offset offset) { + //TODO: here we will need to search the padding or container widgets that can contains EdgeInsets param + // to adjust correctly the position + _searchChild(); + if (richTextProxyChild == null) { + throw Exception( + "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); + } + return richTextProxyChild!.getPositionForOffset(offset); + } + + @override + TextRange getWordBoundary(TextPosition position) { + _searchChild(); + if (richTextProxyChild == null) { + throw Exception( + "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); + } + return richTextProxyChild!.getWordBoundary(position); + } + + @override + double get preferredLineHeight { + _searchChild(); + return richTextProxyChild!.preferredLineHeight; + } +} + class RichTextProxy extends SingleChildRenderObjectWidget { /// Child argument should be an instance of RichText widget. const RichTextProxy({ From bac2888530b8458cc9526482f531b368f6eb48be Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Tue, 27 Aug 2024 23:09:02 -0400 Subject: [PATCH 08/13] Chore: removed builder configs and inline and block builders too --- .../editor/config/editor_configurations.dart | 7 +- lib/src/editor/editor.dart | 2 - .../block_builder_configurations.dart | 45 ---------- .../config/base_builder_configuration.dart | 25 ------ .../inline_builder_configurations.dart | 29 ------- ...ations.dart => leading_block_builder.dart} | 7 +- lib/src/editor/raw_editor/builders/utils.dart | 9 -- .../config/raw_editor_configurations.dart | 4 +- .../editor/raw_editor/raw_editor_state.dart | 40 +++------ .../bullet_point_leading.dart | 2 +- .../check_box_leading.dart | 2 +- .../codeblock_line_number_leading.dart | 2 +- .../number_point_leading.dart | 2 +- lib/src/editor/widgets/proxy.dart | 87 ------------------- lib/src/editor/widgets/text/text_block.dart | 3 +- 15 files changed, 22 insertions(+), 244 deletions(-) delete mode 100644 lib/src/editor/raw_editor/builders/block_builder_configurations.dart delete mode 100644 lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart delete mode 100644 lib/src/editor/raw_editor/builders/inline_builder_configurations.dart rename lib/src/editor/raw_editor/builders/{config/leading_configurations.dart => leading_block_builder.dart} (93%) delete mode 100644 lib/src/editor/raw_editor/builders/utils.dart diff --git a/lib/src/editor/config/editor_configurations.dart b/lib/src/editor/config/editor_configurations.dart index f85836a5b..1a577edb3 100644 --- a/lib/src/editor/config/editor_configurations.dart +++ b/lib/src/editor/config/editor_configurations.dart @@ -11,7 +11,7 @@ import '../../editor_toolbar_shared/config/quill_shared_configurations.dart'; import '../../toolbar/theme/quill_dialog_theme.dart'; import '../editor_builder.dart'; import '../embed/embed_editor_builder.dart'; -import '../raw_editor/builders/utils.dart'; +import '../raw_editor/builders/leading_block_builder.dart'; import '../raw_editor/raw_editor.dart'; import '../widgets/default_styles.dart'; import '../widgets/delegate.dart'; @@ -88,13 +88,11 @@ class QuillEditorConfigurations extends Equatable { this.scribbleAreaInsets, this.readOnlyMouseCursor = SystemMouseCursors.text, this.onPerformAction, - this.customTextLineNodeBuilder, this.customLeadingBlockBuilder, }); final QuillSharedConfigurations sharedConfigurations; - final TextLineNodeBuilder? customTextLineNodeBuilder; final LeadingBlockNodeBuilder? customLeadingBlockBuilder; @Deprecated('controller will be removed in future versions.') @@ -451,7 +449,6 @@ class QuillEditorConfigurations extends Equatable { ContentInsertionConfiguration? contentInsertionConfiguration, GlobalKey? editorKey, TextSelectionThemeData? textSelectionThemeData, - TextLineNodeBuilder? customTextLineNodeBuilder, LeadingBlockNodeBuilder? customLeadingBlockBuilder, bool? requestKeyboardFocusOnCheckListChanged, QuillEditorElementOptions? elementOptions, @@ -466,8 +463,6 @@ class QuillEditorConfigurations extends Equatable { return QuillEditorConfigurations( sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations, // ignore: deprecated_member_use_from_same_package - customTextLineNodeBuilder: - customTextLineNodeBuilder ?? this.customTextLineNodeBuilder, customLeadingBlockBuilder: customLeadingBlockBuilder ?? this.customLeadingBlockBuilder, // ignore: deprecated_member_use_from_same_package diff --git a/lib/src/editor/editor.dart b/lib/src/editor/editor.dart index 02528cb10..18b03529a 100644 --- a/lib/src/editor/editor.dart +++ b/lib/src/editor/editor.dart @@ -300,8 +300,6 @@ class QuillEditorState extends State configurations: QuillRawEditorConfigurations( customLeadingBuilder: widget.configurations.customLeadingBlockBuilder, - customTextLineNodeBuilder: - widget.configurations.customTextLineNodeBuilder, focusNode: widget.focusNode, scrollController: widget.scrollController, scrollable: configurations.scrollable, diff --git a/lib/src/editor/raw_editor/builders/block_builder_configurations.dart b/lib/src/editor/raw_editor/builders/block_builder_configurations.dart deleted file mode 100644 index 443c49370..000000000 --- a/lib/src/editor/raw_editor/builders/block_builder_configurations.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../../common/structs/horizontal_spacing.dart'; -import '../../../common/structs/vertical_spacing.dart'; -import '../../../document/nodes/block.dart'; -import '../../widgets/cursor.dart'; -import 'config/base_builder_configuration.dart'; - -typedef CheckBoxTapHandler = Function(int offset, bool value); - -/// TODO: implement this configurations for block lines -class BlockBuilderConfiguration extends BaseBuilderConfiguration { - BlockBuilderConfiguration({ - required this.selectionColor, - required this.verticalSpacing, - required this.horizontalSpacing, - required this.cursorCont, - required this.indentLevelCounts, - required this.clearIndents, - required this.onCheckboxTap, - required this.selection, - required this.hasFocus, - required this.enableInteractiveSelection, - required this.checkBoxReadOnly, - required super.textDirection, - required super.node, - required super.customRecognizerBuilder, - required super.customStyleBuilder, - required super.customLinkPrefixes, - required super.readOnly, - required super.styles, - }); - final Color selectionColor; - - final VerticalSpacing verticalSpacing; - final HorizontalSpacing horizontalSpacing; - final CursorCont cursorCont; - final Map indentLevelCounts; - final bool clearIndents; - final CheckBoxTapHandler onCheckboxTap; - final TextSelection selection; - final bool hasFocus; - final bool enableInteractiveSelection; - final bool? checkBoxReadOnly; -} diff --git a/lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart b/lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart deleted file mode 100644 index 1015eb9c6..000000000 --- a/lib/src/editor/raw_editor/builders/config/base_builder_configuration.dart +++ /dev/null @@ -1,25 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../../../document/nodes/node.dart'; -import '../../../widgets/default_styles.dart'; -import '../../../widgets/delegate.dart'; - -/// The base class for the configurations of the custom nodes builders -abstract class BaseBuilderConfiguration { - BaseBuilderConfiguration({ - required this.textDirection, - required this.node, - required this.customRecognizerBuilder, - required this.customStyleBuilder, - required this.customLinkPrefixes, - required this.readOnly, - required this.styles, - }); - - final TextDirection textDirection; - final T node; - final CustomRecognizerBuilder? customRecognizerBuilder; - final CustomStyleBuilder? customStyleBuilder; - final List customLinkPrefixes; - final bool readOnly; - final DefaultStyles? styles; -} diff --git a/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart b/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart deleted file mode 100644 index c19eecc73..000000000 --- a/lib/src/editor/raw_editor/builders/inline_builder_configurations.dart +++ /dev/null @@ -1,29 +0,0 @@ -import '../../../document/nodes/leaf.dart'; -import '../../../document/nodes/line.dart'; -import '../../../document/nodes/node.dart'; -import '../../embed/embed_editor_builder.dart'; -import '../../widgets/link.dart'; -import 'config/base_builder_configuration.dart'; - -typedef LaunchURL = void Function(String); -typedef LinkActionPicker = Future Function(Node); - -class InlineBuilderConfiguration extends BaseBuilderConfiguration { - InlineBuilderConfiguration({ - required super.textDirection, - required this.onLaunchUrl, - required this.linkActionPicker, - required this.embedBuilder, - required super.node, - required super.customRecognizerBuilder, - required super.customStyleBuilder, - required super.customLinkPrefixes, - required super.readOnly, - required super.styles, - required this.devicePixelRatioOf, - }); - final double devicePixelRatioOf; - final LaunchURL? onLaunchUrl; - final LinkActionPicker linkActionPicker; - final EmbedBuilder Function(Embed) embedBuilder; -} diff --git a/lib/src/editor/raw_editor/builders/config/leading_configurations.dart b/lib/src/editor/raw_editor/builders/leading_block_builder.dart similarity index 93% rename from lib/src/editor/raw_editor/builders/config/leading_configurations.dart rename to lib/src/editor/raw_editor/builders/leading_block_builder.dart index 6b3c163ec..de06c6001 100644 --- a/lib/src/editor/raw_editor/builders/config/leading_configurations.dart +++ b/lib/src/editor/raw_editor/builders/leading_block_builder.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; -import '../../../../document/attribute.dart'; -import '../../../style_widgets/checkbox_point.dart'; +import '../../../document/attribute.dart'; +import '../../../document/nodes/node.dart'; +import '../../style_widgets/checkbox_point.dart'; + +typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); class LeadingConfigurations { LeadingConfigurations({ diff --git a/lib/src/editor/raw_editor/builders/utils.dart b/lib/src/editor/raw_editor/builders/utils.dart deleted file mode 100644 index 6b3637aba..000000000 --- a/lib/src/editor/raw_editor/builders/utils.dart +++ /dev/null @@ -1,9 +0,0 @@ -import 'package:flutter/material.dart'; -import '../../../document/nodes/node.dart'; -import '../../widgets/text/text_line.dart'; -import 'config/leading_configurations.dart'; -import 'inline_builder_configurations.dart'; - -typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); -typedef TextLineNodeBuilder = Widget? Function( - Node, TextLine, InlineBuilderConfiguration); diff --git a/lib/src/editor/raw_editor/config/raw_editor_configurations.dart b/lib/src/editor/raw_editor/config/raw_editor_configurations.dart index 30ef97b5d..becaa506d 100644 --- a/lib/src/editor/raw_editor/config/raw_editor_configurations.dart +++ b/lib/src/editor/raw_editor/config/raw_editor_configurations.dart @@ -36,7 +36,7 @@ import '../../../editor/widgets/default_styles.dart'; import '../../../editor/widgets/delegate.dart'; import '../../../editor/widgets/link.dart'; import '../../../toolbar/theme/quill_dialog_theme.dart'; -import '../builders/utils.dart'; +import '../builders/leading_block_builder.dart'; @immutable class QuillRawEditorConfigurations extends Equatable { @@ -94,7 +94,6 @@ class QuillRawEditorConfigurations extends Equatable { this.magnifierConfiguration, this.onPerformAction, this.customLeadingBuilder, - this.customTextLineNodeBuilder, }); /// Controls the document being edited. @@ -106,7 +105,6 @@ class QuillRawEditorConfigurations extends Equatable { final ScrollController scrollController; final bool scrollable; final double scrollBottomInset; - final TextLineNodeBuilder? customTextLineNodeBuilder; final LeadingBlockNodeBuilder? customLeadingBuilder; /// Additional space around the editor contents. diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index 2b9c14bb2..eaf06d660 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -43,7 +43,6 @@ import '../widgets/proxy.dart'; import '../widgets/text/text_block.dart'; import '../widgets/text/text_line.dart'; import '../widgets/text/text_selection.dart'; -import 'builders/inline_builder_configurations.dart'; import 'raw_editor.dart'; import 'raw_editor_actions.dart'; import 'raw_editor_render_object.dart'; @@ -1041,50 +1040,31 @@ class QuillRawEditorState extends EditorState EditableTextLine _getEditableTextLineFromNode( Line node, BuildContext context) { - final lineConfiguration = InlineBuilderConfiguration( + final textLine = TextLine( + line: node, textDirection: _textDirection, - onLaunchUrl: widget.configurations.onLaunchUrl, - linkActionPicker: _linkActionPicker, embedBuilder: widget.configurations.embedBuilder, - node: node, - customRecognizerBuilder: widget.configurations.customRecognizerBuilder, customStyleBuilder: widget.configurations.customStyleBuilder, - customLinkPrefixes: widget.configurations.customLinkPrefixes, - devicePixelRatioOf: MediaQuery.devicePixelRatioOf(context), - readOnly: widget.configurations.readOnly, + customRecognizerBuilder: widget.configurations.customRecognizerBuilder, styles: _styles!, - ); - final textLine = TextLine( - line: node, - textDirection: lineConfiguration.textDirection, - embedBuilder: lineConfiguration.embedBuilder, - customStyleBuilder: lineConfiguration.customStyleBuilder, - customRecognizerBuilder: lineConfiguration.customRecognizerBuilder, - styles: lineConfiguration.styles!, - readOnly: lineConfiguration.readOnly, + readOnly: widget.configurations.readOnly, controller: controller, - onLaunchUrl: lineConfiguration.onLaunchUrl, - linkActionPicker: lineConfiguration.linkActionPicker, - customLinkPrefixes: lineConfiguration.customLinkPrefixes, + linkActionPicker: _linkActionPicker, + onLaunchUrl: widget.configurations.onLaunchUrl, + customLinkPrefixes: widget.configurations.customLinkPrefixes, ); - // TODO: we need to verify if the custom text line builder have its child [TextLine] - var customTextLine = widget.configurations.customTextLineNodeBuilder - ?.call(node, textLine, lineConfiguration); - if (customTextLine != null) { - customTextLine = CustomTextProxy(customTextLine); - } final editableTextLine = EditableTextLine( node, null, - customTextLine ?? textLine, + textLine, _getHorizontalSpacingForLine(node, _styles), _getVerticalSpacingForLine(node, _styles), - lineConfiguration.textDirection, + _textDirection, controller.selection, widget.configurations.selectionColor, widget.configurations.enableInteractiveSelection, _hasFocus, - lineConfiguration.devicePixelRatioOf, + MediaQuery.devicePixelRatioOf(context), _cursorCont); return editableTextLine; } diff --git a/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart index 306a1b549..952533ec1 100644 --- a/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/bullet_point_leading.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../raw_editor/builders/leading_block_builder.dart'; import '../../style_widgets/style_widgets.dart'; Widget bulletPointLeading(LeadingConfigurations config) => diff --git a/lib/src/editor/widgets/default_leading_components/check_box_leading.dart b/lib/src/editor/widgets/default_leading_components/check_box_leading.dart index 5e9cb32d7..cf4883921 100644 --- a/lib/src/editor/widgets/default_leading_components/check_box_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/check_box_leading.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../raw_editor/builders/leading_block_builder.dart'; import '../../style_widgets/style_widgets.dart'; Widget checkboxLeading(LeadingConfigurations config) => diff --git a/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart b/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart index d9c799a65..3a0a1c66b 100644 --- a/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/codeblock_line_number_leading.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../raw_editor/builders/leading_block_builder.dart'; import '../../style_widgets/style_widgets.dart'; Widget codeBlockLineNumberLeading(LeadingConfigurations config) => diff --git a/lib/src/editor/widgets/default_leading_components/number_point_leading.dart b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart index e6297cb4c..375aac2aa 100644 --- a/lib/src/editor/widgets/default_leading_components/number_point_leading.dart +++ b/lib/src/editor/widgets/default_leading_components/number_point_leading.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import '../../raw_editor/builders/config/leading_configurations.dart'; +import '../../raw_editor/builders/leading_block_builder.dart'; import '../../style_widgets/style_widgets.dart'; Widget numberPointLeading(LeadingConfigurations config) => diff --git a/lib/src/editor/widgets/proxy.dart b/lib/src/editor/widgets/proxy.dart index 206d2030e..9504a11f6 100644 --- a/lib/src/editor/widgets/proxy.dart +++ b/lib/src/editor/widgets/proxy.dart @@ -133,93 +133,6 @@ class RenderEmbedProxy extends RenderProxyBox implements RenderContentProxyBox { double get preferredLineHeight => size.height; } -class CustomTextProxy extends SingleChildRenderObjectWidget { - const CustomTextProxy(Widget child, {super.key}) : super(child: child); - - @override - RenderCustomTextProxy createRenderObject(BuildContext context) { - return RenderCustomTextProxy(null); - } -} - -class RenderCustomTextProxy extends RenderProxyBox - implements RenderContentProxyBox { - RenderCustomTextProxy(super.child); - - RenderParagraphProxy? richTextProxyChild; - - @override - List getBoxesForSelection(TextSelection selection) { - _searchChild(); - if (richTextProxyChild == null) { - throw Exception( - "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); - } - return richTextProxyChild!.getBoxesForSelection(selection); - } - - void _searchChild() { - final box = child; - if (richTextProxyChild != null) return; - box!.visitChildren((renderObject) { - if (renderObject is RenderParagraphProxy) { - richTextProxyChild = renderObject; - return; - } - }); - } - - @override - double? getFullHeightForCaret(TextPosition position) { - _searchChild(); - if (richTextProxyChild == null) { - throw Exception( - "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); - } - return richTextProxyChild!.getFullHeightForCaret(position); - } - - @override - Offset getOffsetForCaret(TextPosition position, Rect caretPrototype) { - //TODO: here we will need to search the padding or container widgets that can contains EdgeInsets param - // to adjust correctly the offset of the caret - _searchChild(); - if (richTextProxyChild == null) { - throw Exception( - "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); - } - return richTextProxyChild!.getOffsetForCaret(position, caretPrototype); - } - - @override - TextPosition getPositionForOffset(Offset offset) { - //TODO: here we will need to search the padding or container widgets that can contains EdgeInsets param - // to adjust correctly the position - _searchChild(); - if (richTextProxyChild == null) { - throw Exception( - "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); - } - return richTextProxyChild!.getPositionForOffset(offset); - } - - @override - TextRange getWordBoundary(TextPosition position) { - _searchChild(); - if (richTextProxyChild == null) { - throw Exception( - "The customTextLineNodeBuilder need to have on some of it's children the TextLine child"); - } - return richTextProxyChild!.getWordBoundary(position); - } - - @override - double get preferredLineHeight { - _searchChild(); - return richTextProxyChild!.preferredLineHeight; - } -} - class RichTextProxy extends SingleChildRenderObjectWidget { /// Child argument should be an instance of RichText widget. const RichTextProxy({ diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 6f3287d45..b60077c47 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -13,8 +13,7 @@ import '../../../toolbar/base_toolbar.dart'; import '../../editor.dart'; import '../../embed/embed_editor_builder.dart'; import '../../provider.dart'; -import '../../raw_editor/builders/config/leading_configurations.dart'; -import '../../raw_editor/builders/utils.dart'; +import '../../raw_editor/builders/leading_block_builder.dart'; import '../box.dart'; import '../cursor.dart'; import '../default_leading_components/leading_components.dart'; From 3d2e201a96098922eb064179fe12baa049b622b0 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Tue, 27 Aug 2024 23:18:34 -0400 Subject: [PATCH 09/13] Fix: getDirectionOfNode does not return the current direction if it is passed in the args --- lib/src/delta/delta_diff.dart | 9 ++++++--- lib/src/editor/raw_editor/raw_editor_state.dart | 10 +--------- lib/src/editor/widgets/text/text_block.dart | 10 +--------- 3 files changed, 8 insertions(+), 21 deletions(-) diff --git a/lib/src/delta/delta_diff.dart b/lib/src/delta/delta_diff.dart index 92e63d9ae..74a690b21 100644 --- a/lib/src/delta/delta_diff.dart +++ b/lib/src/delta/delta_diff.dart @@ -88,10 +88,13 @@ int getPositionDelta(Delta user, Delta actual) { return diff; } -TextDirection getDirectionOfNode(Node node) { +TextDirection getDirectionOfNode(Node node, [TextDirection? currentDirection]) { final direction = node.style.attributes[Attribute.direction.key]; - if (direction == Attribute.rtl) { + // If it is RTL, then create the opposite direction + if (currentDirection == TextDirection.rtl && direction == Attribute.rtl) { + return TextDirection.ltr; + } else if (direction == Attribute.rtl) { return TextDirection.rtl; } - return TextDirection.ltr; + return currentDirection ?? TextDirection.ltr; } diff --git a/lib/src/editor/raw_editor/raw_editor_state.dart b/lib/src/editor/raw_editor/raw_editor_state.dart index eaf06d660..bec089581 100644 --- a/lib/src/editor/raw_editor/raw_editor_state.dart +++ b/lib/src/editor/raw_editor/raw_editor_state.dart @@ -976,15 +976,7 @@ class QuillRawEditorState extends EditorState } prevNodeOl = attrs[Attribute.list.key] == Attribute.ol; - var nodeTextDirection = getDirectionOfNode(node); - // verify if the direction from nodeTextDirection is the default direction - // and watch if the system language is a RTL language and avoid putting - // to the edge of the left side any checkbox or list point/number if is a - // RTL language - if (nodeTextDirection == TextDirection.ltr && - _textDirection == TextDirection.rtl) { - nodeTextDirection = TextDirection.rtl; - } + final nodeTextDirection = getDirectionOfNode(node, _textDirection); if (node is Line) { final editableTextLine = _getEditableTextLineFromNode(node, context); result.add(Directionality( diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index b60077c47..06d420558 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -197,15 +197,7 @@ class EditableTextBlock extends StatelessWidget { MediaQuery.devicePixelRatioOf(context), cursorCont, ); - var nodeTextDirection = getDirectionOfNode(line); - // verify if the direction from nodeTextDirection is the default direction - // and watch if the system language is a RTL language and avoid putting - // to the edge of the left side any checkbox or list point/number if is a - // RTL language - if (nodeTextDirection == TextDirection.ltr && - textDirection == TextDirection.rtl) { - nodeTextDirection = TextDirection.rtl; - } + final nodeTextDirection = getDirectionOfNode(line, textDirection); children.add( Directionality( textDirection: nodeTextDirection, From ffb3a5c4f6b093546f61ccc9a2b61322a852fe36 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Tue, 27 Aug 2024 23:28:45 -0400 Subject: [PATCH 10/13] Chore: removed unnecessary ignore in editor_configurations --- lib/src/editor/config/editor_configurations.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/src/editor/config/editor_configurations.dart b/lib/src/editor/config/editor_configurations.dart index 1a577edb3..f4366be7b 100644 --- a/lib/src/editor/config/editor_configurations.dart +++ b/lib/src/editor/config/editor_configurations.dart @@ -462,7 +462,6 @@ class QuillEditorConfigurations extends Equatable { }) { return QuillEditorConfigurations( sharedConfigurations: sharedConfigurations ?? this.sharedConfigurations, - // ignore: deprecated_member_use_from_same_package customLeadingBlockBuilder: customLeadingBlockBuilder ?? this.customLeadingBlockBuilder, // ignore: deprecated_member_use_from_same_package From cebf932320782f819316e77a462e969c88ad7438 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Wed, 28 Aug 2024 15:38:03 -0400 Subject: [PATCH 11/13] Fix: added missing export for leading configurations --- lib/flutter_quill.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index ded853647..f473f09c7 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -20,6 +20,7 @@ export 'src/document/style.dart'; export 'src/editor/editor.dart'; export 'src/editor/embed/embed_editor_builder.dart'; export 'src/editor/provider.dart'; +export 'src/editor/raw_editor/builders/leading_block_builder.dart'; export 'src/editor/raw_editor/config/raw_editor_configurations.dart'; export 'src/editor/raw_editor/quill_single_child_scroll_view.dart'; export 'src/editor/raw_editor/raw_editor.dart'; From 35821317b0aea5940d6a271cd57acc3100110267 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Wi=C5=9Bniewski?= Date: Thu, 29 Aug 2024 19:37:11 +0200 Subject: [PATCH 12/13] =?UTF-8?q?add=20ability=20to=20customize=20leading?= =?UTF-8?q?=20point=20width=20and=20leading=20point=20indent=E2=80=A6=20(#?= =?UTF-8?q?1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * add ability to customize leading point width and leading point indent width * fix one minor discrepancy --- lib/flutter_quill.dart | 2 + lib/src/editor/widgets/default_styles.dart | 10 +- lib/src/editor/widgets/text/text_block.dart | 118 ++++++++++++-------- 3 files changed, 82 insertions(+), 48 deletions(-) diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index f473f09c7..36a81a2ac 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -31,6 +31,8 @@ export 'src/editor/style_widgets/style_widgets.dart'; export 'src/editor/widgets/cursor.dart'; export 'src/editor/widgets/default_styles.dart'; export 'src/editor/widgets/link.dart'; +export 'src/editor/widgets/text/text_block.dart' + show TextBlockUtils, LeadingBlockIndentWidth, LeadingBlockNumberPointWidth; export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service.dart'; export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart'; export 'src/editor_toolbar_controller_shared/copy_cut_service/default_copy_cut_service.dart'; diff --git a/lib/src/editor/widgets/default_styles.dart b/lib/src/editor/widgets/default_styles.dart index 2ef7b0671..a9944c3b7 100644 --- a/lib/src/editor/widgets/default_styles.dart +++ b/lib/src/editor/widgets/default_styles.dart @@ -6,6 +6,7 @@ import '../../common/utils/platform.dart'; import '../../document/attribute.dart'; import '../../document/style.dart'; import '../style_widgets/checkbox_point.dart'; +import 'text/text_block.dart'; class QuillStyles extends InheritedWidget { const QuillStyles({ @@ -160,10 +161,15 @@ class DefaultListBlockStyle extends DefaultTextBlockStyle { super.verticalSpacing, super.lineSpacing, super.decoration, - this.checkboxUIBuilder, - ); + this.checkboxUIBuilder, { + this.indentWidthBuilder = TextBlockUtils.defaultIndentWidthBuilder, + this.numberPointWidthBuilder = + TextBlockUtils.defaultNumberPointWidthBuilder, + }); final QuillCheckboxBuilder? checkboxUIBuilder; + final LeadingBlockIndentWidth indentWidthBuilder; + final LeadingBlockNumberPointWidth numberPointWidthBuilder; } @immutable diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 06d420558..6fe5483cf 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -157,6 +157,12 @@ class EditableTextBlock extends StatelessWidget { List _buildChildren(BuildContext context, Map indentLevelCounts, bool clearIndents) { final defaultStyles = QuillStyles.getStyles(context, false); + final numberPointWidthBuilder = + defaultStyles?.lists?.numberPointWidthBuilder ?? + TextBlockUtils.defaultNumberPointWidthBuilder; + final indentWidthBuilder = defaultStyles?.lists?.indentWidthBuilder ?? + TextBlockUtils.defaultIndentWidthBuilder; + final count = block.children.length; final children = []; if (clearIndents) { @@ -187,7 +193,7 @@ class EditableTextBlock extends StatelessWidget { customLinkPrefixes: customLinkPrefixes, customRecognizerBuilder: customRecognizerBuilder, ), - _getIndentWidth(context, count), + indentWidthBuilder(block, context, count, numberPointWidthBuilder), _getSpacingForLine(line, index, count, defaultStyles), textDirection, textSelection, @@ -208,20 +214,6 @@ class EditableTextBlock extends StatelessWidget { return children.toList(growable: false); } - double _numberPointWidth(double fontSize, int count) { - final length = '$count'.length; - switch (length) { - case 1: - case 2: - return fontSize * 2; - default: - // 3 -> 2.5 - // 4 -> 3 - // 5 -> 3.5 - return fontSize * (length - (length - 2) / 2); - } - } - Widget? _buildLeading({ required BuildContext context, required Line line, @@ -232,6 +224,9 @@ class EditableTextBlock extends StatelessWidget { final defaultStyles = QuillStyles.getStyles(context, false)!; final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; final attrs = line.style.attributes; + final numberPointWidthBuilder = + defaultStyles.lists?.numberPointWidthBuilder ?? + TextBlockUtils.defaultNumberPointWidthBuilder; // Of the color button final fontColor = @@ -298,9 +293,9 @@ class EditableTextBlock extends StatelessWidget { color: defaultStyles.code!.style.color!.withOpacity(0.4), ), width: isOrdered || isCodeBlock - ? _numberPointWidth(fontSize, count) + ? numberPointWidthBuilder(fontSize, count) : isUnordered - ? fontSize * 2 + ? numberPointWidthBuilder(fontSize, 1) // same as fontSize * 2 : null, padding: isOrdered || isUnordered ? fontSize / 2 @@ -342,35 +337,6 @@ class EditableTextBlock extends StatelessWidget { return null; } - HorizontalSpacing _getIndentWidth(BuildContext context, int count) { - final defaultStyles = QuillStyles.getStyles(context, false)!; - final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; - final attrs = block.style.attributes; - - final indent = attrs[Attribute.indent.key]; - var extraIndent = 0.0; - if (indent != null && indent.value != null) { - extraIndent = fontSize * indent.value; - } - - if (attrs.containsKey(Attribute.blockQuote.key)) { - return HorizontalSpacing(fontSize + extraIndent, 0); - } - - var baseIndent = 0.0; - - if (attrs.containsKey(Attribute.list.key)) { - baseIndent = fontSize * 2; - if (attrs[Attribute.list.key] == Attribute.ol) { - baseIndent = _numberPointWidth(fontSize, count); - } else if (attrs.containsKey(Attribute.codeBlock.key)) { - baseIndent = _numberPointWidth(fontSize, count); - } - } - - return HorizontalSpacing(baseIndent + extraIndent, 0); - } - VerticalSpacing _getSpacingForLine( Line node, int index, @@ -800,3 +766,63 @@ class _EditableBlock extends MultiChildRenderObjectWidget { ..contentPadding = _contentPadding; } } + +typedef LeadingBlockIndentWidth = HorizontalSpacing Function( + Block block, + BuildContext context, + int count, + LeadingBlockNumberPointWidth numberPointWidthDelegate); + +typedef LeadingBlockNumberPointWidth = double Function( + double fontSize, int count); + +class TextBlockUtils { + TextBlockUtils._(); + + static HorizontalSpacing defaultIndentWidthBuilder( + Block block, + BuildContext context, + int count, + LeadingBlockNumberPointWidth numberPointWidthBuilder) { + final defaultStyles = QuillStyles.getStyles(context, false)!; + final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; + final attrs = block.style.attributes; + + final indent = attrs[Attribute.indent.key]; + var extraIndent = 0.0; + if (indent != null && indent.value != null) { + extraIndent = fontSize * indent.value; + } + + if (attrs.containsKey(Attribute.blockQuote.key)) { + return HorizontalSpacing(fontSize + extraIndent, 0); + } + + var baseIndent = 0.0; + + if (attrs.containsKey(Attribute.list.key)) { + baseIndent = fontSize * 2; + if (attrs[Attribute.list.key] == Attribute.ol) { + baseIndent = numberPointWidthBuilder(fontSize, count); + } else if (attrs.containsKey(Attribute.codeBlock.key)) { + baseIndent = numberPointWidthBuilder(fontSize, count); + } + } + + return HorizontalSpacing(baseIndent + extraIndent, 0); + } + + static double defaultNumberPointWidthBuilder(double fontSize, int count) { + final length = '$count'.length; + switch (length) { + case 1: + case 2: + return fontSize * 2; + default: + // 3 -> 2.5 + // 4 -> 3 + // 5 -> 3.5 + return fontSize * (length - (length - 2) / 2); + } + } +} From eca3b2cc19e86614c2b710ceaa1e87238cd76d15 Mon Sep 17 00:00:00 2001 From: CatHood0 Date: Thu, 29 Aug 2024 14:00:09 -0400 Subject: [PATCH 13/13] Chore: added comments and moved TextBlockUtils to its own file --- lib/flutter_quill.dart | 3 +- .../builders/leading_block_builder.dart | 9 ++- lib/src/editor/widgets/default_styles.dart | 2 +- lib/src/editor/widgets/text/text_block.dart | 61 +--------------- .../widgets/text/utils/text_block_utils.dart | 69 +++++++++++++++++++ 5 files changed, 80 insertions(+), 64 deletions(-) create mode 100644 lib/src/editor/widgets/text/utils/text_block_utils.dart diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index 36a81a2ac..862ef2d55 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -31,8 +31,7 @@ export 'src/editor/style_widgets/style_widgets.dart'; export 'src/editor/widgets/cursor.dart'; export 'src/editor/widgets/default_styles.dart'; export 'src/editor/widgets/link.dart'; -export 'src/editor/widgets/text/text_block.dart' - show TextBlockUtils, LeadingBlockIndentWidth, LeadingBlockNumberPointWidth; +export 'src/editor/widgets/text/utils/text_block_utils.dart'; export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service.dart'; export 'src/editor_toolbar_controller_shared/copy_cut_service/copy_cut_service_provider.dart'; export 'src/editor_toolbar_controller_shared/copy_cut_service/default_copy_cut_service.dart'; diff --git a/lib/src/editor/raw_editor/builders/leading_block_builder.dart b/lib/src/editor/raw_editor/builders/leading_block_builder.dart index de06c6001..eccb9d99d 100644 --- a/lib/src/editor/raw_editor/builders/leading_block_builder.dart +++ b/lib/src/editor/raw_editor/builders/leading_block_builder.dart @@ -5,6 +5,13 @@ import '../../style_widgets/checkbox_point.dart'; typedef LeadingBlockNodeBuilder = Widget? Function(Node, LeadingConfigurations); +/// This class contains all necessary values +/// to build the leading for lists and codeblocks +/// +/// If you want to customize the number point of the codeblock +/// please, take care about it, because the default +/// implementation uses the same leading of +/// ordered list to show lines with correct format class LeadingConfigurations { LeadingConfigurations({ required this.attribute, @@ -34,7 +41,7 @@ class LeadingConfigurations { final double? width; final double? padding; - /// these values are used if the leading is from a check list + // these values are used if the leading is from a check list final QuillCheckboxBuilder? uiBuilder; final double? lineSize; final bool? enabled; diff --git a/lib/src/editor/widgets/default_styles.dart b/lib/src/editor/widgets/default_styles.dart index a9944c3b7..8ae38d0d6 100644 --- a/lib/src/editor/widgets/default_styles.dart +++ b/lib/src/editor/widgets/default_styles.dart @@ -6,7 +6,7 @@ import '../../common/utils/platform.dart'; import '../../document/attribute.dart'; import '../../document/style.dart'; import '../style_widgets/checkbox_point.dart'; -import 'text/text_block.dart'; +import 'text/utils/text_block_utils.dart'; class QuillStyles extends InheritedWidget { const QuillStyles({ diff --git a/lib/src/editor/widgets/text/text_block.dart b/lib/src/editor/widgets/text/text_block.dart index 6fe5483cf..55fdf5fa8 100644 --- a/lib/src/editor/widgets/text/text_block.dart +++ b/lib/src/editor/widgets/text/text_block.dart @@ -22,6 +22,7 @@ import '../delegate.dart'; import '../link.dart'; import 'text_line.dart'; import 'text_selection.dart'; +import 'utils/text_block_utils.dart'; const List arabianRomanNumbers = [ 1000, @@ -766,63 +767,3 @@ class _EditableBlock extends MultiChildRenderObjectWidget { ..contentPadding = _contentPadding; } } - -typedef LeadingBlockIndentWidth = HorizontalSpacing Function( - Block block, - BuildContext context, - int count, - LeadingBlockNumberPointWidth numberPointWidthDelegate); - -typedef LeadingBlockNumberPointWidth = double Function( - double fontSize, int count); - -class TextBlockUtils { - TextBlockUtils._(); - - static HorizontalSpacing defaultIndentWidthBuilder( - Block block, - BuildContext context, - int count, - LeadingBlockNumberPointWidth numberPointWidthBuilder) { - final defaultStyles = QuillStyles.getStyles(context, false)!; - final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; - final attrs = block.style.attributes; - - final indent = attrs[Attribute.indent.key]; - var extraIndent = 0.0; - if (indent != null && indent.value != null) { - extraIndent = fontSize * indent.value; - } - - if (attrs.containsKey(Attribute.blockQuote.key)) { - return HorizontalSpacing(fontSize + extraIndent, 0); - } - - var baseIndent = 0.0; - - if (attrs.containsKey(Attribute.list.key)) { - baseIndent = fontSize * 2; - if (attrs[Attribute.list.key] == Attribute.ol) { - baseIndent = numberPointWidthBuilder(fontSize, count); - } else if (attrs.containsKey(Attribute.codeBlock.key)) { - baseIndent = numberPointWidthBuilder(fontSize, count); - } - } - - return HorizontalSpacing(baseIndent + extraIndent, 0); - } - - static double defaultNumberPointWidthBuilder(double fontSize, int count) { - final length = '$count'.length; - switch (length) { - case 1: - case 2: - return fontSize * 2; - default: - // 3 -> 2.5 - // 4 -> 3 - // 5 -> 3.5 - return fontSize * (length - (length - 2) / 2); - } - } -} diff --git a/lib/src/editor/widgets/text/utils/text_block_utils.dart b/lib/src/editor/widgets/text/utils/text_block_utils.dart new file mode 100644 index 000000000..9a1b5c552 --- /dev/null +++ b/lib/src/editor/widgets/text/utils/text_block_utils.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import '../../../../common/structs/horizontal_spacing.dart'; +import '../../../../document/attribute.dart'; +import '../../../../document/nodes/block.dart'; +import '../../default_styles.dart'; + +typedef LeadingBlockIndentWidth = HorizontalSpacing Function( + Block block, + BuildContext context, + int count, + LeadingBlockNumberPointWidth numberPointWidthDelegate); + +typedef LeadingBlockNumberPointWidth = double Function( + double fontSize, int count); + +class TextBlockUtils { + TextBlockUtils._(); + + /// Get the horizontalSpacing using the default + /// implementation provided by [Flutter Quill] + static HorizontalSpacing defaultIndentWidthBuilder( + Block block, + BuildContext context, + int count, + LeadingBlockNumberPointWidth numberPointWidthBuilder) { + final defaultStyles = QuillStyles.getStyles(context, false)!; + final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; + final attrs = block.style.attributes; + + final indent = attrs[Attribute.indent.key]; + var extraIndent = 0.0; + if (indent != null && indent.value != null) { + extraIndent = fontSize * indent.value; + } + + if (attrs.containsKey(Attribute.blockQuote.key)) { + return HorizontalSpacing(fontSize + extraIndent, 0); + } + + var baseIndent = 0.0; + + if (attrs.containsKey(Attribute.list.key)) { + baseIndent = fontSize * 2; + if (attrs[Attribute.list.key] == Attribute.ol) { + baseIndent = numberPointWidthBuilder(fontSize, count); + } else if (attrs.containsKey(Attribute.codeBlock.key)) { + baseIndent = numberPointWidthBuilder(fontSize, count); + } + } + + return HorizontalSpacing(baseIndent + extraIndent, 0); + } + + /// Get the width for the number point leading using the default + /// implementation provided by [Flutter Quill] + static double defaultNumberPointWidthBuilder(double fontSize, int count) { + final length = '$count'.length; + switch (length) { + case 1: + case 2: + return fontSize * 2; + default: + // 3 -> 2.5 + // 4 -> 3 + // 5 -> 3.5 + return fontSize * (length - (length - 2) / 2); + } + } +}