瀏覽代碼

feat: auto pop up the menu after inserting image block (#3698)

Lucas.Xu 1 年之前
父節點
當前提交
11daa15b89

+ 22 - 6
frontend/appflowy_flutter/integration_test/document/document_with_image_block_test.dart

@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/uplo
 import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
 import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu;
 import 'package:easy_localization/easy_localization.dart';
+import 'package:flowy_infra_ui/flowy_infra_ui.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter/services.dart';
 import 'package:flutter_test/flutter_test.dart';
@@ -41,8 +42,13 @@ void main() {
       await tester.editor.tapSlashMenuItemWithName('Image');
       expect(find.byType(CustomImageBlockComponent), findsOneWidget);
       expect(find.byType(ImagePlaceholder), findsOneWidget);
-
-      await tester.tapButton(find.byType(ImagePlaceholder));
+      expect(
+        find.descendant(
+          of: find.byType(ImagePlaceholder),
+          matching: find.byType(AppFlowyPopover),
+        ),
+        findsOneWidget,
+      );
       expect(find.byType(UploadImageMenu), findsOneWidget);
 
       final image = await rootBundle.load('assets/test/images/sample.jpeg');
@@ -84,8 +90,13 @@ void main() {
       await tester.editor.tapSlashMenuItemWithName('Image');
       expect(find.byType(CustomImageBlockComponent), findsOneWidget);
       expect(find.byType(ImagePlaceholder), findsOneWidget);
-
-      await tester.tapButton(find.byType(ImagePlaceholder));
+      expect(
+        find.descendant(
+          of: find.byType(ImagePlaceholder),
+          matching: find.byType(AppFlowyPopover),
+        ),
+        findsOneWidget,
+      );
       expect(find.byType(UploadImageMenu), findsOneWidget);
 
       await tester.tapButtonWithName(
@@ -133,8 +144,13 @@ void main() {
         await tester.editor.tapSlashMenuItemWithName('Image');
         expect(find.byType(CustomImageBlockComponent), findsOneWidget);
         expect(find.byType(ImagePlaceholder), findsOneWidget);
-
-        await tester.tapButton(find.byType(ImagePlaceholder));
+        expect(
+          find.descendant(
+            of: find.byType(ImagePlaceholder),
+            matching: find.byType(AppFlowyPopover),
+          ),
+          findsOneWidget,
+        );
         expect(find.byType(UploadImageMenu), findsOneWidget);
 
         await tester.tapButtonWithName(

+ 4 - 0
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart

@@ -3,6 +3,8 @@ import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter/material.dart';
 import 'package:provider/provider.dart';
 
+const kImagePlaceholderKey = 'imagePlaceholderKey';
+
 typedef CustomImageBlockComponentMenuBuilder = Widget Function(
   Node node,
   CustomImageBlockComponentState state,
@@ -93,8 +95,10 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
         MediaQuery.of(context).size.width;
     final height = attributes[ImageBlockKeys.height]?.toDouble();
 
+    final imagePlaceholderKey = node.extraInfos?[kImagePlaceholderKey];
     Widget child = src.isEmpty
         ? ImagePlaceholder(
+            key: imagePlaceholderKey is GlobalKey ? imagePlaceholderKey : null,
             node: node,
           )
         : ResizableImage(

+ 2 - 2
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_placeholder.dart

@@ -28,10 +28,10 @@ class ImagePlaceholder extends StatefulWidget {
   final Node node;
 
   @override
-  State<ImagePlaceholder> createState() => _ImagePlaceholderState();
+  State<ImagePlaceholder> createState() => ImagePlaceholderState();
 }
 
-class _ImagePlaceholderState extends State<ImagePlaceholder> {
+class ImagePlaceholderState extends State<ImagePlaceholder> {
   final controller = PopoverController();
   late final editorState = context.read<EditorState>();
 

+ 52 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/editor_plugins/image/image_selection_menu.dart

@@ -1,4 +1,7 @@
+import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
+import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
 import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
+import 'package:flutter/material.dart';
 
 final customImageMenuItem = SelectionMenuItem(
   name: AppFlowyEditorLocalizations.current.image,
@@ -9,6 +12,54 @@ final customImageMenuItem = SelectionMenuItem(
   ),
   keywords: ['image', 'picture', 'img', 'photo'],
   handler: (editorState, menuService, context) async {
-    return await editorState.insertImageNode('');
+    // use the key to retrieve the state of the image block to show the popover automatically
+    final imagePlaceholderKey = GlobalKey<ImagePlaceholderState>();
+    await editorState.insertEmptyImageBlock(imagePlaceholderKey);
+
+    WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
+      imagePlaceholderKey.currentState?.controller.show();
+    });
   },
 );
+
+extension InsertImage on EditorState {
+  Future<void> insertEmptyImageBlock(GlobalKey key) async {
+    final selection = this.selection;
+    if (selection == null || !selection.isCollapsed) {
+      return;
+    }
+    final node = getNodeAtPath(selection.end.path);
+    if (node == null) {
+      return;
+    }
+    final emptyImage = imageNode(url: '')
+      ..extraInfos = {
+        kImagePlaceholderKey: key,
+      };
+    final transaction = this.transaction;
+    // if the current node is empty paragraph, replace it with image node
+    if (node.type == ParagraphBlockKeys.type &&
+        (node.delta?.isEmpty ?? false)) {
+      transaction
+        ..insertNode(
+          node.path,
+          emptyImage,
+        )
+        ..deleteNode(node);
+    } else {
+      transaction.insertNode(
+        node.path.next,
+        emptyImage,
+      );
+    }
+
+    transaction.afterSelection = Selection.collapsed(
+      Position(
+        path: node.path.next,
+        offset: 0,
+      ),
+    );
+
+    return apply(transaction);
+  }
+}

+ 3 - 3
frontend/appflowy_flutter/pubspec.lock

@@ -54,8 +54,8 @@ packages:
     dependency: "direct main"
     description:
       path: "."
-      ref: "194fe2f"
-      resolved-ref: "194fe2fec9ce00baa2d5f2afbbfe0a45b3a7b158"
+      ref: d1027c4
+      resolved-ref: d1027c4d83a8cf78227e7d742c050ca945cef23a
       url: "https://github.com/AppFlowy-IO/appflowy-editor.git"
     source: git
     version: "1.4.4"
@@ -1890,4 +1890,4 @@ packages:
     version: "1.1.1"
 sdks:
   dart: ">=3.0.0 <4.0.0"
-  flutter: ">=3.10.1"
+  flutter: ">=3.10.1"

+ 1 - 1
frontend/appflowy_flutter/pubspec.yaml

@@ -47,7 +47,7 @@ dependencies:
   appflowy_editor:
     git:
       url: https://github.com/AppFlowy-IO/appflowy-editor.git
-      ref: "194fe2f"
+      ref: "d1027c4"
   appflowy_popover:
     path: packages/appflowy_popover