diff --git a/lib/src/editor/editor_component/service/keyboard_service.dart b/lib/src/editor/editor_component/service/keyboard_service.dart index 5f39f340e..458cc0ddd 100644 --- a/lib/src/editor/editor_component/service/keyboard_service.dart +++ b/lib/src/editor/editor_component/service/keyboard_service.dart @@ -1,5 +1,6 @@ import 'package:appflowy_editor/appflowy_editor.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; /// [AppFlowyKeyboardService] is responsible for processing shortcut keys, /// like command, shift, control keys. @@ -41,4 +42,67 @@ abstract class AppFlowyKeyboardService { /// /// Used in mobile void enableKeyBoard(Selection selection); + + /// Register interceptor + void registerInterceptor(AppFlowyKeyboardServiceInterceptor interceptor); + + /// Unregister interceptor + void unregisterInterceptor(AppFlowyKeyboardServiceInterceptor interceptor); +} + +/// [AppFlowyKeyboardServiceInterceptor] is used to intercept the keyboard service. +/// +/// If the interceptor returns `true`, the keyboard service will not perform +/// the built-in operation. +abstract class AppFlowyKeyboardServiceInterceptor { + /// Intercept insert operation + Future interceptInsert( + TextEditingDeltaInsertion insertion, + EditorState editorState, + List characterShortcutEvents, + ) async { + return false; + } + + /// Intercept delete operation + Future interceptDelete( + TextEditingDeltaDeletion deletion, + EditorState editorState, + ) async { + return false; + } + + /// Intercept replace operation + Future interceptReplace( + TextEditingDeltaReplacement replacement, + EditorState editorState, + List characterShortcutEvents, + ) async { + return false; + } + + /// Intercept non-text update operation + Future interceptNonTextUpdate( + TextEditingDeltaNonTextUpdate nonTextUpdate, + EditorState editorState, + List characterShortcutEvents, + ) async { + return false; + } + + /// Intercept perform action operation + Future interceptPerformAction( + TextInputAction action, + EditorState editorState, + ) async { + return false; + } + + /// Intercept floating cursor operation + Future interceptFloatingCursor( + RawFloatingCursorPoint point, + EditorState editorState, + ) async { + return false; + } } 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 0355d74bc..c06c94ecd 100644 --- a/lib/src/editor/editor_component/service/keyboard_service_widget.dart +++ b/lib/src/editor/editor_component/service/keyboard_service_widget.dart @@ -36,6 +36,8 @@ class KeyboardServiceWidgetState extends State late final TextInputService textInputService; late final FocusNode focusNode; + final List interceptors = []; + // previous selection Selection? previousSelection; @@ -61,36 +63,7 @@ class KeyboardServiceWidgetState extends State editorState.service.selectionService .registerGestureInterceptor(interceptor); - textInputService = NonDeltaTextInputService( - onInsert: (insertion) async => await onInsert( - insertion, - editorState, - widget.characterShortcutEvents, - ), - onDelete: (deletion) async => await onDelete( - deletion, - editorState, - ), - onReplace: (replacement) async => await onReplace( - replacement, - editorState, - widget.characterShortcutEvents, - ), - onNonTextUpdate: (nonTextUpdate) async => await onNonTextUpdate( - nonTextUpdate, - editorState, - widget.characterShortcutEvents, - ), - onPerformAction: (action) async => await onPerformAction( - action, - editorState, - ), - onFloatingCursor: (point) => onFloatingCursorUpdate( - point, - editorState, - ), - contentInsertionConfiguration: widget.contentInsertionConfiguration, - ); + textInputService = buildTextInputService(); focusNode = widget.focusNode ?? FocusNode(debugLabel: 'keyboard service'); focusNode.addListener(_onFocusChanged); @@ -162,6 +135,16 @@ class KeyboardServiceWidgetState extends State return child; } + @override + void registerInterceptor(AppFlowyKeyboardServiceInterceptor interceptor) { + interceptors.add(interceptor); + } + + @override + void unregisterInterceptor(AppFlowyKeyboardServiceInterceptor interceptor) { + interceptors.remove(interceptor); + } + /// handle hardware keyboard KeyEventResult _onKeyEvent(FocusNode node, KeyEvent event) { if ((event is! KeyDownEvent && event is! KeyRepeatEvent) || @@ -344,4 +327,130 @@ class KeyboardServiceWidgetState extends State } } } + + NonDeltaTextInputService buildTextInputService() { + return NonDeltaTextInputService( + onInsert: (insertion) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptInsert( + insertion, + editorState, + widget.characterShortcutEvents, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onInsert - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onInsert( + insertion, + editorState, + widget.characterShortcutEvents, + ); + }, + onDelete: (deletion) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptDelete( + deletion, + editorState, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onDelete - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onDelete( + deletion, + editorState, + ); + }, + onReplace: (replacement) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptReplace( + replacement, + editorState, + widget.characterShortcutEvents, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onReplace - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onReplace( + replacement, + editorState, + widget.characterShortcutEvents, + ); + }, + onNonTextUpdate: (nonTextUpdate) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptNonTextUpdate( + nonTextUpdate, + editorState, + widget.characterShortcutEvents, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onNonTextUpdate - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onNonTextUpdate( + nonTextUpdate, + editorState, + widget.characterShortcutEvents, + ); + }, + onPerformAction: (action) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptPerformAction( + action, + editorState, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onPerformAction - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onPerformAction( + action, + editorState, + ); + }, + onFloatingCursor: (point) async { + for (final interceptor in interceptors) { + final result = await interceptor.interceptFloatingCursor( + point, + editorState, + ); + if (result) { + AppFlowyEditorLog.input.info( + 'keyboard service onFloatingCursor - intercepted by interceptor: $interceptor', + ); + return; + } + } + + await onFloatingCursorUpdate( + point, + editorState, + ); + }, + contentInsertionConfiguration: widget.contentInsertionConfiguration, + ); + } }