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

This commit is contained in:
dab246
2025-07-29 03:25:26 +07:00
committed by Dat H. Pham
parent b2989521d9
commit 226f6520a7
15 changed files with 515 additions and 266 deletions
@@ -1,6 +1,7 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:tmail_ui_user/features/base/extensions/logical_key_set_helper.dart'; import 'package:tmail_ui_user/features/base/extensions/logical_key_set_helper.dart';
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_list_action_shortcut_type.dart';
import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart'; import 'package:tmail_ui_user/features/base/shortcut/mail/mail_view_action_shortcut_type.dart';
class AppShortcutManager { class AppShortcutManager {
@@ -24,4 +25,21 @@ class AppShortcutManager {
return null; return null;
} }
} }
static MailListActionShortcutType? getMailListActionFromEvent(KeyEvent event) {
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
log('AppShortcutManager::getMailListActionFromEvent: Keys pressed: $keysPressed');
if (keysPressed.isOnly(LogicalKeyboardKey.delete) ||
keysPressed.isOnly(LogicalKeyboardKey.backspace)) {
return MailListActionShortcutType.delete;
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyN)) {
return MailListActionShortcutType.newMessage;
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyQ)) {
return MailListActionShortcutType.markAsRead;
} else if (keysPressed.isOnly(LogicalKeyboardKey.keyU)) {
return MailListActionShortcutType.markAsUnread;
} else {
return null;
}
}
} }
@@ -0,0 +1,40 @@
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/list_presentation_email_extension.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
enum MailListActionShortcutType {
delete,
newMessage,
markAsRead,
markAsUnread;
EmailActionType? getEmailActionType({
required List<PresentationEmail> selectedEmails,
PresentationMailbox? selectedMailbox,
}) {
if (selectedEmails.isEmpty &&
this != MailListActionShortcutType.newMessage) {
return null;
}
switch(this) {
case MailListActionShortcutType.delete:
return selectedMailbox?.isDeletePermanentlyEnabled == true
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash;
case MailListActionShortcutType.newMessage:
return EmailActionType.compose;
case MailListActionShortcutType.markAsRead:
return selectedEmails.isAllEmailRead
? null
: EmailActionType.markAsRead;
case MailListActionShortcutType.markAsUnread:
return selectedEmails.isAllEmailUnread
? null
: EmailActionType.markAsUnread;
}
}
}
@@ -443,11 +443,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
); );
mailboxDashBoardController.clearEmailUIAction(); mailboxDashBoardController.clearEmailUIAction();
} else if (action is TriggerMailViewKeyboardShortcutAction) { } else if (action is TriggerMailViewKeyboardShortcutAction) {
mailboxDashBoardController.clearEmailUIAction();
if (_currentEmailId == null ||
action.email.id != _currentEmailId) {
return;
}
handleMailActionByShortcutAction( handleMailActionByShortcutAction(
actionType: action.actionType, actionType: action.actionType,
email: action.email, email: action.email,
); );
mailboxDashBoardController.clearEmailUIAction();
} }
})); }));
@@ -157,4 +157,6 @@ class SynchronizeEmailSortOrderAction extends DashBoardAction {
List<Object?> get props => [emailSortOrderType]; List<Object?> get props => [emailSortOrderType];
} }
class ReclaimMailListKeyboardShortcutFocusAction extends DashBoardAction {} class ReclaimMailListKeyboardShortcutFocusAction extends DashBoardAction {}
class ClearMailListKeyboardShortcutFocusAction extends DashBoardAction {}
@@ -37,7 +37,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_
import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/restore_deleted_message_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/restore_deleted_message_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/unsubscribe_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/unsubscribe_email_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/bindings/email_interactor_bindings.dart'; import 'package:tmail_ui_user/features/email/presentation/bindings/email_bindings.dart';
import 'package:tmail_ui_user/features/home/domain/repository/session_repository.dart'; import 'package:tmail_ui_user/features/home/domain/repository/session_repository.dart';
import 'package:tmail_ui_user/features/home/domain/usecases/store_session_interactor.dart'; import 'package:tmail_ui_user/features/home/domain/usecases/store_session_interactor.dart';
import 'package:tmail_ui_user/features/identity_creator/data/datasource/identity_creator_data_source.dart'; import 'package:tmail_ui_user/features/identity_creator/data/datasource/identity_creator_data_source.dart';
@@ -155,7 +155,7 @@ class MailboxDashBoardBindings extends BaseBindings {
SendingQueueBindings().dependencies(); SendingQueueBindings().dependencies();
MailboxBindings().dependencies(); MailboxBindings().dependencies();
ThreadBindings().dependencies(); ThreadBindings().dependencies();
EmailInteractorBindings().dependencies(); EmailBindings().dependencies();
SearchEmailBindings().dependencies(); SearchEmailBindings().dependencies();
QuotasBindings().dependencies(); QuotasBindings().dependencies();
ThreadDetailBindings().dependencies(); ThreadDetailBindings().dependencies();
@@ -3286,6 +3286,9 @@ class MailboxDashBoardController extends ReloadableController
bool get isEmailOpened => bool get isEmailOpened =>
dashboardRoute.value == DashboardRoutes.threadDetailed; dashboardRoute.value == DashboardRoutes.threadDetailed;
bool get isEmailListDisplayed =>
dashboardRoute.value == DashboardRoutes.thread;
@override @override
void onClose() { void onClose() {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
@@ -1,6 +1,7 @@
import 'package:core/utils/app_logger.dart'; import 'package:core/utils/app_logger.dart';
import 'package:get/get_rx/src/rx_workers/rx_workers.dart'; import 'package:get/get_rx/src/rx_workers/rx_workers.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/thread_detail/presentation/action/thread_detail_ui_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
@@ -14,7 +15,7 @@ extension HandleReactiveObxVariableExtension on MailboxDashBoardController {
searchInputFocusWorker = ever( searchInputFocusWorker = ever(
searchController.isSearchInputFocused, searchController.isSearchInputFocused,
_onSearchInputFocusChanged onSearchInputFocusChanged
); );
} }
@@ -22,13 +23,17 @@ extension HandleReactiveObxVariableExtension on MailboxDashBoardController {
log('$runtimeType::_onAdvancedSearchVisibleChanged: visible is $visible | isEmailOpened = $isEmailOpened'); log('$runtimeType::_onAdvancedSearchVisibleChanged: visible is $visible | isEmailOpened = $isEmailOpened');
if (isEmailOpened) { if (isEmailOpened) {
_performThreadDetailUIActionWhenEmailOpened(visible); _performThreadDetailUIActionWhenEmailOpened(visible);
} else if (isEmailListDisplayed) {
_performDashboardUIActionWhenEmailListDisplayed(visible);
} }
} }
void _onSearchInputFocusChanged(bool focused) { void onSearchInputFocusChanged(bool focused) {
log('$runtimeType::_onSearchInputFocusChanged: focused is $focused | isEmailOpened = $isEmailOpened'); log('$runtimeType::_onSearchInputFocusChanged: focused is $focused | isEmailOpened = $isEmailOpened');
if (isEmailOpened) { if (isEmailOpened) {
_performThreadDetailUIActionWhenEmailOpened(focused); _performThreadDetailUIActionWhenEmailOpened(focused);
} else if (isEmailListDisplayed) {
_performDashboardUIActionWhenEmailListDisplayed(focused);
} }
} }
@@ -40,6 +45,14 @@ extension HandleReactiveObxVariableExtension on MailboxDashBoardController {
dispatchThreadDetailUIAction(emailAction); dispatchThreadDetailUIAction(emailAction);
} }
void _performDashboardUIActionWhenEmailListDisplayed(bool focused) {
final dashboardAction = focused
? ClearMailListKeyboardShortcutFocusAction()
: ReclaimMailListKeyboardShortcutFocusAction();
dispatchAction(dashboardAction);
}
void disposeReactiveObxVariableListener() { void disposeReactiveObxVariableListener() {
advancedSearchVisibleWorker?.dispose(); advancedSearchVisibleWorker?.dispose();
advancedSearchVisibleWorker = null; advancedSearchVisibleWorker = null;
@@ -65,6 +65,7 @@ import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/mailbo
import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_mailbox_arguments.dart'; import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/new_mailbox_arguments.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/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_reactive_obx_variable_extension.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/search/mailbox/presentation/search_mailbox_bindings.dart'; import 'package:tmail_ui_user/features/search/mailbox/presentation/search_mailbox_bindings.dart';
import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart';
@@ -90,6 +91,7 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
final listMailboxSearched = RxList<PresentationMailbox>(); final listMailboxSearched = RxList<PresentationMailbox>();
final textInputSearchController = TextEditingController(); final textInputSearchController = TextEditingController();
late Debouncer<String> _deBouncerTime; late Debouncer<String> _deBouncerTime;
FocusNode? searchFocusNode;
PresentationMailbox? get selectedMailbox => dashboardController.selectedMailbox.value; PresentationMailbox? get selectedMailbox => dashboardController.selectedMailbox.value;
@@ -124,6 +126,9 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
super.onInit(); super.onInit();
_initializeDebounceTimeTextSearchChange(); _initializeDebounceTimeTextSearchChange();
_registerObxStreamListener(); _registerObxStreamListener();
if (PlatformInfo.isWeb) {
_registerInputFocusListener();
}
_getAllMailboxAction(); _getAllMailboxAction();
} }
@@ -215,6 +220,23 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
}); });
} }
void _registerInputFocusListener() {
searchFocusNode = FocusNode();
searchFocusNode?.addListener(_onSearchFocusChanged);
searchFocusNode?.requestFocus();
}
void _onSearchFocusChanged() {
log('$runtimeType::_onSearchFocusChanged: ${searchFocusNode?.hasFocus}');
final hasFocus = searchFocusNode?.hasFocus ?? false;
dashboardController.onSearchInputFocusChanged(hasFocus);
}
void _disposeInputFocusListener() {
searchFocusNode?.removeListener(_onSearchFocusChanged);
searchFocusNode?.dispose();
}
void _getAllMailboxAction() { void _getAllMailboxAction() {
if (session != null && accountId != null) { if (session != null && accountId != null) {
getAllMailbox(session!, accountId!); getAllMailbox(session!, accountId!);
@@ -837,6 +859,9 @@ class SearchMailboxController extends BaseMailboxController with MailboxActionHa
void onClose() { void onClose() {
textInputSearchController.dispose(); textInputSearchController.dispose();
_deBouncerTime.cancel(); _deBouncerTime.cancel();
if (PlatformInfo.isWeb) {
_disposeInputFocusListener();
}
super.onClose(); super.onClose();
} }
} }
@@ -38,14 +38,18 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( if (PlatformInfo.isWeb) {
body: GestureDetector( return Scaffold(
onTap: () => FocusScope.of(context).unfocus(), body: PointerInterceptor(child: _buildSearchBody(context)),
child: PlatformInfo.isWeb );
? PointerInterceptor(child: _buildSearchBody(context)) } else {
: SafeArea(child: _buildSearchBody(context)), return Scaffold(
), body: GestureDetector(
); onTap: FocusScope.of(context).unfocus,
child: SafeArea(child: _buildSearchBody(context)),
),
);
}
} }
Widget _buildSearchBody(BuildContext context) { Widget _buildSearchBody(BuildContext context) {
@@ -150,6 +154,7 @@ class SearchMailboxView extends GetWidget<SearchMailboxController>
autoFocus: true, autoFocus: true,
maxLines: 1, maxLines: 1,
controller: controller.textInputSearchController, controller: controller.textInputSearchController,
focusNode: controller.searchFocusNode,
textDirection: DirectionUtils.getDirectionByLanguage(context), textDirection: DirectionUtils.getDirectionByLanguage(context),
textStyle: ThemeUtils.textStyleBodyBody2( textStyle: ThemeUtils.textStyleBodyBody2(
color: Colors.black, color: Colors.black,
@@ -0,0 +1,76 @@
import 'dart:async';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.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_list_action_shortcut_type.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_shift_selection_email_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/mail_list_shortcut_action_view_event.dart';
import 'package:tmail_ui_user/features/thread/presentation/thread_controller.dart';
extension HandleKeyboardShortcutActionsExtension on ThreadController {
void onKeyboardShortcutInit() {
if (PlatformInfo.isWeb) {
keyboardFocusNode = FocusNode();
shortcutActionEventController =
StreamController<MailListShortcutActionViewEvent>();
shortcutActionEventSubscription = shortcutActionEventController
?.stream
.debounceTime(const Duration(milliseconds: 300))
.listen(_onShortcutActionViewEvent);
}
}
void _onShortcutActionViewEvent(MailListShortcutActionViewEvent event) {
pressEmailSelectionAction(event.actionType, event.listPresentationEmail);
}
void onKeyboardShortcutDispose() {
if (PlatformInfo.isWeb) {
keyboardFocusNode?.dispose();
shortcutActionEventSubscription?.cancel();
shortcutActionEventController?.close();
}
}
void clearMailShortcutFocus() {
if (keyboardFocusNode?.hasFocus == true) {
keyboardFocusNode?.unfocus();
}
}
void refocusMailShortcutFocus() {
if (keyboardFocusNode?.hasFocus == false) {
keyboardFocusNode?.requestFocus();
}
}
void onKeyDownEventAction(KeyEvent event) {
handleShiftSelectionEmail(event);
final shortcutType = AppShortcutManager.getMailListActionFromEvent(event);
log('$runtimeType::onKeyDownEventAction:🔥Shortcut triggered: $shortcutType');
if (shortcutType == null) return;
handleMailListShortcutAction(shortcutType);
}
void handleMailListShortcutAction(MailListActionShortcutType shortcutType) {
log('$runtimeType::handleMailListShortcutAction: 🔥 Shortcut triggered: $shortcutType');
final currentMailbox = selectedMailbox;
if (currentMailbox == null) return;
final selectedEmails = listEmailSelected;
final emailActionType = shortcutType.getEmailActionType(
selectedEmails: selectedEmails,
selectedMailbox: currentMailbox,
);
if (emailActionType == null) return;
shortcutActionEventController?.add(
MailListShortcutActionViewEvent(emailActionType, selectedEmails),
);
}
}
@@ -0,0 +1,23 @@
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/thread/presentation/thread_controller.dart';
extension HandleShiftSelectionEmailExtension on ThreadController {
void onKeyUpEventAction(KeyEvent event) {
handleShiftSelectionEmail(event);
}
void handleShiftSelectionEmail(KeyEvent event) {
final keysPressed = HardwareKeyboard.instance.logicalKeysPressed;
final shiftPressed = keysPressed.isShift();
log('$runtimeType::handleShiftSelectionEmail:Event = ${event.runtimeType} keysPressed = $keysPressed | shiftPressed = $shiftPressed');
if (event is KeyDownEvent && shiftPressed) {
rangeSelectionMode = true;
}
if (event is KeyUpEvent && !shiftPressed) {
rangeSelectionMode = false;
}
}
}
@@ -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 MailListShortcutActionViewEvent extends ViewEvent {
final EmailActionType actionType;
final List<PresentationEmail> listPresentationEmail;
MailListShortcutActionViewEvent(this.actionType, this.listPresentationEmail);
@override
List<Object?> get props => [actionType, listPresentationEmail];
}
@@ -7,7 +7,6 @@ import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart'; import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart';
@@ -24,12 +23,14 @@ import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanent
import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/move_to_mailbox_state.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/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/action/dashboard_action.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/search_controller.dart' as search;
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/move_emails_to_mailbox_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
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/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/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/search/email_sort_order_type.dart';
@@ -60,11 +61,13 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_emails_i
import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/refresh_changes_emails_in_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/search_more_email_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/list_presentation_email_extensions.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/refresh_thread_detail_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/refresh_thread_detail_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/mixin/email_action_controller.dart'; import 'package:tmail_ui_user/features/thread/presentation/mixin/email_action_controller.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/mail_list_shortcut_action_view_event.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception.dart'; import 'package:tmail_ui_user/main/exceptions/remote_exception.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart';
@@ -89,7 +92,7 @@ class ThreadController extends BaseController with EmailActionController {
final CleanAndGetEmailsInMailboxInteractor cleanAndGetEmailsInMailboxInteractor; final CleanAndGetEmailsInMailboxInteractor cleanAndGetEmailsInMailboxInteractor;
final listEmailDrag = <PresentationEmail>[].obs; final listEmailDrag = <PresentationEmail>[].obs;
bool _rangeSelectionMode = false; bool rangeSelectionMode = false;
final openingEmail = RxBool(false); final openingEmail = RxBool(false);
final loadingMoreStatus = Rx(LoadingMoreStatus.idle); final loadingMoreStatus = Rx(LoadingMoreStatus.idle);
@@ -101,7 +104,9 @@ class ThreadController extends BaseController with EmailActionController {
@visibleForTesting @visibleForTesting
bool isListEmailScrollViewJumping = false; bool isListEmailScrollViewJumping = false;
WebSocketQueueHandler? _webSocketQueueHandler; WebSocketQueueHandler? _webSocketQueueHandler;
FocusNode? focusNodeKeyBoard; FocusNode? keyboardFocusNode;
StreamController<MailListShortcutActionViewEvent>? shortcutActionEventController;
StreamSubscription<MailListShortcutActionViewEvent>? shortcutActionEventSubscription;
StreamSubscription<html.Event>? _resizeBrowserStreamSubscription; StreamSubscription<html.Event>? _resizeBrowserStreamSubscription;
@@ -133,8 +138,8 @@ class ThreadController extends BaseController with EmailActionController {
void onInit() { void onInit() {
_registerObxStreamListener(); _registerObxStreamListener();
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
focusNodeKeyBoard = FocusNode();
_registerBrowserResizeListener(); _registerBrowserResizeListener();
onKeyboardShortcutInit();
} }
_initWebSocketQueueHandler(); _initWebSocketQueueHandler();
super.onInit(); super.onInit();
@@ -150,10 +155,9 @@ class ThreadController extends BaseController with EmailActionController {
void onClose() { void onClose() {
_currentMemoryMailboxId = null; _currentMemoryMailboxId = null;
listEmailController.dispose(); listEmailController.dispose();
focusNodeKeyBoard?.dispose();
focusNodeKeyBoard = null;
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
_resizeBrowserStreamSubscription?.cancel(); _resizeBrowserStreamSubscription?.cancel();
onKeyboardShortcutDispose();
} }
_webSocketQueueHandler?.dispose(); _webSocketQueueHandler?.dispose();
super.onClose(); super.onClose();
@@ -231,11 +235,19 @@ class ThreadController extends BaseController with EmailActionController {
@override @override
void onDone() { void onDone() {
viewState.value.map((success) { viewState.value.fold(
if (success is GetAllEmailSuccess) { (failure) {
_handleOnDoneGetAllEmailSuccess(success); if (failure is GetAllEmailFailure ||
} failure is CleanAndGetAllEmailFailure) {
}); _handleOnDoneGetAllEmailFailure();
}
},
(success) {
if (success is GetAllEmailSuccess) {
_handleOnDoneGetAllEmailSuccess();
}
},
);
} }
void _initWebSocketQueueHandler() { void _initWebSocketQueueHandler() {
@@ -328,6 +340,12 @@ class ThreadController extends BaseController with EmailActionController {
} }
canSearchMore = true; canSearchMore = true;
mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.emailsInCurrentMailbox.clear();
} else if (action is ReclaimMailListKeyboardShortcutFocusAction) {
refocusMailShortcutFocus();
mailboxDashBoardController.clearDashBoardAction();
} else if (action is ClearMailListKeyboardShortcutFocusAction) {
clearMailShortcutFocus();
mailboxDashBoardController.clearDashBoardAction();
} }
}); });
@@ -508,9 +526,19 @@ class ThreadController extends BaseController with EmailActionController {
} }
} }
void _handleOnDoneGetAllEmailSuccess(GetAllEmailSuccess success) { void _handleOnDoneGetAllEmailSuccess() {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
_validateBrowserHeight(); _validateBrowserHeight();
if (mailboxDashBoardController.isEmailListDisplayed) {
refocusMailShortcutFocus();
}
}
}
void _handleOnDoneGetAllEmailFailure() {
if (PlatformInfo.isWeb && mailboxDashBoardController.isEmailListDisplayed) {
refocusMailShortcutFocus();
} }
} }
@@ -862,7 +890,7 @@ class ThreadController extends BaseController with EmailActionController {
void selectEmail(PresentationEmail presentationEmailSelected) { void selectEmail(PresentationEmail presentationEmailSelected) {
final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox;
if (_rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) { if (rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) {
_rangeSelectionEmailsAction(presentationEmailSelected); _rangeSelectionEmailsAction(presentationEmailSelected);
} else { } else {
final newEmailList = emailsInCurrentMailbox final newEmailList = emailsInCurrentMailbox
@@ -875,7 +903,7 @@ class ThreadController extends BaseController with EmailActionController {
.firstWhereOrNull((e) => e.id == presentationEmailSelected.id); .firstWhereOrNull((e) => e.id == presentationEmailSelected.id);
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
focusNodeKeyBoard?.requestFocus(); refocusMailShortcutFocus();
} }
if (mailboxDashBoardController.emailsInCurrentMailbox.isAllSelectionInActive) { if (mailboxDashBoardController.emailsInCurrentMailbox.isAllSelectionInActive) {
@@ -896,6 +924,9 @@ class ThreadController extends BaseController with EmailActionController {
mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.updateEmailList(newEmailList);
mailboxDashBoardController.currentSelectMode.value = SelectMode.ACTIVE; mailboxDashBoardController.currentSelectMode.value = SelectMode.ACTIVE;
mailboxDashBoardController.listEmailSelected.value = listEmailSelected; mailboxDashBoardController.listEmailSelected.value = listEmailSelected;
if (PlatformInfo.isWeb) {
refocusMailShortcutFocus();
}
} }
List<PresentationEmail> get listEmailSelected => List<PresentationEmail> get listEmailSelected =>
@@ -1140,6 +1171,9 @@ class ThreadController extends BaseController with EmailActionController {
cancelSelectEmail(); cancelSelectEmail();
moveEmailsToArchive(selectionEmail); moveEmailsToArchive(selectionEmail);
break; break;
case EmailActionType.compose:
mailboxDashBoardController.openComposer(ComposerArguments());
break;
default: default:
break; break;
} }
@@ -1233,20 +1267,6 @@ class ThreadController extends BaseController with EmailActionController {
} }
} }
KeyEventResult handleKeyEvent(FocusNode node, KeyEvent event) {
final shiftEvent = event.logicalKey == LogicalKeyboardKey.shiftLeft || event.logicalKey == LogicalKeyboardKey.shiftRight;
if (event is KeyDownEvent && shiftEvent) {
_rangeSelectionMode = true;
}
if (event is KeyUpEvent && shiftEvent) {
_rangeSelectionMode = false;
}
return shiftEvent
? KeyEventResult.handled
: KeyEventResult.ignored;
}
PresentationEmail generateEmailByPlatform(PresentationEmail currentEmail) { PresentationEmail generateEmailByPlatform(PresentationEmail currentEmail) {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
final route = RouteUtils.createUrlWebLocationBar( final route = RouteUtils.createUrlWebLocationBar(
@@ -1369,7 +1389,10 @@ class ThreadController extends BaseController with EmailActionController {
mailboxDashBoardController.onDragMailbox(isDrag); mailboxDashBoardController.onDragMailbox(isDrag);
} }
Future<bool> swipeEmailAction(BuildContext context, PresentationEmail email, DismissDirection direction) async { Future<bool> swipeEmailAction(
PresentationEmail email,
DismissDirection direction,
) async {
if (direction == DismissDirection.startToEnd) { if (direction == DismissDirection.startToEnd) {
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);
+227 -220
View File
@@ -10,6 +10,7 @@ import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart'; import 'package:tmail_ui_user/features/base/mixin/popup_menu_widget_mixin.dart';
import 'package:tmail_ui_user/features/base/widget/clean_messages_banner.dart'; import 'package:tmail_ui_user/features/base/widget/clean_messages_banner.dart';
import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart'; import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart';
import 'package:tmail_ui_user/features/base/widget/keyboard/keyboard_handler_wrapper.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart'; import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
import 'package:tmail_ui_user/features/base/widget/report_message_banner.dart'; import 'package:tmail_ui_user/features/base/widget/report_message_banner.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';
@@ -31,10 +32,12 @@ import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_sta
import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_trash_folder_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/get_all_email_state.dart';
import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/search_email_state.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_keyboard_shortcut_actions_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_open_context_menu_filter_email_action_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_open_context_menu_filter_email_action_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_press_email_selection_action.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_press_email_selection_action.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_pull_to_refresh_list_email_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_select_message_filter_extension.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_select_message_filter_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_shift_selection_email_extension.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/loading_more_status.dart';
import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart'; import 'package:tmail_ui_user/features/thread/presentation/styles/item_email_tile_styles.dart';
@@ -57,238 +60,246 @@ class ThreadView extends GetWidget<ThreadController>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( final bodyWidget = Scaffold(
onTap: FocusManager.instance.primaryFocus?.unfocus, resizeToAvoidBottomInset: false,
child: Scaffold( backgroundColor: Colors.white,
resizeToAvoidBottomInset: false, body: Portal(
backgroundColor: Colors.white, child: Row(children: [
body: Portal( if (_supportVerticalDivider(context))
child: Row(children: [ const VerticalDivider(),
if (_supportVerticalDivider(context)) Expanded(child: SafeArea(
const VerticalDivider(), right: controller.responsiveUtils.isLandscapeMobile(context),
Expanded(child: SafeArea( left: controller.responsiveUtils.isLandscapeMobile(context),
right: controller.responsiveUtils.isLandscapeMobile(context), child: Column(
left: controller.responsiveUtils.isLandscapeMobile(context), crossAxisAlignment: CrossAxisAlignment.start,
child: Column( children: [
crossAxisAlignment: CrossAxisAlignment.start, if (!controller.responsiveUtils.isWebDesktop(context))
children: [ ... [
if (!controller.responsiveUtils.isWebDesktop(context)) Obx(() {
... [ return MobileAppBarThreadWidget(
Obx(() { responsiveUtils: controller.responsiveUtils,
return MobileAppBarThreadWidget(
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
mailboxSelected: controller.selectedMailbox,
listEmailSelected: controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected,
selectMode: controller.mailboxDashBoardController.currentSelectMode.value,
filterOption: controller.mailboxDashBoardController.filterMessageOption.value,
openMailboxAction: controller.openMailboxLeftMenu,
onCancelSelectionAction: controller.cancelSelectEmail,
onContextMenuFilterEmailAction: controller.responsiveUtils.isScreenWithShortestSide(context)
? (filterOption) => controller.handleSelectMessageFilter(context, filterOption)
: null,
onPopupMenuFilterEmailAction: !controller.responsiveUtils.isScreenWithShortestSide(context)
? (filterOption, position) => controller.handleOpenContextMenuFilterEmailAction(context, position, filterOption)
: null,
onPressEmailSelectionActionClick: (type, emails) =>
controller.handlePressEmailSelectionAction(
context,
type,
emails,
controller.selectedMailbox,
),
);
}),
if (PlatformInfo.isMobile)
Obx(() {
if (!controller.networkConnectionController.isNetworkConnectionAvailable()) {
return NetworkConnectionBannerWidget();
} else {
return const SizedBox.shrink();
}
}),
SearchBarView(
key: const Key('email_search_bar_view'),
imagePaths: controller.imagePaths, imagePaths: controller.imagePaths,
margin: ThreadViewStyle.getBannerMargin( mailboxSelected: controller.selectedMailbox,
context, listEmailSelected: controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected,
controller.responsiveUtils), selectMode: controller.mailboxDashBoardController.currentSelectMode.value,
hintTextSearch: AppLocalizations.of(context).search_emails, filterOption: controller.mailboxDashBoardController.filterMessageOption.value,
onOpenSearchViewAction: controller.goToSearchView openMailboxAction: controller.openMailboxLeftMenu,
), onCancelSelectionAction: controller.cancelSelectEmail,
Obx(() { onContextMenuFilterEmailAction: controller.responsiveUtils.isScreenWithShortestSide(context)
final spamController = controller ? (filterOption) => controller.handleSelectMessageFilter(context, filterOption)
.mailboxDashBoardController : null,
.spamReportController; onPopupMenuFilterEmailAction: !controller.responsiveUtils.isScreenWithShortestSide(context)
? (filterOption, position) => controller.handleOpenContextMenuFilterEmailAction(context, position, filterOption)
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled; : null,
onPressEmailSelectionActionClick: (type, emails) =>
final isSpamFolderSelected = controller controller.handlePressEmailSelectionAction(
.mailboxDashBoardController
.selectedMailbox
.value
?.isSpam == true;
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
if (isSpamReportDisabled ||
isPresentationSpamMailboxIsNull ||
isSpamFolderSelected) {
return const SizedBox.shrink();
}
return ReportMessageBanner(
imagePaths: controller.imagePaths,
message: AppLocalizations
.of(context)
.countMessageInSpam(
spamController.numberOfUnreadSpamEmails,
),
positiveName: AppLocalizations.of(context).view,
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils,
),
isDesktop: controller
.responsiveUtils
.isDesktop(context),
onPositiveAction: spamController.openMailbox,
onNegativeAction: () =>
spamController.dismissSpamReportAction(
context, context,
type,
emails,
controller.selectedMailbox,
), ),
); );
}), }),
QuotasBannerWidget(), if (PlatformInfo.isMobile)
Obx(() { Obx(() {
final vacation = controller.mailboxDashBoardController.vacationResponse.value; if (!controller.networkConnectionController.isNetworkConnectionAvailable()) {
if (vacation?.vacationResponderIsValid == true) { return NetworkConnectionBannerWidget();
return VacationNotificationMessageWidget(
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils),
vacationResponse: vacation!,
actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting,
actionEndNow: controller.mailboxDashBoardController.disableVacationResponder
);
} else { } else {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
}), }),
Obx(() => RecoverDeletedMessageLoadingBannerWidget( SearchBarView(
isLoading: controller.mailboxDashBoardController.isRecoveringDeletedMessage.value, key: const Key('email_search_bar_view'),
horizontalLoadingWidget: horizontalLoadingWidget, imagePaths: controller.imagePaths,
responsiveUtils: controller.responsiveUtils, margin: ThreadViewStyle.getBannerMargin(
)),
],
Obx(() {
final dashboardController = controller
.mailboxDashBoardController;
final selectedMailbox = dashboardController
.selectedMailbox
.value;
bool showTrashBanner = dashboardController
.isEmptyTrashBannerEnabledOnMobile(
context, context,
selectedMailbox, controller.responsiveUtils),
); hintTextSearch: AppLocalizations.of(context).search_emails,
bool showSpamBanner = dashboardController onOpenSearchViewAction: controller.goToSearchView
.isEmptySpamBannerEnabledOnMobile( ),
context, Obx(() {
selectedMailbox, final spamController = controller
);
if (showTrashBanner) {
return CleanMessagesBanner(
responsiveUtils: controller.responsiveUtils,
key: const Key('empty_trash_banner'),
message: AppLocalizations
.of(context)
.message_delete_all_email_in_trash_button,
positiveAction: AppLocalizations
.of(context)
.empty_trash_now,
onPositiveAction: () =>
controller.deleteSelectionEmailsPermanently(
context,
DeleteActionType.all,
),
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils,
),
);
} else if (showSpamBanner) {
return CleanMessagesBanner(
responsiveUtils: controller.responsiveUtils,
message: AppLocalizations
.of(context)
.bannerDeleteAllSpamEmailsMessage,
positiveAction: AppLocalizations
.of(context)
.deleteAllSpamEmailsNow,
onPositiveAction: () => controller
.mailboxDashBoardController .mailboxDashBoardController
.openDialogEmptySpamFolder(context), .spamReportController;
final isSpamReportDisabled = spamController.spamReportState.value == SpamReportState.disabled;
final isSpamFolderSelected = controller
.mailboxDashBoardController
.selectedMailbox
.value
?.isSpam == true;
final isPresentationSpamMailboxIsNull = spamController.presentationSpamMailbox.value == null;
if (isSpamReportDisabled ||
isPresentationSpamMailboxIsNull ||
isSpamFolderSelected) {
return const SizedBox.shrink();
}
return ReportMessageBanner(
imagePaths: controller.imagePaths,
message: AppLocalizations
.of(context)
.countMessageInSpam(
spamController.numberOfUnreadSpamEmails,
),
positiveName: AppLocalizations.of(context).view,
margin: ThreadViewStyle.getBannerMargin( margin: ThreadViewStyle.getBannerMargin(
context, context,
controller.responsiveUtils, controller.responsiveUtils,
), ),
); isDesktop: controller
} else { .responsiveUtils
return const SizedBox.shrink( .isDesktop(context),
key: Key('clean_message_banner_not_visible'), onPositiveAction: spamController.openMailbox,
); onNegativeAction: () =>
} spamController.dismissSpamReportAction(
}),
if (!controller.responsiveUtils.isDesktop(context))
_buildMailboxActionProgressBanner(context),
Obx(() => ThreadViewLoadingBarWidget(viewState: controller.viewState.value)),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.white,
child: Obx(() {
return Visibility(
visible: controller.openingEmail.isFalse,
child: _buildResultListEmail(
context, context,
controller.mailboxDashBoardController.emailsInCurrentMailbox ),
) );
}),
QuotasBannerWidget(),
Obx(() {
final vacation = controller.mailboxDashBoardController.vacationResponse.value;
if (vacation?.vacationResponderIsValid == true) {
return VacationNotificationMessageWidget(
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils),
vacationResponse: vacation!,
actionGotoVacationSetting: controller.mailboxDashBoardController.goToVacationSetting,
actionEndNow: controller.mailboxDashBoardController.disableVacationResponder
); );
}) } else {
) return const SizedBox.shrink();
), }
] }),
) Obx(() => RecoverDeletedMessageLoadingBannerWidget(
)) isLoading: controller.mailboxDashBoardController.isRecoveringDeletedMessage.value,
]), horizontalLoadingWidget: horizontalLoadingWidget,
), responsiveUtils: controller.responsiveUtils,
floatingActionButton: Column( )),
mainAxisAlignment: MainAxisAlignment.end, ],
children: [ Obx(() {
Container( final dashboardController = controller
padding: const EdgeInsetsDirectional.only(end: 4.0), .mailboxDashBoardController;
child: ScrollToTopButtonWidget( final selectedMailbox = dashboardController
scrollController: controller.listEmailController, .selectedMailbox
onTap: controller.scrollToTop, .value;
responsiveUtils: controller.responsiveUtils,
icon: SvgPicture.asset( bool showTrashBanner = dashboardController
controller.imagePaths.icArrowUpOutline, .isEmptyTrashBannerEnabledOnMobile(
width: ScrollToTopButtonWidgetStyles.iconWidth, context,
height: ScrollToTopButtonWidgetStyles.iconHeight, selectedMailbox,
fit: BoxFit.fill, );
colorFilter: Colors.white.asFilter(), bool showSpamBanner = dashboardController
), .isEmptySpamBannerEnabledOnMobile(
context,
selectedMailbox,
);
if (showTrashBanner) {
return CleanMessagesBanner(
responsiveUtils: controller.responsiveUtils,
key: const Key('empty_trash_banner'),
message: AppLocalizations
.of(context)
.message_delete_all_email_in_trash_button,
positiveAction: AppLocalizations
.of(context)
.empty_trash_now,
onPositiveAction: () =>
controller.deleteSelectionEmailsPermanently(
context,
DeleteActionType.all,
),
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils,
),
);
} else if (showSpamBanner) {
return CleanMessagesBanner(
responsiveUtils: controller.responsiveUtils,
message: AppLocalizations
.of(context)
.bannerDeleteAllSpamEmailsMessage,
positiveAction: AppLocalizations
.of(context)
.deleteAllSpamEmailsNow,
onPositiveAction: () => controller
.mailboxDashBoardController
.openDialogEmptySpamFolder(context),
margin: ThreadViewStyle.getBannerMargin(
context,
controller.responsiveUtils,
),
);
} else {
return const SizedBox.shrink(
key: Key('clean_message_banner_not_visible'),
);
}
}),
if (!controller.responsiveUtils.isDesktop(context))
_buildMailboxActionProgressBanner(context),
Obx(() => ThreadViewLoadingBarWidget(viewState: controller.viewState.value)),
Expanded(
child: Container(
alignment: Alignment.center,
color: Colors.white,
child: Obx(() {
return Visibility(
visible: controller.openingEmail.isFalse,
child: _buildResultListEmail(
context,
controller.mailboxDashBoardController.emailsInCurrentMailbox
)
);
})
)
),
]
)
))
]),
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Container(
padding: const EdgeInsetsDirectional.only(end: 4.0),
child: ScrollToTopButtonWidget(
scrollController: controller.listEmailController,
onTap: controller.scrollToTop,
responsiveUtils: controller.responsiveUtils,
icon: SvgPicture.asset(
controller.imagePaths.icArrowUpOutline,
width: ScrollToTopButtonWidgetStyles.iconWidth,
height: ScrollToTopButtonWidgetStyles.iconHeight,
fit: BoxFit.fill,
colorFilter: Colors.white.asFilter(),
), ),
), ),
const SizedBox(height: 24), ),
_buildFloatingButtonCompose(context), const SizedBox(height: 24),
], _buildFloatingButtonCompose(context),
), ],
), ),
); );
if (PlatformInfo.isWeb && controller.keyboardFocusNode != null) {
return KeyboardHandlerWrapper(
onKeyDownEventAction: controller.onKeyDownEventAction,
onKeyUpEventAction: controller.onKeyUpEventAction,
focusNode: controller.keyboardFocusNode!,
child: bodyWidget,
);
} else {
return bodyWidget;
}
} }
bool _supportVerticalDivider(BuildContext context) { bool _supportVerticalDivider(BuildContext context) {
@@ -409,14 +420,7 @@ class ThreadView extends GetWidget<ThreadController>
return NotificationListener<ScrollNotification>( return NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotificationListener, onNotification: _handleScrollNotificationListener,
child: PlatformInfo.isMobile child: listView,
? listView
: Focus(
focusNode: controller.focusNodeKeyBoard,
autofocus: true,
onKeyEvent: controller.handleKeyEvent,
child: listView,
),
); );
} }
@@ -582,7 +586,10 @@ class ThreadView extends GetWidget<ThreadController>
), ),
) )
: null, : null,
confirmDismiss: (direction) => controller.swipeEmailAction(context, presentationEmail, direction), confirmDismiss: (direction) => controller.swipeEmailAction(
presentationEmail,
direction,
),
child: EmailTileBuilder( child: EmailTileBuilder(
key: Key('email_tile_builder_${presentationEmail.id?.asString}'), key: Key('email_tile_builder_${presentationEmail.id?.asString}'),
presentationEmail: presentationEmail, presentationEmail: presentationEmail,
@@ -35,9 +35,6 @@ extension HandleMailShortcutActionsExtension on ThreadDetailController {
void onKeyboardShortcutInit() { void onKeyboardShortcutInit() {
if (PlatformInfo.isWeb) { if (PlatformInfo.isWeb) {
keyboardShortcutFocusNode = FocusNode(); keyboardShortcutFocusNode = FocusNode();
keyboardShortcutFocusNode?.addListener(() {
log('$runtimeType::onInit:keyboardShortcutFocusNode: ${keyboardShortcutFocusNode?.hasFocus}');
});
shortcutActionEventController = shortcutActionEventController =
StreamController<MailViewShortcutActionViewEvent>(); StreamController<MailViewShortcutActionViewEvent>();
shortcutActionEventSubscription = shortcutActionEventController shortcutActionEventSubscription = shortcutActionEventController