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/checkbox_semantics.dart';
|
||||||
export 'presentation/views/semantics/text_field_semantics.dart';
|
export 'presentation/views/semantics/text_field_semantics.dart';
|
||||||
export 'presentation/views/semantics/icon_semantics.dart';
|
export 'presentation/views/semantics/icon_semantics.dart';
|
||||||
|
export 'presentation/views/shortcut/key_shortcut.dart';
|
||||||
|
|
||||||
// Resources
|
// Resources
|
||||||
export 'presentation/resources/assets_paths.dart';
|
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/constants/constants_ui.dart';
|
||||||
import 'package:core/presentation/extensions/color_extension.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/app_logger.dart';
|
||||||
import 'package:core/utils/html/html_interaction.dart';
|
import 'package:core/utils/html/html_interaction.dart';
|
||||||
import 'package:core/utils/html/html_template.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 OnClickHyperLinkAction = Function(Uri?);
|
||||||
typedef OnMailtoClicked = void Function(Uri? uri);
|
typedef OnMailtoClicked = void Function(Uri? uri);
|
||||||
|
typedef OnIFrameKeyboardShortcutAction = void Function(KeyShortcut keyShortcut);
|
||||||
|
|
||||||
class HtmlContentViewerOnWeb extends StatefulWidget {
|
class HtmlContentViewerOnWeb extends StatefulWidget {
|
||||||
|
|
||||||
@@ -30,6 +32,8 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
|||||||
|
|
||||||
final OnClickHyperLinkAction? onClickHyperLinkAction;
|
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
|
// if widthContent is bigger than width of htmlContent, set this to true let widget able to resize to width of htmlContent
|
||||||
final bool allowResizeToDocumentSize;
|
final bool allowResizeToDocumentSize;
|
||||||
|
|
||||||
@@ -66,6 +70,7 @@ class HtmlContentViewerOnWeb extends StatefulWidget {
|
|||||||
this.htmlContentMinWidth = ConstantsUI.htmlContentMinWidth,
|
this.htmlContentMinWidth = ConstantsUI.htmlContentMinWidth,
|
||||||
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
|
this.offsetHtmlContentHeight = ConstantsUI.htmlContentOffsetHeight,
|
||||||
this.viewMaxHeight,
|
this.viewMaxHeight,
|
||||||
|
this.onIFrameKeyboardShortcutAction,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -116,6 +121,9 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
} else if (_isScrollingIsAvailable && _isScrollEndEventTriggered(type)) {
|
} else if (_isScrollingIsAvailable && _isScrollEndEventTriggered(type)) {
|
||||||
_handleIframeOnScrollEndListener(data, widget.scrollController!);
|
_handleIframeOnScrollEndListener(data, widget.scrollController!);
|
||||||
return;
|
return;
|
||||||
|
} else if (_isIframeKeyboardEventTriggered(type)) {
|
||||||
|
_handleOnIFrameKeyboardEvent(data);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data['message'] == iframeOnLoadMessage) {
|
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
|
@override
|
||||||
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
|
void didUpdateWidget(covariant HtmlContentViewerOnWeb oldWidget) {
|
||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
@@ -397,6 +420,8 @@ class _HtmlContentViewerOnWebState extends State<HtmlContentViewerOnWeb>
|
|||||||
viewId: _createdViewId,
|
viewId: _createdViewId,
|
||||||
onScrollChangedEvent: onScrollChangedEvent,
|
onScrollChangedEvent: onScrollChangedEvent,
|
||||||
),
|
),
|
||||||
|
if (widget.onIFrameKeyboardShortcutAction != null)
|
||||||
|
HtmlInteraction.scriptHandleIframeKeyboardListener(_createdViewId),
|
||||||
].join();
|
].join();
|
||||||
|
|
||||||
final htmlTemplate = HtmlUtils.generateHtmlDocument(
|
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 = '''
|
static const String scriptsHandleContentSizeChanged = '''
|
||||||
<script>
|
<script type="text/javascript">
|
||||||
const bodyResizeObserver = new ResizeObserver(entries => {
|
const bodyResizeObserver = new ResizeObserver(entries => {
|
||||||
window.flutter_inappwebview.callHandler('$contentSizeChangedEventJSChannelName', '');
|
window.flutter_inappwebview.callHandler('$contentSizeChangedEventJSChannelName', '');
|
||||||
})
|
})
|
||||||
@@ -70,7 +70,7 @@ class HtmlInteraction {
|
|||||||
''';
|
''';
|
||||||
|
|
||||||
static const String scriptsHandleLazyLoadingBackgroundImage = '''
|
static const String scriptsHandleLazyLoadingBackgroundImage = '''
|
||||||
<script>
|
<script type="text/javascript">
|
||||||
const lazyImages = document.querySelectorAll('[lazy]');
|
const lazyImages = document.querySelectorAll('[lazy]');
|
||||||
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
|
const lazyImageObserver = new IntersectionObserver((entries, observer) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
@@ -378,4 +378,19 @@ class HtmlInteraction {
|
|||||||
</script>
|
</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,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -105,4 +105,14 @@ class OpenAttachmentListAction extends EmailUIAction {
|
|||||||
isDisplayAllAttachments,
|
isDisplayAllAttachments,
|
||||||
screenHeight,
|
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_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/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_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/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/blob_calendar_event.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.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,
|
isDisplayAllAttachments: action.isDisplayAllAttachments,
|
||||||
);
|
);
|
||||||
mailboxDashBoardController.clearEmailUIAction();
|
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) {
|
if (session != null && accountId != null) {
|
||||||
final moveActionRequest = await emailActionReactor.moveToMailbox(
|
final moveActionRequest = await emailActionReactor.moveToMailbox(
|
||||||
session!,
|
session!,
|
||||||
@@ -1267,7 +1274,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
),
|
),
|
||||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
);
|
);
|
||||||
if (!context.mounted || moveActionRequest == null) return;
|
if (moveActionRequest == null) return;
|
||||||
mailboxDashBoardController.moveToMailbox(
|
mailboxDashBoardController.moveToMailbox(
|
||||||
session!,
|
session!,
|
||||||
accountId!,
|
accountId!,
|
||||||
@@ -1275,12 +1282,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
moveActionRequest.emailIdsWithReadStatus,
|
moveActionRequest.emailIdsWithReadStatus,
|
||||||
);
|
);
|
||||||
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
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) {
|
if (session != null && accountId != null) {
|
||||||
final moveActionRequest = emailActionReactor.moveToTrash(
|
final moveActionRequest = emailActionReactor.moveToTrash(
|
||||||
email,
|
email,
|
||||||
@@ -1289,7 +1296,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||||
);
|
);
|
||||||
if (!context.mounted || moveActionRequest == null) return;
|
if (moveActionRequest == null) return;
|
||||||
mailboxDashBoardController.moveToMailbox(
|
mailboxDashBoardController.moveToMailbox(
|
||||||
session!,
|
session!,
|
||||||
accountId!,
|
accountId!,
|
||||||
@@ -1297,12 +1304,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
moveActionRequest.emailIdsWithReadStatus,
|
moveActionRequest.emailIdsWithReadStatus,
|
||||||
);
|
);
|
||||||
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
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) {
|
if (session != null && accountId != null) {
|
||||||
final moveActionRequest = emailActionReactor.moveToSpam(
|
final moveActionRequest = emailActionReactor.moveToSpam(
|
||||||
email,
|
email,
|
||||||
@@ -1311,7 +1318,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||||
);
|
);
|
||||||
if (!context.mounted || moveActionRequest == null) return;
|
if (moveActionRequest == null) return;
|
||||||
mailboxDashBoardController.moveToMailbox(
|
mailboxDashBoardController.moveToMailbox(
|
||||||
session!,
|
session!,
|
||||||
accountId!,
|
accountId!,
|
||||||
@@ -1319,12 +1326,12 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
moveActionRequest.emailIdsWithReadStatus,
|
moveActionRequest.emailIdsWithReadStatus,
|
||||||
);
|
);
|
||||||
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
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) {
|
if (session != null && accountId != null) {
|
||||||
final moveActionRequest = emailActionReactor.unSpam(
|
final moveActionRequest = emailActionReactor.unSpam(
|
||||||
email,
|
email,
|
||||||
@@ -1333,7 +1340,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
|
||||||
);
|
);
|
||||||
if (!context.mounted || moveActionRequest == null) return;
|
if (moveActionRequest == null) return;
|
||||||
mailboxDashBoardController.moveToMailbox(
|
mailboxDashBoardController.moveToMailbox(
|
||||||
session!,
|
session!,
|
||||||
accountId!,
|
accountId!,
|
||||||
@@ -1341,7 +1348,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
moveActionRequest.emailIdsWithReadStatus,
|
moveActionRequest.emailIdsWithReadStatus,
|
||||||
);
|
);
|
||||||
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
||||||
_threadDetailController?.closeThreadDetailAction(context);
|
_threadDetailController?.closeThreadDetailAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1370,7 +1377,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
toastManager.showMessageSuccess(success);
|
toastManager.showMessageSuccess(success);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) {
|
void handleEmailAction(
|
||||||
|
PresentationEmail presentationEmail,
|
||||||
|
EmailActionType actionType,
|
||||||
|
) {
|
||||||
switch(actionType) {
|
switch(actionType) {
|
||||||
case EmailActionType.markAsUnread:
|
case EmailActionType.markAsUnread:
|
||||||
if (session != null && accountId != null) {
|
if (session != null && accountId != null) {
|
||||||
@@ -1403,31 +1413,31 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EmailActionType.moveToMailbox:
|
case EmailActionType.moveToMailbox:
|
||||||
moveToMailbox(context, presentationEmail);
|
moveToMailbox(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.moveToTrash:
|
case EmailActionType.moveToTrash:
|
||||||
moveToTrash(context, presentationEmail);
|
moveToTrash(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.deletePermanently:
|
case EmailActionType.deletePermanently:
|
||||||
deleteEmailPermanently(context, presentationEmail);
|
deleteEmailPermanently(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.moveToSpam:
|
case EmailActionType.moveToSpam:
|
||||||
moveToSpam(context, presentationEmail);
|
moveToSpam(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.unSpam:
|
case EmailActionType.unSpam:
|
||||||
unSpam(context, presentationEmail);
|
unSpam(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.createRule:
|
case EmailActionType.createRule:
|
||||||
quickCreatingRule(context, presentationEmail.from!.first);
|
quickCreatingRule(presentationEmail.from!.first);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.unsubscribe:
|
case EmailActionType.unsubscribe:
|
||||||
_unsubscribeEmail(context, presentationEmail);
|
_unsubscribeEmail(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.archiveMessage:
|
case EmailActionType.archiveMessage:
|
||||||
archiveMessage(context, presentationEmail);
|
archiveMessage(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.printAll:
|
case EmailActionType.printAll:
|
||||||
_printEmail(context, presentationEmail);
|
_printEmail(presentationEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.downloadMessageAsEML:
|
case EmailActionType.downloadMessageAsEML:
|
||||||
_downloadMessageAsEML(presentationEmail);
|
_downloadMessageAsEML(presentationEmail);
|
||||||
@@ -1441,6 +1451,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
case EmailActionType.replyAll:
|
case EmailActionType.replyAll:
|
||||||
case EmailActionType.replyToList:
|
case EmailActionType.replyToList:
|
||||||
case EmailActionType.forward:
|
case EmailActionType.forward:
|
||||||
|
case EmailActionType.compose:
|
||||||
pressEmailAction(actionType, presentationEmail);
|
pressEmailAction(actionType, presentationEmail);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -1488,14 +1499,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void deleteEmailPermanently(BuildContext context, PresentationEmail email) {
|
void deleteEmailPermanently(PresentationEmail email) {
|
||||||
emailActionReactor.deleteEmailPermanently(
|
emailActionReactor.deleteEmailPermanently(
|
||||||
email,
|
email,
|
||||||
onDeleteEmailRequest: (email) {
|
onDeleteEmailRequest: (email) {
|
||||||
popBack();
|
popBack();
|
||||||
mailboxDashBoardController.deleteEmailPermanently(email);
|
mailboxDashBoardController.deleteEmailPermanently(email);
|
||||||
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
if (_threadDetailController?.emailIdsPresentation.length == 1) {
|
||||||
_threadDetailController?.closeThreadDetailAction(context);
|
_threadDetailController?.closeThreadDetailAction();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responsiveUtils: responsiveUtils,
|
responsiveUtils: responsiveUtils,
|
||||||
@@ -1689,7 +1700,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void quickCreatingRule(BuildContext context, EmailAddress emailAddress) async {
|
void quickCreatingRule(EmailAddress emailAddress) {
|
||||||
popBack();
|
popBack();
|
||||||
|
|
||||||
if (accountId != null && session != null) {
|
if (accountId != null && session != null) {
|
||||||
@@ -1782,7 +1793,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
|
|||||||
threadDetailController?.isDisplayAllAttachments = false;
|
threadDetailController?.isDisplayAllAttachments = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) {
|
void _unsubscribeEmail(PresentationEmail presentationEmail) {
|
||||||
emailActionReactor.unsubscribeEmail(
|
emailActionReactor.unsubscribeEmail(
|
||||||
presentationEmail,
|
presentationEmail,
|
||||||
emailUnsubscribe: emailUnsubscribe.value,
|
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(
|
emailActionReactor.archiveMessage(
|
||||||
email,
|
email,
|
||||||
onArchiveEmailRequest: (presentationEmail) {
|
onArchiveEmailRequest: mailboxDashBoardController.archiveMessage,
|
||||||
mailboxDashBoardController.archiveMessage(context, presentationEmail);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _printEmail(BuildContext context, PresentationEmail email) {
|
void _printEmail(PresentationEmail email) {
|
||||||
if (_printEmailButtonState == ButtonState.disabled) {
|
if (_printEmailButtonState == ButtonState.disabled) {
|
||||||
log('SingleEmailController::_printEmail: Print email started');
|
log('SingleEmailController::_printEmail: Print email started');
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
this.isFirstEmailInThreadDetail = false,
|
this.isFirstEmailInThreadDetail = false,
|
||||||
this.threadSubject,
|
this.threadSubject,
|
||||||
this.onToggleThreadDetailCollapseExpand,
|
this.onToggleThreadDetailCollapseExpand,
|
||||||
|
this.onIFrameKeyboardShortcutAction,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
final bool isFirstEmailInThreadDetail;
|
final bool isFirstEmailInThreadDetail;
|
||||||
final String? threadSubject;
|
final String? threadSubject;
|
||||||
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
final VoidCallback? onToggleThreadDetailCollapseExpand;
|
||||||
|
final OnIFrameKeyboardShortcutAction? onIFrameKeyboardShortcutAction;
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -83,7 +85,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
mailboxContain: _getMailboxContain(currentEmail),
|
mailboxContain: _getMailboxContain(currentEmail),
|
||||||
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
|
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
|
||||||
onBackAction: () => controller.closeEmailView(context: context),
|
onBackAction: () => controller.closeEmailView(context: context),
|
||||||
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
|
onEmailActionClick: controller.handleEmailAction,
|
||||||
onMoreActionClick: (presentationEmail, position) {
|
onMoreActionClick: (presentationEmail, position) {
|
||||||
return controller.emailActionReactor.handleMoreEmailAction(
|
return controller.emailActionReactor.handleMoreEmailAction(
|
||||||
mailboxContain: controller.getMailboxContain(presentationEmail),
|
mailboxContain: controller.getMailboxContain(presentationEmail),
|
||||||
@@ -92,7 +94,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
ownEmailAddress: controller.ownEmailAddress,
|
ownEmailAddress: controller.ownEmailAddress,
|
||||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
handleEmailAction: controller.handleEmailAction,
|
||||||
additionalActions: [],
|
additionalActions: [],
|
||||||
emailIsRead: presentationEmail.hasRead,
|
emailIsRead: presentationEmail.hasRead,
|
||||||
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
|
openBottomSheetContextMenu: controller.mailboxDashBoardController.openBottomSheetContextMenu,
|
||||||
@@ -256,7 +258,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
maxBodyHeight: bodyConstraints.maxHeight,
|
maxBodyHeight: bodyConstraints.maxHeight,
|
||||||
isInsideThreadCollapsed: false,
|
isInsideThreadCollapsed: false,
|
||||||
openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
|
openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
|
||||||
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
|
onEmailActionClick: controller.handleEmailAction,
|
||||||
isInsideThreadDetailView: isInsideThreadDetailView,
|
isInsideThreadDetailView: isInsideThreadDetailView,
|
||||||
emailLoaded: controller.currentEmailLoaded.value,
|
emailLoaded: controller.currentEmailLoaded.value,
|
||||||
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
|
||||||
@@ -266,7 +268,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
ownEmailAddress: controller.ownEmailAddress,
|
ownEmailAddress: controller.ownEmailAddress,
|
||||||
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
|
handleEmailAction: controller.handleEmailAction,
|
||||||
additionalActions: [
|
additionalActions: [
|
||||||
EmailActionType.reply,
|
EmailActionType.reply,
|
||||||
EmailActionType.forward,
|
EmailActionType.forward,
|
||||||
@@ -360,6 +362,7 @@ class EmailView extends GetWidget<SingleEmailController> {
|
|||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
enableQuoteToggle: true,
|
enableQuoteToggle: true,
|
||||||
fontSize: isMobileResponsive ? 16 : 14,
|
fontSize: isMobileResponsive ? 16 : 14,
|
||||||
|
onIFrameKeyboardShortcutAction: onIFrameKeyboardShortcutAction,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (PlatformInfo.isIOS) {
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -155,4 +155,6 @@ class SynchronizeEmailSortOrderAction extends DashBoardAction {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [emailSortOrderType];
|
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_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_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_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_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/handle_store_email_sort_order_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/initialize_app_language.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;
|
int minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete;
|
||||||
EmailSortOrderType currentSortOrder = SearchEmailFilter.defaultSortOrder;
|
EmailSortOrderType currentSortOrder = SearchEmailFilter.defaultSortOrder;
|
||||||
PaywallController? paywallController;
|
PaywallController? paywallController;
|
||||||
|
Worker? advancedSearchVisibleWorker;
|
||||||
|
Worker? searchInputFocusWorker;
|
||||||
|
|
||||||
final StreamController<Either<Failure, Success>> _progressStateController =
|
final StreamController<Either<Failure, Success>> _progressStateController =
|
||||||
StreamController<Either<Failure, Success>>.broadcast();
|
StreamController<Either<Failure, Success>>.broadcast();
|
||||||
@@ -376,6 +379,7 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
listSearchFilterScrollController = ScrollController();
|
listSearchFilterScrollController = ScrollController();
|
||||||
twakeAppManager.setExecutingBeforeReconnect(false);
|
twakeAppManager.setExecutingBeforeReconnect(false);
|
||||||
|
registerReactiveObxVariableListener();
|
||||||
}
|
}
|
||||||
if (PlatformInfo.isIOS) {
|
if (PlatformInfo.isIOS) {
|
||||||
_registerPendingCurrentEmailIdInNotification();
|
_registerPendingCurrentEmailIdInNotification();
|
||||||
@@ -3039,18 +3043,17 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void archiveMessage(BuildContext context, PresentationEmail email) {
|
void archiveMessage(PresentationEmail email) {
|
||||||
final mailboxContain = email.findMailboxContain(mapMailboxById);
|
final mailboxContain = email.findMailboxContain(mapMailboxById);
|
||||||
if (mailboxContain != null) {
|
if (mailboxContain != null) {
|
||||||
final archiveMailboxId = getMailboxIdByRole(PresentationMailbox.roleArchive);
|
final archiveMailboxId = getMailboxIdByRole(PresentationMailbox.roleArchive);
|
||||||
final archiveMailboxPath = mapMailboxById[archiveMailboxId]?.getDisplayName(context);
|
|
||||||
if (archiveMailboxId != null) {
|
if (archiveMailboxId != null) {
|
||||||
final moveToArchiveMailboxRequest = MoveToMailboxRequest(
|
final moveToArchiveMailboxRequest = MoveToMailboxRequest(
|
||||||
{mailboxContain.id: [email.id!]},
|
{mailboxContain.id: [email.id!]},
|
||||||
archiveMailboxId,
|
archiveMailboxId,
|
||||||
MoveAction.moving,
|
MoveAction.moving,
|
||||||
EmailActionType.moveToMailbox,
|
EmailActionType.moveToMailbox,
|
||||||
destinationPath: archiveMailboxPath
|
destinationPath: getMailboxNameById(archiveMailboxId),
|
||||||
);
|
);
|
||||||
moveToMailbox(
|
moveToMailbox(
|
||||||
sessionCurrent!,
|
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 {
|
void _handleRestoreDeletedMessageSuccess(EmailRecoveryActionId emailRecoveryActionId) async {
|
||||||
log('MailboxDashBoardController::_handleRestoreDeletedMessageSuccess():emailRecoveryActionId: $emailRecoveryActionId');
|
log('MailboxDashBoardController::_handleRestoreDeletedMessageSuccess():emailRecoveryActionId: $emailRecoveryActionId');
|
||||||
_getRestoredDeletedMessage(emailRecoveryActionId);
|
_getRestoredDeletedMessage(emailRecoveryActionId);
|
||||||
@@ -3271,10 +3283,14 @@ class MailboxDashBoardController extends ReloadableController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get isEmailOpened =>
|
||||||
|
dashboardRoute.value == DashboardRoutes.threadDetailed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
if (PlatformInfo.isWeb) {
|
if (PlatformInfo.isWeb) {
|
||||||
listSearchFilterScrollController?.dispose();
|
listSearchFilterScrollController?.dispose();
|
||||||
|
disposeReactiveObxVariableListener();
|
||||||
}
|
}
|
||||||
if (PlatformInfo.isIOS) {
|
if (PlatformInfo.isIOS) {
|
||||||
_iosNotificationManager?.dispose();
|
_iosNotificationManager?.dispose();
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
final listFilterOnSuggestionForm = RxList<QuickSearchFilter>();
|
final listFilterOnSuggestionForm = RxList<QuickSearchFilter>();
|
||||||
final simpleSearchIsActivated = RxBool(false);
|
final simpleSearchIsActivated = RxBool(false);
|
||||||
final advancedSearchIsActivated = RxBool(false);
|
final advancedSearchIsActivated = RxBool(false);
|
||||||
|
final isSearchInputFocused = RxBool(false);
|
||||||
|
|
||||||
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
SearchQuery? get searchQuery => searchEmailFilter.value.text;
|
||||||
|
|
||||||
@@ -59,6 +60,17 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
this._getAllRecentSearchLatestInteractor,
|
this._getAllRecentSearchLatestInteractor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
searchFocus.addListener(_onSearchFocusChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onSearchFocusChanged() {
|
||||||
|
log('SearchController::_onSearchFocusChanged: ${searchFocus.hasFocus}');
|
||||||
|
isSearchInputFocused.value = searchFocus.hasFocus;
|
||||||
|
}
|
||||||
|
|
||||||
void openAdvanceSearch() {
|
void openAdvanceSearch() {
|
||||||
isAdvancedSearchViewOpen.value = true;
|
isAdvancedSearchViewOpen.value = true;
|
||||||
}
|
}
|
||||||
@@ -322,6 +334,7 @@ class SearchController extends BaseController with DateRangePickerMixin {
|
|||||||
@override
|
@override
|
||||||
void onClose() {
|
void onClose() {
|
||||||
searchInputController.dispose();
|
searchInputController.dispose();
|
||||||
|
searchFocus.removeListener(_onSearchFocusChanged);
|
||||||
searchFocus.dispose();
|
searchFocus.dispose();
|
||||||
super.onClose();
|
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/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/extensions/composer_arguments_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.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/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/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/app_routes.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
|
|
||||||
@@ -111,6 +113,9 @@ extension OpenAndCloseComposerExtension on MailboxDashBoardController {
|
|||||||
twakeAppManager.setHasComposer(false);
|
twakeAppManager.setHasComposer(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dispatchThreadDetailUIAction(ReclaimMailViewKeyboardShortcutFocusAction());
|
||||||
|
dispatchAction(ReclaimMailListKeyboardShortcutFocusAction());
|
||||||
|
|
||||||
_handleResultAfterCloseComposer(result);
|
_handleResultAfterCloseComposer(result);
|
||||||
|
|
||||||
if (composerId != null) {
|
if (composerId != null) {
|
||||||
|
|||||||
@@ -68,137 +68,141 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
ResponsiveWidget(
|
ResponsiveWidget(
|
||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
desktop: Scaffold(
|
desktop: Scaffold(
|
||||||
body: GestureDetector(
|
backgroundColor: AppColor.colorBgDesktop,
|
||||||
onTap: () => FocusScope.of(context).unfocus(),
|
body: Column(
|
||||||
child: Container(
|
children: [
|
||||||
color: AppColor.colorBgDesktop,
|
FutureBuilder(
|
||||||
child: Column(children: [
|
future: CozyConfigManager().isInsideCozy,
|
||||||
FutureBuilder(
|
builder: (context, snapshot) {
|
||||||
future: CozyConfigManager().isInsideCozy,
|
if (snapshot.data == true) {
|
||||||
builder: (context, snapshot) {
|
return const SizedBox.shrink();
|
||||||
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(() {
|
return NavigationBarWidget(
|
||||||
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(
|
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
onTapAction: () =>
|
accountId: accountId,
|
||||||
controller.openComposer(ComposerArguments()),
|
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(
|
Expanded(
|
||||||
width: ResponsiveUtils.defaultSizeMenu,
|
child: Column(
|
||||||
child: Obx(() {
|
children: [
|
||||||
if (controller.searchMailboxActivated.isTrue) {
|
const SizedBox(height: 16),
|
||||||
return const SearchMailboxView(
|
FutureBuilder(
|
||||||
backgroundColor: AppColor.colorBgDesktop
|
future: CozyConfigManager().isInsideCozy,
|
||||||
);
|
builder: (context, snapshot) {
|
||||||
} else {
|
if (snapshot.data != true) {
|
||||||
return MailboxView();
|
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(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(bottom: 16),
|
padding: const EdgeInsets.only(bottom: 16),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: MediaQuery.sizeOf(context).width * 0.4,
|
width: MediaQuery.sizeOf(context).width * 0.4,
|
||||||
height: 44,
|
height: 44,
|
||||||
child: SearchInputFormWidget(
|
child: SearchInputFormWidget(
|
||||||
fontSize: 15,
|
fontSize: 15,
|
||||||
contentPadding: const EdgeInsets.only(bottom: 4),
|
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,
|
|
||||||
),
|
),
|
||||||
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,
|
||||||
Obx(() => RecoverDeletedMessageLoadingBannerWidget(
|
responsiveUtils: controller.responsiveUtils,
|
||||||
isLoading: controller.isRecoveringDeletedMessage.value,
|
)),
|
||||||
horizontalLoadingWidget: horizontalLoadingWidget,
|
Obx(() => MarkMailboxAsReadLoadingBanner(
|
||||||
responsiveUtils: controller.responsiveUtils,
|
viewState: controller.viewStateMailboxActionProgress.value,
|
||||||
)),
|
)),
|
||||||
Obx(() => MarkMailboxAsReadLoadingBanner(
|
Obx(() {
|
||||||
viewState: controller.viewStateMailboxActionProgress.value,
|
final spamController = controller.spamReportController;
|
||||||
)),
|
|
||||||
Obx(() {
|
|
||||||
final spamController = controller.spamReportController;
|
|
||||||
|
|
||||||
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled;
|
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled;
|
||||||
|
|
||||||
final isSpamFolderSelected = controller
|
final isSpamFolderSelected = controller
|
||||||
.selectedMailbox
|
.selectedMailbox
|
||||||
.value
|
.value
|
||||||
?.isSpam == true;
|
?.isSpam == true;
|
||||||
|
|
||||||
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
|
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
|
||||||
|
|
||||||
final isEmailOpened = controller.dashboardRoute.value == DashboardRoutes.threadDetailed;
|
final isEmailOpened = controller.dashboardRoute.value == DashboardRoutes.threadDetailed;
|
||||||
|
|
||||||
@@ -209,91 +213,96 @@ class MailboxDashBoardView extends BaseMailboxDashBoardView {
|
|||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
|
|
||||||
return ReportMessageBanner(
|
return ReportMessageBanner(
|
||||||
imagePaths: controller.imagePaths,
|
imagePaths: controller.imagePaths,
|
||||||
message: AppLocalizations
|
message: AppLocalizations
|
||||||
.of(context)
|
.of(context)
|
||||||
.countMessageInSpam(
|
.countMessageInSpam(
|
||||||
spamController.numberOfUnreadSpamEmails,
|
spamController.numberOfUnreadSpamEmails,
|
||||||
),
|
),
|
||||||
positiveName: AppLocalizations.of(context).view,
|
positiveName: AppLocalizations.of(context).view,
|
||||||
isDesktop: controller
|
isDesktop: controller
|
||||||
.responsiveUtils
|
.responsiveUtils
|
||||||
.isDesktop(context),
|
.isDesktop(context),
|
||||||
margin: SpamReportBannerWebStyles.bannerMargin,
|
margin: SpamReportBannerWebStyles.bannerMargin,
|
||||||
onPositiveAction: spamController.openMailbox,
|
onPositiveAction: spamController.openMailbox,
|
||||||
onNegativeAction: () =>
|
onNegativeAction: () =>
|
||||||
spamController.dismissSpamReportAction(context),
|
spamController.dismissSpamReportAction(context),
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
QuotasBannerWidget(),
|
QuotasBannerWidget(),
|
||||||
_buildVacationNotificationMessage(context),
|
_buildVacationNotificationMessage(context),
|
||||||
Obx(() {
|
Obx(() {
|
||||||
final selectedMailbox = controller
|
final selectedMailbox = controller
|
||||||
.selectedMailbox
|
.selectedMailbox
|
||||||
.value;
|
.value;
|
||||||
|
|
||||||
bool showTrashBanner = controller
|
bool showTrashBanner = controller
|
||||||
.isEmptyTrashBannerEnabledOnWeb(
|
.isEmptyTrashBannerEnabledOnWeb(
|
||||||
context,
|
context,
|
||||||
selectedMailbox,
|
selectedMailbox,
|
||||||
);
|
);
|
||||||
bool showSpamBanner = controller
|
bool showSpamBanner = controller
|
||||||
.isEmptySpamBannerEnabledOnWeb(
|
.isEmptySpamBannerEnabledOnWeb(
|
||||||
context,
|
context,
|
||||||
selectedMailbox,
|
selectedMailbox,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (showTrashBanner) {
|
if (showTrashBanner) {
|
||||||
return CleanMessagesBanner(
|
return CleanMessagesBanner(
|
||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
message: AppLocalizations
|
message: AppLocalizations
|
||||||
.of(context)
|
.of(context)
|
||||||
.message_delete_all_email_in_trash_button,
|
.message_delete_all_email_in_trash_button,
|
||||||
positiveAction: AppLocalizations
|
positiveAction: AppLocalizations
|
||||||
.of(context)
|
.of(context)
|
||||||
.empty_trash_now,
|
.empty_trash_now,
|
||||||
onPositiveAction: controller.emptyTrashAction,
|
onPositiveAction: controller.emptyTrashAction,
|
||||||
margin: const EdgeInsetsDirectional.only(
|
margin: const EdgeInsetsDirectional.only(
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
end: 16,
|
end: 16,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else if (showSpamBanner) {
|
} else if (showSpamBanner) {
|
||||||
return CleanMessagesBanner(
|
return CleanMessagesBanner(
|
||||||
responsiveUtils: controller.responsiveUtils,
|
responsiveUtils: controller.responsiveUtils,
|
||||||
message: AppLocalizations
|
message: AppLocalizations
|
||||||
.of(context)
|
.of(context)
|
||||||
.bannerDeleteAllSpamEmailsMessage,
|
.bannerDeleteAllSpamEmailsMessage,
|
||||||
positiveAction: AppLocalizations
|
positiveAction: AppLocalizations
|
||||||
.of(context)
|
.of(context)
|
||||||
.deleteAllSpamEmailsNow,
|
.deleteAllSpamEmailsNow,
|
||||||
onPositiveAction: () =>
|
onPositiveAction: () =>
|
||||||
controller.openDialogEmptySpamFolder(context),
|
controller.openDialogEmptySpamFolder(context),
|
||||||
margin: const EdgeInsetsDirectional.only(
|
margin: const EdgeInsetsDirectional.only(
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
end: 16,
|
end: 16,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
_buildListButtonQuickSearchFilter(context),
|
_buildListButtonQuickSearchFilter(context),
|
||||||
Expanded(child: Obx(() {
|
Expanded(
|
||||||
switch(controller.dashboardRoute.value) {
|
child: Obx(() {
|
||||||
case DashboardRoutes.thread:
|
switch(controller.dashboardRoute.value) {
|
||||||
return _buildThreadViewForWebDesktop(context);
|
case DashboardRoutes.thread:
|
||||||
case DashboardRoutes.threadDetailed:
|
return _buildThreadViewForWebDesktop(context);
|
||||||
return const ThreadDetailView();
|
case DashboardRoutes.threadDetailed:
|
||||||
default:
|
return const ThreadDetailView();
|
||||||
return const SizedBox.shrink();
|
default:
|
||||||
}
|
return const SizedBox.shrink();
|
||||||
}))
|
}
|
||||||
]))
|
}),
|
||||||
]))
|
)
|
||||||
]),
|
],
|
||||||
),
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
tabletLarge: Obx(() {
|
tabletLarge: Obx(() {
|
||||||
|
|||||||
@@ -341,8 +341,8 @@ mixin EmailActionController {
|
|||||||
AppUtils.launchLink(email.routeWebAsString);
|
AppUtils.launchLink(email.routeWebAsString);
|
||||||
}
|
}
|
||||||
|
|
||||||
void archiveMessage(BuildContext context, PresentationEmail email) {
|
void archiveMessage(PresentationEmail email) {
|
||||||
mailboxDashBoardController.archiveMessage(context, email);
|
mailboxDashBoardController.archiveMessage(email);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasArchiveMailbox() {
|
bool hasArchiveMailbox() {
|
||||||
|
|||||||
@@ -1198,9 +1198,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
openEmailInNewTabAction(selectedEmail);
|
openEmailInNewTabAction(selectedEmail);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.archiveMessage:
|
case EmailActionType.archiveMessage:
|
||||||
if (currentContext != null) {
|
archiveMessage(selectedEmail);
|
||||||
archiveMessage(currentContext!, selectedEmail);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case EmailActionType.editAsNewEmail:
|
case EmailActionType.editAsNewEmail:
|
||||||
editAsNewEmail(selectedEmail);
|
editAsNewEmail(selectedEmail);
|
||||||
@@ -1376,7 +1374,7 @@ class ThreadController extends BaseController with EmailActionController {
|
|||||||
ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread;
|
ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread;
|
||||||
markAsEmailRead(email, readActions, MarkReadAction.swipeOnThread);
|
markAsEmailRead(email, readActions, MarkReadAction.swipeOnThread);
|
||||||
} else if (direction == DismissDirection.endToStart) {
|
} else if (direction == DismissDirection.endToStart) {
|
||||||
archiveMessage(context, email);
|
archiveMessage(email);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,4 +51,8 @@ class LoadThreadDetailAfterSelectedEmailAction extends ThreadDetailUIAction {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [threadId, isSentMailbox];
|
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/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/mailbox_dashboard/presentation/model/dashboard_routes.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
|
||||||
|
|
||||||
extension CloseThreadDetailAction on ThreadDetailController {
|
extension CloseThreadDetailAction on ThreadDetailController {
|
||||||
void closeThreadDetailAction(BuildContext? context) {
|
void closeThreadDetailAction() {
|
||||||
if (isSearchRunning) {
|
if (isSearchRunning) {
|
||||||
mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail);
|
mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
+9
-2
@@ -1,12 +1,13 @@
|
|||||||
import 'package:core/utils/platform_info.dart';
|
import 'package:core/utils/platform_info.dart';
|
||||||
import 'package:flutter/material.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:model/extensions/presentation_email_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/email_view.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/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_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_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/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/thread_detail_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.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: () {
|
onToggleThreadDetailCollapseExpand: () {
|
||||||
toggleThreadDetailCollapseExpand(presentationEmail);
|
toggleThreadDetailCollapseExpand(presentationEmail);
|
||||||
},
|
},
|
||||||
|
onIFrameKeyboardShortcutAction: keyboardShortcutFocusNode != null
|
||||||
|
? onIFrameKeyboardShortcutAction
|
||||||
|
: null,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -91,6 +95,9 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
|
|||||||
onToggleThreadDetailCollapseExpand: () {
|
onToggleThreadDetailCollapseExpand: () {
|
||||||
toggleThreadDetailCollapseExpand(presentationEmail);
|
toggleThreadDetailCollapseExpand(presentationEmail);
|
||||||
},
|
},
|
||||||
|
onIFrameKeyboardShortcutAction: keyboardShortcutFocusNode != null
|
||||||
|
? onIFrameKeyboardShortcutAction
|
||||||
|
: null,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
);
|
);
|
||||||
}).toList();
|
}).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/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/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/thread_detail_controller.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
|
||||||
|
|
||||||
extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
||||||
void handleMarkMultipleEmailsReadSuccess(
|
void handleMarkMultipleEmailsReadSuccess(
|
||||||
@@ -17,7 +16,7 @@ extension HandleMarkMultipleEmailsReadSuccess on ThreadDetailController {
|
|||||||
if (readActions == ReadActions.markAsRead) return;
|
if (readActions == ReadActions.markAsRead) return;
|
||||||
|
|
||||||
if (emailsInThreadDetailInfo.length == 1) {
|
if (emailsInThreadDetailInfo.length == 1) {
|
||||||
closeThreadDetailAction(currentContext);
|
closeThreadDetailAction();
|
||||||
return;
|
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/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/thread_detail_controller.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/utils/thread_detail_presentation_utils.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 {
|
extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
||||||
void handleRefreshThreadDetailAction(
|
void handleRefreshThreadDetailAction(
|
||||||
@@ -86,7 +85,7 @@ extension HandleRefreshThreadDetailAction on ThreadDetailController {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (afterRefreshedEmailIds.isEmpty) {
|
if (afterRefreshedEmailIds.isEmpty) {
|
||||||
closeThreadDetailAction(currentContext);
|
closeThreadDetailAction();
|
||||||
return;
|
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/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/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/thread_detail_controller.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
|
||||||
|
|
||||||
extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
||||||
void markCollapsedEmailReadSuccess(MarkAsEmailReadSuccess success) {
|
void markCollapsedEmailReadSuccess(MarkAsEmailReadSuccess success) {
|
||||||
@@ -41,7 +40,7 @@ extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
|
|||||||
) {
|
) {
|
||||||
if (success.readActions == ReadActions.markAsRead) return;
|
if (success.readActions == ReadActions.markAsRead) return;
|
||||||
if (emailIdsPresentation.length == 1) {
|
if (emailIdsPresentation.length == 1) {
|
||||||
closeThreadDetailAction(currentContext);
|
closeThreadDetailAction();
|
||||||
} else {
|
} else {
|
||||||
emailIdsPresentation[success.emailId] = emailIdsPresentation
|
emailIdsPresentation[success.emailId] = emailIdsPresentation
|
||||||
[success.emailId]
|
[success.emailId]
|
||||||
|
|||||||
+2
-2
@@ -72,7 +72,7 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
|||||||
case EmailActionType.deletePermanently:
|
case EmailActionType.deletePermanently:
|
||||||
mailboxDashBoardController.permanentDeleteMultipleEmailInThreadDetail(
|
mailboxDashBoardController.permanentDeleteMultipleEmailInThreadDetail(
|
||||||
emailsInThreadDetailInfo,
|
emailsInThreadDetailInfo,
|
||||||
onConfirm: () => closeThreadDetailAction(currentContext),
|
onConfirm: closeThreadDetailAction,
|
||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case EmailActionType.moveToMailbox:
|
case EmailActionType.moveToMailbox:
|
||||||
@@ -183,7 +183,7 @@ extension OnThreadDetailActionClick on ThreadDetailController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _moveToMailbox(MailboxId mailboxId, EmailActionType emailActionType) {
|
void _moveToMailbox(MailboxId mailboxId, EmailActionType emailActionType) {
|
||||||
closeThreadDetailAction(currentContext);
|
closeThreadDetailAction();
|
||||||
mailboxDashBoardController.moveMultipleEmailInThreadDetail(
|
mailboxDashBoardController.moveMultipleEmailInThreadDetail(
|
||||||
emailsInThreadDetailInfo,
|
emailsInThreadDetailInfo,
|
||||||
destinationMailboxId: mailboxId,
|
destinationMailboxId: mailboxId,
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ extension ThreadDetailOnSelectedEmailUpdated on ThreadDetailController {
|
|||||||
BuildContext? context,
|
BuildContext? context,
|
||||||
) {
|
) {
|
||||||
if (selectedEmail?.id == null) {
|
if (selectedEmail?.id == null) {
|
||||||
closeThreadDetailAction(context);
|
closeThreadDetailAction();
|
||||||
return;
|
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_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_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_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/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/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/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/features/thread_detail/presentation/thread_detail_manager.dart';
|
||||||
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
|
||||||
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
|
||||||
@@ -124,6 +126,9 @@ class ThreadDetailController extends BaseController {
|
|||||||
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
|
||||||
bool loadThreadOnThreadChanged = false;
|
bool loadThreadOnThreadChanged = false;
|
||||||
bool isDisplayAllAttachments = false;
|
bool isDisplayAllAttachments = false;
|
||||||
|
FocusNode? keyboardShortcutFocusNode;
|
||||||
|
StreamController<MailViewShortcutActionViewEvent>? shortcutActionEventController;
|
||||||
|
StreamSubscription<MailViewShortcutActionViewEvent>? shortcutActionEventSubscription;
|
||||||
|
|
||||||
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
AccountId? get accountId => mailboxDashBoardController.accountId.value;
|
||||||
Session? get session => mailboxDashBoardController.sessionCurrent;
|
Session? get session => mailboxDashBoardController.sessionCurrent;
|
||||||
@@ -197,6 +202,10 @@ class ThreadDetailController extends BaseController {
|
|||||||
threadId: action.threadId,
|
threadId: action.threadId,
|
||||||
isSentMailbox: action.isSentMailbox,
|
isSentMailbox: action.isSentMailbox,
|
||||||
);
|
);
|
||||||
|
} else if (action is ReclaimMailViewKeyboardShortcutFocusAction) {
|
||||||
|
refocusMailShortcutFocus();
|
||||||
|
} else if (action is ClearMailViewKeyboardShortcutFocusAction) {
|
||||||
|
clearMailShortcutFocus();
|
||||||
}
|
}
|
||||||
// Reset [threadDetailUIAction] to original value
|
// Reset [threadDetailUIAction] to original value
|
||||||
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
mailboxDashBoardController.dispatchThreadDetailUIAction(
|
||||||
@@ -212,6 +221,8 @@ class ThreadDetailController extends BaseController {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onKeyboardShortcutInit();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _validateLoadThread(ThreadId? threadId) {
|
bool _validateLoadThread(ThreadId? threadId) {
|
||||||
@@ -295,4 +306,10 @@ class ThreadDetailController extends BaseController {
|
|||||||
}
|
}
|
||||||
super.handleFailureViewState(failure);
|
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:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:model/mailbox/presentation_mailbox.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/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/styles/email_view_app_bar_widget_styles.dart';
|
||||||
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.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_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/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/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/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/extension/parsing_email_opened_properties_extension.dart';
|
||||||
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.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)) {
|
if (controller.responsiveUtils.isDesktop(context)) {
|
||||||
return Container(
|
bodyWidget = Container(
|
||||||
decoration: const BoxDecoration(
|
decoration: const BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(20)),
|
borderRadius: BorderRadius.all(Radius.circular(20)),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
@@ -219,7 +221,17 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
|
|||||||
child: bodyWidget,
|
child: bodyWidget,
|
||||||
);
|
);
|
||||||
} else {
|
} 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 ResponsiveUtils responsiveUtils;
|
||||||
final ImagePaths imagePaths;
|
final ImagePaths imagePaths;
|
||||||
final bool isSearchRunning;
|
final bool isSearchRunning;
|
||||||
final void Function(BuildContext context) closeThreadDetailAction;
|
final VoidCallback closeThreadDetailAction;
|
||||||
final bool threadActionReady;
|
final bool threadActionReady;
|
||||||
final bool threadDetailIsStarred;
|
final bool threadDetailIsStarred;
|
||||||
final bool isThreadDetailEnabled;
|
final bool isThreadDetailEnabled;
|
||||||
@@ -99,7 +99,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
|||||||
builder: (_, constraints) {
|
builder: (_, constraints) {
|
||||||
Widget backButton = EmailViewBackButton(
|
Widget backButton = EmailViewBackButton(
|
||||||
imagePaths: imagePaths,
|
imagePaths: imagePaths,
|
||||||
onBackAction: () => closeThreadDetailAction(context),
|
onBackAction: closeThreadDetailAction,
|
||||||
mailboxContain: mailboxContain,
|
mailboxContain: mailboxContain,
|
||||||
isSearchActivated: isSearchRunning,
|
isSearchActivated: isSearchRunning,
|
||||||
maxWidth: constraints.maxWidth,
|
maxWidth: constraints.maxWidth,
|
||||||
@@ -164,7 +164,7 @@ class ThreadDetailAppBar extends StatelessWidget {
|
|||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvokedWithResult: (_, __) {
|
onPopInvokedWithResult: (_, __) {
|
||||||
closeThreadDetailAction(context);
|
closeThreadDetailAction();
|
||||||
},
|
},
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import 'package:model/model.dart';
|
|||||||
extension ListPresentationEmailExtension on List<PresentationEmail> {
|
extension ListPresentationEmailExtension on List<PresentationEmail> {
|
||||||
bool get isAllEmailRead => every((email) => email.hasRead);
|
bool get isAllEmailRead => every((email) => email.hasRead);
|
||||||
|
|
||||||
|
bool get isAllEmailUnread => every((email) => !email.hasRead);
|
||||||
|
|
||||||
bool get isAllEmailStarred => every((email) => email.hasStarred);
|
bool get isAllEmailStarred => every((email) => email.hasStarred);
|
||||||
|
|
||||||
bool get isAllSelectionInActive {
|
bool get isAllSelectionInActive {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import 'package:model/email/eml_attachment.dart';
|
|||||||
import 'package:model/email/presentation_email.dart';
|
import 'package:model/email/presentation_email.dart';
|
||||||
import 'package:model/extensions/email_address_extension.dart';
|
import 'package:model/extensions/email_address_extension.dart';
|
||||||
import 'package:model/extensions/list_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/extensions/utc_date_extension.dart';
|
||||||
import 'package:model/mailbox/presentation_mailbox.dart';
|
import 'package:model/mailbox/presentation_mailbox.dart';
|
||||||
import 'package:model/mailbox/select_mode.dart';
|
import 'package:model/mailbox/select_mode.dart';
|
||||||
@@ -207,4 +208,13 @@ extension PresentationEmailExtension on PresentationEmail {
|
|||||||
keywords?.addAll(newKeywords);
|
keywords?.addAll(newKeywords);
|
||||||
keywords?.removeWhere((key, value) => !value);
|
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