Browse Source

fix: #1778 (#1946)

* fix: [FR] The text formatting toolbar should appear after the selection #1778

* chore: format code
Lucas.Xu 2 years ago
parent
commit
b89c69f294

+ 44 - 8
frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/selection_service.dart

@@ -1,3 +1,5 @@
+import 'dart:async';
+
 import 'package:appflowy_editor/src/flutter/overlay.dart';
 import 'package:appflowy_editor/src/infra/log.dart';
 import 'package:appflowy_editor/src/service/context_menu/built_in_context_menu_item.dart';
@@ -121,6 +123,9 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
   EditorState get editorState => widget.editorState;
 
+  // Toolbar
+  Timer? _toolbarTimer;
+
   @override
   void initState() {
     super.initState();
@@ -144,6 +149,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
     clearSelection();
     WidgetsBinding.instance.removeObserver(this);
     currentSelection.removeListener(_onSelectionChange);
+    _clearToolbar();
 
     super.dispose();
   }
@@ -236,7 +242,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
     // clear cursor areas
 
     // hide toolbar
-    editorState.service.toolbarService?.hide();
+    // editorState.service.toolbarService?.hide();
 
     // clear context menu
     _clearContextMenu();
@@ -482,13 +488,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
 
     Overlay.of(context)?.insertAll(_selectionAreas);
 
-    if (toolbarOffset != null && layerLink != null) {
-      editorState.service.toolbarService?.showInOffset(
-        toolbarOffset,
-        alignment!,
-        layerLink,
-      );
-    }
+    // show toolbar
+    _showToolbarWithDelay(toolbarOffset, layerLink, alignment!);
   }
 
   void _updateCursorAreas(Position position) {
@@ -502,6 +503,7 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
     currentSelectedNodes = [node];
 
     _showCursor(node, position);
+    _clearToolbar();
   }
 
   void _showCursor(Node node, Position position) {
@@ -628,6 +630,40 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
     _scrollUpOrDownIfNeeded();
   }
 
+  void _showToolbarWithDelay(
+    Offset? toolbarOffset,
+    LayerLink? layerLink,
+    Alignment alignment, {
+    Duration delay = const Duration(milliseconds: 400),
+  }) {
+    if (toolbarOffset == null && layerLink == null) {
+      _clearToolbar();
+      return;
+    }
+    if (_toolbarTimer?.isActive ?? false) {
+      _toolbarTimer?.cancel();
+    }
+    _toolbarTimer = Timer(
+      delay,
+      () {
+        if (toolbarOffset != null && layerLink != null) {
+          editorState.service.toolbarService?.showInOffset(
+            toolbarOffset,
+            alignment,
+            layerLink,
+          );
+        }
+      },
+    );
+  }
+
+  void _clearToolbar() {
+    editorState.service.toolbarService?.hide();
+    if (_toolbarTimer?.isActive ?? false) {
+      _toolbarTimer?.cancel();
+    }
+  }
+
   void _showDebugLayerIfNeeded({Offset? offset}) {
     // remove false to show debug overlay.
     // if (kDebugMode && false) {

+ 10 - 2
frontend/appflowy_flutter/packages/appflowy_editor/lib/src/service/toolbar_service.dart

@@ -7,7 +7,11 @@ import 'package:appflowy_editor/src/extensions/object_extensions.dart';
 
 abstract class AppFlowyToolbarService {
   /// Show the toolbar widget beside the offset.
-  void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink);
+  void showInOffset(
+    Offset offset,
+    Alignment alignment,
+    LayerLink layerLink,
+  );
 
   /// Hide the toolbar widget.
   void hide();
@@ -45,7 +49,11 @@ class _FlowyToolbarState extends State<FlowyToolbar>
   }
 
   @override
-  void showInOffset(Offset offset, Alignment alignment, LayerLink layerLink) {
+  void showInOffset(
+    Offset offset,
+    Alignment alignment,
+    LayerLink layerLink,
+  ) {
     hide();
     final items = _filterItems(toolbarItems);
     if (items.isEmpty) {

+ 12 - 0
frontend/appflowy_flutter/packages/appflowy_editor/test/render/rich_text/toolbar_rich_text_test.dart

@@ -25,6 +25,7 @@ void main() async {
 
       await editor.updateSelection(h1);
 
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
 
       final h1Button = find.byWidgetPredicate((widget) {
@@ -52,6 +53,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(h2);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
 
       final h2Button = find.byWidgetPredicate((widget) {
@@ -77,6 +79,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(h3);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
 
       final h3Button = find.byWidgetPredicate((widget) {
@@ -104,6 +107,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(underline);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final underlineButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -132,6 +136,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(bold);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final boldButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -159,6 +164,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(italic);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final italicButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -187,6 +193,7 @@ void main() async {
 
       await editor.updateSelection(strikeThrough);
 
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final strikeThroughButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -214,6 +221,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(code);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final codeButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -250,6 +258,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(quote);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final quoteButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -276,6 +285,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(bulletList);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final bulletListButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -306,6 +316,7 @@ void main() async {
           end: Position(path: [0], offset: singleLineText.length));
 
       await editor.updateSelection(selection);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final highlightButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {
@@ -343,6 +354,7 @@ void main() async {
       );
 
       await editor.updateSelection(selection);
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       final colorButton = find.byWidgetPredicate((widget) {
         if (widget is ToolbarItemWidget) {

+ 1 - 0
frontend/appflowy_flutter/packages/appflowy_editor/test/service/internal_key_event_handlers/format_style_handler_test.dart

@@ -245,6 +245,7 @@ Future<void> _testLinkMenuInSingleTextSelection(WidgetTester tester) async {
   await editor.updateSelection(selection);
 
   // show toolbar
+  await tester.pumpAndSettle(const Duration(milliseconds: 500));
   expect(find.byType(ToolbarWidget), findsOneWidget);
 
   // trigger the link menu

+ 8 - 0
frontend/appflowy_flutter/packages/appflowy_editor/test/service/toolbar_service_test.dart

@@ -24,6 +24,7 @@ void main() async {
       );
       await editor.updateSelection(selection);
 
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
 
       // no link item
@@ -72,6 +73,7 @@ void main() async {
       await editor.updateSelection(
         Selection.single(path: [0], startOffset: 0, endOffset: text.length),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
 
       void testHighlight(bool expectedValue) {
@@ -138,6 +140,7 @@ void main() async {
       await editor.updateSelection(
         Selection.single(path: [0], startOffset: 0, endOffset: text.length),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       var itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.h1');
       expect(itemWidget.isHighlight, true);
@@ -145,6 +148,7 @@ void main() async {
       await editor.updateSelection(
         Selection.single(path: [1], startOffset: 0, endOffset: text.length),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.quote');
       expect(itemWidget.isHighlight, true);
@@ -152,6 +156,7 @@ void main() async {
       await editor.updateSelection(
         Selection.single(path: [2], startOffset: 0, endOffset: text.length),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       itemWidget = _itemWidgetForId(tester, 'appflowy.toolbar.bulleted_list');
       expect(itemWidget.isHighlight, true);
@@ -183,6 +188,7 @@ void main() async {
       await editor.updateSelection(
         Selection.single(path: [2], startOffset: text.length, endOffset: 0),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       expect(
         _itemWidgetForId(tester, 'appflowy.toolbar.h1').isHighlight,
@@ -199,6 +205,7 @@ void main() async {
           end: Position(path: [1], offset: 0),
         ),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       expect(
         _itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight,
@@ -211,6 +218,7 @@ void main() async {
           end: Position(path: [0], offset: 0),
         ),
       );
+      await tester.pumpAndSettle(const Duration(milliseconds: 500));
       expect(find.byType(ToolbarWidget), findsOneWidget);
       expect(
         _itemWidgetForId(tester, 'appflowy.toolbar.bold').isHighlight,