feat: integrate awesome_lints and upgrade to Flutter 3.38 (#1173)

* chore: update Flutter SDK to version 3.38.5

Upgrade Flutter from 3.32.4 to 3.38.5 to meet awesome_lints
requirement for Dart SDK 3.10.0+. Update CI workflow to use
the new Flutter version and add custom_lint verification step.

* chore: integrate awesome_lints with custom lint rules

Add awesome_lints and custom_lint dependencies, configure
analysis_options.yaml to enable newline lint rules: newline_before_case,
newline_before_constructor, newline_before_method, and newline_before_return.

* style: apply newline formatting per awesome_lints rules

Add blank lines before case clauses, return statements, method
declarations, and constructors throughout the codebase to comply
with awesome_lints newline rules.

* fix: replace deprecated translate with leftTranslateByDouble

Update Matrix4.translate() calls to use the new leftTranslateByDouble()
API to resolve deprecation warnings in Flutter SDK 3.38.5.

* chore: improve code readability with blank line formatting
This commit is contained in:
Lucas 2025-12-23 15:12:57 +08:00 • committed by GitHub
commit f882d958d9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
229 changed files with 850 additions and 5 deletions

2
.fvmrc
View file

@ -1,3 +1,3 @@
{
"flutter": "3.32.4"
"flutter": "3.38.5"
}

View file

@ -33,7 +33,7 @@ jobs:
- uses: subosito/flutter-action@v2
with:
channel: "stable"
flutter-version: "3.32.4"
flutter-version: "3.38.5"
cache: true
- name: Run tests
@ -42,6 +42,7 @@ jobs:
flutter pub get
flutter analyze .
dart format --set-exit-if-changed .
dart run custom_lint
flutter test --coverage
- name: Upload coverage to Codecov

View file

@ -11,4 +11,14 @@ linter:
analyzer:
exclude:
- lib/src/l10n/**
- lib/src/l10n/**
plugins:
- custom_lint
custom_lint:
enable_all_lint_rules: false
rules:
- newline_before_case
- newline_before_constructor
- newline_before_method
- newline_before_return

View file

@ -22,6 +22,7 @@ class DocumentV0 {
final document = Map<String, Object>.from(json['document'] as Map);
final root = NodeV0.fromJson(document);
return DocumentV0(root: root);
}
@ -31,6 +32,7 @@ class DocumentV0 {
type: 'editor',
children: LinkedList<NodeV0>()..add(TextNodeV0.empty()),
);
return DocumentV0(
root: root,
);
@ -54,6 +56,7 @@ class DocumentV0 {
for (final node in nodes) {
target.insertBefore(node);
}
return true;
}
@ -62,6 +65,7 @@ class DocumentV0 {
for (var i = 0; i < nodes.length; i++) {
parent.insert(nodes.elementAt(i), index: path.last + i);
}
return true;
}
@ -83,6 +87,7 @@ class DocumentV0 {
target = next;
length--;
}
return true;
}
@ -96,6 +101,7 @@ class DocumentV0 {
return false;
}
target.updateAttributes(attributes);
return true;
}
@ -109,6 +115,7 @@ class DocumentV0 {
return false;
}
target.delta = target.delta.compose(delta);
return true;
}

View file

@ -87,6 +87,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
if (subtype != null) {
return '$type/$subtype';
}
return type;
}
@ -94,6 +95,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
if (attributes[BuiltInAttributeKey.subtype] is String) {
return attributes[BuiltInAttributeKey.subtype] as String;
}
return null;
}
@ -136,6 +138,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
entry.parent = this;
children.add(entry);
notifyListeners();
return;
}
@ -188,6 +191,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
if (attributes.isNotEmpty) {
map['attributes'] = attributes;
}
return map;
}
@ -208,6 +212,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
);
}
}
return node;
}
@ -222,6 +227,7 @@ final class NodeV0 extends ChangeNotifier with LinkedListEntry<NodeV0> {
}
index += 1;
}
return parent!._computePath([index, ...previous]);
}
}
@ -245,7 +251,9 @@ final class TextNodeV0 extends NodeV0 {
);
Delta _delta;
Delta get delta => _delta;
set delta(Delta v) {
_delta = v;
notifyListeners();
@ -255,6 +263,7 @@ final class TextNodeV0 extends NodeV0 {
Map<String, Object> toJson() {
final map = super.toJson();
map['delta'] = delta.toJson();
return map;
}
@ -277,6 +286,7 @@ final class TextNodeV0 extends NodeV0 {
);
}
}
return textNode;
}
@ -293,6 +303,7 @@ extension NodeV0Equality on Iterable<NodeV0> {
return false;
}
}
return true;
}

View file

@ -71,6 +71,7 @@ List<Operation> diffNodes(Node oldNode, Node newNode) {
}
}
}
return combinedOperations;
}
@ -99,6 +100,7 @@ List<Operation> diffNodes(Node oldNode, Node newNode) {
}
}
}
return combinedOperations;
}

View file

@ -41,6 +41,7 @@ class Document {
final document = Map<String, Object>.from(json['document'] as Map);
final root = Node.fromJson(document);
return Document(root: root);
}
@ -51,6 +52,7 @@ class Document {
type: 'document',
children: LinkedList<Node>()..add(TextNode.empty()),
);
return Document(
root: root,
);
@ -66,6 +68,7 @@ class Document {
type: 'page',
children: withInitialText ? [paragraphNode()] : [],
);
return Document(
root: root,
);
@ -83,6 +86,7 @@ class Document {
while (current != null && current.children.isNotEmpty) {
current = current.children.last;
}
return current;
}
@ -110,6 +114,7 @@ class Document {
for (final node in nodes) {
target.insertBefore(node);
}
return true;
}
@ -118,6 +123,7 @@ class Document {
for (var i = 0; i < nodes.length; i++) {
parent.insert(nodes.elementAt(i), index: path.last + i);
}
return true;
}
@ -139,6 +145,7 @@ class Document {
target = next;
length--;
}
return true;
}
@ -147,6 +154,7 @@ class Document {
// if the path is empty, it means the root node.
if (path.isEmpty) {
root.updateAttributes(attributes);
return true;
}
final target = nodeAtPath(path);
@ -154,6 +162,7 @@ class Document {
return false;
}
target.updateAttributes(attributes);
return true;
}
@ -168,6 +177,7 @@ class Document {
return false;
}
target.updateAttributes({'delta': (targetDelta.compose(delta)).toJson()});
return true;
}

View file

@ -84,8 +84,10 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
/// The children of the node.
final LinkedList<Node> _children;
List<Node> get children {
_cacheChildren ??= _children.toList(growable: false);
return _cacheChildren!;
}
@ -93,6 +95,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
/// The attributes of the node.
Attributes _attributes;
Attributes get attributes => {..._attributes};
/// The path of the node.
@ -138,6 +141,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
final index = path.first;
final child = childAtIndexOrNull(index);
return child?.childAtPath(path.sublist(1));
}
@ -161,6 +165,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
if (_children.isEmpty) {
_children.add(entry);
notifyListeners();
return;
}
@ -213,6 +218,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
parent?.notifyListeners();
parent = null;
return true;
}
@ -235,6 +241,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
if (attributes['delta'] is List) {
return Delta.fromJson(attributes['delta']);
}
return null;
}
@ -253,6 +260,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
// filter the null value
map['data'] = attributes..removeWhere((_, value) => value == null);
}
return map;
}
@ -281,6 +289,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
}
node.externalValues = externalValues;
node.extraInfos = extraInfos;
return node;
}
@ -297,6 +306,7 @@ final class Node extends ChangeNotifier with LinkedListEntry<Node> {
return previous;
}
final index = parent.children.indexOf(this);
return parent._computePath([index, ...previous]);
}
@ -351,8 +361,10 @@ final class TextNode extends Node {
String get subtype => '';
Delta _delta;
@override
Delta get delta => _delta;
set delta(Delta v) {
_delta = v;
notifyListeners();
@ -362,6 +374,7 @@ final class TextNode extends Node {
Map<String, Object> toJson() {
final map = super.toJson();
map['delta'] = delta.toJson();
return map;
}
@ -385,6 +398,7 @@ final class TextNode extends Node {
);
}
}
return textNode;
}
@ -401,6 +415,7 @@ extension NodeEquality on Iterable<Node> {
return false;
}
}
return true;
}

View file

@ -30,6 +30,7 @@ class NodeIterator implements Iterator<Node> {
if (!_began) {
_currentNode = startNode;
_began = true;
return true;
}
@ -40,6 +41,7 @@ class NodeIterator implements Iterator<Node> {
if (endNode != null && endNode == node) {
_currentNode = null;
return false;
}
@ -49,6 +51,7 @@ class NodeIterator implements Iterator<Node> {
_currentNode = node.next!;
} else if (node.parent == null) {
_currentNode = null;
return false;
} else {
while (node.parent != null) {
@ -74,6 +77,7 @@ class NodeIterator implements Iterator<Node> {
while (moveNext()) {
result.add(current);
}
return result;
}
}

View file

@ -14,6 +14,7 @@ extension PathExtensions on Path {
if (equals(other)) {
return true;
}
return this > other;
}
@ -32,6 +33,7 @@ extension PathExtensions on Path {
if (this.length < other.length) {
return false;
}
return true;
}
@ -39,6 +41,7 @@ extension PathExtensions on Path {
if (equals(other)) {
return true;
}
return this < other;
}
@ -57,6 +60,7 @@ extension PathExtensions on Path {
if (this.length > other.length) {
return false;
}
return true;
}
@ -66,6 +70,7 @@ extension PathExtensions on Path {
return nextPath;
}
final last = nextPath.last;
return nextPath
..removeLast()
..add(last + 1);
@ -77,6 +82,7 @@ extension PathExtensions on Path {
return nextPath;
}
final last = nextPath.last;
return nextPath
..removeLast()
..add(last + n);
@ -92,6 +98,7 @@ extension PathExtensions on Path {
return previousPath;
}
final last = previousPath.last;
return previousPath
..removeLast()
..add(max(0, last - 1));
@ -103,6 +110,7 @@ extension PathExtensions on Path {
return previousPath;
}
final last = previousPath.last;
return previousPath
..removeLast()
..add(max(0, last - n));
@ -112,6 +120,7 @@ extension PathExtensions on Path {
if (isEmpty) {
return this;
}
return Path.from(this, growable: true)..removeLast();
}
@ -130,6 +139,7 @@ extension PathExtensions on Path {
return false;
}
}
return true;
}
@ -145,6 +155,7 @@ extension PathExtensions on Path {
if (isSameDepth) {
return result && selection.start.path.length == length;
}
return result;
}
}

View file

@ -5,6 +5,7 @@ import 'package:appflowy_editor/appflowy_editor.dart';
/// If the document is empty, it will create a new paragraph node.
class AtLeastOneEditableNodeRule extends DocumentRule {
const AtLeastOneEditableNodeRule();
@override
bool shouldApply({
required EditorState editorState,
@ -15,6 +16,7 @@ class AtLeastOneEditableNodeRule extends DocumentRule {
return false;
}
final document = editorState.document;
return document.root.children.isEmpty;
}

View file

@ -131,6 +131,7 @@ class TextInsert extends TextOperation {
if (_attributes != null && _attributes.isNotEmpty) {
result['attributes'] = attributes;
}
return result;
}
@ -168,6 +169,7 @@ class TextRetain extends TextOperation {
if (_attributes != null && _attributes.isNotEmpty) {
result['attributes'] = attributes;
}
return result;
}
@ -256,11 +258,13 @@ class Delta extends Iterable<TextOperation> {
final lastOp = _operations.last;
if (lastOp is TextDelete && textOperation is TextDelete) {
lastOp.length += textOperation.length;
return;
}
if (_mapEquals(lastOp.attributes, textOperation.attributes)) {
if (lastOp is TextInsert && textOperation is TextInsert) {
lastOp.text += textOperation.text;
return;
}
// if there is an delete before the insert
@ -269,10 +273,12 @@ class Delta extends Iterable<TextOperation> {
_operations.removeLast();
_operations.add(textOperation);
_operations.add(lastOp);
return;
}
if (lastOp is TextRetain && textOperation is TextRetain) {
lastOp.length += textOperation.length;
return;
}
}
@ -387,6 +393,7 @@ class Delta extends Iterable<TextOperation> {
delta._operations.isNotEmpty &&
delta._operations.last == newOp) {
final rest = Delta(operations: thisIter.rest());
return (delta + rest)..chop();
}
} else if (otherOp is TextDelete && (thisOp is TextRetain)) {
@ -409,8 +416,10 @@ class Delta extends Iterable<TextOperation> {
return op.text;
}
assert(false, 'diff() called with non-insert operations');
return '';
}).join();
return text;
}).toList();
@ -430,11 +439,13 @@ class Delta extends Iterable<TextOperation> {
opLength = min(otherIter.peekLength(), length);
retDelta.add(otherIter.next(opLength));
break;
case diff_match_patch.DIFF_DELETE:
opLength = min(length, thisIter.peekLength());
thisIter.next(opLength);
retDelta.delete(opLength);
break;
case diff_match_patch.DIFF_EQUAL:
opLength = min(
min(thisIter.peekLength(), otherIter.peekLength()),
@ -460,6 +471,7 @@ class Delta extends Iterable<TextOperation> {
length -= opLength;
}
}
return retDelta..trim();
}
@ -480,6 +492,7 @@ class Delta extends Iterable<TextOperation> {
operations.add(other._operations[0]);
operations.addAll(other._operations.sublist(1));
}
return Delta(operations: operations);
}
@ -499,6 +512,7 @@ class Delta extends Iterable<TextOperation> {
if (other is! Delta) {
return false;
}
return listEquals(_operations, other._operations);
}
@ -515,6 +529,7 @@ class Delta extends Iterable<TextOperation> {
inverted.delete(op.length);
} else if (op is TextRetain && op.attributes == null) {
inverted.retain(op.length);
return previousValue + op.length;
} else if (op is TextDelete || op is TextRetain) {
final length = op.length;
@ -529,10 +544,13 @@ class Delta extends Iterable<TextOperation> {
);
}
}
return previousValue + length;
}
return previousValue;
});
return inverted..chop();
}
@ -554,6 +572,7 @@ class Delta extends Iterable<TextOperation> {
final content = toPlainText();
final boundary = CharacterBoundary(content);
final index = boundary.getLeadingTextBoundaryAt(pos - 1);
return index ?? 0;
}
@ -571,12 +590,14 @@ class Delta extends Iterable<TextOperation> {
}
final boundary = CharacterBoundary(content);
final index = boundary.getTrailingTextBoundaryAt(pos);
return index ?? content.length;
}
String toPlainText() {
_plainText ??=
_operations.whereType<TextInsert>().map((op) => op.text).join();
return _plainText!;
}
@ -634,8 +655,10 @@ class _OpIterator {
int peekLength() {
if (_index < _operations.length) {
final op = _operations[_index];
return op.length - _offset;
}
return _maxInt;
}
@ -661,8 +684,10 @@ class _OpIterator {
switch (nextOp) {
case TextDelete _:
return TextDelete(length: length);
case TextRetain _:
return TextRetain(length, attributes: nextOp.attributes);
case TextInsert _:
return TextInsert(
nextOp.text.substring(offset, offset + length),
@ -683,6 +708,7 @@ class _OpIterator {
final rest = _operations.sublist(_index);
_offset = offset;
_index = index;
return [next] + rest;
}
}
@ -692,5 +718,6 @@ bool _mapEquals<T, U>(Map<T, U>? a, Map<T, U>? b) {
if ((a == null || a.isEmpty) && (b == null || b.isEmpty)) {
return true;
}
return mapEquals(a, b);
}

View file

@ -34,6 +34,7 @@ class InsertOperation extends Operation {
final nodes = (json['nodes'] as List)
.map((n) => Node.fromJson(n))
.toList(growable: false);
return InsertOperation(path, nodes);
}
@ -81,6 +82,7 @@ class DeleteOperation extends Operation {
final nodes = (json['nodes'] as List)
.map((n) => Node.fromJson(n))
.toList(growable: false);
return DeleteOperation(path, nodes);
}
@ -128,6 +130,7 @@ class UpdateOperation extends Operation {
final path = json['path'] as Path;
final oldAttributes = json['oldAttributes'] as Attributes;
final attributes = json['attributes'] as Attributes;
return UpdateOperation(
path,
attributes,
@ -191,6 +194,7 @@ class UpdateTextOperation extends Operation {
final path = json['path'] as Path;
final delta = Delta.fromJson(json['delta']);
final inverted = Delta.fromJson(json['inverted']);
return UpdateTextOperation(path, delta, inverted);
}
@ -255,6 +259,7 @@ Path transformPath(Path preInsertPath, Path b, [int delta = 1]) {
Operation? transformOperation(Operation a, Operation b) {
if (a is InsertOperation) {
final newPath = transformPath(a.path, b.path, a.nodes.length);
return b.copyWith(path: newPath);
} else if (a is DeleteOperation) {
if (b is DeleteOperation) {
@ -265,6 +270,7 @@ Operation? transformOperation(Operation a, Operation b) {
}
}
final newPath = transformPath(a.path, b.path, -1 * a.nodes.length);
return b.copyWith(path: newPath);
}

View file

@ -22,6 +22,7 @@ class Transaction {
compose();
markNeedsComposing = false;
}
return _operations;
}
@ -144,6 +145,7 @@ class Transaction {
if (beforeSelection != null) {
json['before_selection'] = beforeSelection!.toJson();
}
return json;
}
@ -165,6 +167,7 @@ class Transaction {
op.inverted.compose(last.inverted),
);
operations[_operations.length - 1] = newOp;
return;
}
}
@ -208,12 +211,14 @@ extension TextTransaction on Transaction {
final delta = node.delta;
if (delta == null) {
assert(false, 'The node must have a delta.');
return;
}
if (index < 0 || index > delta.length) {
AppFlowyEditorLog.editor
.info('The index($index) is out of range or negative.');
return;
}
@ -244,6 +249,7 @@ extension TextTransaction on Transaction {
final delta = node.delta;
if (delta == null) {
assert(false, 'The node must have a delta.');
return;
}
@ -272,6 +278,7 @@ extension TextTransaction on Transaction {
final delta = node.delta;
if (delta == null) {
assert(false, 'The node must have a delta.');
return;
}
@ -416,6 +423,7 @@ extension TextTransaction on Transaction {
void compose() {
if (_composeMap.isEmpty) {
markNeedsComposing = false;
return;
}
for (final entry in _composeMap.entries) {
@ -460,6 +468,7 @@ extension TextTransaction on Transaction {
selection.endIndex - selection.startIndex,
texts.first,
);
return;
}

View file

@ -9,10 +9,13 @@ mixin BlockComponentAlignMixin {
switch (alignString) {
case 'center':
return Alignment.center;
case 'right':
return Alignment.centerRight;
case 'left':
return Alignment.centerLeft;
default:
return null;
}

View file

@ -39,7 +39,9 @@ class _BlockComponentActionWrapperState
bool isDisposed = false;
bool _alwaysShowActions = false;
bool get alwaysShowActions => _alwaysShowActions;
@override
set alwaysShowActions(bool alwaysShowActions) {
if (isDisposed) {

View file

@ -98,6 +98,7 @@ EdgeInsets _indentPadding(Node node, TextDirection textDirection) {
switch (textDirection) {
case TextDirection.ltr:
return const EdgeInsets.only(left: 24.0);
case TextDirection.rtl:
return const EdgeInsets.only(right: 24.0);
}

View file

@ -57,5 +57,6 @@ CommandShortcutEventHandler _convertToParagraphCommandHandler = (editorState) {
..deleteNode(node)
..afterSelection = transaction.beforeSelection;
editorState.apply(transaction);
return KeyEventResult.handled;
};

View file

@ -29,6 +29,7 @@ Future<bool> insertNewLineInType(
if (node != null && node.path.length > 1) {
return KeyEventResult.ignored != outdentCommand.execute(editorState);
}
return KeyEventResult.ignored !=
convertToParagraphCommand.execute(editorState);
}
@ -42,5 +43,6 @@ Future<bool> insertNewLineInType(
},
),
);
return true;
}

View file

@ -72,5 +72,6 @@ Future<bool> formatMarkdownSymbol(
..afterSelection = afterSelection;
await editorState.apply(transaction);
return true;
}

Some files were not shown because too many files have changed in this diff Show more