Code cleanup and mirgrate to dart 3 as minimum

pull/1486/head
Ellet 2 years ago
parent 7638535a33
commit 2610dab118
No known key found for this signature in database
GPG Key ID: C488CC70BBCEF0D1
  1. 6
      lib/src/models/config/others/animations.dart
  2. 2
      lib/src/models/documents/nodes/block.dart
  3. 2
      lib/src/models/documents/nodes/container.dart
  4. 6
      lib/src/models/documents/nodes/leaf.dart
  5. 2
      lib/src/models/documents/nodes/line.dart
  6. 4
      lib/src/models/documents/nodes/node.dart
  7. 6
      lib/src/test/widget_tester_extension.dart
  8. 15
      lib/src/utils/delta.dart
  9. 7
      lib/src/utils/experimental.dart
  10. 14
      lib/src/utils/string.dart
  11. 18
      lib/src/widgets/controller.dart
  12. 6
      lib/src/widgets/cursor.dart
  13. 51
      lib/src/widgets/default_styles.dart
  14. 19
      lib/src/widgets/delegate.dart
  15. 7
      lib/src/widgets/editor/editor.dart
  16. 3
      lib/src/widgets/embeds.dart
  17. 5
      lib/src/widgets/keyboard_listener.dart
  18. 8
      lib/src/widgets/proxy.dart
  19. 9
      lib/src/widgets/quill_single_child_scroll_view.dart
  20. 15
      lib/src/widgets/raw_editor/raw_editor.dart
  21. 4
      lib/src/widgets/style_widgets/bullet_point.dart
  22. 9
      lib/src/widgets/style_widgets/checkbox_point.dart
  23. 8
      lib/src/widgets/style_widgets/number_point.dart
  24. 72
      lib/src/widgets/text_block.dart
  25. 120
      lib/src/widgets/text_line.dart
  26. 6
      lib/src/widgets/text_selection.dart
  27. 4
      pubspec.yaml
  28. 2
      test/bug_fix_test.dart

@ -1,10 +1,8 @@
import 'package:equatable/equatable.dart'; import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart' show immutable; import 'package:meta/meta.dart' show experimental, immutable;
import '../../../utils/experimental.dart';
@immutable @immutable
@Experimental('This class might removed') @experimental
class QuillAnimationConfigurations extends Equatable { class QuillAnimationConfigurations extends Equatable {
const QuillAnimationConfigurations({ const QuillAnimationConfigurations({
required this.checkBoxPointItem, required this.checkBoxPointItem,

@ -13,7 +13,7 @@ import 'node.dart';
/// - Text Alignment /// - Text Alignment
/// - Text Direction /// - Text Direction
/// - Code Block /// - Code Block
class Block extends Container<Line?> { base class Block extends Container<Line?> {
/// Creates new unmounted [Block]. /// Creates new unmounted [Block].
@override @override
Node newInstance() => Block(); Node newInstance() => Block();

@ -14,7 +14,7 @@ import 'node.dart';
/// ///
/// Most of the operation handling logic is implemented by [Line] /// Most of the operation handling logic is implemented by [Line]
/// and [QuillText]. /// and [QuillText].
abstract class Container<T extends Node?> extends Node { abstract base class Container<T extends Node?> extends Node {
final LinkedList<Node> _children = LinkedList<Node>(); final LinkedList<Node> _children = LinkedList<Node>();
/// List of children. /// List of children.

@ -8,7 +8,7 @@ import 'line.dart';
import 'node.dart'; import 'node.dart';
/// A leaf in Quill document tree. /// A leaf in Quill document tree.
abstract class Leaf extends Node { abstract base class Leaf extends Node {
/// Creates a new [Leaf] with specified [data]. /// Creates a new [Leaf] with specified [data].
factory Leaf(Object data) { factory Leaf(Object data) {
if (data is Embeddable) { if (data is Embeddable) {
@ -216,7 +216,7 @@ abstract class Leaf extends Node {
/// The reason we are renamed quill Text to [QuillText] so it doesn't /// The reason we are renamed quill Text to [QuillText] so it doesn't
/// conflict with the one from the widgets, material or cupertino library /// conflict with the one from the widgets, material or cupertino library
/// ///
class QuillText extends Leaf { base class QuillText extends Leaf {
QuillText([String text = '']) QuillText([String text = ''])
: assert(!text.contains('\n')), : assert(!text.contains('\n')),
super.val(text); super.val(text);
@ -249,7 +249,7 @@ class QuillText extends Leaf {
/// necessarily mean the embed will look according to that style. For instance, /// necessarily mean the embed will look according to that style. For instance,
/// applying "bold" style to an image gives no effect, while adding a "link" to /// applying "bold" style to an image gives no effect, while adding a "link" to
/// an image actually makes the image react to user's action. /// an image actually makes the image react to user's action.
class Embed extends Leaf { base class Embed extends Leaf {
Embed(Embeddable data) : super.val(data); Embed(Embeddable data) : super.val(data);
// Refer to https://www.fileformat.info/info/unicode/char/fffc/index.htm // Refer to https://www.fileformat.info/info/unicode/char/fffc/index.htm

@ -19,7 +19,7 @@ import 'node.dart';
/// ///
/// When a line contains an embed, it fully occupies the line, no other embeds /// When a line contains an embed, it fully occupies the line, no other embeds
/// or text nodes are allowed. /// or text nodes are allowed.
class Line extends Container<Leaf?> { base class Line extends Container<Leaf?> {
@override @override
Leaf get defaultChild => QuillText(); Leaf get defaultChild => QuillText();

@ -17,7 +17,7 @@ import 'line.dart';
/// ///
/// The current parent node is exposed by the [parent] property. A node is /// The current parent node is exposed by the [parent] property. A node is
/// considered [mounted] when the [parent] property is not `null`. /// considered [mounted] when the [parent] property is not `null`.
abstract class Node extends LinkedListEntry<Node> { abstract base class Node extends LinkedListEntry<Node> {
/// Current parent of this node. May be null if this node is not mounted. /// Current parent of this node. May be null if this node is not mounted.
Container? parent; Container? parent;
@ -127,7 +127,7 @@ abstract class Node extends LinkedListEntry<Node> {
} }
/// Root node of document tree. /// Root node of document tree.
class Root extends Container<Container<Node?>> { base class Root extends Container<Container<Node?>> {
@override @override
Node newInstance() => Root(); Node newInstance() => Root();

@ -12,9 +12,9 @@ extension QuillEnterText on WidgetTester {
final editor = state<QuillEditorState>( final editor = state<QuillEditorState>(
find.descendant( find.descendant(
of: finder, of: finder,
matching: matching: find.byType(QuillEditor, skipOffstage: finder.skipOffstage),
find.byType(QuillEditor, skipOffstage: finder.skipOffstage), matchRoot: true,
matchRoot: true), ),
); );
editor.widget.focusNode.requestFocus(); editor.widget.focusNode.requestFocus();
await pump(); await pump();

@ -1,13 +1,20 @@
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:ui'; import 'dart:ui';
import 'package:meta/meta.dart' show immutable;
import '../models/documents/attribute.dart'; import '../models/documents/attribute.dart';
import '../models/documents/nodes/node.dart'; import '../models/documents/nodes/node.dart';
import '../models/quill_delta.dart'; import '../models/quill_delta.dart';
// Diff between two texts - old text and new text // Diff between two texts - old text and new text
@immutable
class Diff { class Diff {
Diff(this.start, this.deleted, this.inserted); const Diff({
required this.start,
required this.deleted,
required this.inserted,
});
// Start index in old text at which changes begin. // Start index in old text at which changes begin.
final int start; final int start;
@ -37,7 +44,11 @@ Diff getDiff(String oldText, String newText, int cursorPosition) {
start++) {} start++) {}
final deleted = (start >= end) ? '' : oldText.substring(start, end); final deleted = (start >= end) ? '' : oldText.substring(start, end);
final inserted = newText.substring(start, end + delta); final inserted = newText.substring(start, end + delta);
return Diff(start, deleted, inserted); return Diff(
start: start,
deleted: deleted,
inserted: inserted,
);
} }
int getPositionDelta(Delta user, Delta actual) { int getPositionDelta(Delta user, Delta actual) {

@ -1,7 +0,0 @@
import 'package:flutter/foundation.dart' show immutable;
@immutable
class Experimental {
const Experimental([this.reason = 'Experimental feature']);
final String reason;
}

@ -19,20 +19,6 @@ Map<String, String> parseKeyValuePairs(String s, Set<String> targetKeys) {
return result; return result;
} }
@Deprecated('Use replaceStyleStringWithSize instead')
String replaceStyleString(
String s,
double width,
double height,
) {
return replaceStyleStringWithSize(
s,
width: width,
height: height,
isMobile: true,
);
}
String replaceStyleStringWithSize( String replaceStyleStringWithSize(
String s, { String s, {
required double width, required double width,

@ -333,18 +333,23 @@ class QuillController extends ChangeNotifier {
void moveCursorToStart() { void moveCursorToStart() {
updateSelection( updateSelection(
const TextSelection.collapsed(offset: 0), ChangeSource.LOCAL); const TextSelection.collapsed(offset: 0),
ChangeSource.LOCAL,
);
} }
void moveCursorToPosition(int position) { void moveCursorToPosition(int position) {
updateSelection( updateSelection(
TextSelection.collapsed(offset: position), ChangeSource.LOCAL); TextSelection.collapsed(offset: position),
ChangeSource.LOCAL,
);
} }
void moveCursorToEnd() { void moveCursorToEnd() {
updateSelection( updateSelection(
TextSelection.collapsed(offset: plainTextEditingValue.text.length), TextSelection.collapsed(offset: plainTextEditingValue.text.length),
ChangeSource.LOCAL); ChangeSource.LOCAL,
);
} }
void updateSelection(TextSelection textSelection, ChangeSource source) { void updateSelection(TextSelection textSelection, ChangeSource source) {
@ -359,8 +364,11 @@ class QuillController extends ChangeNotifier {
textSelection = selection.copyWith( textSelection = selection.copyWith(
baseOffset: delta.transformPosition(selection.baseOffset, force: false), baseOffset: delta.transformPosition(selection.baseOffset, force: false),
extentOffset: extentOffset: delta.transformPosition(
delta.transformPosition(selection.extentOffset, force: false)); selection.extentOffset,
force: false,
),
);
if (selection != textSelection) { if (selection != textSelection) {
_updateSelection(textSelection, source); _updateSelection(textSelection, source);
} }

@ -257,7 +257,11 @@ class CursorPainter {
/// [offset] is global top left (x, y) of text line /// [offset] is global top left (x, y) of text line
/// [position] is relative (x) in text line /// [position] is relative (x) in text line
void paint( void paint(
Canvas canvas, Offset offset, TextPosition position, bool lineHasEmbed) { Canvas canvas,
Offset offset,
TextPosition position,
bool lineHasEmbed,
) {
// relative (x, y) to global offset // relative (x, y) to global offset
var relativeCaretOffset = editable!.getOffsetForCaret(position, prototype); var relativeCaretOffset = editable!.getOffsetForCaret(position, prototype);
if (lineHasEmbed && relativeCaretOffset == Offset.zero) { if (lineHasEmbed && relativeCaretOffset == Offset.zero) {

@ -249,13 +249,21 @@ class DefaultStyles {
), ),
const VerticalSpacing(8, 0), const VerticalSpacing(8, 0),
const VerticalSpacing(0, 0), const VerticalSpacing(0, 0),
null), null,
),
paragraph: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), paragraph: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0), null), const VerticalSpacing(0, 0), null),
bold: const TextStyle(fontWeight: FontWeight.bold), bold: const TextStyle(fontWeight: FontWeight.bold),
subscript: const TextStyle(fontFeatures: [FontFeature.subscripts()]), subscript: const TextStyle(
superscript: fontFeatures: [
const TextStyle(fontFeatures: [FontFeature.superscripts()]), FontFeature.subscripts(),
],
),
superscript: const TextStyle(
fontFeatures: [
FontFeature.superscripts(),
],
),
italic: const TextStyle(fontStyle: FontStyle.italic), italic: const TextStyle(fontStyle: FontStyle.italic),
small: const TextStyle(fontSize: 12), small: const TextStyle(fontSize: 12),
underline: const TextStyle(decoration: TextDecoration.underline), underline: const TextStyle(decoration: TextDecoration.underline),
@ -288,7 +296,12 @@ class DefaultStyles {
const VerticalSpacing(0, 0), const VerticalSpacing(0, 0),
null), null),
lists: DefaultListBlockStyle( lists: DefaultListBlockStyle(
baseStyle, baseSpacing, const VerticalSpacing(0, 6), null, null), baseStyle,
baseSpacing,
const VerticalSpacing(0, 6),
null,
null,
),
quote: DefaultTextBlockStyle( quote: DefaultTextBlockStyle(
TextStyle(color: baseStyle.color!.withOpacity(0.6)), TextStyle(color: baseStyle.color!.withOpacity(0.6)),
baseSpacing, baseSpacing,
@ -297,7 +310,8 @@ class DefaultStyles {
border: Border( border: Border(
left: BorderSide(width: 4, color: Colors.grey.shade300), left: BorderSide(width: 4, color: Colors.grey.shade300),
), ),
)), ),
),
code: DefaultTextBlockStyle( code: DefaultTextBlockStyle(
TextStyle( TextStyle(
color: Colors.blue.shade900.withOpacity(0.9), color: Colors.blue.shade900.withOpacity(0.9),
@ -312,11 +326,23 @@ class DefaultStyles {
borderRadius: BorderRadius.circular(2), borderRadius: BorderRadius.circular(2),
)), )),
indent: DefaultTextBlockStyle( indent: DefaultTextBlockStyle(
baseStyle, baseSpacing, const VerticalSpacing(0, 6), null), baseStyle,
align: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), baseSpacing,
const VerticalSpacing(0, 0), null), const VerticalSpacing(0, 6),
leading: DefaultTextBlockStyle(baseStyle, const VerticalSpacing(0, 0), null,
const VerticalSpacing(0, 0), null), ),
align: DefaultTextBlockStyle(
baseStyle,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
null,
),
leading: DefaultTextBlockStyle(
baseStyle,
const VerticalSpacing(0, 0),
const VerticalSpacing(0, 0),
null,
),
sizeSmall: const TextStyle(fontSize: 10), sizeSmall: const TextStyle(fontSize: 10),
sizeLarge: const TextStyle(fontSize: 18), sizeLarge: const TextStyle(fontSize: 18),
sizeHuge: const TextStyle(fontSize: 22)); sizeHuge: const TextStyle(fontSize: 22));
@ -347,6 +373,7 @@ class DefaultStyles {
leading: other.leading ?? leading, leading: other.leading ?? leading,
sizeSmall: other.sizeSmall ?? sizeSmall, sizeSmall: other.sizeSmall ?? sizeSmall,
sizeLarge: other.sizeLarge ?? sizeLarge, sizeLarge: other.sizeLarge ?? sizeLarge,
sizeHuge: other.sizeHuge ?? sizeHuge); sizeHuge: other.sizeHuge ?? sizeHuge,
);
} }
} }

@ -314,8 +314,10 @@ class EditorTextSelectionGestureDetectorBuilder {
void onDragSelectionUpdate( void onDragSelectionUpdate(
//DragStartDetails startDetails, //DragStartDetails startDetails,
DragUpdateDetails updateDetails) { DragUpdateDetails updateDetails) {
renderEditor!.extendSelection(updateDetails.globalPosition, renderEditor!.extendSelection(
cause: SelectionChangedCause.drag); updateDetails.globalPosition,
cause: SelectionChangedCause.drag,
);
} }
/// Handler for [EditorTextSelectionGestureDetector.onDragSelectionEnd]. /// Handler for [EditorTextSelectionGestureDetector.onDragSelectionEnd].
@ -341,16 +343,16 @@ class EditorTextSelectionGestureDetectorBuilder {
/// the handlers provided by this builder. /// the handlers provided by this builder.
/// ///
/// The [child] or its subtree should contain [EditableText]. /// The [child] or its subtree should contain [EditableText].
Widget build( Widget build({
{required HitTestBehavior behavior, required HitTestBehavior behavior,
required Widget child, required Widget child,
Key? key, Key? key,
bool detectWordBoundary = true}) { bool detectWordBoundary = true,
}) {
return EditorTextSelectionGestureDetector( return EditorTextSelectionGestureDetector(
key: key, key: key,
onTapDown: onTapDown, onTapDown: onTapDown,
onForcePressStart: onForcePressStart: delegate.forcePressEnabled ? onForcePressStart : null,
delegate.forcePressEnabled ? onForcePressStart : null,
onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null, onForcePressEnd: delegate.forcePressEnabled ? onForcePressEnd : null,
onSingleTapUp: onSingleTapUp, onSingleTapUp: onSingleTapUp,
onSingleTapCancel: onSingleTapCancel, onSingleTapCancel: onSingleTapCancel,
@ -364,6 +366,7 @@ class EditorTextSelectionGestureDetectorBuilder {
onDragSelectionEnd: onDragSelectionEnd, onDragSelectionEnd: onDragSelectionEnd,
behavior: behavior, behavior: behavior,
detectWordBoundary: detectWordBoundary, detectWordBoundary: detectWordBoundary,
child: child); child: child,
);
} }
} }

@ -1,10 +1,5 @@
import 'dart:math' as math; import 'dart:math' as math;
// ignore: unnecessary_import
// import 'dart:typed_data';
// The project maanged to compiled successfully without the import
// do we still need this import (dart:typed_data)??
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
@ -1495,7 +1490,7 @@ class RenderEditor extends RenderEditableContainerBox
} }
} }
class QuillVerticalCaretMovementRun extends Iterator<TextPosition> { class QuillVerticalCaretMovementRun implements Iterator<TextPosition> {
QuillVerticalCaretMovementRun._( QuillVerticalCaretMovementRun._(
this._editor, this._editor,
this._currentTextPosition, this._currentTextPosition,

@ -32,4 +32,5 @@ typedef EmbedButtonBuilder = Widget Function(
QuillController controller, QuillController controller,
double toolbarIconSize, double toolbarIconSize,
QuillIconTheme? iconTheme, QuillIconTheme? iconTheme,
QuillDialogTheme? dialogTheme); QuillDialogTheme? dialogTheme,
);

@ -44,8 +44,9 @@ class QuillKeyboardListenerState extends State<QuillKeyboardListener> {
final QuillPressedKeys _pressedKeys = QuillPressedKeys(); final QuillPressedKeys _pressedKeys = QuillPressedKeys();
bool _keyEvent(KeyEvent event) { bool _keyEvent(KeyEvent event) {
_pressedKeys _pressedKeys._updatePressedKeys(
._updatePressedKeys(HardwareKeyboard.instance.logicalKeysPressed); HardwareKeyboard.instance.logicalKeysPressed,
);
return false; return false;
} }

@ -6,8 +6,12 @@ import 'package:flutter/widgets.dart';
import 'box.dart'; import 'box.dart';
class BaselineProxy extends SingleChildRenderObjectWidget { class BaselineProxy extends SingleChildRenderObjectWidget {
const BaselineProxy({Key? key, Widget? child, this.textStyle, this.padding}) const BaselineProxy({
: super(key: key, child: child); Key? key,
Widget? child,
this.textStyle,
this.padding,
}) : super(key: key, child: child);
final TextStyle? textStyle; final TextStyle? textStyle;
final EdgeInsets? padding; final EdgeInsets? padding;

@ -14,10 +14,10 @@ class QuillSingleChildScrollView extends StatelessWidget {
const QuillSingleChildScrollView({ const QuillSingleChildScrollView({
required this.controller, required this.controller,
required this.viewportBuilder, required this.viewportBuilder,
Key? key, super.key,
this.physics, this.physics,
this.restorationId, this.restorationId,
}) : super(key: key); });
/// An object that can be used to control the position to which this scroll /// An object that can be used to control the position to which this scroll
/// view is scrolled. /// view is scrolled.
@ -48,7 +48,10 @@ class QuillSingleChildScrollView extends StatelessWidget {
AxisDirection _getDirection(BuildContext context) { AxisDirection _getDirection(BuildContext context) {
return getAxisDirectionFromAxisReverseAndDirectionality( return getAxisDirectionFromAxisReverseAndDirectionality(
context, Axis.vertical, false); context,
Axis.vertical,
false,
);
} }
@override @override

@ -437,9 +437,6 @@ class RawEditorState extends EditorState
// in the web browser, but we do unfocus for all other kinds of events. // in the web browser, but we do unfocus for all other kinds of events.
switch (event.kind) { switch (event.kind) {
case ui.PointerDeviceKind.touch: case ui.PointerDeviceKind.touch:
// if (isWeb()) {
// widget.focusNode.unfocus();
// }
break; break;
case ui.PointerDeviceKind.mouse: case ui.PointerDeviceKind.mouse:
case ui.PointerDeviceKind.stylus: case ui.PointerDeviceKind.stylus:
@ -449,7 +446,7 @@ class RawEditorState extends EditorState
break; break;
case ui.PointerDeviceKind.trackpad: case ui.PointerDeviceKind.trackpad:
throw UnimplementedError( throw UnimplementedError(
'Unexpected pointer down event for trackpad', 'Unexpected pointer down event for trackpad.',
); );
} }
break; break;
@ -461,7 +458,7 @@ class RawEditorState extends EditorState
default: default:
throw UnsupportedError( throw UnsupportedError(
'The platform ${defaultTargetPlatform.name} is not supported in the' 'The platform ${defaultTargetPlatform.name} is not supported in the'
' _defaultOnTapOutside', ' _defaultOnTapOutside()',
); );
} }
} }
@ -474,8 +471,11 @@ class RawEditorState extends EditorState
var _doc = controller.document; var _doc = controller.document;
if (_doc.isEmpty() && widget.placeholder != null) { if (_doc.isEmpty() && widget.placeholder != null) {
final raw = widget.placeholder?.replaceAll(r'"', '\\"'); final raw = widget.placeholder?.replaceAll(r'"', '\\"');
_doc = Document.fromJson(jsonDecode( _doc = Document.fromJson(
'[{"attributes":{"placeholder":true},"insert":"$raw\\n"}]')); jsonDecode(
'[{"attributes":{"placeholder":true},"insert":"$raw\\n"}]',
),
);
} }
Widget child = CompositedTransformTarget( Widget child = CompositedTransformTarget(
@ -1435,7 +1435,6 @@ class RawEditorState extends EditorState
@override @override
void requestKeyboard() { void requestKeyboard() {
if (controller.skipRequestKeyboard) { if (controller.skipRequestKeyboard) {
// TODO: There is a bug, requestKeyboard is being called 2-4 times!
// and that just by one simple change // and that just by one simple change
controller.skipRequestKeyboard = false; controller.skipRequestKeyboard = false;
return; return;

@ -1,7 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class QuillBulletPoint extends StatelessWidget { class QuillEditorBulletPoint extends StatelessWidget {
const QuillBulletPoint({ const QuillEditorBulletPoint({
required this.style, required this.style,
required this.width, required this.width,
this.padding = 0, this.padding = 0,

@ -3,8 +3,8 @@ import 'package:flutter_animate/flutter_animate.dart';
import '../../utils/extensions/build_context.dart'; import '../../utils/extensions/build_context.dart';
class CheckboxPoint extends StatefulWidget { class QuillEditorCheckboxPoint extends StatefulWidget {
const CheckboxPoint({ const QuillEditorCheckboxPoint({
required this.size, required this.size,
required this.value, required this.value,
required this.enabled, required this.enabled,
@ -20,10 +20,11 @@ class CheckboxPoint extends StatefulWidget {
final QuillCheckboxBuilder? uiBuilder; final QuillCheckboxBuilder? uiBuilder;
@override @override
_CheckboxPointState createState() => _CheckboxPointState(); _QuillEditorCheckboxPointState createState() =>
_QuillEditorCheckboxPointState();
} }
class _CheckboxPointState extends State<CheckboxPoint> { class _QuillEditorCheckboxPointState extends State<QuillEditorCheckboxPoint> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final uiBuilder = widget.uiBuilder; final uiBuilder = widget.uiBuilder;

@ -3,8 +3,8 @@ import 'package:flutter/material.dart';
import '../../models/documents/attribute.dart'; import '../../models/documents/attribute.dart';
import '../text_block.dart'; import '../text_block.dart';
class QuillNumberPoint extends StatelessWidget { class QuillEditorNumberPoint extends StatelessWidget {
const QuillNumberPoint({ const QuillEditorNumberPoint({
required this.index, required this.index,
required this.indentLevelCounts, required this.indentLevelCounts,
required this.count, required this.count,
@ -13,8 +13,8 @@ class QuillNumberPoint extends StatelessWidget {
required this.attrs, required this.attrs,
this.withDot = true, this.withDot = true,
this.padding = 0.0, this.padding = 0.0,
Key? key, super.key,
}) : super(key: key); });
final int index; final int index;
final Map<int?, int> indentLevelCounts; final Map<int?, int> indentLevelCounts;

@ -146,7 +146,13 @@ class EditableTextBlock extends StatelessWidget {
index++; index++;
final editableTextLine = EditableTextLine( final editableTextLine = EditableTextLine(
line, line,
_buildLeading(context, line, index, indentLevelCounts, count), _buildLeading(
context: context,
line: line,
index: index,
indentLevelCounts: indentLevelCounts,
count: count,
),
TextLine( TextLine(
line: line, line: line,
textDirection: textDirection, textDirection: textDirection,
@ -194,14 +200,19 @@ class EditableTextBlock extends StatelessWidget {
} }
} }
Widget? _buildLeading(BuildContext context, Line line, int index, Widget? _buildLeading({
Map<int, int> indentLevelCounts, int count) { required BuildContext context,
required Line line,
required int index,
required Map<int, int> indentLevelCounts,
required int count,
}) {
final defaultStyles = QuillStyles.getStyles(context, false)!; final defaultStyles = QuillStyles.getStyles(context, false)!;
final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16; final fontSize = defaultStyles.paragraph?.style.fontSize ?? 16;
final attrs = line.style.attributes; final attrs = line.style.attributes;
if (attrs[Attribute.list.key] == Attribute.ol) { if (attrs[Attribute.list.key] == Attribute.ol) {
return QuillNumberPoint( return QuillEditorNumberPoint(
index: index, index: index,
indentLevelCounts: indentLevelCounts, indentLevelCounts: indentLevelCounts,
count: count, count: count,
@ -213,7 +224,7 @@ class EditableTextBlock extends StatelessWidget {
} }
if (attrs[Attribute.list.key] == Attribute.ul) { if (attrs[Attribute.list.key] == Attribute.ul) {
return QuillBulletPoint( return QuillEditorBulletPoint(
style: style:
defaultStyles.leading!.style.copyWith(fontWeight: FontWeight.bold), defaultStyles.leading!.style.copyWith(fontWeight: FontWeight.bold),
width: fontSize * 2, width: fontSize * 2,
@ -223,7 +234,7 @@ class EditableTextBlock extends StatelessWidget {
if (attrs[Attribute.list.key] == Attribute.checked || if (attrs[Attribute.list.key] == Attribute.checked ||
attrs[Attribute.list.key] == Attribute.unchecked) { attrs[Attribute.list.key] == Attribute.unchecked) {
return CheckboxPoint( return QuillEditorCheckboxPoint(
size: fontSize, size: fontSize,
value: attrs[Attribute.list.key] == Attribute.checked, value: attrs[Attribute.list.key] == Attribute.checked,
enabled: !readOnly, enabled: !readOnly,
@ -233,7 +244,7 @@ class EditableTextBlock extends StatelessWidget {
} }
if (attrs.containsKey(Attribute.codeBlock.key) && if (attrs.containsKey(Attribute.codeBlock.key) &&
context.requireQuillEditorElementOptions.codeBlock.enableLineNumbers) { context.requireQuillEditorElementOptions.codeBlock.enableLineNumbers) {
return QuillNumberPoint( return QuillEditorNumberPoint(
index: index, index: index,
indentLevelCounts: indentLevelCounts, indentLevelCounts: indentLevelCounts,
count: count, count: count,
@ -278,7 +289,11 @@ class EditableTextBlock extends StatelessWidget {
} }
VerticalSpacing _getSpacingForLine( VerticalSpacing _getSpacingForLine(
Line node, int index, int count, DefaultStyles? defaultStyles) { Line node,
int index,
int count,
DefaultStyles? defaultStyles,
) {
var top = 0.0, bottom = 0.0; var top = 0.0, bottom = 0.0;
final attrs = block.style.attributes; final attrs = block.style.attributes;
@ -301,7 +316,7 @@ class EditableTextBlock extends StatelessWidget {
throw 'Invalid level $level'; throw 'Invalid level $level';
} }
} else { } else {
late VerticalSpacing lineSpacing; final VerticalSpacing lineSpacing;
if (attrs.containsKey(Attribute.blockQuote.key)) { if (attrs.containsKey(Attribute.blockQuote.key)) {
lineSpacing = defaultStyles!.quote!.lineSpacing; lineSpacing = defaultStyles!.quote!.lineSpacing;
} else if (attrs.containsKey(Attribute.indent.key)) { } else if (attrs.containsKey(Attribute.indent.key)) {
@ -502,10 +517,16 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
return TextSelectionPoint( return TextSelectionPoint(
Offset(0, preferredLineHeight(selection.extent)) + Offset(0, preferredLineHeight(selection.extent)) +
getOffsetForCaret(selection.extent), getOffsetForCaret(selection.extent),
null); null,
);
} }
final baseNode = container.queryChild(selection.start, false).node; final baseNode = container
.queryChild(
selection.start,
false,
)
.node;
var baseChild = firstChild; var baseChild = firstChild;
while (baseChild != null) { while (baseChild != null) {
if (baseChild.container == baseNode) { if (baseChild.container == baseNode) {
@ -516,10 +537,16 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
assert(baseChild != null); assert(baseChild != null);
final basePoint = baseChild!.getBaseEndpointForSelection( final basePoint = baseChild!.getBaseEndpointForSelection(
localSelection(baseChild.container, selection, true)); localSelection(
baseChild.container,
selection,
true,
),
);
return TextSelectionPoint( return TextSelectionPoint(
basePoint.point + (baseChild.parentData as BoxParentData).offset, basePoint.point + (baseChild.parentData as BoxParentData).offset,
basePoint.direction); basePoint.direction,
);
} }
@override @override
@ -528,7 +555,8 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
return TextSelectionPoint( return TextSelectionPoint(
Offset(0, preferredLineHeight(selection.extent)) + Offset(0, preferredLineHeight(selection.extent)) +
getOffsetForCaret(selection.extent), getOffsetForCaret(selection.extent),
null); null,
);
} }
final extentNode = container.queryChild(selection.end, false).node; final extentNode = container.queryChild(selection.end, false).node;
@ -543,10 +571,16 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
assert(extentChild != null); assert(extentChild != null);
final extentPoint = extentChild!.getExtentEndpointForSelection( final extentPoint = extentChild!.getExtentEndpointForSelection(
localSelection(extentChild.container, selection, true)); localSelection(
extentChild.container,
selection,
true,
),
);
return TextSelectionPoint( return TextSelectionPoint(
extentPoint.point + (extentChild.parentData as BoxParentData).offset, extentPoint.point + (extentChild.parentData as BoxParentData).offset,
extentPoint.direction); extentPoint.direction,
);
} }
@override @override
@ -576,8 +610,10 @@ class RenderEditableTextBlock extends RenderEditableContainerBox
offset.translate(decorationPadding.left, decorationPadding.top); offset.translate(decorationPadding.left, decorationPadding.top);
_painter!.paint(context.canvas, decorationOffset, filledConfiguration); _painter!.paint(context.canvas, decorationOffset, filledConfiguration);
if (debugSaveCount != context.canvas.getSaveCount()) { if (debugSaveCount != context.canvas.getSaveCount()) {
throw '${_decoration.runtimeType} painter had mismatching save and ' throw StateError(
'restore calls.'; '${_decoration.runtimeType} painter had mismatching save and '
'restore calls.',
);
} }
if (decoration.isComplex) { if (decoration.isComplex) {
context.setIsComplexHint(); context.setIsComplexHint();

@ -43,8 +43,8 @@ class TextLine extends StatefulWidget {
this.customStyleBuilder, this.customStyleBuilder,
this.customRecognizerBuilder, this.customRecognizerBuilder,
this.customLinkPrefixes = const <String>[], this.customLinkPrefixes = const <String>[],
Key? key, super.key,
}) : super(key: key); });
final Line line; final Line line;
final TextDirection? textDirection; final TextDirection? textDirection;
@ -88,7 +88,7 @@ class _TextLineState extends State<TextLine> {
// In editing mode it depends on the platform: // In editing mode it depends on the platform:
// Desktop platforms (macos, linux, windows): // Desktop platforms (macOS, Linux, Windows):
// only allow Meta (Control) + Click combinations // only allow Meta (Control) + Click combinations
if (isDesktop()) { if (isDesktop()) {
return _metaOrControlPressed; return _metaOrControlPressed;
@ -581,7 +581,7 @@ class EditableTextLine extends RenderObjectWidget {
} }
} }
enum TextLineSlot { LEADING, BODY } enum TextLineSlot { leading, body }
class RenderEditableTextLine extends RenderEditableBox { class RenderEditableTextLine extends RenderEditableBox {
/// Creates new editable paragraph render box. /// Creates new editable paragraph render box.
@ -595,7 +595,8 @@ class RenderEditableTextLine extends RenderEditableBox {
this.padding, this.padding,
this.color, this.color,
this.cursorCont, this.cursorCont,
this.inlineCodeStyle); this.inlineCodeStyle,
);
RenderBox? _leading; RenderBox? _leading;
RenderContentProxyBox? _body; RenderContentProxyBox? _body;
@ -715,11 +716,11 @@ class RenderEditableTextLine extends RenderEditableBox {
} }
void setLeading(RenderBox? l) { void setLeading(RenderBox? l) {
_leading = _updateChild(_leading, l, TextLineSlot.LEADING); _leading = _updateChild(_leading, l, TextLineSlot.leading);
} }
void setBody(RenderContentProxyBox? b) { void setBody(RenderContentProxyBox? b) {
_body = _updateChild(_body, b, TextLineSlot.BODY) as RenderContentProxyBox?; _body = _updateChild(_body, b, TextLineSlot.body) as RenderContentProxyBox?;
} }
void setInlineCodeStyle(InlineCodeStyle newStyle) { void setInlineCodeStyle(InlineCodeStyle newStyle) {
@ -744,7 +745,10 @@ class RenderEditableTextLine extends RenderEditableBox {
} }
RenderBox? _updateChild( RenderBox? _updateChild(
RenderBox? old, RenderBox? newChild, TextLineSlot slot) { RenderBox? old,
RenderBox? newChild,
TextLineSlot slot,
) {
if (old != null) { if (old != null) {
dropChild(old); dropChild(old);
children.remove(slot); children.remove(slot);
@ -801,7 +805,8 @@ class RenderEditableTextLine extends RenderEditableBox {
final targetBox = first ? boxes.first : boxes.last; final targetBox = first ? boxes.first : boxes.last;
return TextSelectionPoint( return TextSelectionPoint(
Offset(first ? targetBox.start : targetBox.end, targetBox.bottom), Offset(first ? targetBox.start : targetBox.end, targetBox.bottom),
targetBox.direction); targetBox.direction,
);
} }
@override @override
@ -814,9 +819,12 @@ class RenderEditableTextLine extends RenderEditableBox {
.where((element) => element.top < lineDy && element.bottom > lineDy) .where((element) => element.top < lineDy && element.bottom > lineDy)
.toList(growable: false); .toList(growable: false);
return TextRange( return TextRange(
start: start: getPositionForOffset(
getPositionForOffset(Offset(lineBoxes.first.left, lineDy)).offset, Offset(lineBoxes.first.left, lineDy),
end: getPositionForOffset(Offset(lineBoxes.last.right, lineDy)).offset); ).offset,
end: getPositionForOffset(
Offset(lineBoxes.last.right, lineDy),
).offset);
} }
@override @override
@ -886,7 +894,9 @@ class RenderEditableTextLine extends RenderEditableBox {
/// of the cursor for iOS is approximate and obtained through an eyeball /// of the cursor for iOS is approximate and obtained through an eyeball
/// comparison. /// comparison.
void _computeCaretPrototype() { void _computeCaretPrototype() {
if (isAppleOS()) { // If the cursor is taller only on iOS and not AppleOS then we should check
// only for iOS instead of AppleOS (macOS for example)
if (isIOS()) {
_caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2); _caretPrototype = Rect.fromLTWH(0, 0, cursorWidth, cursorHeight + 2);
} else { } else {
_caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0); _caretPrototype = Rect.fromLTWH(0, 2, cursorWidth, cursorHeight - 4.0);
@ -1090,8 +1100,13 @@ class RenderEditableTextLine extends RenderEditableBox {
} else { } else {
final parentData = _leading!.parentData as BoxParentData; final parentData = _leading!.parentData as BoxParentData;
final effectiveOffset = offset + parentData.offset; final effectiveOffset = offset + parentData.offset;
context.paintChild(_leading!, context.paintChild(
Offset(size.width - _leading!.size.width, effectiveOffset.dy)); _leading!,
Offset(
size.width - _leading!.size.width,
effectiveOffset.dy,
),
);
} }
} }
@ -1106,18 +1121,29 @@ class RenderEditableTextLine extends RenderEditableBox {
continue; continue;
} }
final textRange = TextSelection( final textRange = TextSelection(
baseOffset: item.offset, extentOffset: item.offset + item.length); baseOffset: item.offset,
extentOffset: item.offset + item.length,
);
final rects = _body!.getBoxesForSelection(textRange); final rects = _body!.getBoxesForSelection(textRange);
final paint = Paint()..color = inlineCodeStyle.backgroundColor!; final paint = Paint()..color = inlineCodeStyle.backgroundColor!;
for (final box in rects) { for (final box in rects) {
final rect = box.toRect().translate(0, 1).shift(effectiveOffset); final rect = box.toRect().translate(0, 1).shift(effectiveOffset);
if (inlineCodeStyle.radius == null) { if (inlineCodeStyle.radius == null) {
final paintRect = Rect.fromLTRB( final paintRect = Rect.fromLTRB(
rect.left - 2, rect.top, rect.right + 2, rect.bottom); rect.left - 2,
rect.top,
rect.right + 2,
rect.bottom,
);
context.canvas.drawRect(paintRect, paint); context.canvas.drawRect(paintRect, paint);
} else { } else {
final paintRect = RRect.fromLTRBR(rect.left - 2, rect.top, final paintRect = RRect.fromLTRBR(
rect.right + 2, rect.bottom, inlineCodeStyle.radius!); rect.left - 2,
rect.top,
rect.right + 2,
rect.bottom,
inlineCodeStyle.radius!,
);
context.canvas.drawRRect(paintRect, paint); context.canvas.drawRRect(paintRect, paint);
} }
} }
@ -1154,10 +1180,20 @@ class RenderEditableTextLine extends RenderEditableBox {
if (line.isEmpty && if (line.isEmpty &&
textSelection.baseOffset <= line.offset && textSelection.baseOffset <= line.offset &&
textSelection.extentOffset > line.offset) { textSelection.extentOffset > line.offset) {
final lineHeight = final lineHeight = preferredLineHeight(
preferredLineHeight(TextPosition(offset: line.offset)); TextPosition(
_selectedRects offset: line.offset,
?.add(TextBox.fromLTRBD(0, 0, 3, lineHeight, textDirection)); ),
);
_selectedRects?.add(
TextBox.fromLTRBD(
0,
0,
3,
lineHeight,
textDirection,
),
);
} }
_paintSelection(context, effectiveOffset); _paintSelection(context, effectiveOffset);
@ -1179,12 +1215,18 @@ class RenderEditableTextLine extends RenderEditableBox {
? TextPosition( ? TextPosition(
offset: cursorCont.floatingCursorTextPosition.value!.offset - offset: cursorCont.floatingCursorTextPosition.value!.offset -
line.documentOffset, line.documentOffset,
affinity: cursorCont.floatingCursorTextPosition.value!.affinity) affinity: cursorCont.floatingCursorTextPosition.value!.affinity,
)
: TextPosition( : TextPosition(
offset: textSelection.extentOffset - line.documentOffset, offset: textSelection.extentOffset - line.documentOffset,
affinity: textSelection.base.affinity); affinity: textSelection.base.affinity,
);
_cursorPainter.paint( _cursorPainter.paint(
context.canvas, effectiveOffset, position, lineHasEmbed); context.canvas,
effectiveOffset,
position,
lineHasEmbed,
);
} }
@override @override
@ -1197,7 +1239,8 @@ class RenderEditableTextLine extends RenderEditableBox {
hitTest: (result, transformed) { hitTest: (result, transformed) {
assert(transformed == position - childParentData.offset); assert(transformed == position - childParentData.offset);
return _leading!.hitTest(result, position: transformed); return _leading!.hitTest(result, position: transformed);
}); },
);
if (isHit) return true; if (isHit) return true;
} }
if (_body == null) return false; if (_body == null) return false;
@ -1207,14 +1250,19 @@ class RenderEditableTextLine extends RenderEditableBox {
position: position, position: position,
hitTest: (result, position) { hitTest: (result, position) {
return _body!.hitTest(result, position: position); return _body!.hitTest(result, position: position);
}); },
);
} }
@override @override
Rect getLocalRectForCaret(TextPosition position) { Rect getLocalRectForCaret(TextPosition position) {
final caretOffset = getOffsetForCaret(position); final caretOffset = getOffsetForCaret(position);
var rect = var rect = Rect.fromLTWH(
Rect.fromLTWH(0, 0, cursorWidth, cursorHeight).shift(caretOffset); 0,
0,
cursorWidth,
cursorHeight,
).shift(caretOffset);
final cursorOffset = cursorCont.style.offset; final cursorOffset = cursorCont.style.offset;
// Add additional cursor offset (generally only if on iOS). // Add additional cursor offset (generally only if on iOS).
if (cursorOffset != null) rect = rect.shift(cursorOffset); if (cursorOffset != null) rect = rect.shift(cursorOffset);
@ -1272,16 +1320,16 @@ class _TextLineElement extends RenderObjectElement {
@override @override
void mount(Element? parent, dynamic newSlot) { void mount(Element? parent, dynamic newSlot) {
super.mount(parent, newSlot); super.mount(parent, newSlot);
_mountChild(widget.leading, TextLineSlot.LEADING); _mountChild(widget.leading, TextLineSlot.leading);
_mountChild(widget.body, TextLineSlot.BODY); _mountChild(widget.body, TextLineSlot.body);
} }
@override @override
void update(EditableTextLine newWidget) { void update(EditableTextLine newWidget) {
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
_updateChild(widget.leading, TextLineSlot.LEADING); _updateChild(widget.leading, TextLineSlot.leading);
_updateChild(widget.body, TextLineSlot.BODY); _updateChild(widget.body, TextLineSlot.body);
} }
@override @override
@ -1318,10 +1366,10 @@ class _TextLineElement extends RenderObjectElement {
void _updateRenderObject(RenderBox? child, TextLineSlot? slot) { void _updateRenderObject(RenderBox? child, TextLineSlot? slot) {
switch (slot) { switch (slot) {
case TextLineSlot.LEADING: case TextLineSlot.leading:
renderObject.setLeading(child); renderObject.setLeading(child);
break; break;
case TextLineSlot.BODY: case TextLineSlot.body:
renderObject.setBody(child as RenderContentProxyBox?); renderObject.setBody(child as RenderContentProxyBox?);
break; break;
default: default:

@ -842,8 +842,10 @@ class _EditorTextSelectionGestureDetectorState
void _handleDragUpdate(DragUpdateDetails details) { void _handleDragUpdate(DragUpdateDetails details) {
_lastDragUpdateDetails = details; _lastDragUpdateDetails = details;
_dragUpdateThrottleTimer ??= _dragUpdateThrottleTimer ??= Timer(
Timer(const Duration(milliseconds: 50), _handleDragUpdateThrottled); const Duration(milliseconds: 50),
_handleDragUpdateThrottled,
);
} }
/// Drag updates are being throttled to avoid excessive text layouts in text /// Drag updates are being throttled to avoid excessive text layouts in text

@ -17,7 +17,8 @@ platforms:
windows: windows:
environment: environment:
sdk: ">=2.17.0 <4.0.0" sdk: '>=3.1.3 <4.0.0'
# sdk: ">=2.17.0 <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.10.0"
dependencies: dependencies:
@ -40,5 +41,6 @@ dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
meta: ^1.9.1
flutter: null flutter: null

@ -130,7 +130,7 @@ void main() {
controller.formatSelection(Attribute.unchecked); controller.formatSelection(Attribute.unchecked);
editor.focusNode.unfocus(); editor.focusNode.unfocus();
await tester.pump(); await tester.pump();
await tester.tap(find.byType(CheckboxPoint)); await tester.tap(find.byType(QuillEditorCheckboxPoint));
expect(tester.takeException(), isNull); expect(tester.takeException(), isNull);
}); });
}); });

Loading…
Cancel
Save