fix(app): a click in a short editor no longer freezes the app
The editor scrolls a new selection into view by keeping a box around it, 220 px by default, inside its viewport. A card's field is shorter than that (180 px in the PDF reader, 200 in the composer), so once its content could scroll, as with a screenshot taller than the field, the box stuck out at both ends: the editor scrolled up and down for ever, drew no frame again, and took ever more memory. RichContentEditor now keeps the box at most half its own height (autoScrollEdgeOffset). Found clicking a screenshot in a card in the PDF reader, on Linux. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
parent
a966080d99
commit
475ff99ead
3 changed files with 84 additions and 10 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
|
|
@ -126,16 +127,28 @@ class _RichContentEditorState() extends State<RichContentEditor> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final blocks = blocksOf(context);
|
||||
final editor = AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: editorStyleOf(context),
|
||||
blockComponentBuilders: buildersWithUnknown(blocks),
|
||||
characterShortcutEvents: characterShortcutsOf(
|
||||
blocks,
|
||||
slashMenu(context, blocks),
|
||||
final editor = LayoutBuilder(
|
||||
builder: (context, constraints) => AppFlowyEditor(
|
||||
editorState: _editorState,
|
||||
editorStyle: editorStyleOf(context),
|
||||
blockComponentBuilders: buildersWithUnknown(blocks),
|
||||
characterShortcutEvents: characterShortcutsOf(
|
||||
blocks,
|
||||
slashMenu(context, blocks),
|
||||
),
|
||||
autoFocus: widget.autofocus,
|
||||
focusNode: _focus,
|
||||
// The box the editor keeps around the selection while scrolling it
|
||||
// into view has to fit inside the editor: a taller one, as the
|
||||
// default is for a card's field, sticks out at both ends, and the
|
||||
// editor then scrolls up and down for ever, the app frozen.
|
||||
autoScrollEdgeOffset: constraints.hasBoundedHeight
|
||||
? math.min(
|
||||
appFlowyEditorAutoScrollEdgeOffset,
|
||||
constraints.maxHeight / 2,
|
||||
)
|
||||
: appFlowyEditorAutoScrollEdgeOffset,
|
||||
),
|
||||
autoFocus: widget.autofocus,
|
||||
focusNode: _focus,
|
||||
);
|
||||
if (!widget.formatBar) {
|
||||
return editor;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:halkyon_learn_flutter/l10n/app_localizations.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/content/rich_content.dart';
|
||||
import 'package:halkyon_learn_flutter/src/core/editor/rich_content_editor.dart';
|
||||
import 'package:material_ui/material_ui.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('a click in an editor shorter than its content selects there, '
|
||||
'and bringing the selection into view comes to an end', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
home: Scaffold(
|
||||
body: ListView(
|
||||
children: [
|
||||
SizedBox(
|
||||
// As short as a card's field: shorter than the box the
|
||||
// editor keeps around the selection while scrolling to it.
|
||||
height: 150,
|
||||
child: RichContentEditor(
|
||||
initialContent: RichContent.plain(
|
||||
[for (var line = 1; line <= 40; line++) 'Line $line']
|
||||
.join('\n'),
|
||||
),
|
||||
onChanged: (_) {},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
await tester.pump(const Duration(seconds: 1));
|
||||
|
||||
await tester.tap(find.text('Line 3', findRichText: true));
|
||||
// The frame comes: with a box taller than the editor, the editor
|
||||
// scrolled up and down for ever and never drew one.
|
||||
for (var frame = 0; frame < 5; frame++) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
|
||||
final editorState = tester
|
||||
.widget<AppFlowyEditor>(find.byType(AppFlowyEditor))
|
||||
.editorState;
|
||||
expect(editorState.selection?.start.path, [2]);
|
||||
});
|
||||
}
|
||||
|
|
@ -230,7 +230,7 @@ void main() {
|
|||
});
|
||||
|
||||
testWidgets('a screenshot of a region of the page goes into the card as '
|
||||
'an image that knows its page', (tester) async {
|
||||
'an image that knows its page, which a click selects', (tester) async {
|
||||
final id = await stored();
|
||||
await read(tester, id);
|
||||
await tester.tap(find.text('Create flashcard'));
|
||||
|
|
@ -269,6 +269,19 @@ void main() {
|
|||
expect(image.attributes[imagePdfKey], id);
|
||||
expect(image.attributes[imagePageKey], 1);
|
||||
expect(find.text('Screenshot saved'), findsOneWidget);
|
||||
|
||||
// Taller than the field, the image lets it scroll; a click on it once
|
||||
// made the editor scroll up and down for ever.
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(RichContentEditor).first,
|
||||
matching: find.byType(Image),
|
||||
),
|
||||
);
|
||||
for (var frame = 0; frame < 5; frame++) {
|
||||
await tester.pump(const Duration(milliseconds: 100));
|
||||
}
|
||||
expect(editorState.selection?.start.path, image.path);
|
||||
});
|
||||
|
||||
testWidgets('on a phone, the pages fill the screen, the panels open from '
|
||||
|
|
|
|||
Loading…
Reference in a new issue