diff --git a/lib/src/editor/block_component/base_component/widget/full_screen_overlay_entry.dart b/lib/src/editor/block_component/base_component/widget/full_screen_overlay_entry.dart index ba03f2204..253531cc7 100644 --- a/lib/src/editor/block_component/base_component/widget/full_screen_overlay_entry.dart +++ b/lib/src/editor/block_component/base_component/widget/full_screen_overlay_entry.dart @@ -9,6 +9,7 @@ class FullScreenOverlayEntry { this.right, required this.builder, this.tapToDismiss = true, + this.dismissCallback, }); final double? top; @@ -17,6 +18,7 @@ class FullScreenOverlayEntry { final double? right; final WidgetBuilder builder; final bool tapToDismiss; + final VoidCallback? dismissCallback; OverlayEntry? _entry; @@ -34,6 +36,7 @@ class FullScreenOverlayEntry { // remove this from the overlay when tapped the opaque layer _entry?.remove(); _entry = null; + dismissCallback?.call(); } }, child: Stack( diff --git a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart index 0a9baf38c..4c9a4dc84 100644 --- a/lib/src/editor/block_component/image_block_component/image_upload_widget.dart +++ b/lib/src/editor/block_component/image_block_component/image_upload_widget.dart @@ -16,12 +16,15 @@ void showImageMenu( editorState.insertImageNode(text); menuService.dismiss(); imageMenuEntry.remove(); + keepEditorFocusNotifier.value -= 1; } + keepEditorFocusNotifier.value += 1; imageMenuEntry = FullScreenOverlayEntry( left: left, top: top, bottom: bottom, + dismissCallback: () => keepEditorFocusNotifier.value -= 1, builder: (context) => UploadImageMenu( backgroundColor: menuService.style.selectionMenuBackgroundColor, headerColor: menuService.style.selectionMenuItemTextColor, diff --git a/lib/src/editor/editor_component/service/keyboard_service_widget.dart b/lib/src/editor/editor_component/service/keyboard_service_widget.dart index 3e1ed3b0d..4d864358c 100644 --- a/lib/src/editor/editor_component/service/keyboard_service_widget.dart +++ b/lib/src/editor/editor_component/service/keyboard_service_widget.dart @@ -73,6 +73,8 @@ class KeyboardServiceWidgetState extends State focusNode = widget.focusNode ?? FocusNode(debugLabel: 'keyboard service'); focusNode.addListener(_onFocusChanged); + + keepEditorFocusNotifier.addListener(_onKeepEditorFocusChanged); } @override @@ -85,6 +87,7 @@ class KeyboardServiceWidgetState extends State if (widget.focusNode == null) { focusNode.dispose(); } + keepEditorFocusNotifier.removeListener(_onKeepEditorFocusChanged); super.dispose(); } @@ -232,6 +235,9 @@ class KeyboardServiceWidgetState extends State // clear the selection when the focus is lost. if (PlatformExtension.isDesktop && !focusNode.hasFocus) { + if (keepEditorFocusNotifier.value > 0) { + return; + } final children = WidgetsBinding.instance.focusManager.primaryFocus?.children; if (children != null && !children.contains(focusNode)) { @@ -240,6 +246,16 @@ class KeyboardServiceWidgetState extends State } } + void _onKeepEditorFocusChanged() { + Log.editor.debug( + 'keyboard service - on keep editor focus changed: ${keepEditorFocusNotifier.value}}', + ); + + if (keepEditorFocusNotifier.value == 0) { + focusNode.requestFocus(); + } + } + // only verify on macOS. void _updateCaretPosition(Selection? selection) { if (selection == null || !selection.isCollapsed) { diff --git a/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart b/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart index ddc38f15f..023760d2e 100644 --- a/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart +++ b/lib/src/editor/toolbar/desktop/items/link/link_toolbar_item.dart @@ -72,15 +72,17 @@ void showLinkMenu( OverlayEntry? overlay; void dismissOverlay() { + keepEditorFocusNotifier.value -= 1; overlay?.remove(); overlay = null; - editorState.service.keyboardService?.enable(); } + keepEditorFocusNotifier.value += 1; overlay = FullScreenOverlayEntry( top: top, bottom: bottom, left: left, + dismissCallback: () => keepEditorFocusNotifier.value -= 1, builder: (context) { return LinkMenu( linkText: linkText, @@ -114,5 +116,4 @@ void showLinkMenu( ).build(); Overlay.of(context).insert(overlay!); - editorState.service.keyboardService?.disable(); } diff --git a/lib/src/render/image/image_upload_widget.dart b/lib/src/render/image/image_upload_widget.dart index a31390796..fe8189495 100644 --- a/lib/src/render/image/image_upload_widget.dart +++ b/lib/src/render/image/image_upload_widget.dart @@ -1,8 +1,4 @@ -import 'package:appflowy_editor/src/core/document/node.dart'; -import 'package:appflowy_editor/src/editor_state.dart'; -import 'package:appflowy_editor/src/infra/flowy_svg.dart'; -import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart'; -import 'package:appflowy_editor/src/render/style/editor_style.dart'; +import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; // void showImageMenu( diff --git a/lib/src/render/selection_menu/selection_menu_widget.dart b/lib/src/render/selection_menu/selection_menu_widget.dart index 708629a9b..1b174365e 100644 --- a/lib/src/render/selection_menu/selection_menu_widget.dart +++ b/lib/src/render/selection_menu/selection_menu_widget.dart @@ -254,6 +254,7 @@ class _SelectionMenuWidgetState extends State { _showingItems = widget.items; + keepEditorFocusNotifier.value += 1; WidgetsBinding.instance.addPostFrameCallback((_) { _focusNode.requestFocus(); }); @@ -262,6 +263,7 @@ class _SelectionMenuWidgetState extends State { @override void dispose() { _focusNode.dispose(); + keepEditorFocusNotifier.value -= 1; super.dispose(); } diff --git a/lib/src/service/editor_service.dart b/lib/src/service/editor_service.dart index bc8c1f3d3..6561cf681 100644 --- a/lib/src/service/editor_service.dart +++ b/lib/src/service/editor_service.dart @@ -3,6 +3,15 @@ import 'package:appflowy_editor/src/flutter/overlay.dart'; import 'package:flutter/material.dart' hide Overlay, OverlayEntry; import 'package:provider/provider.dart'; +// workaround for the issue: +// the popover will grab the focus even if it's inside the editor +// setup a global value to indicate whether the focus should be grabbed +// increase the value when the popover is opened +// decrease the value when the popover is closed +// only grab the focus when the value is 0 +// the operation must be paired +ValueNotifier keepEditorFocusNotifier = ValueNotifier(0); + class AppFlowyEditor extends StatefulWidget { @Deprecated('Use AppFlowyEditor.custom or AppFlowyEditor.standard instead') const AppFlowyEditor({ diff --git a/test/render/image/image_upload_widget_test.dart b/test/render/image/image_upload_widget_test.dart index 4afbfed5c..18d9b8d3f 100644 --- a/test/render/image/image_upload_widget_test.dart +++ b/test/render/image/image_upload_widget_test.dart @@ -1,5 +1,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter_test/flutter_test.dart'; +import 'package:appflowy_editor/src/editor/block_component/image_block_component/image_upload_widget.dart'; import '../../new/infra/testable_editor.dart'; void main() { @@ -9,9 +10,8 @@ void main() { ..addParagraph(initialText: 'Welcome to AppFlowy'); await editor.startTesting(); - await editor.updateSelection( - Selection.single(path: [0], startOffset: 19), - ); + final selection = Selection.single(path: [0], startOffset: 19); + await editor.updateSelection(selection); await editor.pressKey(character: '/'); await tester.pumpAndSettle(); @@ -23,6 +23,14 @@ void main() { await tester.tap(imageMenuItemFinder); await tester.pumpAndSettle(); + expect(find.byType(UploadImageMenu), findsOneWidget); + + expect(editor.selection, selection); + + await tester.tapAt(Offset.zero); + await tester.pumpAndSettle(); + expect(find.byType(UploadImageMenu), findsNothing); + expect(editor.selection, selection); await editor.dispose(); });