Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Fixed

- Fixed a crash when long-pressing text after removing a link.
- Fixed an issue where bullet points became visually detached from the text body when toggling text direction formatting (RTL) by locking the list leading block to the editor's base text direction.
- Fixed a brief toolbar flicker when tapping a checkbox: the header, inline, and color buttons momentarily reflected the tapped line's style and the checklist button briefly toggled before the selection was restored. The checkbox tap's gesture-driven caret move is now ignored and the checkbox is formatted silently.
- Fixed typed text being inserted at the previous caret position on Android after moving the caret with a tap/mouse by keeping the platform IME's editing state in sync with the selection even when the keyboard is hidden.
Expand Down
5 changes: 4 additions & 1 deletion lib/src/editor/raw_editor/raw_editor_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -1159,7 +1159,10 @@ class QuillRawEditorState extends EditorState
}

Future<LinkMenuAction> _linkActionPicker(Node linkNode) async {
final link = linkNode.style.attributes[Attribute.link.key]!.value!;
final link = linkNode.style.attributes[Attribute.link.key]?.value;
if (link is! String) {
return LinkMenuAction.none;
}
return widget.config.linkActionPickerDelegate(context, link, linkNode);
}

Expand Down
15 changes: 13 additions & 2 deletions lib/src/editor/widgets/text/text_line.dart
Original file line number Diff line number Diff line change
Expand Up @@ -661,6 +661,10 @@ class _TextLineState extends State<TextLine> {

GestureRecognizer? _getRecognizer(Node segment, bool isLink) {
if (_linkRecognizers.containsKey(segment)) {
if (!isLink) {
_linkRecognizers.remove(segment)?.dispose();
return null;
}
return _linkRecognizers[segment]!;
}

Expand Down Expand Up @@ -698,7 +702,10 @@ class _TextLineState extends State<TextLine> {
}

void _tapNodeLink(Node node) {
final link = node.style.attributes[Attribute.link.key]!.value;
final link = node.style.attributes[Attribute.link.key]?.value;
if (link is! String) {
return;
}

_tapLink(link);
}
Expand All @@ -723,7 +730,10 @@ class _TextLineState extends State<TextLine> {
}

Future<void> _longPressLink(Node node) async {
final link = node.style.attributes[Attribute.link.key]!.value!;
final link = node.style.attributes[Attribute.link.key]?.value;
if (link is! String) {
return;
}
final action = await widget.linkActionPicker(node);
switch (action) {
case LinkMenuAction.launch:
Expand All @@ -739,6 +749,7 @@ class _TextLineState extends State<TextLine> {
range.end - range.start,
Attribute.link,
);
_linkRecognizers.remove(node)?.dispose();
break;
case LinkMenuAction.none:
break;
Expand Down
78 changes: 78 additions & 0 deletions test/editor/link_remove_recognizer_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/quill_delta.dart';
import 'package:flutter_test/flutter_test.dart';

import '../common/utils/quill_test_app.dart';

void main() {
LongPressGestureRecognizer? findLinkLongPress(WidgetTester tester) {
LongPressGestureRecognizer? found;
for (final rich in tester.widgetList<RichText>(find.byType(RichText))) {
void walk(InlineSpan span) {
if (span is TextSpan) {
if (span.recognizer is LongPressGestureRecognizer) {
found = span.recognizer as LongPressGestureRecognizer;
}
span.children?.forEach(walk);
}
}

walk(rich.text);
}
return found;
}

testWidgets('2271 long-pressing text after removing a link does not throw', (
tester,
) async {
debugDefaultTargetPlatformOverride = TargetPlatform.android;

final document = Document.fromDelta(
Delta()
..insert('linktext', {'link': 'https://example.com'})
..insert('\n'),
);
final controller = QuillController(
document: document,
selection: const TextSelection.collapsed(offset: 0),
);
addTearDown(controller.dispose);

await tester.pumpWidget(
QuillTestApp.withScaffold(
SizedBox(
height: 200,
child: QuillEditor.basic(
controller: controller,
config: QuillEditorConfig(
linkActionPickerDelegate: (context, link, node) async {
return LinkMenuAction.remove;
},
),
),
),
),
);
await tester.pumpAndSettle();

final firstRecognizer = findLinkLongPress(tester);
expect(firstRecognizer, isNotNull);
expect(firstRecognizer!.onLongPress, isNotNull);

firstRecognizer.onLongPress!();
await tester.pumpAndSettle();

expect(
controller.document.toDelta().toList().any(
(op) => op.attributes != null && op.attributes!.containsKey('link'),
),
isFalse,
);

expect(findLinkLongPress(tester), isNull);
debugDefaultTargetPlatformOverride = null;
});
}