TF-3911 Support mail keyboard shortcut actions in email detailed view on web
This commit is contained in:
@@ -106,6 +106,7 @@ export 'presentation/views/button/multi_click_widget.dart';
|
||||
export 'presentation/views/semantics/checkbox_semantics.dart';
|
||||
export 'presentation/views/semantics/text_field_semantics.dart';
|
||||
export 'presentation/views/semantics/icon_semantics.dart';
|
||||
export 'presentation/views/shortcut/key_shortcut.dart';
|
||||
|
||||
// Resources
|
||||
export 'presentation/resources/assets_paths.dart';
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:core/presentation/constants/constants_ui.dart';
|
||||
import 'package:core/presentation/extensions/color_extension.dart';
|
||||
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/html/html_interaction.dart';
|
||||
import 'package:core/utils/html/html_template.dart';
|
||||
@@ -14,6 +15,7 @@ import 'package:universal_html/html.dart' as html;
|
||||
|
||||
typedef OnClickHyperLinkAction = Function(Uri?);
|
||||
typedef OnMailtoClicked = void Function(Uri? uri);
|
||||
typedef OnIFrameKeyboardShortcutAction = void Function(KeyShortcut keyShortcut);
|
||||
|
||||
class HtmlContentViewerOnWeb extends StatefulWidget {
|
||||
|
||||
@@ -30,6 +32,8 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
||||
|
||||
final OnClickHyperLinkAction? onClickHyperLinkAction;
|
||||
|
||||
final OnIFrameKeyboardShortcutAction? onIFrameKeyboardShortcutAction;
|
||||
|
||||
// if widthContent is bigger than width of htmlContent, set this to true let widget able to resize to width of htmlContent
|
||||
final bool allowResizeToDocumentSize;
|
||||
|
||||
@@ -66,6 +70,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
||||
this.htmlContentMinWidth = ConstantsUI.htmlContentMinWidth,
|
||||
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
|
||||
this.viewMaxHeight,
|
||||
this.onIFrameKeyboardShortcutAction,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@@ -116,6 +121,9 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
} else if (_isScrollingIsAvailable && _isScrollEndEventTriggered(type)) {
|
||||
_handleIframeOnScrollEndListener(data, widget.scrollController!);
|
||||
return;
|
||||
} else if (_isIframeKeyboardEventTriggered(type)) {
|
||||
_handleOnIFrameKeyboardEvent(data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data['message'] == iframeOnLoadMessage) {
|
||||
@@ -257,6 +265,21 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
}
|
||||
}
|
||||
|
||||
bool _isIframeKeyboardEventTriggered(String? type) {
|
||||
return widget.onIFrameKeyboardShortcutAction != null &&
|
||||
type?.contains('toDart: iframeKeydown') == true;
|
||||
}
|
||||
|
||||
void _handleOnIFrameKeyboardEvent(dynamic data) {
|
||||
final shortcut = KeyShortcut(
|
||||
key: data['key'] as String,
|
||||
code: data['code'] as String,
|
||||
shift: data['shift'] == true,
|
||||
);
|
||||
log('_HtmlContentViewerOnWebState::_handleOnIFrameKeyboardEvent:📥 Shortcut pressed: $shortcut');
|
||||
widget.onIFrameKeyboardShortcutAction?.call(shortcut);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
@@ -397,6 +420,8 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
||||
viewId: _createdViewId,
|
||||
onScrollChangedEvent: onScrollChangedEvent,
|
||||
),
|
||||
if (widget.onIFrameKeyboardShortcutAction != null)
|
||||
HtmlInteraction.scriptHandleIframeKeyboardListener(_createdViewId),
|
||||
].join();
|
||||
|
||||
final htmlTemplate = HtmlUtils.generateHtmlDocument(
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import 'package:equatable/equatable.dart';
|
||||
|
||||
class KeyShortcut with EquatableMixin{
|
||||
final String key;
|
||||
final String code;
|
||||
final bool shift;
|
||||
|
||||
KeyShortcut({
|
||||
required this.key,
|
||||
required this.code,
|
||||
this.shift = false,
|
||||
});
|
||||
|
||||
bool matches(String expectedKey, {bool shift = false}) {
|
||||
return key.toLowerCase() == expectedKey.toLowerCase() &&
|
||||
this.shift == shift;
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [key, code, shift];
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class HtmlInteraction {
|
||||
''';
|
||||
|
||||
static const String scriptsHandleContentSizeChanged = '''
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
const bodyResizeObserver = new ResizeObserver(entries => {
|
||||
window.flutter_inappwebview.callHandler('$contentSizeChangedEventJSChannelName', '');
|
||||
})
|
||||
@@ -70,7 +70,7 @@ class HtmlInteraction {
|
||||
''';
|
||||
|
||||
static const String scriptsHandleLazyLoadingBackgroundImage = '''
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
const lazyImages = document.querySelectorAll('[lazy]');
|
||||
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
|
||||
entries.forEach((entry) => {
|
||||
@@ -378,4 +378,19 @@ class HtmlInteraction {
|
||||
</script>
|
||||
|
||||
''';
|
||||
|
||||
static String scriptHandleIframeKeyboardListener(String viewId) => '''
|
||||
<script type="text/javascript">
|
||||
window.addEventListener('keydown', function (event) {
|
||||
const payload = {
|
||||
view: '$viewId',
|
||||
type: 'toDart: iframeKeydown',
|
||||
key: event.key,
|
||||
code: event.code,
|
||||
shift: event.shiftKey
|
||||
};
|
||||
window.parent.postMessage(JSON.stringify(payload), "*");
|
||||
});
|
||||
</script>
|
||||
''';
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
extension LogicalKeySetHelper on Set<LogicalKeyboardKey> {
|
||||
bool isKey(LogicalKeyboardKey key) => contains(key);
|
||||
|
||||
bool isOnly(LogicalKeyboardKey key) => length == 1 && contains(key);
|
||||
|
||||
bool isShift() =>
|
||||
contains(LogicalKeyboardKey.shiftLeft) ||
|
||||
contains(LogicalKeyboardKey.shiftRight);
|
||||
|
||||
bool isShiftPlus(LogicalKeyboardKey key) => isShift() && contains(key);
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
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/mail/mail_view_action_shortcut_type.dart';
|
||||
|
||||
class AppShortcutManager {
|
||||
static MailViewActionShortcutType? getMailViewActionFromEvent(KeyEvent event) {
|
||||
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
|
||||
log('AppShortcutManager::getMailViewActionFromEvent: Keys pressed: $keysPressed');
|
||||
if (keysPressed.isOnly(LogicalKeyboardKey.keyR)) {
|
||||
return MailViewActionShortcutType.reply;
|
||||
} else if (keysPressed.isShiftPlus(LogicalKeyboardKey.keyR)) {
|
||||
return MailViewActionShortcutType.replyAll;
|
||||
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyF)) {
|
||||
return MailViewActionShortcutType.forward;
|
||||
} else if (keysPressed.isOnly(LogicalKeyboardKey.delete) ||
|
||||
keysPressed.isOnly(LogicalKeyboardKey.backspace)) {
|
||||
return MailViewActionShortcutType.delete;
|
||||
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyN)) {
|
||||
return MailViewActionShortcutType.newMessage;
|
||||
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyU)) {
|
||||
return MailViewActionShortcutType.markAsUnread;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
|
||||
enum MailViewActionShortcutType {
|
||||
reply,
|
||||
replyAll,
|
||||
forward,
|
||||
delete,
|
||||
newMessage,
|
||||
markAsUnread;
|
||||
|
||||
EmailActionType? getEmailActionType({
|
||||
required PresentationEmail currentEmail,
|
||||
required String ownerEmailAddress,
|
||||
}) {
|
||||
switch(this) {
|
||||
case MailViewActionShortcutType.reply:
|
||||
return EmailActionType.reply;
|
||||
case MailViewActionShortcutType.replyAll:
|
||||
if (currentEmail.isReplyAllEnabled(ownerEmailAddress)) {
|
||||
return EmailActionType.replyAll;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
case MailViewActionShortcutType.forward:
|
||||
return EmailActionType.forward;
|
||||
case MailViewActionShortcutType.delete:
|
||||
return currentEmail.isDeletePermanentlyEnabled
|
||||
? EmailActionType.deletePermanently
|
||||
: EmailActionType.moveToTrash;
|
||||
case MailViewActionShortcutType.newMessage:
|
||||
return EmailActionType.compose;
|
||||
case MailViewActionShortcutType.markAsUnread:
|
||||
return EmailActionType.markAsUnread;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
typedef OnHandleKeyEventAction = void Function(KeyEvent event);
|
||||
|
||||
class KeyboardHandlerWrapper extends StatelessWidget {
|
||||
final Widget child;
|
||||
final FocusNode focusNode;
|
||||
final OnHandleKeyEventAction onKeyDownEventAction;
|
||||
final OnHandleKeyEventAction? onKeyUpEventAction;
|
||||
|
||||
const KeyboardHandlerWrapper({
|
||||
Key? key,
|
||||
required this.child,
|
||||
required this.focusNode,
|
||||
required this.onKeyDownEventAction,
|
||||
this.onKeyUpEventAction,
|
||||
}) : super(key: key);
|
||||
|
||||
void _handleKeyEvent(KeyEvent event) {
|
||||
if (event is KeyDownEvent) {
|
||||
onKeyDownEventAction(event);
|
||||
} else if (event is KeyUpEvent) {
|
||||
onKeyUpEventAction?.call(event);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return KeyboardListener(
|
||||
focusNode: focusNode,
|
||||
autofocus: true,
|
||||
onKeyEvent: _handleKeyEvent,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -106,3 +106,13 @@ class OpenAttachmentListAction extends EmailUIAction {
|
||||
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) {
|
||||
|
||||
+14
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -156,3 +156,5 @@ class SynchronizeEmailSortOrderAction extends DashBoardAction {
|
||||
@override
|
||||
List<Object?> get props => [emailSortOrderType];
|
||||
}
|
||||
|
||||
class ReclaimMailListKeyboardShortcutFocusAction extends DashBoardAction {}
|
||||
+19
-3
@@ -121,6 +121,7 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_clear_mailbox_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_create_new_rule_filter.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_preferences_setting_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_reactive_obx_variable_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_save_email_as_draft_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_store_email_sort_order_extension.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/initialize_app_language.dart';
|
||||
@@ -310,6 +311,8 @@ class MailboxDashBoardController extends ReloadableController
|
||||
int minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete;
|
||||
EmailSortOrderType currentSortOrder = SearchEmailFilter.defaultSortOrder;
|
||||
PaywallController? paywallController;
|
||||
Worker? advancedSearchVisibleWorker;
|
||||
Worker? searchInputFocusWorker;
|
||||
|
||||
final StreamController<Either<Failure, Success>> _progressStateController =
|
||||
StreamController<Either<Failure, Success>>.broadcast();
|
||||
@@ -376,6 +379,7 @@ class MailboxDashBoardController extends ReloadableController
|
||||
if (PlatformInfo.isWeb) {
|
||||
listSearchFilterScrollController = ScrollController();
|
||||
twakeAppManager.setExecutingBeforeReconnect(false);
|
||||
registerReactiveObxVariableListener();
|
||||
}
|
||||
if (PlatformInfo.isIOS) {
|
||||
_registerPendingCurrentEmailIdInNotification();
|
||||
@@ -3039,18 +3043,17 @@ class MailboxDashBoardController extends ReloadableController
|
||||
return false;
|
||||
}
|
||||
|
||||
void archiveMessage(BuildContext context, PresentationEmail email) {
|
||||
void archiveMessage(PresentationEmail email) {
|
||||
final mailboxContain = email.findMailboxContain(mapMailboxById);
|
||||
if (mailboxContain != null) {
|
||||
final archiveMailboxId = getMailboxIdByRole(PresentationMailbox.roleArchive);
|
||||
final archiveMailboxPath = mapMailboxById[archiveMailboxId]?.getDisplayName(context);
|
||||
if (archiveMailboxId != null) {
|
||||
final moveToArchiveMailboxRequest = MoveToMailboxRequest(
|
||||
{mailboxContain.id: [email.id!]},
|
||||
archiveMailboxId,
|
||||
MoveAction.moving,
|
||||
EmailActionType.moveToMailbox,
|
||||
destinationPath: archiveMailboxPath
|
||||
destinationPath: getMailboxNameById(archiveMailboxId),
|
||||
);
|
||||
moveToMailbox(
|
||||
sessionCurrent!,
|
||||
@@ -3062,6 +3065,15 @@ class MailboxDashBoardController extends ReloadableController
|
||||
}
|
||||
}
|
||||
|
||||
String getMailboxNameById(MailboxId mailboxId) {
|
||||
final mailbox = mapMailboxById[mailboxId];
|
||||
if (currentContext != null) {
|
||||
return mailbox?.getDisplayName(currentContext!) ?? '';
|
||||
} else {
|
||||
return mailbox?.name?.name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
void _handleRestoreDeletedMessageSuccess(EmailRecoveryActionId emailRecoveryActionId) async {
|
||||
log('MailboxDashBoardController::_handleRestoreDeletedMessageSuccess():emailRecoveryActionId: $emailRecoveryActionId');
|
||||
_getRestoredDeletedMessage(emailRecoveryActionId);
|
||||
@@ -3271,10 +3283,14 @@ class MailboxDashBoardController extends ReloadableController
|
||||
}
|
||||
}
|
||||
|
||||
bool get isEmailOpened =>
|
||||
dashboardRoute.value == DashboardRoutes.threadDetailed;
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
listSearchFilterScrollController?.dispose();
|
||||
disposeReactiveObxVariableListener();
|
||||
}
|
||||
if (PlatformInfo.isIOS) {
|
||||
_iosNotificationManager?.dispose();
|
||||
|
||||
@@ -47,6 +47,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
final listFilterOnSuggestionForm = RxList<QuickSearchFilter>();
|
||||
final simpleSearchIsActivated = RxBool(false);
|
||||
final advancedSearchIsActivated = RxBool(false);
|
||||
final isSearchInputFocused = RxBool(false);
|
||||
|
||||
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
||||
|
||||
@@ -59,6 +60,17 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
this._getAllRecentSearchLatestInteractor,
|
||||
);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
searchFocus.addListener(_onSearchFocusChanged);
|
||||
}
|
||||
|
||||
void _onSearchFocusChanged() {
|
||||
log('SearchController::_onSearchFocusChanged: ${searchFocus.hasFocus}');
|
||||
isSearchInputFocused.value = searchFocus.hasFocus;
|
||||
}
|
||||
|
||||
void openAdvanceSearch() {
|
||||
isAdvancedSearchViewOpen.value = true;
|
||||
}
|
||||
@@ -322,6 +334,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
||||
@override
|
||||
void onClose() {
|
||||
searchInputController.dispose();
|
||||
searchFocus.removeListener(_onSearchFocusChanged);
|
||||
searchFocus.dispose();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:get/get_rx/src/rx_workers/rx_workers.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
|
||||
extension HandleReactiveObxVariableExtension on MailboxDashBoardController {
|
||||
|
||||
void registerReactiveObxVariableListener() {
|
||||
advancedSearchVisibleWorker = ever(
|
||||
searchController.isAdvancedSearchViewOpen,
|
||||
_onAdvancedSearchVisibleChanged
|
||||
);
|
||||
|
||||
searchInputFocusWorker = ever(
|
||||
searchController.isSearchInputFocused,
|
||||
_onSearchInputFocusChanged
|
||||
);
|
||||
}
|
||||
|
||||
void _onAdvancedSearchVisibleChanged(bool visible) {
|
||||
log('$runtimeType::_onAdvancedSearchVisibleChanged: visible is $visible | isEmailOpened = $isEmailOpened');
|
||||
if (isEmailOpened) {
|
||||
_performThreadDetailUIActionWhenEmailOpened(visible);
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearchInputFocusChanged(bool focused) {
|
||||
log('$runtimeType::_onSearchInputFocusChanged: focused is $focused | isEmailOpened = $isEmailOpened');
|
||||
if (isEmailOpened) {
|
||||
_performThreadDetailUIActionWhenEmailOpened(focused);
|
||||
}
|
||||
}
|
||||
|
||||
void _performThreadDetailUIActionWhenEmailOpened(bool focused) {
|
||||
final emailAction = focused
|
||||
? ClearMailViewKeyboardShortcutFocusAction()
|
||||
: ReclaimMailViewKeyboardShortcutFocusAction();
|
||||
|
||||
dispatchThreadDetailUIAction(emailAction);
|
||||
}
|
||||
|
||||
void disposeReactiveObxVariableListener() {
|
||||
advancedSearchVisibleWorker?.dispose();
|
||||
advancedSearchVisibleWorker = null;
|
||||
searchInputFocusWorker?.dispose();
|
||||
searchInputFocusWorker = null;
|
||||
}
|
||||
}
|
||||
+5
@@ -11,8 +11,10 @@ import 'package:tmail_ui_user/features/composer/presentation/composer_view.dart'
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/extensions/composer_arguments_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
|
||||
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
|
||||
import 'package:tmail_ui_user/main/routes/app_routes.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
@@ -111,6 +113,9 @@ extension OpenAndCloseComposerExtension on MailboxDashBoardController {
|
||||
twakeAppManager.setHasComposer(false);
|
||||
}
|
||||
|
||||
dispatchThreadDetailUIAction(ReclaimMailViewKeyboardShortcutFocusAction());
|
||||
dispatchAction(ReclaimMailListKeyboardShortcutFocusAction());
|
||||
|
||||
_handleResultAfterCloseComposer(result);
|
||||
|
||||
if (composerId != null) {
|
||||
|
||||
@@ -68,137 +68,141 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
||||
ResponsiveWidget(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
desktop: Scaffold(
|
||||
body: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Container(
|
||||
color: AppColor.colorBgDesktop,
|
||||
child: Column(children: [
|
||||
FutureBuilder(
|
||||
future: CozyConfigManager().isInsideCozy,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return const SizedBox.shrink();
|
||||
backgroundColor: AppColor.colorBgDesktop,
|
||||
body: Column(
|
||||
children: [
|
||||
FutureBuilder(
|
||||
future: CozyConfigManager().isInsideCozy,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data == true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
final accountId = controller.accountId.value;
|
||||
String accountDisplayName = controller.ownEmailAddress.value;
|
||||
final contactSupportCapability = accountId != null
|
||||
? controller.sessionCurrent?.getContactSupportCapability(accountId)
|
||||
: null;
|
||||
if (accountDisplayName.trim().isEmpty) {
|
||||
accountDisplayName = controller
|
||||
.sessionCurrent
|
||||
?.getOwnEmailAddressOrUsername() ?? '';
|
||||
}
|
||||
|
||||
return Obx(() {
|
||||
final accountId = controller.accountId.value;
|
||||
String accountDisplayName = controller.ownEmailAddress.value;
|
||||
final contactSupportCapability = accountId != null
|
||||
? controller.sessionCurrent?.getContactSupportCapability(accountId)
|
||||
: null;
|
||||
if (accountDisplayName.trim().isEmpty) {
|
||||
accountDisplayName = controller
|
||||
.sessionCurrent
|
||||
?.getOwnEmailAddressOrUsername() ?? '';
|
||||
}
|
||||
|
||||
return NavigationBarWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
accountId: accountId,
|
||||
ownEmailAddress: accountDisplayName,
|
||||
contactSupportCapability: contactSupportCapability,
|
||||
searchForm: SearchInputFormWidget(),
|
||||
appGridController:
|
||||
controller.appGridDashboardController,
|
||||
settingActionTypes: ProfileSettingActionType.values,
|
||||
onTapApplicationLogoAction:
|
||||
controller.redirectToInboxAction,
|
||||
onTapContactSupportAction: (contactSupport) =>
|
||||
controller.onGetHelpOrReportBug(contactSupport),
|
||||
onProfileSettingActionTypeClick: (actionType) =>
|
||||
controller.handleProfileSettingActionTypeClick(
|
||||
context: context,
|
||||
actionType: actionType,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(child: Row(children: [
|
||||
Column(children: [
|
||||
ComposeButtonWidget(
|
||||
return NavigationBarWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
onTapAction: () =>
|
||||
controller.openComposer(ComposerArguments()),
|
||||
accountId: accountId,
|
||||
ownEmailAddress: accountDisplayName,
|
||||
contactSupportCapability: contactSupportCapability,
|
||||
searchForm: SearchInputFormWidget(),
|
||||
appGridController:
|
||||
controller.appGridDashboardController,
|
||||
settingActionTypes: ProfileSettingActionType.values,
|
||||
onTapApplicationLogoAction:
|
||||
controller.redirectToInboxAction,
|
||||
onTapContactSupportAction: (contactSupport) =>
|
||||
controller.onGetHelpOrReportBug(contactSupport),
|
||||
onProfileSettingActionTypeClick: (actionType) =>
|
||||
controller.handleProfileSettingActionTypeClick(
|
||||
context: context,
|
||||
actionType: actionType,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
),
|
||||
Expanded(
|
||||
child: Row(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
ComposeButtonWidget(
|
||||
imagePaths: controller.imagePaths,
|
||||
onTapAction: () =>
|
||||
controller.openComposer(ComposerArguments()),
|
||||
),
|
||||
Expanded(child: SizedBox(
|
||||
width: ResponsiveUtils.defaultSizeMenu,
|
||||
child: Obx(() {
|
||||
if (controller.searchMailboxActivated.isTrue) {
|
||||
return const SearchMailboxView(
|
||||
backgroundColor: AppColor.colorBgDesktop
|
||||
);
|
||||
} else {
|
||||
return MailboxView();
|
||||
}
|
||||
})
|
||||
))
|
||||
],
|
||||
),
|
||||
Expanded(child: SizedBox(
|
||||
width: ResponsiveUtils.defaultSizeMenu,
|
||||
child: Obx(() {
|
||||
if (controller.searchMailboxActivated.isTrue) {
|
||||
return const SearchMailboxView(
|
||||
backgroundColor: AppColor.colorBgDesktop
|
||||
);
|
||||
} else {
|
||||
return MailboxView();
|
||||
}
|
||||
})
|
||||
))
|
||||
]),
|
||||
Expanded(child: Column(children: [
|
||||
const SizedBox(height: 16),
|
||||
FutureBuilder(
|
||||
future: CozyConfigManager().isInsideCozy,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data != true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
Expanded(
|
||||
child: Column(
|
||||
children: [
|
||||
const SizedBox(height: 16),
|
||||
FutureBuilder(
|
||||
future: CozyConfigManager().isInsideCozy,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.data != true) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.4,
|
||||
height: 44,
|
||||
child: SearchInputFormWidget(
|
||||
fontSize: 15,
|
||||
contentPadding: const EdgeInsets.only(bottom: 4),
|
||||
),
|
||||
),
|
||||
const Spacer(),
|
||||
Obx(() {
|
||||
String accountDisplayName =
|
||||
controller.ownEmailAddress.value;
|
||||
if (accountDisplayName.trim().isEmpty) {
|
||||
accountDisplayName = controller
|
||||
.sessionCurrent
|
||||
?.getOwnEmailAddressOrUsername() ?? '';
|
||||
}
|
||||
return ProfileSettingIcon(
|
||||
ownEmailAddress: accountDisplayName,
|
||||
settingActionTypes: ProfileSettingActionType.values,
|
||||
onProfileSettingActionTypeClick: (actionType) =>
|
||||
controller.handleProfileSettingActionTypeClick(
|
||||
context: context,
|
||||
actionType: actionType,
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: MediaQuery.sizeOf(context).width * 0.4,
|
||||
height: 44,
|
||||
child: SearchInputFormWidget(
|
||||
fontSize: 15,
|
||||
contentPadding: const EdgeInsets.only(bottom: 4),
|
||||
),
|
||||
),
|
||||
isInsideCozy: true,
|
||||
);
|
||||
}),
|
||||
],
|
||||
const Spacer(),
|
||||
Obx(() {
|
||||
String accountDisplayName =
|
||||
controller.ownEmailAddress.value;
|
||||
if (accountDisplayName.trim().isEmpty) {
|
||||
accountDisplayName = controller
|
||||
.sessionCurrent
|
||||
?.getOwnEmailAddressOrUsername() ?? '';
|
||||
}
|
||||
return ProfileSettingIcon(
|
||||
ownEmailAddress: accountDisplayName,
|
||||
settingActionTypes: ProfileSettingActionType.values,
|
||||
onProfileSettingActionTypeClick: (actionType) =>
|
||||
controller.handleProfileSettingActionTypeClick(
|
||||
context: context,
|
||||
actionType: actionType,
|
||||
),
|
||||
isInsideCozy: true,
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
),
|
||||
Obx(() => RecoverDeletedMessageLoadingBannerWidget(
|
||||
isLoading: controller.isRecoveringDeletedMessage.value,
|
||||
horizontalLoadingWidget: horizontalLoadingWidget,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
)),
|
||||
Obx(() => MarkMailboxAsReadLoadingBanner(
|
||||
viewState: controller.viewStateMailboxActionProgress.value,
|
||||
)),
|
||||
Obx(() {
|
||||
final spamController = controller.spamReportController;
|
||||
Obx(() => RecoverDeletedMessageLoadingBannerWidget(
|
||||
isLoading: controller.isRecoveringDeletedMessage.value,
|
||||
horizontalLoadingWidget: horizontalLoadingWidget,
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
)),
|
||||
Obx(() => MarkMailboxAsReadLoadingBanner(
|
||||
viewState: controller.viewStateMailboxActionProgress.value,
|
||||
)),
|
||||
Obx(() {
|
||||
final spamController = controller.spamReportController;
|
||||
|
||||
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled;
|
||||
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled;
|
||||
|
||||
final isSpamFolderSelected = controller
|
||||
.selectedMailbox
|
||||
.value
|
||||
?.isSpam == true;
|
||||
final isSpamFolderSelected = controller
|
||||
.selectedMailbox
|
||||
.value
|
||||
?.isSpam == true;
|
||||
|
||||
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
|
||||
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
|
||||
|
||||
final isEmailOpened = controller.dashboardRoute.value == DashboardRoutes.threadDetailed;
|
||||
|
||||
@@ -209,91 +213,96 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return ReportMessageBanner(
|
||||
imagePaths: controller.imagePaths,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.countMessageInSpam(
|
||||
spamController.numberOfUnreadSpamEmails,
|
||||
),
|
||||
positiveName: AppLocalizations.of(context).view,
|
||||
isDesktop: controller
|
||||
.responsiveUtils
|
||||
.isDesktop(context),
|
||||
margin: SpamReportBannerWebStyles.bannerMargin,
|
||||
onPositiveAction: spamController.openMailbox,
|
||||
onNegativeAction: () =>
|
||||
spamController.dismissSpamReportAction(context),
|
||||
);
|
||||
}),
|
||||
QuotasBannerWidget(),
|
||||
_buildVacationNotificationMessage(context),
|
||||
Obx(() {
|
||||
final selectedMailbox = controller
|
||||
.selectedMailbox
|
||||
.value;
|
||||
return ReportMessageBanner(
|
||||
imagePaths: controller.imagePaths,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.countMessageInSpam(
|
||||
spamController.numberOfUnreadSpamEmails,
|
||||
),
|
||||
positiveName: AppLocalizations.of(context).view,
|
||||
isDesktop: controller
|
||||
.responsiveUtils
|
||||
.isDesktop(context),
|
||||
margin: SpamReportBannerWebStyles.bannerMargin,
|
||||
onPositiveAction: spamController.openMailbox,
|
||||
onNegativeAction: () =>
|
||||
spamController.dismissSpamReportAction(context),
|
||||
);
|
||||
}),
|
||||
QuotasBannerWidget(),
|
||||
_buildVacationNotificationMessage(context),
|
||||
Obx(() {
|
||||
final selectedMailbox = controller
|
||||
.selectedMailbox
|
||||
.value;
|
||||
|
||||
bool showTrashBanner = controller
|
||||
.isEmptyTrashBannerEnabledOnWeb(
|
||||
context,
|
||||
selectedMailbox,
|
||||
);
|
||||
bool showSpamBanner = controller
|
||||
.isEmptySpamBannerEnabledOnWeb(
|
||||
context,
|
||||
selectedMailbox,
|
||||
);
|
||||
bool showTrashBanner = controller
|
||||
.isEmptyTrashBannerEnabledOnWeb(
|
||||
context,
|
||||
selectedMailbox,
|
||||
);
|
||||
bool showSpamBanner = controller
|
||||
.isEmptySpamBannerEnabledOnWeb(
|
||||
context,
|
||||
selectedMailbox,
|
||||
);
|
||||
|
||||
if (showTrashBanner) {
|
||||
return CleanMessagesBanner(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.message_delete_all_email_in_trash_button,
|
||||
positiveAction: AppLocalizations
|
||||
.of(context)
|
||||
.empty_trash_now,
|
||||
onPositiveAction: controller.emptyTrashAction,
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
bottom: 8,
|
||||
end: 16,
|
||||
),
|
||||
);
|
||||
} else if (showSpamBanner) {
|
||||
return CleanMessagesBanner(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.bannerDeleteAllSpamEmailsMessage,
|
||||
positiveAction: AppLocalizations
|
||||
.of(context)
|
||||
.deleteAllSpamEmailsNow,
|
||||
onPositiveAction: () =>
|
||||
controller.openDialogEmptySpamFolder(context),
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
bottom: 8,
|
||||
end: 16,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
_buildListButtonQuickSearchFilter(context),
|
||||
Expanded(child: Obx(() {
|
||||
switch(controller.dashboardRoute.value) {
|
||||
case DashboardRoutes.thread:
|
||||
return _buildThreadViewForWebDesktop(context);
|
||||
case DashboardRoutes.threadDetailed:
|
||||
return const ThreadDetailView();
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}))
|
||||
]))
|
||||
]))
|
||||
]),
|
||||
),
|
||||
if (showTrashBanner) {
|
||||
return CleanMessagesBanner(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.message_delete_all_email_in_trash_button,
|
||||
positiveAction: AppLocalizations
|
||||
.of(context)
|
||||
.empty_trash_now,
|
||||
onPositiveAction: controller.emptyTrashAction,
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
bottom: 8,
|
||||
end: 16,
|
||||
),
|
||||
);
|
||||
} else if (showSpamBanner) {
|
||||
return CleanMessagesBanner(
|
||||
responsiveUtils: controller.responsiveUtils,
|
||||
message: AppLocalizations
|
||||
.of(context)
|
||||
.bannerDeleteAllSpamEmailsMessage,
|
||||
positiveAction: AppLocalizations
|
||||
.of(context)
|
||||
.deleteAllSpamEmailsNow,
|
||||
onPositiveAction: () =>
|
||||
controller.openDialogEmptySpamFolder(context),
|
||||
margin: const EdgeInsetsDirectional.only(
|
||||
bottom: 8,
|
||||
end: 16,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
_buildListButtonQuickSearchFilter(context),
|
||||
Expanded(
|
||||
child: Obx(() {
|
||||
switch(controller.dashboardRoute.value) {
|
||||
case DashboardRoutes.thread:
|
||||
return _buildThreadViewForWebDesktop(context);
|
||||
case DashboardRoutes.threadDetailed:
|
||||
return const ThreadDetailView();
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
tabletLarge: Obx(() {
|
||||
|
||||
@@ -341,8 +341,8 @@ mixin EmailActionController {
|
||||
AppUtils.launchLink(email.routeWebAsString);
|
||||
}
|
||||
|
||||
void archiveMessage(BuildContext context, PresentationEmail email) {
|
||||
mailboxDashBoardController.archiveMessage(context, email);
|
||||
void archiveMessage(PresentationEmail email) {
|
||||
mailboxDashBoardController.archiveMessage(email);
|
||||
}
|
||||
|
||||
bool hasArchiveMailbox() {
|
||||
|
||||
@@ -1198,9 +1198,7 @@ class ThreadController extends BaseController with EmailActionController {
|
||||
openEmailInNewTabAction(selectedEmail);
|
||||
break;
|
||||
case EmailActionType.archiveMessage:
|
||||
if (currentContext != null) {
|
||||
archiveMessage(currentContext!, selectedEmail);
|
||||
}
|
||||
archiveMessage(selectedEmail);
|
||||
break;
|
||||
case EmailActionType.editAsNewEmail:
|
||||
editAsNewEmail(selectedEmail);
|
||||
@@ -1376,7 +1374,7 @@ class ThreadController extends BaseController with EmailActionController {
|
||||
ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread;
|
||||
markAsEmailRead(email, readActions, MarkReadAction.swipeOnThread);
|
||||
} else if (direction == DismissDirection.endToStart) {
|
||||
archiveMessage(context, email);
|
||||
archiveMessage(email);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -52,3 +52,7 @@ class LoadThreadDetailAfterSelectedEmailAction extends ThreadDetailUIAction {
|
||||
@override
|
||||
List<Object?> get props => [threadId, isSentMailbox];
|
||||
}
|
||||
|
||||
class ReclaimMailViewKeyboardShortcutFocusAction extends ThreadDetailUIAction {}
|
||||
|
||||
class ClearMailViewKeyboardShortcutFocusAction extends ThreadDetailUIAction {}
|
||||
@@ -1,10 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension CloseThreadDetailAction on ThreadDetailController {
|
||||
void closeThreadDetailAction(BuildContext? context) {
|
||||
void closeThreadDetailAction() {
|
||||
if (isSearchRunning) {
|
||||
mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail);
|
||||
} else {
|
||||
|
||||
+9
-2
@@ -1,12 +1,13 @@
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:model/extensions/presentation_email_extension.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
|
||||
import 'package:model/email/email_in_thread_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_open_email_address_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_load_more_segments.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collapse_expand.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart';
|
||||
@@ -78,6 +79,9 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
onToggleThreadDetailCollapseExpand: () {
|
||||
toggleThreadDetailCollapseExpand(presentationEmail);
|
||||
},
|
||||
onIFrameKeyboardShortcutAction: keyboardShortcutFocusNode != null
|
||||
? onIFrameKeyboardShortcutAction
|
||||
: null,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
}
|
||||
@@ -91,6 +95,9 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
||||
onToggleThreadDetailCollapseExpand: () {
|
||||
toggleThreadDetailCollapseExpand(presentationEmail);
|
||||
},
|
||||
onIFrameKeyboardShortcutAction: keyboardShortcutFocusNode != null
|
||||
? onIFrameKeyboardShortcutAction
|
||||
: null,
|
||||
scrollController: scrollController,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/app_shortcut_manager.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/key_shortcut_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/mail_view_shortcut_action_view_event.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
|
||||
extension HandleMailShortcutActionsExtension on ThreadDetailController {
|
||||
|
||||
void onKeyDownEventAction(KeyEvent event) {
|
||||
final shortcutType = AppShortcutManager.getMailViewActionFromEvent(event);
|
||||
log('$runtimeType::onKeyDownEventAction:🔥Shortcut triggered: $shortcutType');
|
||||
if (shortcutType == null) return;
|
||||
handleMailShortcutAction(shortcutType);
|
||||
}
|
||||
|
||||
PresentationEmail? get currentExpandedEmail {
|
||||
final expandedEmailId = currentExpandedEmailId.value;
|
||||
if (expandedEmailId == null) return null;
|
||||
|
||||
final expandedPresentationEmail = emailIdsPresentation[expandedEmailId];
|
||||
if (expandedPresentationEmail == null) return null;
|
||||
|
||||
return expandedPresentationEmail;
|
||||
}
|
||||
|
||||
void onKeyboardShortcutInit() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
keyboardShortcutFocusNode = FocusNode();
|
||||
keyboardShortcutFocusNode?.addListener(() {
|
||||
log('$runtimeType::onInit:keyboardShortcutFocusNode: ${keyboardShortcutFocusNode?.hasFocus}');
|
||||
});
|
||||
shortcutActionEventController =
|
||||
StreamController<MailViewShortcutActionViewEvent>();
|
||||
shortcutActionEventSubscription = shortcutActionEventController
|
||||
?.stream
|
||||
.debounceTime(const Duration(milliseconds: 300))
|
||||
.listen(_onShortcutActionViewEvent);
|
||||
}
|
||||
}
|
||||
|
||||
void _onShortcutActionViewEvent(MailViewShortcutActionViewEvent event) {
|
||||
mailboxDashBoardController.dispatchEmailUIAction(
|
||||
TriggerMailViewKeyboardShortcutAction(
|
||||
event.actionType,
|
||||
event.presentationEmail,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void onKeyboardShortcutDispose() {
|
||||
if (PlatformInfo.isWeb) {
|
||||
keyboardShortcutFocusNode?.dispose();
|
||||
shortcutActionEventSubscription?.cancel();
|
||||
shortcutActionEventController?.close();
|
||||
}
|
||||
}
|
||||
|
||||
void handleMailShortcutAction(MailViewActionShortcutType shortcutType) {
|
||||
log('$runtimeType::handleMailShortcutAction:🔥ShortcutType: $shortcutType');
|
||||
final expandedEmail = currentExpandedEmail;
|
||||
|
||||
if (expandedEmail == null) return;
|
||||
|
||||
final emailActionType = shortcutType.getEmailActionType(
|
||||
currentEmail: expandedEmail,
|
||||
ownerEmailAddress: mailboxDashBoardController.ownEmailAddress.value,
|
||||
);
|
||||
log('$runtimeType::onKeyDownEventAction:🔥EmailActionType: $emailActionType');
|
||||
if (emailActionType == null) return;
|
||||
|
||||
shortcutActionEventController?.add(
|
||||
MailViewShortcutActionViewEvent(emailActionType, expandedEmail),
|
||||
);
|
||||
}
|
||||
|
||||
void clearMailShortcutFocus() {
|
||||
if (keyboardShortcutFocusNode?.hasFocus == true) {
|
||||
keyboardShortcutFocusNode?.unfocus();
|
||||
}
|
||||
}
|
||||
|
||||
void refocusMailShortcutFocus() {
|
||||
if (keyboardShortcutFocusNode?.hasFocus == false) {
|
||||
keyboardShortcutFocusNode?.requestFocus();
|
||||
}
|
||||
}
|
||||
|
||||
void onIFrameKeyboardShortcutAction(KeyShortcut keyShortcut) {
|
||||
log('$runtimeType::onIFrameKeyboardShortcutAction: ${keyShortcut.toString()}');
|
||||
final shortcutType = keyShortcut.mailViewActionShortcutType;
|
||||
log('$runtimeType::onIFrameKeyboardShortcutAction:🔥MailViewActionShortcutType: $shortcutType');
|
||||
if (shortcutType == null) return;
|
||||
handleMailShortcutAction(shortcutType);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -5,7 +5,6 @@ import 'package:model/email/read_actions.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collapse_expand.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
||||
void handleMarkMultipleEmailsReadSuccess(
|
||||
@@ -17,7 +16,7 @@ extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
||||
if (readActions == ReadActions.markAsRead) return;
|
||||
|
||||
if (emailsInThreadDetailInfo.length == 1) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
closeThreadDetailAction();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -14,7 +14,6 @@ import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
void handleRefreshThreadDetailAction(
|
||||
@@ -86,7 +85,7 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||
);
|
||||
|
||||
if (afterRefreshedEmailIds.isEmpty) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
closeThreadDetailAction();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
|
||||
import 'package:core/presentation/views/shortcut/key_shortcut.dart';
|
||||
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart';
|
||||
|
||||
extension KeyShortcutExtension on KeyShortcut {
|
||||
MailViewActionShortcutType? get mailViewActionShortcutType {
|
||||
if (matches('r')) {
|
||||
return MailViewActionShortcutType.reply;
|
||||
} else if (matches('r', shift: true)) {
|
||||
return MailViewActionShortcutType.replyAll;
|
||||
} else if (matches('f')) {
|
||||
return MailViewActionShortcutType.forward;
|
||||
} else if (matches('u')) {
|
||||
return MailViewActionShortcutType.markAsUnread;
|
||||
} else if (matches('n')) {
|
||||
return MailViewActionShortcutType.newMessage;
|
||||
} else if (matches('backspace') || matches('delete')) {
|
||||
return MailViewActionShortcutType.delete;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -5,7 +5,6 @@ import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_sta
|
||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
|
||||
extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
||||
void markCollapsedEmailReadSuccess(MarkAsEmailReadSuccess success) {
|
||||
@@ -41,7 +40,7 @@ extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
||||
) {
|
||||
if (success.readActions == ReadActions.markAsRead) return;
|
||||
if (emailIdsPresentation.length == 1) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
closeThreadDetailAction();
|
||||
} else {
|
||||
emailIdsPresentation[success.emailId] = emailIdsPresentation
|
||||
[success.emailId]
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
||||
case EmailActionType.deletePermanently:
|
||||
mailboxDashBoardController.permanentDeleteMultipleEmailInThreadDetail(
|
||||
emailsInThreadDetailInfo,
|
||||
onConfirm: () => closeThreadDetailAction(currentContext),
|
||||
onConfirm: closeThreadDetailAction,
|
||||
);
|
||||
break;
|
||||
case EmailActionType.moveToMailbox:
|
||||
@@ -183,7 +183,7 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
||||
}
|
||||
|
||||
void _moveToMailbox(MailboxId mailboxId, EmailActionType emailActionType) {
|
||||
closeThreadDetailAction(currentContext);
|
||||
closeThreadDetailAction();
|
||||
mailboxDashBoardController.moveMultipleEmailInThreadDetail(
|
||||
emailsInThreadDetailInfo,
|
||||
destinationMailboxId: mailboxId,
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
|
||||
BuildContext? context,
|
||||
) {
|
||||
if (selectedEmail?.id == null) {
|
||||
closeThreadDetailAction(context);
|
||||
closeThreadDetailAction();
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import 'package:core/presentation/state/success.dart';
|
||||
import 'package:model/email/email_action_type.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
|
||||
class MailViewShortcutActionViewEvent extends ViewEvent {
|
||||
final EmailActionType actionType;
|
||||
final PresentationEmail presentationEmail;
|
||||
|
||||
MailViewShortcutActionViewEvent(this.actionType, this.presentationEmail);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [actionType, presentationEmail];
|
||||
}
|
||||
@@ -50,9 +50,11 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/hand
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mark_multiple_emails_read_success.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/model/mail_view_shortcut_action_view_event.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||
@@ -124,6 +126,9 @@ class ThreadDetailController extends BaseController {
|
||||
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
||||
bool loadThreadOnThreadChanged = false;
|
||||
bool isDisplayAllAttachments = false;
|
||||
FocusNode? keyboardShortcutFocusNode;
|
||||
StreamController<MailViewShortcutActionViewEvent>? shortcutActionEventController;
|
||||
StreamSubscription<MailViewShortcutActionViewEvent>? shortcutActionEventSubscription;
|
||||
|
||||
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
||||
Session? get session => mailboxDashBoardController.sessionCurrent;
|
||||
@@ -197,6 +202,10 @@ class ThreadDetailController extends BaseController {
|
||||
threadId: action.threadId,
|
||||
isSentMailbox: action.isSentMailbox,
|
||||
);
|
||||
} else if (action is ReclaimMailViewKeyboardShortcutFocusAction) {
|
||||
refocusMailShortcutFocus();
|
||||
} else if (action is ClearMailViewKeyboardShortcutFocusAction) {
|
||||
clearMailShortcutFocus();
|
||||
}
|
||||
// Reset [threadDetailUIAction] to original value
|
||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||
@@ -212,6 +221,8 @@ class ThreadDetailController extends BaseController {
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
onKeyboardShortcutInit();
|
||||
}
|
||||
|
||||
bool _validateLoadThread(ThreadId? threadId) {
|
||||
@@ -295,4 +306,10 @@ class ThreadDetailController extends BaseController {
|
||||
}
|
||||
super.handleFailureViewState(failure);
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
onKeyboardShortcutDispose();
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wrapper.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
|
||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.dart';
|
||||
@@ -17,6 +18,7 @@ import 'package:tmail_ui_user/features/thread_detail/presentation/extension/clos
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_action_status.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_details_email_views.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_detail_action_click.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/on_thread_page_changed.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/parsing_email_opened_properties_extension.dart';
|
||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||
@@ -209,7 +211,7 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
}
|
||||
|
||||
if (controller.responsiveUtils.isDesktop(context)) {
|
||||
return Container(
|
||||
bodyWidget = Container(
|
||||
decoration: const BoxDecoration(
|
||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||
color: Colors.white,
|
||||
@@ -219,7 +221,17 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
||||
child: bodyWidget,
|
||||
);
|
||||
} else {
|
||||
return ColoredBox(color: Colors.white, child: bodyWidget);
|
||||
bodyWidget = ColoredBox(color: Colors.white, child: bodyWidget);
|
||||
}
|
||||
|
||||
if (PlatformInfo.isWeb && controller.keyboardShortcutFocusNode != null) {
|
||||
return KeyboardHandlerWrapper(
|
||||
onKeyDownEventAction: controller.onKeyDownEventAction,
|
||||
focusNode: controller.keyboardShortcutFocusNode!,
|
||||
child: bodyWidget,
|
||||
);
|
||||
} else {
|
||||
return bodyWidget;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
final ResponsiveUtils responsiveUtils;
|
||||
final ImagePaths imagePaths;
|
||||
final bool isSearchRunning;
|
||||
final void Function(BuildContext context) closeThreadDetailAction;
|
||||
final VoidCallback closeThreadDetailAction;
|
||||
final bool threadActionReady;
|
||||
final bool threadDetailIsStarred;
|
||||
final bool isThreadDetailEnabled;
|
||||
@@ -99,7 +99,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
builder: (_, constraints) {
|
||||
Widget backButton = EmailViewBackButton(
|
||||
imagePaths: imagePaths,
|
||||
onBackAction: () => closeThreadDetailAction(context),
|
||||
onBackAction: closeThreadDetailAction,
|
||||
mailboxContain: mailboxContain,
|
||||
isSearchActivated: isSearchRunning,
|
||||
maxWidth: constraints.maxWidth,
|
||||
@@ -164,7 +164,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
||||
return PopScope(
|
||||
canPop: false,
|
||||
onPopInvokedWithResult: (_, __) {
|
||||
closeThreadDetailAction(context);
|
||||
closeThreadDetailAction();
|
||||
},
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:model/model.dart';
|
||||
extension ListPresentationEmailExtension on List<PresentationEmail> {
|
||||
bool get isAllEmailRead => every((email) => email.hasRead);
|
||||
|
||||
bool get isAllEmailUnread => every((email) => !email.hasRead);
|
||||
|
||||
bool get isAllEmailStarred => every((email) => email.hasStarred);
|
||||
|
||||
bool get isAllSelectionInActive {
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:model/email/eml_attachment.dart';
|
||||
import 'package:model/email/presentation_email.dart';
|
||||
import 'package:model/extensions/email_address_extension.dart';
|
||||
import 'package:model/extensions/list_email_address_extension.dart';
|
||||
import 'package:model/extensions/presentation_mailbox_extension.dart';
|
||||
import 'package:model/extensions/utc_date_extension.dart';
|
||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||
import 'package:model/mailbox/select_mode.dart';
|
||||
@@ -207,4 +208,13 @@ extension PresentationEmailExtension on PresentationEmail {
|
||||
keywords?.addAll(newKeywords);
|
||||
keywords?.removeWhere((key, value) => !value);
|
||||
}
|
||||
|
||||
bool get isDeletePermanentlyEnabled {
|
||||
return mailboxContain?.isTrash ?? mailboxContain?.isSpam ?? false;
|
||||
}
|
||||
|
||||
bool isReplyAllEnabled(String ownerEmailAddress) {
|
||||
final countMailAddress = getCountMailAddressWithoutMe(ownerEmailAddress);
|
||||
return countMailAddress > 1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user