TF-3911 Support mail keyboard shortcut actions in email list view on web
This commit is contained in:
@@ -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 {}
|
||||||
+2
-2
@@ -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();
|
||||||
|
|||||||
+3
@@ -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) {
|
||||||
|
|||||||
+15
-2
@@ -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,
|
||||||
|
|||||||
+76
@@ -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),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
+23
@@ -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);
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
-3
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user