TF-3911 Support keyboard shortcut actions ESC to close composer on web
This commit is contained in:
@@ -1,11 +1,14 @@
|
|||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:flutter/services.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/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_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/mail/mail_view_action_shortcut_type.dart';
|
||||||
import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart';
|
import 'package:tmail_ui_user/features/base/shortcut/search/search_action_shortcut_type.dart';
|
||||||
|
|
||||||
class AppShortcutManager {
|
class AppShortcutManager {
|
||||||
|
static const int escapeKeyCode = 27;
|
||||||
|
|
||||||
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
||||||
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
|
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
|
||||||
log('AppShortcutManager::getMailViewActionFromEvent: Keys pressed: $keysPressed');
|
log('AppShortcutManager::getMailViewActionFromEvent: Keys pressed: $keysPressed');
|
||||||
@@ -53,4 +56,23 @@ class AppShortcutManager {
|
|||||||
return null;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
|
||||||
|
enum ComposerActionShortcutType {
|
||||||
|
closeView;
|
||||||
|
}
|
||||||
@@ -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_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_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/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_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/handle_recipients_collapsed_extensions.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.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? ccAddressFocusNodeKeyboard;
|
||||||
FocusNode? bccAddressFocusNodeKeyboard;
|
FocusNode? bccAddressFocusNodeKeyboard;
|
||||||
FocusNode? replyToAddressFocusNodeKeyboard;
|
FocusNode? replyToAddressFocusNodeKeyboard;
|
||||||
|
FocusNode? keyboardShortcutFocusNode;
|
||||||
|
|
||||||
StreamSubscription<html.Event>? _subscriptionOnDragEnter;
|
StreamSubscription<html.Event>? _subscriptionOnDragEnter;
|
||||||
StreamSubscription<html.Event>? _subscriptionOnDragOver;
|
StreamSubscription<html.Event>? _subscriptionOnDragOver;
|
||||||
@@ -258,7 +260,6 @@ class ComposerController extends BaseController
|
|||||||
mailboxDashBoardController.ownEmailAddress.value;
|
mailboxDashBoardController.ownEmailAddress.value;
|
||||||
|
|
||||||
late Worker uploadInlineImageWorker;
|
late Worker uploadInlineImageWorker;
|
||||||
late Worker dashboardViewStateWorker;
|
|
||||||
late bool _isEmailBodyLoaded;
|
late bool _isEmailBodyLoaded;
|
||||||
|
|
||||||
ComposerController(
|
ComposerController(
|
||||||
@@ -299,6 +300,7 @@ class ComposerController extends BaseController
|
|||||||
_listenStreamEvent();
|
_listenStreamEvent();
|
||||||
_beforeReconnectManager.addListener(onBeforeReconnect);
|
_beforeReconnectManager.addListener(onBeforeReconnect);
|
||||||
_injectBinding();
|
_injectBinding();
|
||||||
|
onKeyboardShortcutInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -343,6 +345,7 @@ class ComposerController extends BaseController
|
|||||||
} else {
|
} else {
|
||||||
richTextMobileTabletController = null;
|
richTextMobileTabletController = null;
|
||||||
}
|
}
|
||||||
|
onKeyboardShortcutDispose();
|
||||||
super.onClose();
|
super.onClose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +377,6 @@ class ComposerController extends BaseController
|
|||||||
bccEmailAddressController.dispose();
|
bccEmailAddressController.dispose();
|
||||||
replyToEmailAddressController.dispose();
|
replyToEmailAddressController.dispose();
|
||||||
uploadInlineImageWorker.dispose();
|
uploadInlineImageWorker.dispose();
|
||||||
dashboardViewStateWorker.dispose();
|
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
scrollControllerEmailAddress.removeListener(_scrollControllerEmailAddressListener);
|
scrollControllerEmailAddress.removeListener(_scrollControllerEmailAddressListener);
|
||||||
scrollControllerEmailAddress.dispose();
|
scrollControllerEmailAddress.dispose();
|
||||||
@@ -560,18 +562,7 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _scrollControllerEmailAddressListener() {
|
void _scrollControllerEmailAddressListener() {
|
||||||
if (toEmailAddressController.text.isNotEmpty) {
|
_closeSuggestionBox();
|
||||||
keyToEmailTagEditor.currentState?.closeSuggestionBox();
|
|
||||||
}
|
|
||||||
if (ccEmailAddressController.text.isNotEmpty) {
|
|
||||||
keyCcEmailTagEditor.currentState?.closeSuggestionBox();
|
|
||||||
}
|
|
||||||
if (bccEmailAddressController.text.isNotEmpty) {
|
|
||||||
keyBccEmailTagEditor.currentState?.closeSuggestionBox();
|
|
||||||
}
|
|
||||||
if (replyToEmailAddressController.text.isNotEmpty) {
|
|
||||||
keyReplyToEmailTagEditor.currentState?.closeSuggestionBox();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void createFocusNodeInput() {
|
void createFocusNodeInput() {
|
||||||
@@ -767,7 +758,7 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
_sendButtonState = ButtonState.disabled;
|
_sendButtonState = ButtonState.disabled;
|
||||||
|
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
|
|
||||||
if (toEmailAddressController.text.isNotEmpty
|
if (toEmailAddressController.text.isNotEmpty
|
||||||
|| ccEmailAddressController.text.isNotEmpty
|
|| ccEmailAddressController.text.isNotEmpty
|
||||||
@@ -1142,7 +1133,7 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
void openPickAttachmentMenu(BuildContext context, List<Widget> actionTiles) {
|
void openPickAttachmentMenu(BuildContext context, List<Widget> actionTiles) {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
|
|
||||||
(ContextMenuBuilder(context)
|
(ContextMenuBuilder(context)
|
||||||
..addHeader((ContextMenuHeaderBuilder(const Key('attachment_picker_context_menu_header_builder'))
|
..addHeader((ContextMenuHeaderBuilder(const Key('attachment_picker_context_menu_header_builder'))
|
||||||
@@ -1428,17 +1419,24 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearFocus(BuildContext context) {
|
void clearFocus() {
|
||||||
log('ComposerController::clearFocus:');
|
|
||||||
if (PlatformInfo.isMobile) {
|
if (PlatformInfo.isMobile) {
|
||||||
htmlEditorApi?.unfocus();
|
htmlEditorApi?.unfocus();
|
||||||
|
FocusManager.instance.primaryFocus?.unfocus();
|
||||||
|
} else {
|
||||||
|
toAddressFocusNode?.unfocus();
|
||||||
|
ccAddressFocusNode?.unfocus();
|
||||||
|
bccAddressFocusNode?.unfocus();
|
||||||
|
replyToAddressFocusNode?.unfocus();
|
||||||
}
|
}
|
||||||
FocusScope.of(context).unfocus();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void clickOutsideComposer(BuildContext context) {
|
void clickOutsideComposer() {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
triggerHideRecipientsFiledsWhenUnfocus();
|
triggerHideRecipientsFiledsWhenUnfocus();
|
||||||
|
if (PlatformInfo.isWeb) {
|
||||||
|
refocusKeyboardShortcutFocus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _closeComposerAction({dynamic result, bool closeOverlays = false}) {
|
void _closeComposerAction({dynamic result, bool closeOverlays = false}) {
|
||||||
@@ -1655,8 +1653,8 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void insertImage(BuildContext context, double maxWith) async {
|
void insertImage(BuildContext context, double maxWith) {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
|
|
||||||
if (responsiveUtils.isMobile(context)) {
|
if (responsiveUtils.isMobile(context)) {
|
||||||
maxWithEditor = maxWith - 40;
|
maxWithEditor = maxWith - 40;
|
||||||
@@ -1693,8 +1691,8 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleClickDeleteComposer(BuildContext context) {
|
void handleClickDeleteComposer() {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
_closeComposerAction();
|
_closeComposerAction();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1944,11 +1942,12 @@ class ComposerController extends BaseController
|
|||||||
}
|
}
|
||||||
|
|
||||||
_closeComposerButtonState = ButtonState.disabled;
|
_closeComposerButtonState = ButtonState.disabled;
|
||||||
|
autoCreateEmailTag();
|
||||||
|
|
||||||
if (_validateCloseComposerWithoutSave()) {
|
if (_validateCloseComposerWithoutSave()) {
|
||||||
log('ComposerController::handleClickCloseComposer: ARGUMENTS is NULL or EMAIL NOT LOADED');
|
log('ComposerController::handleClickCloseComposer: ARGUMENTS is NULL or EMAIL NOT LOADED');
|
||||||
_closeComposerButtonState = ButtonState.enabled;
|
_closeComposerButtonState = ButtonState.enabled;
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
_closeComposerAction();
|
_closeComposerAction();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1956,16 +1955,14 @@ class ComposerController extends BaseController
|
|||||||
final isChanged = await _validateEmailChange();
|
final isChanged = await _validateEmailChange();
|
||||||
|
|
||||||
if (isChanged && context.mounted) {
|
if (isChanged && context.mounted) {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
await _showConfirmDialogSaveMessage(context);
|
await _showConfirmDialogSaveMessage(context);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (context.mounted) {
|
_closeComposerButtonState = ButtonState.enabled;
|
||||||
_closeComposerButtonState = ButtonState.enabled;
|
clearFocus();
|
||||||
clearFocus(context);
|
_closeComposerAction();
|
||||||
_closeComposerAction();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _validateCloseComposerWithoutSave() {
|
bool _validateCloseComposerWithoutSave() {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
mobile: MobileContainerView(
|
mobile: MobileContainerView(
|
||||||
onCloseViewAction: () => controller.handleClickCloseComposer(context),
|
onCloseViewAction: () => controller.handleClickCloseComposer(context),
|
||||||
onClearFocusAction: () => controller.clearFocus(context),
|
onClearFocusAction: controller.clearFocus,
|
||||||
backgroundColor: MobileAppBarComposerWidgetStyle.backgroundColor,
|
backgroundColor: MobileAppBarComposerWidgetStyle.backgroundColor,
|
||||||
childBuilder: (_, constraints) => SafeArea(
|
childBuilder: (_, constraints) => SafeArea(
|
||||||
left: !controller.responsiveUtils.isLandscapeMobile(context),
|
left: !controller.responsiveUtils.isLandscapeMobile(context),
|
||||||
@@ -248,7 +248,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) {
|
if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) {
|
||||||
return ViewEntireMessageWithMessageClippedWidget(
|
return ViewEntireMessageWithMessageClippedWidget(
|
||||||
buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(),
|
buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(),
|
||||||
onViewEntireMessageAction: () => controller.viewEntireContent(context),
|
onViewEntireMessageAction: controller.viewEntireContent,
|
||||||
topPadding: 12,
|
topPadding: 12,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -268,7 +268,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
tablet: TabletContainerView(
|
tablet: TabletContainerView(
|
||||||
keyboardRichTextController: controller.richTextMobileTabletController!.richTextController,
|
keyboardRichTextController: controller.richTextMobileTabletController!.richTextController,
|
||||||
onCloseViewAction: () => controller.handleClickCloseComposer(context),
|
onCloseViewAction: () => controller.handleClickCloseComposer(context),
|
||||||
onClearFocusAction: () => controller.clearFocus(context),
|
onClearFocusAction: controller.clearFocus,
|
||||||
childBuilder: (_, constraints) => ColoredBox(
|
childBuilder: (_, constraints) => ColoredBox(
|
||||||
color: ComposerStyle.mobileBackgroundColor,
|
color: ComposerStyle.mobileBackgroundColor,
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -427,7 +427,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) {
|
if (controller.isContentHeightExceeded.isTrue && PlatformInfo.isIOS) {
|
||||||
return ViewEntireMessageWithMessageClippedWidget(
|
return ViewEntireMessageWithMessageClippedWidget(
|
||||||
buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(),
|
buttonActionName: AppLocalizations.of(context).viewEntireMessage.toUpperCase(),
|
||||||
onViewEntireMessageAction: () => controller.viewEntireContent(context),
|
onViewEntireMessageAction: controller.viewEntireContent,
|
||||||
topPadding: 12,
|
topPadding: 12,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -443,7 +443,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
hasReadReceipt: controller.hasRequestReadReceipt.value,
|
hasReadReceipt: controller.hasRequestReadReceipt.value,
|
||||||
isMarkAsImportant: controller.isMarkAsImportant.value,
|
isMarkAsImportant: controller.isMarkAsImportant.value,
|
||||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
deleteComposerAction: controller.handleClickDeleteComposer,
|
||||||
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
||||||
sendMessageAction: () => controller.handleClickSendButton(context),
|
sendMessageAction: () => controller.handleClickSendButton(context),
|
||||||
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context),
|
||||||
|
|||||||
@@ -7,10 +7,12 @@ import 'package:model/email/prefix_email_address.dart';
|
|||||||
import 'package:pointer_interceptor/pointer_interceptor.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/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/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/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/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_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_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/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/extensions/remove_draggable_email_address_between_recipient_fields_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
|
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
|
||||||
@@ -67,12 +69,12 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return ResponsiveWidget(
|
final bodyWidget = ResponsiveWidget(
|
||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
mobile: MobileResponsiveContainerView(
|
mobile: MobileResponsiveContainerView(
|
||||||
childBuilder: (_, constraints) {
|
childBuilder: (_, constraints) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => controller.clickOutsideComposer(context),
|
onTap: controller.clickOutsideComposer,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -96,7 +98,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
toggleMarkAsImportantAction: () => controller.toggleMarkAsImportant(context),
|
||||||
saveToDraftsAction: () => controller.handleClickSaveAsDraftsButton(context),
|
saveToDraftsAction: () => controller.handleClickSaveAsDraftsButton(context),
|
||||||
saveToTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
saveToTemplateAction: () => controller.handleClickSaveAsTemplateButton(context),
|
||||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
deleteComposerAction: controller.handleClickDeleteComposer,
|
||||||
)),
|
)),
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@@ -231,6 +233,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
uploadError: uploadError
|
uploadError: uploadError
|
||||||
),
|
),
|
||||||
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
||||||
|
onKeyDownEditorAction: controller.onKeyDownEditorAction,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -318,7 +321,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
desktop: Obx(() => DesktopResponsiveContainerView(
|
desktop: Obx(() => DesktopResponsiveContainerView(
|
||||||
childBuilder: (_, constraints) {
|
childBuilder: (_, constraints) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => controller.clickOutsideComposer(context),
|
onTap: controller.clickOutsideComposer,
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Obx(() => DesktopAppBarComposerWidget(
|
Obx(() => DesktopAppBarComposerWidget(
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
@@ -468,6 +471,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
uploadError: uploadError
|
uploadError: uploadError
|
||||||
),
|
),
|
||||||
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
||||||
|
onKeyDownEditorAction: controller.onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -514,7 +518,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions,
|
openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions,
|
||||||
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
||||||
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
||||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
deleteComposerAction: controller.handleClickDeleteComposer,
|
||||||
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
||||||
sendMessageAction: () => controller.handleClickSendButton(context),
|
sendMessageAction: () => controller.handleClickSendButton(context),
|
||||||
isEmailChanged: controller.isEmailChanged.isTrue,
|
isEmailChanged: controller.isEmailChanged.isTrue,
|
||||||
@@ -588,7 +592,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
tablet: TabletResponsiveContainerView(
|
tablet: TabletResponsiveContainerView(
|
||||||
childBuilder: (_, constraints) {
|
childBuilder: (_, constraints) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => controller.clickOutsideComposer(context),
|
onTap: controller.clickOutsideComposer,
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Obx(() => DesktopAppBarComposerWidget(
|
Obx(() => DesktopAppBarComposerWidget(
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
@@ -735,6 +739,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
uploadError: uploadError
|
uploadError: uploadError
|
||||||
),
|
),
|
||||||
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
|
||||||
|
onKeyDownEditorAction: controller.onKeyDownEditorAction,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
@@ -780,7 +785,7 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions,
|
openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions,
|
||||||
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
attachFileAction: () => controller.openFilePickerByType(context, FileType.any),
|
||||||
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
insertImageAction: () => controller.insertImage(context, constraints.maxWidth),
|
||||||
deleteComposerAction: () => controller.handleClickDeleteComposer(context),
|
deleteComposerAction: controller.handleClickDeleteComposer,
|
||||||
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context),
|
||||||
sendMessageAction: () => controller.handleClickSendButton(context),
|
sendMessageAction: () => controller.handleClickSendButton(context),
|
||||||
isEmailChanged: controller.isEmailChanged.isTrue,
|
isEmailChanged: controller.isEmailChanged.isTrue,
|
||||||
@@ -844,6 +849,16 @@ class ComposerView extends GetWidget<ComposerController> {
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (controller.keyboardShortcutFocusNode != null) {
|
||||||
|
return KeyboardHandlerWrapper(
|
||||||
|
focusNode: controller.keyboardShortcutFocusNode!,
|
||||||
|
onKeyDownEventAction: controller.onKeyDownEventAction,
|
||||||
|
child: bodyWidget,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return bodyWidget;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRecipientComposerWidget({
|
Widget _buildRecipientComposerWidget({
|
||||||
|
|||||||
+2
-3
@@ -2,7 +2,6 @@
|
|||||||
import 'package:core/presentation/constants/constants_ui.dart';
|
import 'package:core/presentation/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.dart';
|
import 'package:core/presentation/extensions/color_extension.dart';
|
||||||
import 'package:core/utils/app_logger.dart';
|
import 'package:core/utils/app_logger.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:get/get.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/composer_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/composer/presentation/view/mobile/editor_fullscreen_dialog_view.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;
|
isContentHeightExceeded.value = height == ConstantsUI.composerHtmlContentMaxHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> viewEntireContent(BuildContext context) async {
|
Future<void> viewEntireContent() async {
|
||||||
clearFocus(context);
|
clearFocus();
|
||||||
|
|
||||||
final currentContent = await getContentInEditor();
|
final currentContent = await getContentInEditor();
|
||||||
log('HandleContentHeightExceededExtension::showComposerFullscreen:currentContent = $currentContent');
|
log('HandleContentHeightExceededExtension::showComposerFullscreen:currentContent = $currentContent');
|
||||||
|
|||||||
+52
@@ -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!);
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -57,7 +57,7 @@ extension HandleOpenContextMenuExtension on ComposerController {
|
|||||||
handleClickSaveAsTemplateButton(context);
|
handleClickSaveAsTemplateButton(context);
|
||||||
break;
|
break;
|
||||||
case ComposerActionType.delete:
|
case ComposerActionType.delete:
|
||||||
handleClickDeleteComposer(context);
|
handleClickDeleteComposer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||||
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
||||||
final OnInitialContentLoadComplete? onInitialContentLoadComplete;
|
final OnInitialContentLoadComplete? onInitialContentLoadComplete;
|
||||||
|
final OnKeyDownEditorAction? onKeyDownEditorAction;
|
||||||
|
|
||||||
const WebEditorView({
|
const WebEditorView({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -55,6 +56,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
this.onPasteImageSuccessAction,
|
this.onPasteImageSuccessAction,
|
||||||
this.onPasteImageFailureAction,
|
this.onPasteImageFailureAction,
|
||||||
this.onInitialContentLoadComplete,
|
this.onInitialContentLoadComplete,
|
||||||
|
this.onKeyDownEditorAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -85,6 +87,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
case EmailActionType.editDraft:
|
case EmailActionType.editDraft:
|
||||||
case EmailActionType.editSendingEmail:
|
case EmailActionType.editSendingEmail:
|
||||||
@@ -115,6 +118,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
),
|
),
|
||||||
(success) {
|
(success) {
|
||||||
if (success is GetEmailContentLoading) {
|
if (success is GetEmailContentLoading) {
|
||||||
@@ -147,6 +151,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,6 +190,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
(success) {
|
(success) {
|
||||||
@@ -218,6 +224,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -241,6 +248,7 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
|
|||||||
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
onPasteImageSuccessAction: onPasteImageSuccessAction,
|
||||||
onPasteImageFailureAction: onPasteImageFailureAction,
|
onPasteImageFailureAction: onPasteImageFailureAction,
|
||||||
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
onInitialContentLoadComplete: onInitialContentLoadComplete,
|
||||||
|
onKeyDownEditorAction: onKeyDownEditorAction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ typedef OnPasteImageFailureAction = Function(
|
|||||||
String? base64,
|
String? base64,
|
||||||
UploadError uploadError);
|
UploadError uploadError);
|
||||||
typedef OnInitialContentLoadComplete = Function(String text);
|
typedef OnInitialContentLoadComplete = Function(String text);
|
||||||
|
typedef OnKeyDownEditorAction = Function(int? keyCode);
|
||||||
|
|
||||||
class WebEditorWidget extends StatefulWidget {
|
class WebEditorWidget extends StatefulWidget {
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ class WebEditorWidget extends StatefulWidget {
|
|||||||
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
final OnPasteImageSuccessAction? onPasteImageSuccessAction;
|
||||||
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
final OnPasteImageFailureAction? onPasteImageFailureAction;
|
||||||
final OnInitialContentLoadComplete? onInitialContentLoadComplete;
|
final OnInitialContentLoadComplete? onInitialContentLoadComplete;
|
||||||
|
final OnKeyDownEditorAction? onKeyDownEditorAction;
|
||||||
|
|
||||||
const WebEditorWidget({
|
const WebEditorWidget({
|
||||||
super.key,
|
super.key,
|
||||||
@@ -63,6 +65,7 @@ class WebEditorWidget extends StatefulWidget {
|
|||||||
this.onPasteImageSuccessAction,
|
this.onPasteImageSuccessAction,
|
||||||
this.onPasteImageFailureAction,
|
this.onPasteImageFailureAction,
|
||||||
this.onInitialContentLoadComplete,
|
this.onInitialContentLoadComplete,
|
||||||
|
this.onKeyDownEditorAction,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -205,6 +208,7 @@ class _WebEditorState extends State<WebEditorWidget> {
|
|||||||
log('_WebEditorState::build: onSignatureHoverOut');
|
log('_WebEditorState::build: onSignatureHoverOut');
|
||||||
_hideSignatureTooltip();
|
_hideSignatureTooltip();
|
||||||
},
|
},
|
||||||
|
onKeyDown: widget.onKeyDownEditorAction,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user