From d1b9084ffb71d4a68ba3ecb70cb3b3c3429bb6e4 Mon Sep 17 00:00:00 2001 From: "Lucas.Xu" Date: Wed, 27 Sep 2023 09:18:23 +0800 Subject: [PATCH] fix: unable to update selection sometimes when the editor lost focus (#509) --- example/lib/home_page.dart | 11 ++- .../lib/pages/focus_example_for_editor.dart | 69 +++++++++++++++++++ .../selection/desktop_selection_service.dart | 7 +- test/customer/text_field_and_editor_test.dart | 31 ++++++++- 4 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 example/lib/pages/focus_example_for_editor.dart diff --git a/example/lib/home_page.dart b/example/lib/home_page.dart index 584f37a43..07c4840c9 100644 --- a/example/lib/home_page.dart +++ b/example/lib/home_page.dart @@ -6,6 +6,7 @@ import 'dart:math'; import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:example/pages/customize_theme_for_editor.dart'; import 'package:example/pages/editor.dart'; +import 'package:example/pages/focus_example_for_editor.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -154,7 +155,7 @@ class _HomePageState extends State { }), // Theme Demo - _buildSeparator(context, 'Theme Demo'), + _buildSeparator(context, 'Showcases'), _buildListTile(context, 'Custom Theme', () { Navigator.push( context, @@ -173,6 +174,14 @@ class _HomePageState extends State { textDirection: TextDirection.rtl, ); }), + _buildListTile(context, 'Focus Example', () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => const FocusExampleForEditor(), + ), + ); + }), // Encoder Demo _buildSeparator(context, 'Export To X Demo'), diff --git a/example/lib/pages/focus_example_for_editor.dart b/example/lib/pages/focus_example_for_editor.dart new file mode 100644 index 000000000..ec7a4ce62 --- /dev/null +++ b/example/lib/pages/focus_example_for_editor.dart @@ -0,0 +1,69 @@ +import 'dart:convert'; + +import 'package:appflowy_editor/appflowy_editor.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +class FocusExampleForEditor extends StatefulWidget { + const FocusExampleForEditor({super.key}); + + @override + State createState() => _FocusExampleForEditorState(); +} + +class _FocusExampleForEditorState extends State { + late final Future editorState; + + @override + void initState() { + super.initState(); + + final jsonString = PlatformExtension.isDesktopOrWeb + ? rootBundle.loadString('assets/example.json') + : rootBundle.loadString('assets/mobile_example.json'); + editorState = jsonString.then((value) { + return EditorState( + document: Document.fromJson( + Map.from( + json.decode(value), + ), + ), + ); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.black, + title: const Text('Custom Theme For Editor'), + titleTextStyle: const TextStyle(color: Colors.white), + iconTheme: const IconThemeData( + color: Colors.white, + ), + ), + body: Column( + children: [ + SizedBox( + height: 400, + child: FutureBuilder( + future: editorState, + builder: (context, snapshot) { + return !snapshot.hasData + ? const Center(child: CircularProgressIndicator()) + : AppFlowyEditor(editorState: snapshot.data!); + }, + ), + ), + const TextField( + textAlign: TextAlign.center, + decoration: InputDecoration( + hintText: 'Please input something ...', + ), + ), + ], + ), + ); + } +} diff --git a/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart b/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart index f919e3b3e..3bcf2ea34 100644 --- a/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart +++ b/lib/src/editor/editor_component/service/selection/desktop_selection_service.dart @@ -306,7 +306,12 @@ class _DesktopSelectionServiceWidgetState editorState.service.scrollService?.stopAutoScroll(); } - void _updateSelection() {} + void _updateSelection() { + final selection = editorState.selectionNotifier.value; + if (selection == null) { + clearSelection(); + } + } void _showContextMenu(TapDownDetails details) { _clearContextMenu(); diff --git a/test/customer/text_field_and_editor_test.dart b/test/customer/text_field_and_editor_test.dart index e578213c5..ccd3cc4ac 100644 --- a/test/customer/text_field_and_editor_test.dart +++ b/test/customer/text_field_and_editor_test.dart @@ -30,16 +30,45 @@ void main() async { expect(widget.focusNode.hasFocus, false); expect(widget.editorFocusNode.hasFocus, true); }); + + testWidgets('text field + editor, focus issue', (tester) async { + final editorState = EditorState.blank(); + final widget = TextFieldAndEditor( + editorState: editorState, + ); + await tester.pumpWidget(widget); + await tester.pumpAndSettle(); + + final selection = Selection.collapsed(Position(path: [0])); + editorState.selection = selection; + + final textField = find.byType(TextField); + await tester.tap(textField); + await tester.pumpAndSettle(); + expect(widget.focusNode.hasFocus, true); + expect(widget.editorFocusNode.hasFocus, false); + expect(editorState.selection, null); + + await tester.tapAt( + tester.getTopLeft(find.byType(TextBlockComponentWidget)), + ); + await tester.pumpAndSettle(); + expect(widget.focusNode.hasFocus, false); + expect(widget.editorFocusNode.hasFocus, true); + expect(editorState.selection, selection); + }); } class TextFieldAndEditor extends StatelessWidget { TextFieldAndEditor({ super.key, + this.editorState, }); final controller = TextEditingController(); final focusNode = FocusNode(); final editorFocusNode = FocusNode(); + final EditorState? editorState; @override Widget build(BuildContext context) { @@ -61,7 +90,7 @@ class TextFieldAndEditor extends StatelessWidget { ), child: AppFlowyEditor( focusNode: editorFocusNode, - editorState: EditorState.blank(), + editorState: editorState ?? EditorState.blank(), editorStyle: const EditorStyle.mobile(), ), ),