TF-3911 Support keyboard shortcut actions ESC to close composer on web

This commit is contained in:
dab246
2025-07-29 10:28:42 +07:00
committed by Dat H. Pham
parent f169ff5feb
commit 6c86682111
10 changed files with 151 additions and 50 deletions
@@ -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<html.Event>? _subscriptionOnDragEnter;
StreamSubscription<html.Event>? _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<Widget> 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() {