diff --git a/lib/features/base/shortcut/app_shortcut_manager.dart b/lib/features/base/shortcut/app_shortcut_manager.dart index a440a5271..45fc9fd4d 100644 --- a/lib/features/base/shortcut/app_shortcut_manager.dart +++ b/lib/features/base/shortcut/app_shortcut_manager.dart @@ -1,11 +1,14 @@ import 'package:core/utils/app_logger.dart'; import 'package:flutter/services.dart'; import 'package:tmail_ui_user/features/base/extensions/logical_key_set_helper.dart'; +import 'package:tmail_ui_user/features/base/shortcut/composer/composer_action_shortcut_type.dart'; import 'package:tmail_ui_user/features/base/shortcut/mail/mail_list_action_shortcut_type.dart'; import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart'; import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart'; class AppShortcutManager { + static const int escapeKeyCode = 27; + static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) { final keysPressed = HardwareKeyboard.instance.logicalKeysPressed; log('AppShortcutManager::getMailViewActionFromEvent: Keys pressed: $keysPressed'); @@ -53,4 +56,23 @@ class AppShortcutManager { return null; } } + + static ComposerActionShortcutType? getComposerActionFromEvent(KeyEvent event) { + final keysPressed = HardwareKeyboard.instance.logicalKeysPressed; + log('AppShortcutManager::getComposerActionFromEvent: Keys pressed: $keysPressed'); + if (keysPressed.isOnly(LogicalKeyboardKey.escape)) { + return ComposerActionShortcutType.closeView; + } else { + return null; + } + } + + static ComposerActionShortcutType? getComposerActionFromKeyCode(int? keyCode) { + log('AppShortcutManager::getComposerActionFromKeyCode: Keys pressed: $keyCode'); + if (keyCode == escapeKeyCode) { + return ComposerActionShortcutType.closeView; + } else { + return null; + } + } } diff --git a/lib/features/base/shortcut/composer/composer_action_shortcut_type.dart b/lib/features/base/shortcut/composer/composer_action_shortcut_type.dart new file mode 100644 index 000000000..09436a453 --- /dev/null +++ b/lib/features/base/shortcut/composer/composer_action_shortcut_type.dart @@ -0,0 +1,4 @@ + +enum ComposerActionShortcutType { + closeView; +} \ No newline at end of file diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 5d64126b1..43a76d106 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -61,6 +61,7 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/auto_cre import 'package:tmail_ui_user/features/composer/presentation/extensions/get_draft_mailbox_id_for_composer_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/get_outbox_mailbox_id_for_composer_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/get_sent_mailbox_id_for_composer_extension.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_message_failure_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart'; @@ -208,6 +209,7 @@ class ComposerController extends BaseController FocusNode? ccAddressFocusNodeKeyboard; FocusNode? bccAddressFocusNodeKeyboard; FocusNode? replyToAddressFocusNodeKeyboard; + FocusNode? keyboardShortcutFocusNode; StreamSubscription? _subscriptionOnDragEnter; StreamSubscription? _subscriptionOnDragOver; @@ -258,7 +260,6 @@ class ComposerController extends BaseController mailboxDashBoardController.ownEmailAddress.value; late Worker uploadInlineImageWorker; - late Worker dashboardViewStateWorker; late bool _isEmailBodyLoaded; ComposerController( @@ -299,6 +300,7 @@ class ComposerController extends BaseController _listenStreamEvent(); _beforeReconnectManager.addListener(onBeforeReconnect); _injectBinding(); + onKeyboardShortcutInit(); } @override @@ -343,6 +345,7 @@ class ComposerController extends BaseController } else { richTextMobileTabletController = null; } + onKeyboardShortcutDispose(); super.onClose(); } @@ -374,7 +377,6 @@ class ComposerController extends BaseController bccEmailAddressController.dispose(); replyToEmailAddressController.dispose(); uploadInlineImageWorker.dispose(); - dashboardViewStateWorker.dispose(); scrollController.dispose(); scrollControllerEmailAddress.removeListener(_scrollControllerEmailAddressListener); scrollControllerEmailAddress.dispose(); @@ -560,18 +562,7 @@ class ComposerController extends BaseController } void _scrollControllerEmailAddressListener() { - if (toEmailAddressController.text.isNotEmpty) { - keyToEmailTagEditor.currentState?.closeSuggestionBox(); - } - if (ccEmailAddressController.text.isNotEmpty) { - keyCcEmailTagEditor.currentState?.closeSuggestionBox(); - } - if (bccEmailAddressController.text.isNotEmpty) { - keyBccEmailTagEditor.currentState?.closeSuggestionBox(); - } - if (replyToEmailAddressController.text.isNotEmpty) { - keyReplyToEmailTagEditor.currentState?.closeSuggestionBox(); - } + _closeSuggestionBox(); } void createFocusNodeInput() { @@ -767,7 +758,7 @@ class ComposerController extends BaseController } _sendButtonState = ButtonState.disabled; - clearFocus(context); + clearFocus(); if (toEmailAddressController.text.isNotEmpty || ccEmailAddressController.text.isNotEmpty @@ -1142,7 +1133,7 @@ class ComposerController extends BaseController } void openPickAttachmentMenu(BuildContext context, List actionTiles) { - clearFocus(context); + clearFocus(); (ContextMenuBuilder(context) ..addHeader((ContextMenuHeaderBuilder(const Key('attachment_picker_context_menu_header_builder')) @@ -1428,17 +1419,24 @@ class ComposerController extends BaseController } } - void clearFocus(BuildContext context) { - log('ComposerController::clearFocus:'); + void clearFocus() { if (PlatformInfo.isMobile) { htmlEditorApi?.unfocus(); + FocusManager.instance.primaryFocus?.unfocus(); + } else { + toAddressFocusNode?.unfocus(); + ccAddressFocusNode?.unfocus(); + bccAddressFocusNode?.unfocus(); + replyToAddressFocusNode?.unfocus(); } - FocusScope.of(context).unfocus(); } - void clickOutsideComposer(BuildContext context) { - clearFocus(context); + void clickOutsideComposer() { + clearFocus(); triggerHideRecipientsFiledsWhenUnfocus(); + if (PlatformInfo.isWeb) { + refocusKeyboardShortcutFocus(); + } } void _closeComposerAction({dynamic result, bool closeOverlays = false}) { @@ -1655,8 +1653,8 @@ class ComposerController extends BaseController } } - void insertImage(BuildContext context, double maxWith) async { - clearFocus(context); + void insertImage(BuildContext context, double maxWith) { + clearFocus(); if (responsiveUtils.isMobile(context)) { maxWithEditor = maxWith - 40; @@ -1693,8 +1691,8 @@ class ComposerController extends BaseController } } - void handleClickDeleteComposer(BuildContext context) { - clearFocus(context); + void handleClickDeleteComposer() { + clearFocus(); _closeComposerAction(); } @@ -1944,11 +1942,12 @@ class ComposerController extends BaseController } _closeComposerButtonState = ButtonState.disabled; + autoCreateEmailTag(); if (_validateCloseComposerWithoutSave()) { log('ComposerController::handleClickCloseComposer: ARGUMENTS is NULL or EMAIL NOT LOADED'); _closeComposerButtonState = ButtonState.enabled; - clearFocus(context); + clearFocus(); _closeComposerAction(); return; } @@ -1956,16 +1955,14 @@ class ComposerController extends BaseController final isChanged = await _validateEmailChange(); if (isChanged && context.mounted) { - clearFocus(context); + clearFocus(); await _showConfirmDialogSaveMessage(context); return; } - if (context.mounted) { - _closeComposerButtonState = ButtonState.enabled; - clearFocus(context); - _closeComposerAction(); - } + _closeComposerButtonState = ButtonState.enabled; + clearFocus(); + _closeComposerAction(); } bool _validateCloseComposerWithoutSave() { diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index 9307dc6e3..fb14b5b0d 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -45,7 +45,7 @@ class ComposerView extends GetWidget { responsiveUtils: controller.responsiveUtils, mobile: MobileContainerView( onCloseViewAction: () => controller.handleClickCloseComposer(context), - onClearFocusAction: () => controller.clearFocus(context), + onClearFocusAction: controller.clearFocus, backgroundColor: MobileAppBarComposerWidgetStyle.backgroundColor, childBuilder: (_, constraints) => SafeArea( left: !controller.responsiveUtils.isLandscapeMobile(context), @@ -248,7 +248,7 @@ class ComposerView extends GetWidget { if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) { return ViewEntireMessageWithMessageClippedWidget( buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(), - onViewEntireMessageAction: () => controller.viewEntireContent(context), + onViewEntireMessageAction: controller.viewEntireContent, topPadding: 12, ); } else { @@ -268,7 +268,7 @@ class ComposerView extends GetWidget { tablet: TabletContainerView( keyboardRichTextController: controller.richTextMobileTabletController!.richTextController, onCloseViewAction: () => controller.handleClickCloseComposer(context), - onClearFocusAction: () => controller.clearFocus(context), + onClearFocusAction: controller.clearFocus, childBuilder: (_, constraints) => ColoredBox( color: ComposerStyle.mobileBackgroundColor, child: Column( @@ -427,7 +427,7 @@ class ComposerView extends GetWidget { if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) { return ViewEntireMessageWithMessageClippedWidget( buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(), - onViewEntireMessageAction: () => controller.viewEntireContent(context), + onViewEntireMessageAction: controller.viewEntireContent, topPadding: 12, ); } else { @@ -443,7 +443,7 @@ class ComposerView extends GetWidget { imagePaths: controller.imagePaths, hasReadReceipt: controller.hasRequestReadReceipt.value, isMarkAsImportant: controller.isMarkAsImportant.value, - deleteComposerAction: () => controller.handleClickDeleteComposer(context), + deleteComposerAction: controller.handleClickDeleteComposer, saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), sendMessageAction: () => controller.handleClickSendButton(context), requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 1cebd7246..72e3361ac 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -7,10 +7,12 @@ import 'package:model/email/prefix_email_address.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_manager.dart'; import 'package:tmail_ui_user/features/base/widget/dialog_picker/color_dialog_picker.dart'; +import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wrapper.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/composer_print_draft_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_edit_recipient_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_recipients_collapsed_extensions.dart'; +import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/mark_as_important_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/remove_draggable_email_address_between_recipient_fields_extension.dart'; import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart'; @@ -67,12 +69,12 @@ class ComposerView extends GetWidget { } }); - return ResponsiveWidget( + final bodyWidget = ResponsiveWidget( responsiveUtils: controller.responsiveUtils, mobile: MobileResponsiveContainerView( childBuilder: (_, constraints) { return GestureDetector( - onTap: () => controller.clickOutsideComposer(context), + onTap: controller.clickOutsideComposer, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -96,7 +98,7 @@ class ComposerView extends GetWidget { toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context), saveToDraftsAction: () => controller.handleClickSaveAsDraftsButton(context), saveToTemplateAction: () => controller.handleClickSaveAsTemplateButton(context), - deleteComposerAction: () => controller.handleClickDeleteComposer(context), + deleteComposerAction: controller.handleClickDeleteComposer, )), ConstrainedBox( constraints: BoxConstraints( @@ -231,6 +233,7 @@ class ComposerView extends GetWidget { uploadError: uploadError ), onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb, + onKeyDownEditorAction: controller.onKeyDownEditorAction, )); } ), @@ -318,7 +321,7 @@ class ComposerView extends GetWidget { desktop: Obx(() => DesktopResponsiveContainerView( childBuilder: (_, constraints) { return GestureDetector( - onTap: () => controller.clickOutsideComposer(context), + onTap: controller.clickOutsideComposer, child: Column(children: [ Obx(() => DesktopAppBarComposerWidget( imagePaths: controller.imagePaths, @@ -468,6 +471,7 @@ class ComposerView extends GetWidget { uploadError: uploadError ), onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb, + onKeyDownEditorAction: controller.onKeyDownEditorAction, ); }); } @@ -514,7 +518,7 @@ class ComposerView extends GetWidget { openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, attachFileAction: () => controller.openFilePickerByType(context, FileType.any), insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - deleteComposerAction: () => controller.handleClickDeleteComposer(context), + deleteComposerAction: controller.handleClickDeleteComposer, saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), sendMessageAction: () => controller.handleClickSendButton(context), isEmailChanged: controller.isEmailChanged.isTrue, @@ -588,7 +592,7 @@ class ComposerView extends GetWidget { tablet: TabletResponsiveContainerView( childBuilder: (_, constraints) { return GestureDetector( - onTap: () => controller.clickOutsideComposer(context), + onTap: controller.clickOutsideComposer, child: Column(children: [ Obx(() => DesktopAppBarComposerWidget( imagePaths: controller.imagePaths, @@ -735,6 +739,7 @@ class ComposerView extends GetWidget { uploadError: uploadError ), onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb, + onKeyDownEditorAction: controller.onKeyDownEditorAction, )); } ), @@ -780,7 +785,7 @@ class ComposerView extends GetWidget { openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, attachFileAction: () => controller.openFilePickerByType(context, FileType.any), insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - deleteComposerAction: () => controller.handleClickDeleteComposer(context), + deleteComposerAction: controller.handleClickDeleteComposer, saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), sendMessageAction: () => controller.handleClickSendButton(context), isEmailChanged: controller.isEmailChanged.isTrue, @@ -844,6 +849,16 @@ class ComposerView extends GetWidget { }, ) ); + + if (controller.keyboardShortcutFocusNode != null) { + return KeyboardHandlerWrapper( + focusNode: controller.keyboardShortcutFocusNode!, + onKeyDownEventAction: controller.onKeyDownEventAction, + child: bodyWidget, + ); + } else { + return bodyWidget; + } } Widget _buildRecipientComposerWidget({ diff --git a/lib/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart b/lib/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart index c02dc935c..d8c6a9fef 100644 --- a/lib/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart +++ b/lib/features/composer/presentation/extensions/handle_content_height_exceeded_extension.dart @@ -2,7 +2,6 @@ import 'package:core/presentation/constants/constants_ui.dart'; import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/app_logger.dart'; -import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; import 'package:tmail_ui_user/features/composer/presentation/view/mobile/editor_fullscreen_dialog_view.dart'; @@ -14,8 +13,8 @@ extension HandleContentHeightExceededExtension on ComposerController { isContentHeightExceeded.value = height == ConstantsUI.composerHtmlContentMaxHeight; } - Future viewEntireContent(BuildContext context) async { - clearFocus(context); + Future viewEntireContent() async { + clearFocus(); final currentContent = await getContentInEditor(); log('HandleContentHeightExceededExtension::showComposerFullscreen:currentContent = $currentContent'); diff --git a/lib/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart b/lib/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart new file mode 100644 index 000000000..af226acde --- /dev/null +++ b/lib/features/composer/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart @@ -0,0 +1,52 @@ + +import 'package:core/utils/app_logger.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:flutter/material.dart' hide SearchController; +import 'package:tmail_ui_user/features/base/shortcut/app_shortcut_manager.dart'; +import 'package:tmail_ui_user/features/base/shortcut/composer/composer_action_shortcut_type.dart'; +import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; + +extension HandleKeyboardShortcutActionsExtension on ComposerController { + void onKeyDownEditorAction(int? keyCode) { + log('$runtimeType::onKeyDownEditorAction:🔥keyCode: $keyCode'); + final shortcutType = AppShortcutManager.getComposerActionFromKeyCode(keyCode); + log('$runtimeType::onKeyDownEditorAction:🔥Shortcut triggered: $shortcutType'); + if (shortcutType == null) return; + handleComposerShortcutAction(shortcutType); + } + + void onKeyboardShortcutInit() { + if (PlatformInfo.isWeb) { + keyboardShortcutFocusNode = FocusNode(); + keyboardShortcutFocusNode?.addListener(() { + log('$runtimeType::onKeyboardShortcutInit: keyboardShortcutFocusNode: ${keyboardShortcutFocusNode?.hasFocus}'); + }); + } + } + + void onKeyboardShortcutDispose() { + if (PlatformInfo.isWeb) { + keyboardShortcutFocusNode?.dispose(); + } + } + + void refocusKeyboardShortcutFocus() { + if (keyboardShortcutFocusNode?.hasFocus == false) { + keyboardShortcutFocusNode?.requestFocus(); + } + } + + void onKeyDownEventAction(KeyEvent event) { + final shortcutType = AppShortcutManager.getComposerActionFromEvent(event); + log('$runtimeType::onKeyDownEventAction:🔥Shortcut triggered: $shortcutType'); + if (shortcutType == null) return; + handleComposerShortcutAction(shortcutType); + } + + void handleComposerShortcutAction(ComposerActionShortcutType shortcutType) { + log('$runtimeType::handleComposerShortcutAction: 🔥 Shortcut triggered: $shortcutType'); + if (currentContext == null) return; + handleClickCloseComposer(currentContext!); + } +} \ No newline at end of file diff --git a/lib/features/composer/presentation/extensions/handle_open_context_menu_extension.dart b/lib/features/composer/presentation/extensions/handle_open_context_menu_extension.dart index b0381082d..02627901b 100644 --- a/lib/features/composer/presentation/extensions/handle_open_context_menu_extension.dart +++ b/lib/features/composer/presentation/extensions/handle_open_context_menu_extension.dart @@ -57,7 +57,7 @@ extension HandleOpenContextMenuExtension on ComposerController { handleClickSaveAsTemplateButton(context); break; case ComposerActionType.delete: - handleClickDeleteComposer(context); + handleClickDeleteComposer(); break; } } diff --git a/lib/features/composer/presentation/view/web/web_editor_view.dart b/lib/features/composer/presentation/view/web/web_editor_view.dart index 97ef69b95..d66cf1d5d 100644 --- a/lib/features/composer/presentation/view/web/web_editor_view.dart +++ b/lib/features/composer/presentation/view/web/web_editor_view.dart @@ -34,6 +34,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { final OnPasteImageSuccessAction? onPasteImageSuccessAction; final OnPasteImageFailureAction? onPasteImageFailureAction; final OnInitialContentLoadComplete? onInitialContentLoadComplete; + final OnKeyDownEditorAction? onKeyDownEditorAction; const WebEditorView({ super.key, @@ -55,6 +56,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { this.onPasteImageSuccessAction, this.onPasteImageFailureAction, this.onInitialContentLoadComplete, + this.onKeyDownEditorAction, }); @override @@ -85,6 +87,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ); case EmailActionType.editDraft: case EmailActionType.editSendingEmail: @@ -115,6 +118,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ), (success) { if (success is GetEmailContentLoading) { @@ -147,6 +151,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ); } } @@ -185,6 +190,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ); }, (success) { @@ -218,6 +224,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ); } } @@ -241,6 +248,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin { onPasteImageSuccessAction: onPasteImageSuccessAction, onPasteImageFailureAction: onPasteImageFailureAction, onInitialContentLoadComplete: onInitialContentLoadComplete, + onKeyDownEditorAction: onKeyDownEditorAction, ); } } diff --git a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart index d9cc03c6c..5bccf2d43 100644 --- a/lib/features/composer/presentation/widgets/web/web_editor_widget.dart +++ b/lib/features/composer/presentation/widgets/web/web_editor_widget.dart @@ -23,6 +23,7 @@ typedef OnPasteImageFailureAction = Function( String? base64, UploadError uploadError); typedef OnInitialContentLoadComplete = Function(String text); +typedef OnKeyDownEditorAction = Function(int? keyCode); class WebEditorWidget extends StatefulWidget { @@ -43,6 +44,7 @@ class WebEditorWidget extends StatefulWidget { final OnPasteImageSuccessAction? onPasteImageSuccessAction; final OnPasteImageFailureAction? onPasteImageFailureAction; final OnInitialContentLoadComplete? onInitialContentLoadComplete; + final OnKeyDownEditorAction? onKeyDownEditorAction; const WebEditorWidget({ super.key, @@ -63,6 +65,7 @@ class WebEditorWidget extends StatefulWidget { this.onPasteImageSuccessAction, this.onPasteImageFailureAction, this.onInitialContentLoadComplete, + this.onKeyDownEditorAction, }); @override @@ -76,7 +79,7 @@ class _WebEditorState extends State { late HtmlEditorController _editorController; bool _dropListenerRegistered = false; Function(Event)? _dropListener; - + OverlayEntry? _signatureTooltipEntry; final GlobalKey _signatureTooltipKey = GlobalKey(); double _signatureTooltipLeft = 0; @@ -197,7 +200,7 @@ class _WebEditorState extends State { _showSignatureTooltipAtPosition( position, isContentVisible - ? AppLocalizations.of(context).hideSignature + ? AppLocalizations.of(context).hideSignature : AppLocalizations.of(context).showSignature, ); }, @@ -205,6 +208,7 @@ class _WebEditorState extends State { log('_WebEditorState::build: onSignatureHoverOut'); _hideSignatureTooltip(); }, + onKeyDown: widget.onKeyDownEditorAction, ), ); }