TF-3911 Support mail keyboard shortcut actions in email detailed view on web

This commit is contained in:
dab246
2025-07-29 02:00:32 +07:00
committed by Dat H. Pham
parent 1c7778d6cc
commit b2989521d9
36 changed files with 763 additions and 270 deletions
@@ -105,4 +105,14 @@ class OpenAttachmentListAction extends EmailUIAction {
isDisplayAllAttachments,
screenHeight,
];
}
class TriggerMailViewKeyboardShortcutAction extends EmailUIAction {
TriggerMailViewKeyboardShortcutAction(this.actionType, this.email);
final EmailActionType actionType;
final PresentationEmail email;
@override
List<Object?> get props => [actionType, email];
}
@@ -97,6 +97,7 @@ import 'package:tmail_ui_user/features/email/presentation/extensions/attachment_
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_attendee_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_organizer_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/handle_open_attachment_list_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/handle_mail_action_by_shortcut_action_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/update_attendance_status_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/blob_calendar_event.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
@@ -441,6 +442,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
isDisplayAllAttachments: action.isDisplayAllAttachments,
);
mailboxDashBoardController.clearEmailUIAction();
} else if (action is TriggerMailViewKeyboardShortcutAction) {
handleMailActionByShortcutAction(
actionType: action.actionType,
email: action.email,
);
mailboxDashBoardController.clearEmailUIAction();
}
}));
@@ -1255,7 +1262,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void moveToMailbox(BuildContext context, PresentationEmail email) async {
void moveToMailbox(PresentationEmail email) async {
if (session != null && accountId != null) {
final moveActionRequest = await emailActionReactor.moveToMailbox(
session!,
@@ -1267,7 +1274,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
),
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
);
if (!context.mounted || moveActionRequest == null) return;
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
@@ -1275,12 +1282,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
_threadDetailController?.closeThreadDetailAction();
}
}
}
void moveToTrash(BuildContext context, PresentationEmail email) {
void moveToTrash(PresentationEmail email) {
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.moveToTrash(
email,
@@ -1289,7 +1296,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
@@ -1297,12 +1304,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
_threadDetailController?.closeThreadDetailAction();
}
}
}
void moveToSpam(BuildContext context, PresentationEmail email) {
void moveToSpam(PresentationEmail email) {
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.moveToSpam(
email,
@@ -1311,7 +1318,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
@@ -1319,12 +1326,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
_threadDetailController?.closeThreadDetailAction();
}
}
}
void unSpam(BuildContext context, PresentationEmail email) {
void unSpam(PresentationEmail email) {
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.unSpam(
email,
@@ -1333,7 +1340,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
@@ -1341,7 +1348,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
_threadDetailController?.closeThreadDetailAction();
}
}
}
@@ -1370,7 +1377,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
toastManager.showMessageSuccess(success);
}
void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) {
void handleEmailAction(
PresentationEmail presentationEmail,
EmailActionType actionType,
) {
switch(actionType) {
case EmailActionType.markAsUnread:
if (session != null && accountId != null) {
@@ -1403,31 +1413,31 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
break;
case EmailActionType.moveToMailbox:
moveToMailbox(context, presentationEmail);
moveToMailbox(presentationEmail);
break;
case EmailActionType.moveToTrash:
moveToTrash(context, presentationEmail);
moveToTrash(presentationEmail);
break;
case EmailActionType.deletePermanently:
deleteEmailPermanently(context, presentationEmail);
deleteEmailPermanently(presentationEmail);
break;
case EmailActionType.moveToSpam:
moveToSpam(context, presentationEmail);
moveToSpam(presentationEmail);
break;
case EmailActionType.unSpam:
unSpam(context, presentationEmail);
unSpam(presentationEmail);
break;
case EmailActionType.createRule:
quickCreatingRule(context, presentationEmail.from!.first);
quickCreatingRule(presentationEmail.from!.first);
break;
case EmailActionType.unsubscribe:
_unsubscribeEmail(context, presentationEmail);
_unsubscribeEmail(presentationEmail);
break;
case EmailActionType.archiveMessage:
archiveMessage(context, presentationEmail);
archiveMessage(presentationEmail);
break;
case EmailActionType.printAll:
_printEmail(context, presentationEmail);
_printEmail(presentationEmail);
break;
case EmailActionType.downloadMessageAsEML:
_downloadMessageAsEML(presentationEmail);
@@ -1441,6 +1451,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
case EmailActionType.replyAll:
case EmailActionType.replyToList:
case EmailActionType.forward:
case EmailActionType.compose:
pressEmailAction(actionType, presentationEmail);
break;
default:
@@ -1488,14 +1499,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
void deleteEmailPermanently(BuildContext context, PresentationEmail email) {
void deleteEmailPermanently(PresentationEmail email) {
emailActionReactor.deleteEmailPermanently(
email,
onDeleteEmailRequest: (email) {
popBack();
mailboxDashBoardController.deleteEmailPermanently(email);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
_threadDetailController?.closeThreadDetailAction();
}
},
responsiveUtils: responsiveUtils,
@@ -1689,7 +1700,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
}
void quickCreatingRule(BuildContext context, EmailAddress emailAddress) async {
void quickCreatingRule(EmailAddress emailAddress) {
popBack();
if (accountId != null && session != null) {
@@ -1782,7 +1793,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
threadDetailController?.isDisplayAllAttachments = false;
}
void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) {
void _unsubscribeEmail(PresentationEmail presentationEmail) {
emailActionReactor.unsubscribeEmail(
presentationEmail,
emailUnsubscribe: emailUnsubscribe.value,
@@ -1800,16 +1811,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
}
void archiveMessage(BuildContext context, PresentationEmail email) {
void archiveMessage(PresentationEmail email) {
emailActionReactor.archiveMessage(
email,
onArchiveEmailRequest: (presentationEmail) {
mailboxDashBoardController.archiveMessage(context, presentationEmail);
},
onArchiveEmailRequest: mailboxDashBoardController.archiveMessage,
);
}
void _printEmail(BuildContext context, PresentationEmail email) {
void _printEmail(PresentationEmail email) {
if (_printEmailButtonState == ButtonState.disabled) {
log('SingleEmailController::_printEmail: Print email started');
return;
@@ -51,6 +51,7 @@ class EmailView extends GetWidget<SingleEmailController> {
this.isFirstEmailInThreadDetail = false,
this.threadSubject,
this.onToggleThreadDetailCollapseExpand,
this.onIFrameKeyboardShortcutAction,
this.scrollController,
});
@@ -59,6 +60,7 @@ class EmailView extends GetWidget<SingleEmailController> {
final bool isFirstEmailInThreadDetail;
final String? threadSubject;
final VoidCallback? onToggleThreadDetailCollapseExpand;
final OnIFrameKeyboardShortcutAction? onIFrameKeyboardShortcutAction;
final ScrollController? scrollController;
@override
@@ -83,7 +85,7 @@ class EmailView extends GetWidget<SingleEmailController> {
mailboxContain: _getMailboxContain(currentEmail),
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onEmailActionClick: controller.handleEmailAction,
onMoreActionClick: (presentationEmail, position) {
return controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getMailboxContain(presentationEmail),
@@ -92,7 +94,7 @@ class EmailView extends GetWidget<SingleEmailController> {
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
ownEmailAddress: controller.ownEmailAddress,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
handleEmailAction: controller.handleEmailAction,
additionalActions: [],
emailIsRead: presentationEmail.hasRead,
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
@@ -256,7 +258,7 @@ class EmailView extends GetWidget<SingleEmailController> {
maxBodyHeight: bodyConstraints.maxHeight,
isInsideThreadCollapsed: false,
openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
onEmailActionClick: controller.handleEmailAction,
isInsideThreadDetailView: isInsideThreadDetailView,
emailLoaded: controller.currentEmailLoaded.value,
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
@@ -266,7 +268,7 @@ class EmailView extends GetWidget<SingleEmailController> {
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
ownEmailAddress: controller.ownEmailAddress,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
handleEmailAction: controller.handleEmailAction,
additionalActions: [
EmailActionType.reply,
EmailActionType.forward,
@@ -360,6 +362,7 @@ class EmailView extends GetWidget<SingleEmailController> {
scrollController: scrollController,
enableQuoteToggle: true,
fontSize: isMobileResponsive ? 16 : 14,
onIFrameKeyboardShortcutAction: onIFrameKeyboardShortcutAction,
),
);
} else if (PlatformInfo.isIOS) {
@@ -0,0 +1,14 @@
import 'package:core/utils/app_logger.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
extension HandleMailActionByShortcutActionExtension on SingleEmailController {
void handleMailActionByShortcutAction({
required EmailActionType actionType,
required PresentationEmail email,
}) {
log('$runtimeType::handleMailActionByShortcutAction:🔥Email action type: $actionType');
handleEmailAction(email, actionType);
}
}