import 'dart:async'; import 'package:core/presentation/extensions/either_view_state_extension.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; import 'package:core/utils/platform_info.dart'; import 'package:dartz/dartz.dart'; import 'package:flutter/material.dart'; import 'package:get/get.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/state.dart' as jmap; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_email_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/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/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/presentation/extensions/presentation_mailbox_extension.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/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/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/search_email_filter.dart'; import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart' if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_message.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/websocket/web_socket_queue_handler.dart'; import 'package:tmail_ui_user/features/labels/presentation/delegates/add_list_label_to_list_emails_delegate.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/labels/handle_logic_label_extension.dart'; import 'package:tmail_ui_user/features/search/email/presentation/search_email_bindings.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; import 'package:tmail_ui_user/features/thread/domain/model/filter_message_option.dart'; import 'package:tmail_ui_user/features/thread/domain/model/get_email_request.dart'; import 'package:tmail_ui_user/features/thread/domain/model/search_query.dart'; import 'package:tmail_ui_user/features/thread/domain/state/clean_and_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/get_email_by_id_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/load_more_emails_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/move_multiple_email_to_mailbox_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_all_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/refresh_changes_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_more_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/clean_and_get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_email_by_id_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/get_emails_in_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/load_more_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_more_email_interactor.dart'; import 'package:tmail_ui_user/features/thread/presentation/extensions/handle_email_filter_extension.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/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/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/mail_list_shortcut_action_view_event.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/search_status.dart'; import 'package:tmail_ui_user/main/exceptions/remote/method_level_exception.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; import 'package:tmail_ui_user/main/routes/navigation_router.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/utils/app_config.dart'; import 'package:universal_html/html.dart' as html; typedef StartRangeSelection = int; typedef EndRangeSelection = int; class ThreadController extends BaseController with EmailActionController { final networkConnectionController = Get.find(); final GetEmailsInMailboxInteractor _getEmailsInMailboxInteractor; final RefreshChangesEmailsInMailboxInteractor _refreshChangesEmailsInMailboxInteractor; final LoadMoreEmailsInMailboxInteractor _loadMoreEmailsInMailboxInteractor; final SearchEmailInteractor _searchEmailInteractor; final SearchMoreEmailInteractor _searchMoreEmailInteractor; final GetEmailByIdInteractor _getEmailByIdInteractor; final CleanAndGetEmailsInMailboxInteractor cleanAndGetEmailsInMailboxInteractor; final listEmailDrag = [].obs; bool rangeSelectionMode = false; final openingEmail = RxBool(false); final loadingMoreStatus = Rx(LoadingMoreStatus.idle); bool canLoadMore = true; bool canSearchMore = true; MailboxId? _currentMemoryMailboxId; int _peakEmailCount = 0; final ScrollController listEmailController = ScrollController(); final latestEmailSelectedOrUnselected = Rxn(); @visibleForTesting bool isListEmailScrollViewJumping = false; WebSocketQueueHandler? _webSocketQueueHandler; FocusNode? keyboardFocusNode; StreamController? shortcutActionEventController; StreamSubscription? shortcutActionEventSubscription; StreamSubscription? _resizeBrowserStreamSubscription; AccountId? get _accountId => mailboxDashBoardController.accountId.value; Session? get _session => mailboxDashBoardController.sessionCurrent; PresentationMailbox? get selectedMailbox => mailboxDashBoardController.selectedMailbox.value; MailboxId? get selectedMailboxId => selectedMailbox?.id; search.SearchController get searchController => mailboxDashBoardController.searchController; SearchEmailFilter get _searchEmailFilter => searchController.searchEmailFilter.value; SearchQuery? get searchQuery => _searchEmailFilter.text; ThreadController( this._getEmailsInMailboxInteractor, this._refreshChangesEmailsInMailboxInteractor, this._loadMoreEmailsInMailboxInteractor, this._searchEmailInteractor, this._searchMoreEmailInteractor, this._getEmailByIdInteractor, this.cleanAndGetEmailsInMailboxInteractor, ); @override void onInit() { _registerObxStreamListener(); if (PlatformInfo.isWeb) { _registerBrowserResizeListener(); onKeyboardShortcutInit(); } _initWebSocketQueueHandler(); super.onInit(); } @override void onReady() { consumeState(Stream.value(Right(GetAllEmailLoading()))); super.onReady(); } @override void onClose() { _currentMemoryMailboxId = null; listEmailController.dispose(); if (PlatformInfo.isWeb) { _resizeBrowserStreamSubscription?.cancel(); onKeyboardShortcutDispose(); } _webSocketQueueHandler?.dispose(); super.onClose(); } @override void handleSuccessViewState(Success success) { super.handleSuccessViewState(success); if (success is GetAllEmailSuccess) { _getAllEmailSuccess(success); } else if (success is LoadMoreEmailsSuccess) { _loadMoreEmailsSuccess(success); } else if (success is SearchEmailSuccess) { _searchEmailsSuccess(success); } else if (success is SearchMoreEmailSuccess) { _searchMoreEmailsSuccess(success); } else if (success is SearchingMoreState || success is LoadingMoreEmails) { loadingMoreStatus.value = LoadingMoreStatus.running; } else if (success is GetEmailByIdLoading) { openingEmail.value = true; } else if (success is GetEmailByIdSuccess) { openingEmail.value = false; if (isSearchActive) { _openEmailSearchedFromLocationBar( email: success.email, searchQuery: searchQuery ); } else { if (success.mailboxContain != null) { _openEmailInsideMailboxFromLocationBar(success.email, success.mailboxContain!); } else { _openEmailWithoutMailboxFromLocationBar(success.email); } } } } @override void handleFailureViewState(Failure failure) { super.handleFailureViewState(failure); if (failure is SearchEmailFailure) { mailboxDashBoardController.updateRefreshAllEmailState(Left(RefreshAllEmailFailure())); canSearchMore = false; loadingMoreStatus.value = LoadingMoreStatus.idle; mailboxDashBoardController.emailsInCurrentMailbox.clear(); showRetryToast(failure); } else if (failure is SearchMoreEmailFailure) { loadingMoreStatus.value = LoadingMoreStatus.completed; canSearchMore = true; } else if (failure is LoadMoreEmailsFailure) { loadingMoreStatus.value = LoadingMoreStatus.completed; canLoadMore = true; } else if (failure is GetEmailByIdFailure) { openingEmail.value = false; popAndPush(AppRoutes.unknownRoutePage); } else if (failure is GetAllEmailFailure || failure is CleanAndGetAllEmailFailure) { mailboxDashBoardController.updateRefreshAllEmailState(Left(RefreshAllEmailFailure())); } } @override void handleErrorViewState(Object error, StackTrace stackTrace) { super.handleErrorViewState(error, stackTrace); logWarning('ThreadController::handleErrorViewState(): error: $error | stackTrace: $stackTrace'); _resetLoadingMore(); _handleErrorGetAllOrRefreshChangesEmail(error, stackTrace); } @override void handleUrgentException({Failure? failure, Exception? exception}) { _resetLoadingMore(); clearState(); super.handleUrgentException(failure: failure, exception: exception); } @override void onDone() { viewState.value.fold( (failure) { if (failure is GetAllEmailFailure || failure is CleanAndGetAllEmailFailure) { _handleOnDoneGetAllEmailFailure(); } }, (success) { if (success is GetAllEmailSuccess) { _handleOnDoneGetAllEmailSuccess(success); } }, ); } void _initWebSocketQueueHandler() { _webSocketQueueHandler = WebSocketQueueHandler( processMessageCallback: _handleWebSocketMessage, onErrorCallback: onError, ); } void _resetLoadingMore() { if (loadingMoreStatus.value == LoadingMoreStatus.running) { loadingMoreStatus.value = LoadingMoreStatus.idle; } } void _registerObxStreamListener() { ever(mailboxDashBoardController.selectedMailbox, (mailbox) { log('ThreadController::_registerObxStreamListener:SelectedMailbox: ${mailbox?.id} - ${mailbox?.name} | CurrentMemoryMailboxId: $_currentMemoryMailboxId'); if (mailbox is PresentationMailbox && mailbox.mailboxId != _currentMemoryMailboxId) { _currentMemoryMailboxId = mailbox.id; consumeState(Stream.value(Right(GetAllEmailLoading()))); resetToOriginalValue(); getAllEmailAction( getLatestChanges: mailboxDashBoardController.isFirstSessionLoad, forceEmailQuery: forceEmailQuery, ); mailboxDashBoardController.setIsFirstSessionLoad(false); } else if (mailbox == null) { // disable current mailbox when search active _currentMemoryMailboxId = null; resetToOriginalValue(); } }); ever(searchController.searchState, (searchState) { if (searchState.searchStatus == SearchStatus.ACTIVE) { cancelSelectEmail(); } }); ever(mailboxDashBoardController.dashBoardAction, (action) { if (action is SelectionAllEmailAction) { setSelectAllEmailAction(); mailboxDashBoardController.clearDashBoardAction(); } else if (action is CancelSelectionAllEmailAction) { cancelSelectEmail(); mailboxDashBoardController.clearDashBoardAction(); } else if (action is FilterMessageAction) { filterMessagesAction(action.option); mailboxDashBoardController.clearDashBoardAction(); } else if (action is HandleEmailActionTypeAction) { pressEmailSelectionAction(action.emailAction, action.listEmailSelected); mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailDetailedFromSuggestionQuickSearchAction) { final mailboxContain = action.presentationEmail.findMailboxContain(mailboxDashBoardController.mapMailboxById); final newEmail = generateEmailByPlatform(action.presentationEmail); handleEmailActionType( EmailActionType.preview, newEmail, mailboxContain, ); mailboxDashBoardController.clearDashBoardAction(); } else if (action is StartSearchEmailAction || action is ClearAdvancedSearchFilterEmailAction) { cancelSelectEmail(); _replaceBrowserHistory(); _searchEmail(); mailboxDashBoardController.clearDashBoardAction(); } else if (action is EmptyTrashAction && currentContext != null) { deleteSelectionEmailsPermanently(currentContext!, DeleteActionType.all); mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailInsideMailboxFromLocationBar) { _getEmailByIdFromLocationBar( action.emailId, mailboxContain: action.presentationMailbox, ); mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailWithoutMailboxFromLocationBar) { _getEmailByIdFromLocationBar(action.emailId); mailboxDashBoardController.clearDashBoardAction(); } else if (action is OpenEmailSearchedFromLocationBar) { _handleOpenEmailSearchedFromLocationBar( emailId: action.emailId, searchQuery: action.searchQuery ); } else if (action is SearchEmailFromLocationBar) { _handleSearchEmailFromLocationBar(action.searchQuery); } else if (action is SelectDateRangeToAdvancedSearch || action is ClearDateRangeToAdvancedSearch) { if (listEmailController.hasClients) { listEmailController.jumpTo(0); } canSearchMore = true; mailboxDashBoardController.emailsInCurrentMailbox.clear(); } else if (action is ReclaimMailListKeyboardShortcutFocusAction) { refocusMailShortcutFocus(); mailboxDashBoardController.clearDashBoardAction(); } else if (action is ClearMailListKeyboardShortcutFocusAction) { clearMailShortcutFocus(); mailboxDashBoardController.clearDashBoardAction(); } }); ever(mailboxDashBoardController.emailUIAction, (action) { if (action is RefreshChangeEmailAction) { _refreshEmailChanges(newState: action.newState); } else if (action is RefreshAllEmailAction) { refreshAllEmail(shouldClearCache: PlatformInfo.isWeb); mailboxDashBoardController.clearEmailUIAction(); } }); ever(mailboxDashBoardController.viewState, (viewState) { if (isSearchActive) return; final reactionState = viewState.getOrElse(() => UIState.idle); if (reactionState is MarkAsMailboxReadAllSuccess) { _handleMarkEmailsAsReadByMailboxId(reactionState.mailboxId); } else if (reactionState is MarkAsMailboxReadHasSomeEmailFailure) { mailboxDashBoardController.updateEmailFlagByEmailIds( reactionState.successEmailIds, readAction: ReadActions.markAsRead, ); } else if (reactionState is MoveToMailboxSuccess) { mailboxDashBoardController.handleMoveEmailsToMailbox( originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, destinationMailboxId: reactionState.destinationMailboxId, ); _checkIfCurrentMailboxCanLoadMore(); } else if (reactionState is MoveMultipleEmailToMailboxAllSuccess) { mailboxDashBoardController.handleMoveEmailsToMailbox( originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithEmailIds, destinationMailboxId: reactionState.destinationMailboxId, ); _checkIfCurrentMailboxCanLoadMore(); } else if (reactionState is MoveMultipleEmailToMailboxHasSomeEmailFailure) { mailboxDashBoardController.handleMoveEmailsToMailbox( originalMailboxIdsWithEmailIds: reactionState.originalMailboxIdsWithMoveSucceededEmailIds, destinationMailboxId: reactionState.destinationMailboxId, ); _checkIfCurrentMailboxCanLoadMore(); } else if (reactionState is DeleteEmailPermanentlySuccess) { _handleDeleteEmailsPermanentlyFromMailboxId( reactionState.mailboxId, deletedEmailsCount: 1, ); _checkIfCurrentMailboxCanLoadMore(); } else if (reactionState is DeleteMultipleEmailsPermanentlyAllSuccess) { _handleDeleteEmailsPermanentlyFromMailboxId( reactionState.mailboxId, deletedEmailsCount: reactionState.emailIds.length, ); _checkIfCurrentMailboxCanLoadMore(); } else if (reactionState is DeleteMultipleEmailsPermanentlyHasSomeEmailFailure) { _handleDeleteEmailsPermanentlyFromMailboxId( reactionState.mailboxId, deletedEmailsCount: reactionState.emailIds.length, ); _checkIfCurrentMailboxCanLoadMore(); } }); ever(mailboxDashBoardController.emailsInCurrentMailbox, (emails) { final countEmails = emails.length; logTrace( 'ThreadController::Ever(mailboxDashBoardController.emailsInCurrentMailbox): ' 'Count emails is $countEmails, ' '_peakEmailCount is $_peakEmailCount', ); if (emails.isEmpty) { _peakEmailCount = 0; } else if (countEmails > _peakEmailCount) { _peakEmailCount = countEmails; } }); } void _handleMarkEmailsAsReadByMailboxId(MailboxId mailboxId) { if (mailboxDashBoardController.selectedMailbox.value?.id != mailboxId) return; for (var presentationEmail in mailboxDashBoardController.emailsInCurrentMailbox) { if (presentationEmail.mailboxContain?.id != mailboxId) continue; presentationEmail.keywords?[KeyWordIdentifier.emailSeen] = true; } mailboxDashBoardController.emailsInCurrentMailbox.refresh(); } void _handleDeleteEmailsPermanentlyFromMailboxId( MailboxId? mailboxId, { required int deletedEmailsCount, }) { if (mailboxDashBoardController.selectedMailbox.value?.id != mailboxId) return; final currentMailbox = mailboxDashBoardController.selectedMailbox.value; final currentTotalEmails = currentMailbox?.totalEmails; if (currentMailbox != null && currentTotalEmails != null) { int newTotalEmails = currentTotalEmails.value.value.toInt() - deletedEmailsCount; if (newTotalEmails < 0) newTotalEmails = 0; mailboxDashBoardController.selectedMailbox.value = currentMailbox.copyWith( totalEmails: TotalEmails(UnsignedInt(newTotalEmails)), ); } } void _checkIfCurrentMailboxCanLoadMore() { final currentMailbox = mailboxDashBoardController.selectedMailbox.value; if (currentMailbox == null) return; final totalEmailsCount = currentMailbox.countTotalEmails; if (totalEmailsCount == 0 || mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty ) { return; } dispatchState(Right(GetAllEmailLoading())); } void _registerBrowserResizeListener() { _resizeBrowserStreamSubscription = html.window.onResize.listen((_) { _validateBrowserHeight(); }); } void _validateBrowserHeight() { if (_isAutoLoadMore) { _performAutomaticallyLoadMoreEmails(); } } bool get _isAutoLoadMore { if (!PlatformInfo.isWeb) { return _isNonWebAutoLoadMore; } final browserInnerHeight = html.window.innerHeight ?? 0; final currentListEmails = mailboxDashBoardController.emailsInCurrentMailbox; final totalHeightListEmails = currentListEmails.isEmpty ? 0 : currentListEmails.length * ThreadConstants.defaultMaxHeightEmailItemOnBrowser; final isAutoLoadMore = totalHeightListEmails > 0 && totalHeightListEmails <= browserInnerHeight; logTrace( 'ThreadController::_isAutoLoadMore():' 'BrowserInnerHeight = $browserInnerHeight, ' 'TotalHeightListEmails = $totalHeightListEmails, ' 'ThreadConstants.defaultMaxHeightEmailItemOnBrowser = ${ThreadConstants.defaultMaxHeightEmailItemOnBrowser}, ' 'CountCurrentListEmails = ${currentListEmails.length}, ' 'CanLoadMore = $canLoadMore, ' 'isAutoLoadMore = $isAutoLoadMore ', ); return isAutoLoadMore; } bool get _isNonWebAutoLoadMore { if (!listEmailController.hasClients) { logTrace( 'ThreadController::_isNonWebAutoLoadMore(): listEmailController.hasClients = false', ); return false; } final maxScroll = listEmailController.position.maxScrollExtent; final viewport = listEmailController.position.viewportDimension; final isAutoLoadMore = maxScroll <= viewport; logTrace( 'ThreadController::_isNonWebAutoLoadMore():' 'maxScroll = $maxScroll, ' 'viewport = $viewport, ' 'CanLoadMore = $canLoadMore, ' 'isAutoLoadMore = $isAutoLoadMore ', ); return isAutoLoadMore; } void _performAutomaticallyLoadMoreEmails() { log('ThreadController::_performAutomaticallyLoadMoreEmails:'); handleLoadMoreEmailsRequest(); } bool get forceEmailQuery => PlatformInfo.isWeb && AppConfig.isForceEmailQueryEnabled; void _handleErrorGetAllOrRefreshChangesEmail(Object error, StackTrace stackTrace) async { logWarning('ThreadController::_handleErrorGetAllOrRefreshChangesEmail():Error: $error'); if (error is CannotCalculateChangesMethodResponseException) { await cachingManager.clearAllEmailAndStateCache(); getAllEmailAction(); } else if (error is MethodLevelErrors) { if (currentOverlayContext != null && error.message != null) { appToast.showToastErrorMessage( currentOverlayContext!, error.message?.toString() ?? '' ); } clearState(); } } void resetToOriginalValue() { log('ThreadController::resetToOriginalValue'); mailboxDashBoardController.emailsInCurrentMailbox.clear(); mailboxDashBoardController.listEmailSelected.clear(); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; canLoadMore = true; canSearchMore = true; loadingMoreStatus.value = LoadingMoreStatus.idle; } void _getAllEmailSuccess( GetAllEmailSuccess success, { bool shouldJumpToFirstEmail = true, }) { log('ThreadController::_getAllEmailSuccess: GetAllForMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}'); mailboxDashBoardController.updateRefreshAllEmailState(Right(RefreshAllEmailSuccess())); final currentMailboxId = success.currentMailboxId; final isVirtualFolder = selectedMailbox?.isVirtualFolder == true; if (currentMailboxId != null && (isVirtualFolder || currentMailboxId != selectedMailboxId)) { return; } mailboxDashBoardController.setCurrentEmailState(success.currentEmailState); final newListEmail = success.emailList.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: selectedMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); logTrace( 'ThreadController::_getAllEmailSuccess():' 'MailboxId = ${success.currentMailboxId?.asString}, ' 'ServerEmailCount = ${success.emailList.length}, ' 'DisplayedEmailCount = ${newListEmail.length}, ' 'EmailState = ${success.currentEmailState?.value}', ); mailboxDashBoardController.updateEmailList(newListEmail); if (mailboxDashBoardController.isSelectionEnabled()) { mailboxDashBoardController.listEmailSelected.value = listEmailSelected; } if (shouldJumpToFirstEmail && listEmailController.hasClients) { listEmailController.jumpTo(0); } } void _handleOnDoneGetAllEmailSuccess(GetAllEmailSuccess success) { if (PlatformInfo.isWeb && mailboxDashBoardController.isEmailListDisplayed) { refocusMailShortcutFocus(); } final emailList = success.emailList; log('ThreadController::_handleOnDoneGetAllEmailSuccess: EmailCount = ${emailList.length}'); if (_isAutoLoadMore && emailList.isNotEmpty) { _performAutomaticallyLoadMoreEmails(); } else { canLoadMore = emailList.isNotEmpty; } } void _handleOnDoneGetAllEmailFailure() { log('ThreadController::_handleOnDoneGetAllEmailFailure'); if (PlatformInfo.isWeb && mailboxDashBoardController.isEmailListDisplayed) { refocusMailShortcutFocus(); } if (_isAutoLoadMore) { _performAutomaticallyLoadMoreEmails(); } else { canLoadMore = false; loadingMoreStatus.value = LoadingMoreStatus.idle; } } void _refreshChangesAllEmailSuccess(RefreshChangesAllEmailSuccess success) { log('ThreadController::_refreshChangesAllEmailSuccess: RefreshedMailboxId = ${success.currentMailboxId?.asString} | SELECTED_MAILBOX_ID = ${selectedMailboxId?.asString} | SELECTED_MAILBOX_NAME = ${selectedMailbox?.name?.name}'); final currentMailboxId = success.currentMailboxId; final isVirtualFolder = selectedMailbox?.isVirtualFolder == true; if (currentMailboxId != null && (isVirtualFolder || currentMailboxId != selectedMailboxId)) { return; } mailboxDashBoardController.setCurrentEmailState(success.currentEmailState); log('ThreadController::_refreshChangesAllEmailSuccess: COUNT = ${success.emailList.length}'); final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox; final emailsAfterChanges = success.emailList; final newListEmail = emailsAfterChanges.combine(emailsBeforeChanges); final emailListSynced = newListEmail.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: selectedMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); mailboxDashBoardController.updateEmailList(emailListSynced); if (mailboxDashBoardController.isSelectionEnabled()) { mailboxDashBoardController.listEmailSelected.value = listEmailSelected; } logTrace( 'ThreadController::_refreshChangesAllEmailSuccess():' 'MailboxId = ${success.currentMailboxId?.asString}, ' 'CurrentEmailCount = ${emailsBeforeChanges.length}, ' 'ServerEmailCount = ${success.emailList.length}, ' 'DisplayedEmailCount = ${emailListSynced.length}, ' 'EmailState = ${success.currentEmailState?.value}', ); if (_isAutoLoadMore) { _performAutomaticallyLoadMoreEmails(); } } void getAllEmailAction({ bool getLatestChanges = true, bool forceEmailQuery = false, }) { log('ThreadController::_getAllEmailAction:getLatestChanges = $getLatestChanges'); if (_session != null &&_accountId != null) { consumeState(_getEmailsInMailboxInteractor.execute( _session!, _accountId!, limit: ThreadConstants.defaultLimit, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), emailFilter: getEmailFilterForLoadMailbox(), propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), propertiesUpdated: EmailUtils.getPropertiesForEmailChangeMethod( _session!, _accountId!, ), getLatestChanges: getLatestChanges, useCache: selectedMailbox?.isCacheable ?? false, forceEmailQuery: forceEmailQuery, )); } else { consumeState(Stream.value(Left(GetAllEmailFailure(NotFoundSessionException())))); } } Future refreshAllEmail({bool shouldClearCache = false}) async { if (shouldClearCache) { await cachingManager.clearAllEmailAndStateCache(); } if (searchController.isSearchEmailRunning) { consumeState(Stream.value(Right(SearchingState()))); } else { consumeState(Stream.value(Right(GetAllEmailLoading()))); } canLoadMore = true; loadingMoreStatus.value = LoadingMoreStatus.idle; cancelSelectEmail(); if (searchController.isSearchEmailRunning) { _searchEmail(limit: limitEmailFetched); } else { getAllEmailAction(); } } UnsignedInt get limitEmailFetched { return _peakEmailCount == 0 ? ThreadConstants.defaultLimit : UnsignedInt(_peakEmailCount); } void _refreshEmailChanges({required jmap.State newState}) { log('ThreadController::_refreshEmailChanges(): newState: $newState'); if (_accountId == null || _session == null || mailboxDashBoardController.currentEmailState == null || mailboxDashBoardController.currentEmailState == newState) { return; } log('ThreadController::_refreshEmailChanges: websocket enqueue message:'); _webSocketQueueHandler?.enqueue(WebSocketMessage(newState: newState)); } Future _handleWebSocketMessage(WebSocketMessage message) async { try { if (searchController.isSearchEmailRunning && PlatformInfo.isWeb) { await _refreshChangeSearchEmail(); } else if (selectedMailbox?.isVirtualFolder == true) { await _refreshChangeListEmailsInVirtualFolder(); } else { await _refreshChangeListEmail(); } } catch (e, stackTrace) { logWarning('ThreadController::_handleWebSocketMessage:Error processing state: $e'); onError(e, stackTrace); } finally { if (mailboxDashBoardController.currentEmailState != null) { _webSocketQueueHandler?.removeMessagesUpToCurrent( mailboxDashBoardController.currentEmailState!.value); } } } @visibleForTesting Future refreshChangeSearchEmail() => _refreshChangeSearchEmail(); Future _refreshChangeSearchEmail() async { await _refreshChangeListEmailCache(); log('ThreadController::_refreshChangeSearchEmail:'); canSearchMore = true; loadingMoreStatus.value = LoadingMoreStatus.idle; searchController.updateFilterEmail( positionOption: option( _searchEmailFilter.sortOrderType.isScrollByPosition(), 0, ), beforeOption: !_searchEmailFilter.sortOrderType.isScrollByPosition() ? const None() : null, ); final searchViewState = await _searchEmailInteractor.execute( _session!, _accountId!, limit: limitEmailFetched, position: _searchEmailFilter.position, sort: _searchEmailFilter.sortOrderType.getSortOrder().toNullable(), filter: _searchEmailFilter.mappingToEmailFilterCondition( moreFilterCondition: getFilterCondition(), trashSpamMailboxIds: mailboxDashBoardController.trashSpamMailboxIds, ), properties: EmailUtils.getPropertiesForEmailGetMethod( _session!, _accountId!, ), needRefreshSearchState: true, ).last; dispatchState(searchViewState); final searchState = searchViewState .foldSuccessWithResult(); if (searchState is SearchEmailSuccess) { _searchEmailsSuccess(searchState); } else { mailboxDashBoardController.updateRefreshAllEmailState( Left(RefreshAllEmailFailure())); canSearchMore = false; loadingMoreStatus.value = LoadingMoreStatus.completed; mailboxDashBoardController.emailsInCurrentMailbox.clear(); if (searchState != null) { onDataFailureViewState(searchState); } } } Future> _refreshChangeListEmailCache() async { final refreshState = await _refreshChangesEmailsInMailboxInteractor.execute( _session!, _accountId!, mailboxDashBoardController.currentEmailState!, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), limit: limitEmailFetched, propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod( _session!, _accountId!, ), propertiesUpdated: EmailUtils.getPropertiesForEmailChangeMethod( _session!, _accountId!, ), emailFilter: getEmailFilterForLoadMailbox(), ).last; refreshState.fold( (failure) {}, (success) { if (success is RefreshChangesAllEmailSuccess) { refreshThreadDetail(success.emailChangeResponse); } }, ); return refreshState; } Future _refreshChangeListEmail() async { log('ThreadController::_refreshChangeListEmail:'); final refreshViewState = await _refreshChangeListEmailCache(); final refreshState = refreshViewState .foldSuccessWithResult(); dispatchState(refreshViewState); if (refreshState is RefreshChangesAllEmailSuccess) { _refreshChangesAllEmailSuccess(refreshState); } else if (refreshState != null) { onDataFailureViewState(refreshState); } } Future _refreshChangeListEmailsInVirtualFolder() async { log('ThreadController::_refreshChangeListEmailsInVirtualFolder:'); await _refreshChangeListEmailCache(); final viewState = await _getEmailsInMailboxInteractor .execute( _session!, _accountId!, limit: limitEmailFetched, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), emailFilter: getEmailFilterForLoadMailbox(), propertiesCreated: EmailUtils.getPropertiesForEmailGetMethod( _session!, _accountId!, ), propertiesUpdated: EmailUtils.getPropertiesForEmailChangeMethod( _session!, _accountId!, ), useCache: false, ) .last; dispatchState(viewState); final emailSuccessState = viewState .foldSuccessWithResult(); if (emailSuccessState is GetAllEmailSuccess) { _getAllEmailSuccess(emailSuccessState, shouldJumpToFirstEmail: false); } else if (emailSuccessState != null) { onDataFailureViewState(emailSuccessState); } } void _loadMoreEmails() { log('ThreadController::_loadMoreEmails()::canLoadMore = $canLoadMore'); if (!canLoadMore) return; if (_session != null && _accountId != null) { final currentListEmail = mailboxDashBoardController.emailsInCurrentMailbox; final oldestEmail = currentListEmail.isNotEmpty ? currentListEmail.last : null; final useCache = selectedMailbox?.isCacheable ?? false; final filterOption = mailboxDashBoardController.filterMessageOption.value; logTrace( 'ThreadController::_loadMoreEmails: ' 'OldestEmailID = ${oldestEmail?.id?.asString}, ' 'useCache = $useCache, ' 'filterOption = ${filterOption.name}', ); consumeState(_loadMoreEmailsInMailboxInteractor.execute( GetEmailRequest( _session!, _accountId!, limit: ThreadConstants.defaultLimit, sort: EmailSortOrderType.mostRecent.getSortOrder().toNullable(), filterOption: mailboxDashBoardController.filterMessageOption.value, filter: getFilterConditionForLoadMailbox(oldestEmail: oldestEmail), properties: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), lastEmailId: oldestEmail?.id, useCache: useCache, ) )); } else { consumeState( Stream.value(Left(LoadMoreEmailsFailure(NotFoundSessionException()))), ); } } bool _validatePresentationEmail(PresentationEmail email) { return (_belongToCurrentMailboxId(email) || selectedMailbox?.isVirtualFolder == true || selectedMailbox?.isLabelMailbox == true) && _notDuplicatedInCurrentList(email); } bool _belongToCurrentMailboxId(PresentationEmail email) { return email.mailboxIds != null && email.mailboxIds!.keys.contains(selectedMailboxId); } bool _notDuplicatedInCurrentList(PresentationEmail email) { final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; return emailsInCurrentMailbox.isEmpty || !emailsInCurrentMailbox.map((element) => element.id).contains(email.id); } void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) { final emailList = success.emailList; final appendableList = validateListEmailsLoadMore(emailList); logTrace( 'ThreadController::_loadMoreEmailsSuccess: ' 'ServerEmailCount = ${emailList.length}, ' 'EmailAppendableCount = ${appendableList.length}', ); if (appendableList.isNotEmpty) { mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList); } if (_isAutoLoadMore && emailList.isNotEmpty) { _performAutomaticallyLoadMoreEmails(); } else { canLoadMore = emailList.isNotEmpty; loadingMoreStatus.value = LoadingMoreStatus.completed; } } List validateListEmailsLoadMore(List emailList) { log('ThreadController::validateListEmailsLoadMore: BEFORE_EMAIL_LIST = ${emailList.length}'); final appendableList = emailList .where(_validatePresentationEmail) .toList() .syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: selectedMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); log('ThreadController::validateListEmailsLoadMore: AFTER_EMAIL_LIST = ${appendableList.length}'); return appendableList; } SelectMode getSelectMode(PresentationEmail presentationEmail, PresentationEmail? selectedEmail) { return presentationEmail.id == selectedEmail?.id ? SelectMode.ACTIVE : SelectMode.INACTIVE; } Tuple2 _getSelectionEmailsRange(PresentationEmail presentationEmailSelected) { final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; final emailSelectedIndex = emailsInCurrentMailbox .indexWhere((e) => e.id == presentationEmailSelected.id); final latestEmailSelectedOrUnselectedIndex = emailsInCurrentMailbox .indexWhere((e) => e.id == latestEmailSelectedOrUnselected.value?.id); if (emailSelectedIndex > latestEmailSelectedOrUnselectedIndex) { return Tuple2(latestEmailSelectedOrUnselectedIndex, emailSelectedIndex); } else { return Tuple2(emailSelectedIndex, latestEmailSelectedOrUnselectedIndex); } } bool _checkAllowMakeRangeEmailsSelected(Tuple2 selectionEmailsRange) { final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; return latestEmailSelectedOrUnselected.value?.selectMode == SelectMode.ACTIVE && !emailsInCurrentMailbox.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.ACTIVE) || latestEmailSelectedOrUnselected.value?.selectMode == SelectMode.INACTIVE && emailsInCurrentMailbox.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.INACTIVE); } void _applySelectModeToRangeEmails(Tuple2 selectionEmailsRange, SelectMode selectMode) { final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .asMap() .map((index, email) { return MapEntry(index, index >= selectionEmailsRange.value1 && index <= selectionEmailsRange.value2 ? email.toSelectedEmail(selectMode: selectMode) : email); }) .values .toList(); mailboxDashBoardController.updateEmailList(newEmailList); } void _rangeSelectionEmailsAction(PresentationEmail presentationEmailSelected) { final selectionEmailsRange = _getSelectionEmailsRange(presentationEmailSelected); if (_checkAllowMakeRangeEmailsSelected(selectionEmailsRange)) { _applySelectModeToRangeEmails(selectionEmailsRange, SelectMode.ACTIVE); } else { _applySelectModeToRangeEmails(selectionEmailsRange, SelectMode.INACTIVE); } } void selectEmail(PresentationEmail presentationEmailSelected) { final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; if (rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) { _rangeSelectionEmailsAction(presentationEmailSelected); } else { final newEmailList = emailsInCurrentMailbox .map((email) => email.id == presentationEmailSelected.id ? email.toggleSelect() : email) .toList(); mailboxDashBoardController.updateEmailList(newEmailList); } latestEmailSelectedOrUnselected.value = emailsInCurrentMailbox .firstWhereOrNull((e) => e.id == presentationEmailSelected.id); if (PlatformInfo.isWeb) { refocusMailShortcutFocus(); } if (mailboxDashBoardController.emailsInCurrentMailbox.isAllSelectionInActive) { mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); } else { if (mailboxDashBoardController.currentSelectMode.value == SelectMode.INACTIVE) { mailboxDashBoardController.currentSelectMode.value = SelectMode.ACTIVE; } mailboxDashBoardController.listEmailSelected.value = listEmailSelected; } } void setSelectAllEmailAction() { final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .map((email) => email.toSelectedEmail(selectMode: SelectMode.ACTIVE)) .toList(); mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.ACTIVE; mailboxDashBoardController.listEmailSelected.value = listEmailSelected; if (PlatformInfo.isWeb) { refocusMailShortcutFocus(); } } List get listEmailSelected => mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected; void cancelSelectEmail() { if (mailboxDashBoardController.currentSelectMode.value == SelectMode.INACTIVE) { return; } final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) .toList(); mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); } void filterMessagesAction(FilterMessageOption filterOption) { final newFilterOption = mailboxDashBoardController.filterMessageOption.value == filterOption ? FilterMessageOption.all : filterOption; mailboxDashBoardController.filterMessageOption.value = newFilterOption; if (currentContext != null && currentOverlayContext != null) { appToast.showToastMessage( currentOverlayContext!, newFilterOption.getMessageToast(currentContext!), leadingSVGIcon: newFilterOption.getIconToast(imagePaths)); } if (isSearchActive) { _searchEmail(); } else { refreshAllEmail(); } } bool get isSearchActive => searchController.isSearchEmailRunning; void clearTextSearch() { searchController.clearTextSearch(); } @visibleForTesting void searchEmail({ UnsignedInt? limit, bool needRefreshSearchState = false, }) => _searchEmail( limit: limit, needRefreshSearchState: needRefreshSearchState, ); void _searchEmail({UnsignedInt? limit, bool needRefreshSearchState = false}) { if (_session != null && _accountId != null) { if (!needRefreshSearchState && listEmailController.hasClients) { isListEmailScrollViewJumping = true; listEmailController.jumpTo(0); } if (!needRefreshSearchState) { mailboxDashBoardController.emailsInCurrentMailbox.clear(); } canSearchMore = true; loadingMoreStatus.value = LoadingMoreStatus.idle; searchController.updateFilterEmail( positionOption: option(_searchEmailFilter.sortOrderType.isScrollByPosition(), 0), beforeOption: !_searchEmailFilter.sortOrderType.isScrollByPosition() ? const None() : null, ); searchController.activateSimpleSearch(); consumeState(_searchEmailInteractor.execute( _session!, _accountId!, limit: limit ?? ThreadConstants.defaultLimit, position: _searchEmailFilter.position, sort: _searchEmailFilter.sortOrderType.getSortOrder().toNullable(), filter: _searchEmailFilter.mappingToEmailFilterCondition( moreFilterCondition: getFilterCondition(), trashSpamMailboxIds: mailboxDashBoardController.trashSpamMailboxIds, ), properties: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), needRefreshSearchState: needRefreshSearchState )); } else { consumeState(Stream.value(Left(SearchEmailFailure(NotFoundSessionException())))); } } void _replaceBrowserHistory() { if (PlatformInfo.isWeb) { RouteUtils.replaceBrowserHistory( title: 'SearchEmail', url: RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( mailboxId: _searchEmailFilter.mailbox?.mailboxId, searchQuery: searchQuery, dashboardType: DashboardType.search ) ) ); } } void _searchEmailsSuccess(SearchEmailSuccess success) { mailboxDashBoardController.updateRefreshAllEmailState(Right(RefreshAllEmailSuccess())); final emailList = success.emailList; log('ThreadController::_searchEmailsSuccess: COUNT = ${emailList.length}'); final resultEmailSearchList = emailList .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .toList(); final emailsSearchBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox; final emailsSearchAfterChanges = resultEmailSearchList; final newListEmailSearch = emailsSearchAfterChanges.combine(emailsSearchBeforeChanges); final newEmailListSynced = newListEmailSearch.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: selectedMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: isSearchActive, ); mailboxDashBoardController.updateEmailList(newEmailListSynced); if (mailboxDashBoardController.isSelectionEnabled()) { mailboxDashBoardController.listEmailSelected.value = listEmailSelected; } if (_isAutoLoadMore && emailList.isNotEmpty) { _performAutomaticallyLoadMoreEmails(); } else { canSearchMore = emailList.isNotEmpty; loadingMoreStatus.value = LoadingMoreStatus.completed; } } @visibleForTesting void searchMoreEmails() => _searchMoreEmails(); void _searchMoreEmails() { log('ThreadController::_searchMoreEmails:'); if (!canSearchMore) return; if (_session != null && _accountId != null) { final currentEmailList = mailboxDashBoardController.emailsInCurrentMailbox; final lastEmail = currentEmailList.isNotEmpty ? currentEmailList.last : null; if (_searchEmailFilter.sortOrderType.isScrollByPosition()) { final nextPosition = currentEmailList.length; log('ThreadController::_searchMoreEmails:nextPosition: $nextPosition'); searchController.updateFilterEmail(positionOption: Some(nextPosition)); } else if (_searchEmailFilter.sortOrderType == EmailSortOrderType.oldest) { searchController.updateFilterEmail(startDateOption: optionOf(lastEmail?.receivedAt)); } else { searchController.updateFilterEmail(beforeOption: optionOf(lastEmail?.receivedAt)); } consumeState(_searchMoreEmailInteractor.execute( _session!, _accountId!, limit: ThreadConstants.defaultLimit, sort: _searchEmailFilter.sortOrderType.getSortOrder().toNullable(), position: _searchEmailFilter.position, filter: _searchEmailFilter.mappingToEmailFilterCondition( moreFilterCondition: getFilterCondition(), trashSpamMailboxIds: mailboxDashBoardController.trashSpamMailboxIds, ), properties: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), lastEmailId: lastEmail?.id )); } else { consumeState( Stream.value(Left(SearchMoreEmailFailure(NotFoundSessionException()))), ); } } void _searchMoreEmailsSuccess(SearchMoreEmailSuccess success) { final emailList = success.emailList; log('ThreadController::_searchMoreEmailsSuccess: COUNT = ${emailList.length}'); if (emailList.isNotEmpty) { final resultEmailSearchList = emailList .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .where((email) => mailboxDashBoardController.emailsInCurrentMailbox.every((emailInCurrentMailbox) => emailInCurrentMailbox.id != email.id)) .toList() .syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: selectedMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: isSearchActive, ); mailboxDashBoardController.emailsInCurrentMailbox.addAll(resultEmailSearchList); } if (_isAutoLoadMore && emailList.isNotEmpty) { _performAutomaticallyLoadMoreEmails(); } else { canSearchMore = emailList.isNotEmpty; loadingMoreStatus.value = LoadingMoreStatus.completed; } } void pressEmailSelectionAction( EmailActionType actionType, List selectionEmail ) { final handlers = )>{ EmailActionType.markAsRead: (emails) { cancelSelectEmail(); markAsReadSelectedMultipleEmail(emails, ReadActions.markAsRead); }, EmailActionType.markAsUnread: (emails) { cancelSelectEmail(); markAsReadSelectedMultipleEmail(emails, ReadActions.markAsUnread); }, EmailActionType.markAsStarred: (emails) { cancelSelectEmail(); markAsStarSelectedMultipleEmail(emails, MarkStarAction.markStar); }, EmailActionType.unMarkAsStarred: (emails) { cancelSelectEmail(); markAsStarSelectedMultipleEmail(emails, MarkStarAction.unMarkStar); }, EmailActionType.moveToMailbox: (emails) => moveEmailsToMailbox(emails, onCallbackAction: cancelSelectEmail), EmailActionType.moveToTrash: (emails) { cancelSelectEmail(); moveEmailsToTrash(emails); }, EmailActionType.deletePermanently: _handleDeletePermanentlySelectionEmails, EmailActionType.moveToSpam: (emails) { cancelSelectEmail(); moveEmailsToSpam(emails); }, EmailActionType.unSpam: (emails) { cancelSelectEmail(); unSpamSelectedMultipleEmail(emails); }, EmailActionType.archiveMessage: (emails) { cancelSelectEmail(); moveEmailsToArchive(emails); }, EmailActionType.compose: (_) => mailboxDashBoardController.openComposer(ComposerArguments()), EmailActionType.labelAs: (emails) { openChooseLabelModal(emails); }, }; handlers[actionType]?.call(selectionEmail); } void _handleDeletePermanentlySelectionEmails(List selectionEmail) { final mailboxContainCurrent = isSearchActive ? selectionEmail.getCurrentMailboxContain(mailboxDashBoardController.mapMailboxById) : selectedMailbox; if (mailboxContainCurrent != null && currentContext != null) { deleteSelectionEmailsPermanently( currentContext!, DeleteActionType.multiple, listEmails: selectionEmail, mailboxCurrent: mailboxContainCurrent, onCancelSelectionEmail: cancelSelectEmail, ); } } void openChooseLabelModal(List selectionEmail) { Get.find().openChooseLabelModal( selectedEmails: selectionEmail, imagePaths: imagePaths, onCancel: cancelSelectEmail, onSync: mailboxDashBoardController.syncListLabelForListEmail, ); } void handleEmailActionType( EmailActionType actionType, PresentationEmail selectedEmail, PresentationMailbox? mailboxContain, ) { switch(actionType) { case EmailActionType.preview: if (mailboxContain?.isDrafts == true) { editDraftEmail( presentationEmail: selectedEmail, draftMailboxId: mailboxContain!.id, ); } else if (mailboxContain?.isTemplates == true) { editAsNewEmail(selectedEmail, savedEmailTemplateId: selectedEmail.id); } else { previewEmail(selectedEmail); } break; case EmailActionType.selection: selectEmail(selectedEmail); break; case EmailActionType.markAsRead: markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap); break; case EmailActionType.markAsUnread: markAsEmailRead(selectedEmail, ReadActions.markAsUnread, MarkReadAction.tap); break; case EmailActionType.markAsStarred: markAsStarEmail(selectedEmail, MarkStarAction.markStar); break; case EmailActionType.unMarkAsStarred: markAsStarEmail(selectedEmail, MarkStarAction.unMarkStar); break; case EmailActionType.moveToMailbox: moveToMailbox(selectedEmail, mailboxContain: mailboxContain); break; case EmailActionType.moveToTrash: moveToTrash(selectedEmail, mailboxContain: mailboxContain); break; case EmailActionType.deletePermanently: if (currentContext != null) { deleteEmailPermanently(currentContext!, selectedEmail); } break; case EmailActionType.moveToSpam: moveToSpam(selectedEmail, mailboxContain: mailboxContain); break; case EmailActionType.unSpam: unSpam(selectedEmail); break; case EmailActionType.openInNewTab: openEmailInNewTabAction(selectedEmail); break; case EmailActionType.archiveMessage: archiveMessage(selectedEmail); break; case EmailActionType.editAsNewEmail: editAsNewEmail(selectedEmail); break; default: break; } } bool get isMailboxTrash => selectedMailbox?.isTrash == true; void openMailboxLeftMenu() { mailboxDashBoardController.openMailboxMenuDrawer(); } void goToSearchView() { SearchEmailBindings().dependencies(); _replaceBrowserHistory(); searchController.activateSimpleSearch(); mailboxDashBoardController.dispatchRoute(DashboardRoutes.searchEmail); } void calculateDragValue(PresentationEmail? currentPresentationEmail) { if (currentPresentationEmail != null) { if (currentPresentationEmail.id != null && mailboxDashBoardController.listEmailSelected.findEmail(currentPresentationEmail.id!) != null){ listEmailDrag.clear(); listEmailDrag.addAll(mailboxDashBoardController.listEmailSelected); } else { listEmailDrag.clear(); listEmailDrag.add(currentPresentationEmail); } } } PresentationEmail generateEmailByPlatform(PresentationEmail currentEmail) { if (PlatformInfo.isWeb) { final route = RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( emailId: currentEmail.id, mailboxId: isSearchActive ? currentEmail.mailboxContain?.mailboxId : selectedMailboxId, searchQuery: isSearchActive ? searchQuery : null, dashboardType: isSearchActive ? DashboardType.search : DashboardType.normal ) ); final emailOnWeb = currentEmail.withRouteWeb(route); return emailOnWeb; } else { return currentEmail; } } void _getEmailByIdFromLocationBar( EmailId emailId, { PresentationMailbox? mailboxContain, } ) { if (_session != null && _accountId != null) { consumeState(_getEmailByIdInteractor.execute( _session!, _accountId!, emailId, properties: EmailUtils.getPropertiesForEmailGetMethod(_session!, _accountId!), mailboxContain: mailboxContain, )); } else { logWarning('ThreadController::_getEmailByIdFromLocationBar: session & accountId is NULL'); popAndPush(AppRoutes.unknownRoutePage); } } void _openEmailInsideMailboxFromLocationBar( PresentationEmail email, PresentationMailbox mailboxContain ) { final presentationEmailWithRouter = email.withRouteWeb(RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( emailId: email.id, mailboxId: mailboxContain.browserRouteMailboxId, labelId: mailboxContain.labelId, dashboardType: DashboardType.normal ) )); handleEmailActionType( EmailActionType.preview, presentationEmailWithRouter, mailboxContain, ); } void _openEmailWithoutMailboxFromLocationBar(PresentationEmail email) { final mailboxContain = email.findMailboxContain(mailboxDashBoardController.mapMailboxById); if (mailboxContain != null) { mailboxDashBoardController.setSelectedMailbox(mailboxContain); final presentationEmailWithRouter = email.withRouteWeb(RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( emailId: email.id, mailboxId: mailboxContain.mailboxId, dashboardType: DashboardType.normal ) )); handleEmailActionType( EmailActionType.preview, presentationEmailWithRouter, mailboxContain, ); } else { searchController.enableSearch(); _searchEmail(); final presentationEmailWithRouter = email.withRouteWeb(RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( emailId: email.id, dashboardType: DashboardType.search ) )); handleEmailActionType( EmailActionType.preview, presentationEmailWithRouter, mailboxContain, ); } } void _openEmailSearchedFromLocationBar({ required PresentationEmail email, SearchQuery? searchQuery, }) { final presentationEmailWithRouter = email.withRouteWeb(RouteUtils.createUrlWebLocationBar( AppRoutes.dashboard, router: NavigationRouter( emailId: email.id, searchQuery: searchQuery, dashboardType: DashboardType.search ) )); handleEmailActionType( EmailActionType.preview, presentationEmailWithRouter, email.findMailboxContain(mailboxDashBoardController.mapMailboxById), ); } void onDragMailBox(bool isDrag) { mailboxDashBoardController.onDragMailbox(isDrag); } Future swipeEmailAction( PresentationEmail email, DismissDirection direction, ) async { if (direction == DismissDirection.startToEnd) { ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread; markAsEmailRead(email, readActions, MarkReadAction.swipeOnThread); } else if (direction == DismissDirection.endToStart) { archiveMessage(email); } return false; } DismissDirection getSwipeDirection(bool isWebDesktop, SelectMode selectMode, PresentationEmail email) { if (isWebDesktop) { return DismissDirection.none; } if (selectMode == SelectMode.ACTIVE) { return DismissDirection.none; } return isInArchiveMailbox(email) || !hasArchiveMailbox() || email.mailboxContain?.isChildOfTeamMailboxes == true ? DismissDirection.startToEnd : DismissDirection.horizontal; } bool isInArchiveMailbox(PresentationEmail email) => email.mailboxContain?.isArchive == true; void scrollToTop() { if (listEmailController.hasClients) { listEmailController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); } } void _handleOpenEmailSearchedFromLocationBar({ required EmailId emailId, SearchQuery? searchQuery, }) { searchController.enableSearch(); if (searchQuery != null) { searchController.updateTextSearch(searchQuery.value); searchController.updateFilterEmail( textOption: Some(searchQuery), sortOrderTypeOption: Some(mailboxDashBoardController.currentSortOrder), ); if (currentContext != null) { FocusScope.of(currentContext!).unfocus(); } searchController.searchFocus.unfocus(); } _searchEmail(); _getEmailByIdFromLocationBar(emailId); mailboxDashBoardController.clearDashBoardAction(); } void _handleSearchEmailFromLocationBar(SearchQuery searchQuery) { dispatchState(Right(SearchingState())); searchController.enableSearch(); searchController.updateTextSearch(searchQuery.value); searchController.updateFilterEmail( textOption: Some(searchQuery), sortOrderTypeOption: Some(mailboxDashBoardController.currentSortOrder), ); if (currentContext != null) { FocusScope.of(currentContext!).unfocus(); } searchController.searchFocus.unfocus(); _searchEmail(); mailboxDashBoardController.clearDashBoardAction(); } void handleLoadMoreEmailsRequest() { log('ThreadController::handleLoadMoreEmailsRequest:'); if (isSearchActive) { _searchMoreEmails(); } else { _loadMoreEmails(); } } }