diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/render/selection/toolbar_widget.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/render/selection/toolbar_widget.dart index 68d78f484f..43b3dad432 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/render/selection/toolbar_widget.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/render/selection/toolbar_widget.dart @@ -28,10 +28,12 @@ List defaultListToolbarEventNames = [ 'H1', 'H2', 'H3', - // 'B-List', - // 'N-List', ]; +mixin ToolBarMixin on State { + void hide(); +} + class ToolbarWidget extends StatefulWidget { const ToolbarWidget({ Key? key, @@ -50,7 +52,7 @@ class ToolbarWidget extends StatefulWidget { State createState() => _ToolbarWidgetState(); } -class _ToolbarWidgetState extends State { +class _ToolbarWidgetState extends State with ToolBarMixin { final GlobalKey _listToolbarKey = GlobalKey(); final toolbarHeight = 32.0; @@ -63,21 +65,6 @@ class _ToolbarWidgetState extends State { OverlayEntry? _listToolbarOverlay; - @override - void initState() { - super.initState(); - - widget.editorState.service.selectionService.currentSelection - .addListener(_onSelectionChange); - } - - @override - void dispose() { - widget.editorState.service.selectionService.currentSelection - .removeListener(_onSelectionChange); - super.dispose(); - } - @override Widget build(BuildContext context) { return Positioned( @@ -92,6 +79,12 @@ class _ToolbarWidgetState extends State { ); } + @override + void hide() { + _listToolbarOverlay?.remove(); + _listToolbarOverlay = null; + } + Widget _buildToolbar(BuildContext context) { return Material( borderRadius: BorderRadius.circular(cornerRadius), @@ -212,9 +205,4 @@ class _ToolbarWidgetState extends State { } assert(false, 'Could not find the event handler for $eventName'); } - - void _onSelectionChange() { - _listToolbarOverlay?.remove(); - _listToolbarOverlay = null; - } } diff --git a/frontend/app_flowy/packages/flowy_editor/lib/src/service/toolbar_service.dart b/frontend/app_flowy/packages/flowy_editor/lib/src/service/toolbar_service.dart index aaf52bc20c..636893ab8c 100644 --- a/frontend/app_flowy/packages/flowy_editor/lib/src/service/toolbar_service.dart +++ b/frontend/app_flowy/packages/flowy_editor/lib/src/service/toolbar_service.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flowy_editor/flowy_editor.dart'; import 'package:flowy_editor/src/render/selection/toolbar_widget.dart'; +import 'package:flowy_editor/src/extensions/object_extensions.dart'; abstract class FlowyToolbarService { /// Show the toolbar widget beside the offset. @@ -28,12 +29,15 @@ class FlowyToolbar extends StatefulWidget { class _FlowyToolbarState extends State implements FlowyToolbarService { OverlayEntry? _toolbarOverlay; + final _toolbarWidgetKey = GlobalKey(debugLabel: '_toolbar_widget'); @override void showInOffset(Offset offset, LayerLink layerLink) { - _toolbarOverlay?.remove(); + hide(); + _toolbarOverlay = OverlayEntry( builder: (context) => ToolbarWidget( + key: _toolbarWidgetKey, editorState: widget.editorState, layerLink: layerLink, offset: offset.translate(0, -37.0), @@ -45,6 +49,7 @@ class _FlowyToolbarState extends State @override void hide() { + _toolbarWidgetKey.currentState?.unwrapOrNull()?.hide(); _toolbarOverlay?.remove(); _toolbarOverlay = null; } @@ -55,4 +60,11 @@ class _FlowyToolbarState extends State child: widget.child, ); } + + @override + void dispose() { + hide(); + + super.dispose(); + } } diff --git a/frontend/app_flowy/packages/flowy_editor/test/infra/test_editor.dart b/frontend/app_flowy/packages/flowy_editor/test/infra/test_editor.dart index ddbe4d5b2c..533cace586 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/infra/test_editor.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/infra/test_editor.dart @@ -72,8 +72,20 @@ class EditorWidgetTester { await tester.pumpAndSettle(); } - Future pressLogicKey(LogicalKeyboardKey key) async { - final testRawKeyEventData = TestRawKeyEventData(logicalKey: key).toKeyEvent; + Future pressLogicKey( + LogicalKeyboardKey key, { + bool isControlPressed = false, + bool isShiftPressed = false, + bool isAltPressed = false, + bool isMetaPressed = false, + }) async { + final testRawKeyEventData = TestRawKeyEventData( + logicalKey: key, + isControlPressed: isControlPressed, + isShiftPressed: isShiftPressed, + isAltPressed: isAltPressed, + isMetaPressed: isMetaPressed, + ).toKeyEvent; _editorState.service.keyboardService!.onKey(testRawKeyEventData); await tester.pumpAndSettle(); } diff --git a/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart b/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart index 512ba26574..8fa0e5e4e3 100644 --- a/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart +++ b/frontend/app_flowy/packages/flowy_editor/test/infra/test_raw_key_event.dart @@ -1,7 +1,25 @@ import 'package:flutter/services.dart'; class TestRawKeyEvent extends RawKeyDownEvent { - const TestRawKeyEvent({required super.data}); + const TestRawKeyEvent({ + required super.data, + this.isControlPressed = false, + this.isShiftPressed = false, + this.isAltPressed = false, + this.isMetaPressed = false, + }); + + @override + final bool isControlPressed; + + @override + final bool isShiftPressed; + + @override + final bool isAltPressed; + + @override + final bool isMetaPressed; } class TestRawKeyEventData extends RawKeyEventData { @@ -46,7 +64,13 @@ class TestRawKeyEventData extends RawKeyEventData { String get keyLabel => throw UnimplementedError(); RawKeyEvent get toKeyEvent { - return TestRawKeyEvent(data: this); + return TestRawKeyEvent( + data: this, + isAltPressed: isAltPressed, + isControlPressed: isControlPressed, + isMetaPressed: isMetaPressed, + isShiftPressed: isShiftPressed, + ); } } @@ -67,6 +91,9 @@ extension on LogicalKeyboardKey { if (this == LogicalKeyboardKey.pageUp) { return PhysicalKeyboardKey.pageUp; } + if (this == LogicalKeyboardKey.keyZ) { + return PhysicalKeyboardKey.keyZ; + } throw UnimplementedError(); } } diff --git a/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/redo_undo_handler_test.dart b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/redo_undo_handler_test.dart new file mode 100644 index 0000000000..3bd9a81c52 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_editor/test/service/internal_key_event_handlers/redo_undo_handler_test.dart @@ -0,0 +1,60 @@ +import 'package:flowy_editor/flowy_editor.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import '../../infra/test_editor.dart'; + +void main() async { + setUpAll(() { + TestWidgetsFlutterBinding.ensureInitialized(); + }); + + group('redo_undo_handler_test.dart', () { + // TODO: need to test more cases. + testWidgets('Redo, Undo for backspace key, and selection is downward', + (tester) async { + await _testBackspaceUndoRedo(tester, true); + }); + + testWidgets('Redo, Undo for backspace key, and selection is forward', + (tester) async { + await _testBackspaceUndoRedo(tester, false); + }); + }); +} + +Future _testBackspaceUndoRedo( + WidgetTester tester, bool isDownwardSelection) async { + const text = 'Welcome to Appflowy 😁'; + final editor = tester.editor + ..insertTextNode(text) + ..insertTextNode(text) + ..insertTextNode(text); + await editor.startTesting(); + + final start = Position(path: [0], offset: text.length); + final end = Position(path: [1], offset: text.length); + final selection = Selection( + start: isDownwardSelection ? start : end, + end: isDownwardSelection ? end : start, + ); + await editor.updateSelection(selection); + await editor.pressLogicKey(LogicalKeyboardKey.backspace); + expect(editor.documentLength, 2); + + await editor.pressLogicKey( + LogicalKeyboardKey.keyZ, + isMetaPressed: true, + ); + + expect(editor.documentLength, 3); + expect((editor.nodeAtPath([1]) as TextNode).toRawString(), text); + expect(editor.documentSelection, selection); + + await editor.pressLogicKey( + LogicalKeyboardKey.keyZ, + isMetaPressed: true, + isShiftPressed: true, + ); + + expect(editor.documentLength, 2); +}