Skip to content

Commit b89c69f

Browse files
authored
fix: #1778 (#1946)
* fix: [FR] The text formatting toolbar should appear after the selection #1778 * chore: format code
1 parent 77ff2e9 commit b89c69f

File tree

5 files changed

+75
-10
lines changed

5 files changed

+75
-10
lines changed

frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import 'dart:async';
2+
13
import 'package:appflowy_editor/src/flutter/overlay.dart';
24
import 'package:appflowy_editor/src/infra/log.dart';
35
import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
@@ -121,6 +123,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
121123

122124
EditorState get editorState => widget.editorState;
123125

126+
// Toolbar
127+
Timer? _toolbarTimer;
128+
124129
@override
125130
void initState() {
126131
super.initState();
@@ -144,6 +149,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
144149
clearSelection();
145150
WidgetsBinding.instance.removeObserver(this);
146151
currentSelection.removeListener(_onSelectionChange);
152+
_clearToolbar();
147153

148154
super.dispose();
149155
}
@@ -236,7 +242,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
236242
// clear cursor areas
237243

238244
// hide toolbar
239-
editorState.service.toolbarService?.hide();
245+
// editorState.service.toolbarService?.hide();
240246

241247
// clear context menu
242248
_clearContextMenu();
@@ -482,13 +488,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
482488

483489
Overlay.of(context)?.insertAll(_selectionAreas);
484490

485-
if (toolbarOffset != null && layerLink != null) {
486-
editorState.service.toolbarService?.showInOffset(
487-
toolbarOffset,
488-
alignment!,
489-
layerLink,
490-
);
491-
}
491+
// show toolbar
492+
_showToolbarWithDelay(toolbarOffset, layerLink, alignment!);
492493
}
493494

494495
void _updateCursorAreas(Position position) {
@@ -502,6 +503,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
502503
currentSelectedNodes = [node];
503504

504505
_showCursor(node, position);
506+
_clearToolbar();
505507
}
506508

507509
void _showCursor(Node node, Position position) {
@@ -628,6 +630,40 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
628630
_scrollUpOrDownIfNeeded();
629631
}
630632

633+
void _showToolbarWithDelay(
634+
Offset? toolbarOffset,
635+
LayerLink? layerLink,
636+
Alignment alignment, {
637+
Duration delay = const Duration(milliseconds: 400),
638+
}) {
639+
if (toolbarOffset == null && layerLink == null) {
640+
_clearToolbar();
641+
return;
642+
}
643+
if (_toolbarTimer?.isActive ?? false) {
644+
_toolbarTimer?.cancel();
645+
}
646+
_toolbarTimer = Timer(
647+
delay,
648+
() {
649+
if (toolbarOffset != null && layerLink != null) {
650+
editorState.service.toolbarService?.showInOffset(
651+
toolbarOffset,
652+
alignment,
653+
layerLink,
654+
);
655+
}
656+
},
657+
);
658+
}
659+
660+
void _clearToolbar() {
661+
editorState.service.toolbarService?.hide();
662+
if (_toolbarTimer?.isActive ?? false) {
663+
_toolbarTimer?.cancel();
664+
}
665+
}
666+
631667
void _showDebugLayerIfNeeded({Offset? offset}) {
632668
// remove false to show debug overlay.
633669
// if (kDebugMode && false) {

frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import 'package:appflowy_editor/src/extensions/object_extensions.dart';
77

88
abstract class AppFlowyToolbarService {
99
/// Show the toolbar widget beside the offset.
10-
void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink);
10+
void showInOffset(
11+
Offset offset,
12+
Alignment alignment,
13+
LayerLink layerLink,
14+
);
1115

1216
/// Hide the toolbar widget.
1317
void hide();
@@ -45,7 +49,11 @@ class _FlowyToolbarState extends State<FlowyToolbar>
4549
}
4650

4751
@override
48-
void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink) {
52+
void showInOffset(
53+
Offset offset,
54+
Alignment alignment,
55+
LayerLink layerLink,
56+
) {
4957
hide();
5058
final items = _filterItems(toolbarItems);
5159
if (items.isEmpty) {

frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ void main() async {
2525

2626
await editor.updateSelection(h1);
2727

28+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
2829
expect(find.byType(ToolbarWidget), findsOneWidget);
2930

3031
final h1Button = find.byWidgetPredicate((widget) {
@@ -52,6 +53,7 @@ void main() async {
5253
end: Position(path: [0], offset: singleLineText.length));
5354

5455
await editor.updateSelection(h2);
56+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
5557
expect(find.byType(ToolbarWidget), findsOneWidget);
5658

5759
final h2Button = find.byWidgetPredicate((widget) {
@@ -77,6 +79,7 @@ void main() async {
7779
end: Position(path: [0], offset: singleLineText.length));
7880

7981
await editor.updateSelection(h3);
82+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
8083
expect(find.byType(ToolbarWidget), findsOneWidget);
8184

8285
final h3Button = find.byWidgetPredicate((widget) {
@@ -104,6 +107,7 @@ void main() async {
104107
end: Position(path: [0], offset: singleLineText.length));
105108

106109
await editor.updateSelection(underline);
110+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
107111
expect(find.byType(ToolbarWidget), findsOneWidget);
108112
final underlineButton = find.byWidgetPredicate((widget) {
109113
if (widget is ToolbarItemWidget) {
@@ -132,6 +136,7 @@ void main() async {
132136
end: Position(path: [0], offset: singleLineText.length));
133137

134138
await editor.updateSelection(bold);
139+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
135140
expect(find.byType(ToolbarWidget), findsOneWidget);
136141
final boldButton = find.byWidgetPredicate((widget) {
137142
if (widget is ToolbarItemWidget) {
@@ -159,6 +164,7 @@ void main() async {
159164
end: Position(path: [0], offset: singleLineText.length));
160165

161166
await editor.updateSelection(italic);
167+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
162168
expect(find.byType(ToolbarWidget), findsOneWidget);
163169
final italicButton = find.byWidgetPredicate((widget) {
164170
if (widget is ToolbarItemWidget) {
@@ -187,6 +193,7 @@ void main() async {
187193

188194
await editor.updateSelection(strikeThrough);
189195

196+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
190197
expect(find.byType(ToolbarWidget), findsOneWidget);
191198
final strikeThroughButton = find.byWidgetPredicate((widget) {
192199
if (widget is ToolbarItemWidget) {
@@ -214,6 +221,7 @@ void main() async {
214221
end: Position(path: [0], offset: singleLineText.length));
215222

216223
await editor.updateSelection(code);
224+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
217225
expect(find.byType(ToolbarWidget), findsOneWidget);
218226
final codeButton = find.byWidgetPredicate((widget) {
219227
if (widget is ToolbarItemWidget) {
@@ -250,6 +258,7 @@ void main() async {
250258
end: Position(path: [0], offset: singleLineText.length));
251259

252260
await editor.updateSelection(quote);
261+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
253262
expect(find.byType(ToolbarWidget), findsOneWidget);
254263
final quoteButton = find.byWidgetPredicate((widget) {
255264
if (widget is ToolbarItemWidget) {
@@ -276,6 +285,7 @@ void main() async {
276285
end: Position(path: [0], offset: singleLineText.length));
277286

278287
await editor.updateSelection(bulletList);
288+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
279289
expect(find.byType(ToolbarWidget), findsOneWidget);
280290
final bulletListButton = find.byWidgetPredicate((widget) {
281291
if (widget is ToolbarItemWidget) {
@@ -306,6 +316,7 @@ void main() async {
306316
end: Position(path: [0], offset: singleLineText.length));
307317

308318
await editor.updateSelection(selection);
319+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
309320
expect(find.byType(ToolbarWidget), findsOneWidget);
310321
final highlightButton = find.byWidgetPredicate((widget) {
311322
if (widget is ToolbarItemWidget) {
@@ -343,6 +354,7 @@ void main() async {
343354
);
344355

345356
await editor.updateSelection(selection);
357+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
346358
expect(find.byType(ToolbarWidget), findsOneWidget);
347359
final colorButton = find.byWidgetPredicate((widget) {
348360
if (widget is ToolbarItemWidget) {

frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
245245
await editor.updateSelection(selection);
246246

247247
// show toolbar
248+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
248249
expect(find.byType(ToolbarWidget), findsOneWidget);
249250

250251
// trigger the link menu

frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ void main() async {
2424
);
2525
await editor.updateSelection(selection);
2626

27+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
2728
expect(find.byType(ToolbarWidget), findsOneWidget);
2829

2930
// no link item
@@ -72,6 +73,7 @@ void main() async {
7273
await editor.updateSelection(
7374
Selection.single(path: [0], startOffset: 0, endOffset: text.length),
7475
);
76+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
7577
expect(find.byType(ToolbarWidget), findsOneWidget);
7678

7779
void testHighlight(bool expectedValue) {
@@ -138,20 +140,23 @@ void main() async {
138140
await editor.updateSelection(
139141
Selection.single(path: [0], startOffset: 0, endOffset: text.length),
140142
);
143+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
141144
expect(find.byType(ToolbarWidget), findsOneWidget);
142145
var itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.h1');
143146
expect(itemWidget.isHighlight, true);
144147

145148
await editor.updateSelection(
146149
Selection.single(path: [1], startOffset: 0, endOffset: text.length),
147150
);
151+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
148152
expect(find.byType(ToolbarWidget), findsOneWidget);
149153
itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.quote');
150154
expect(itemWidget.isHighlight, true);
151155

152156
await editor.updateSelection(
153157
Selection.single(path: [2], startOffset: 0, endOffset: text.length),
154158
);
159+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
155160
expect(find.byType(ToolbarWidget), findsOneWidget);
156161
itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.bulleted_list');
157162
expect(itemWidget.isHighlight, true);
@@ -183,6 +188,7 @@ void main() async {
183188
await editor.updateSelection(
184189
Selection.single(path: [2], startOffset: text.length, endOffset: 0),
185190
);
191+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
186192
expect(find.byType(ToolbarWidget), findsOneWidget);
187193
expect(
188194
_itemWidgetForId(tester, 'appflowy.toolbar.h1').isHighlight,
@@ -199,6 +205,7 @@ void main() async {
199205
end: Position(path: [1], offset: 0),
200206
),
201207
);
208+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
202209
expect(find.byType(ToolbarWidget), findsOneWidget);
203210
expect(
204211
_itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight,
@@ -211,6 +218,7 @@ void main() async {
211218
end: Position(path: [0], offset: 0),
212219
),
213220
);
221+
await tester.pumpAndSettle(const Duration(milliseconds: 500));
214222
expect(find.byType(ToolbarWidget), findsOneWidget);
215223
expect(
216224
_itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight,

0 commit comments

Comments
 (0)