Skip to content

Commit

Permalink
feat: add keyboard service interceptor to prevent default ops executi…
Browse files Browse the repository at this point in the history
…on (#902)

* feat: add keyboard service interceptor to prevent default ops execution

* chore: add logs
  • Loading branch information
LucasXu0 authored Sep 26, 2024
1 parent ab977f9 commit 6da7b4e
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 30 deletions.
64 changes: 64 additions & 0 deletions lib/src/editor/editor_component/service/keyboard_service.dart
Original file line number Diff line number Diff line change
@@ -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.
Expand Down Expand Up @@ -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<bool> interceptInsert(
TextEditingDeltaInsertion insertion,
EditorState editorState,
List<CharacterShortcutEvent> characterShortcutEvents,
) async {
return false;
}

/// Intercept delete operation
Future<bool> interceptDelete(
TextEditingDeltaDeletion deletion,
EditorState editorState,
) async {
return false;
}

/// Intercept replace operation
Future<bool> interceptReplace(
TextEditingDeltaReplacement replacement,
EditorState editorState,
List<CharacterShortcutEvent> characterShortcutEvents,
) async {
return false;
}

/// Intercept non-text update operation
Future<bool> interceptNonTextUpdate(
TextEditingDeltaNonTextUpdate nonTextUpdate,
EditorState editorState,
List<CharacterShortcutEvent> characterShortcutEvents,
) async {
return false;
}

/// Intercept perform action operation
Future<bool> interceptPerformAction(
TextInputAction action,
EditorState editorState,
) async {
return false;
}

/// Intercept floating cursor operation
Future<bool> interceptFloatingCursor(
RawFloatingCursorPoint point,
EditorState editorState,
) async {
return false;
}
}
169 changes: 139 additions & 30 deletions lib/src/editor/editor_component/service/keyboard_service_widget.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
late final TextInputService textInputService;
late final FocusNode focusNode;

final List<AppFlowyKeyboardServiceInterceptor> interceptors = [];

// previous selection
Selection? previousSelection;

Expand All @@ -61,36 +63,7 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
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);
Expand Down Expand Up @@ -162,6 +135,16 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
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) ||
Expand Down Expand Up @@ -344,4 +327,130 @@ class KeyboardServiceWidgetState extends State<KeyboardServiceWidget>
}
}
}

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,
);
}
}

0 comments on commit 6da7b4e

Please sign in to comment.