import 'dart:async'; import 'package:core/data/network/download/download_manager.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:debounce_throttle/debounce_throttle.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/properties/properties.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart'; import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/email/email_property.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/email/domain/state/add_a_label_to_a_thread_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/labels/remove_a_label_from_a_thread_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/add_a_label_to_a_thread_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/labels/remove_a_label_from_a_thread_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/print_email_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.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/search/email/presentation/search_email_controller.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_multiple_email_read_state.dart'; import 'package:tmail_ui_user/features/thread/domain/state/mark_as_star_multiple_email_state.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_email_read_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/model/email_in_thread_detail_info.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/state/get_emails_by_ids_state.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/state/get_thread_by_id_state.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_emails_by_ids_interactor.dart'; import 'package:tmail_ui_user/features/thread_detail/domain/usecases/get_thread_by_id_interactor.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/extension/handle_email_moved_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_email_ids_by_thread_id_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_emails_by_ids_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_get_thread_by_id_failure.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mark_multiple_emails_read_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_mail_shortcut_actions_extension.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_refresh_thread_detail_action.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/initialize_thread_detail_emails.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/labels/add_label_to_thread_extension.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/labels/remove_label_from_thread_extension.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_unread_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/quick_create_rule_from_collapsed_email_success.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_selected_email_updated.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/model/mail_view_shortcut_action_view_event.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_manager.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ThreadDetailController extends BaseController { final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor; final GetEmailsByIdsInteractor getEmailsByIdsInteractor; final MarkAsEmailReadInteractor _markAsEmailReadInteractor; final MarkAsStarEmailInteractor _markAsStarEmailInteractor; final PrintEmailInteractor _printEmailInteractor; final GetEmailContentInteractor _getEmailContentInteractor; final MarkAsStarMultipleEmailInteractor markAsStarMultipleEmailInteractor; final MarkAsMultipleEmailReadInteractor markAsMultipleEmailReadInteractor; final AddALabelToAThreadInteractor addALabelToAThreadInteractor; final RemoveALabelFromAThreadInteractor removeALabelFromAThreadInteractor; ThreadDetailController( this._getEmailIdsByThreadIdInteractor, this.getEmailsByIdsInteractor, this._markAsEmailReadInteractor, this._markAsStarEmailInteractor, this._printEmailInteractor, this._getEmailContentInteractor, this.markAsStarMultipleEmailInteractor, this.markAsMultipleEmailReadInteractor, this.addALabelToAThreadInteractor, this.removeALabelFromAThreadInteractor, ); final emailIdsPresentation = {}.obs; final currentExpandedEmailId = Rxn(); final currentEmailLoaded = Rxn(); final emailsInThreadDetailInfo = RxList(); late final EmailActionReactor emailActionReactor; final additionalProperties = Properties({ IndividualHeaderIdentifier.listPostHeader.value, EmailProperty.references, EmailProperty.messageId, }); final cachedEmailLoaded = {}; late final _threadGetDebouncer = Debouncer<({ThreadId? threadId, bool isSentMailbox})?>( const Duration(milliseconds: 500), initialValue: null, checkEquality: false, onChanged: (value) { if (_validateLoadThread(value?.threadId)) { consumeState(_getEmailIdsByThreadIdInteractor.execute( value!.threadId!, session!, accountId!, sentMailboxId!, ownEmailAddress, isSentMailbox: value.isSentMailbox, )); } }, ); final mailboxDashBoardController = Get.find(); final searchEmailController = Get.find(); final networkConnectionController = Get.find(); final threadDetailManager = Get.find(); final downloadManager = Get.find(); ScrollController? scrollController; CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor; bool loadThreadOnThreadChanged = false; bool isDisplayAllAttachments = false; FocusNode? keyboardShortcutFocusNode; StreamController? shortcutActionEventController; StreamSubscription? shortcutActionEventSubscription; AccountId? get accountId => mailboxDashBoardController.accountId.value; Session? get session => mailboxDashBoardController.sessionCurrent; MailboxId? get sentMailboxId => mailboxDashBoardController.getMailboxIdByRole( PresentationMailbox.roleSent, ); String get ownEmailAddress => mailboxDashBoardController.ownEmailAddress.value; bool get isSearchRunning { final isWebSearchRunning = mailboxDashBoardController .searchController .isSearchEmailRunning; final isMobileSearchRunning = searchEmailController .searchIsRunning .value == true; return isWebSearchRunning || isMobileSearchRunning; } bool get networkConnected => networkConnectionController.isNetworkConnectionAvailable(); bool get isThreadDetailEnabled => threadDetailManager.isThreadDetailEnabled; GlobalObjectKey? get expandedEmailHtmlViewKey => currentExpandedEmailId.value != null ? GlobalObjectKey(currentExpandedEmailId.value!) : null; @override void onInit() { super.onInit(); ever(mailboxDashBoardController.accountId, (accountId) { if (accountId == null) return; injectRuleFilterBindings(session, accountId); _createNewEmailRuleFilterInteractor = getBinding(); emailActionReactor = EmailActionReactor( _markAsEmailReadInteractor, _markAsStarEmailInteractor, _createNewEmailRuleFilterInteractor, _printEmailInteractor, _getEmailContentInteractor, ); }); ever(mailboxDashBoardController.selectedEmail, (presentationEmail) async { onSelectedEmailUpdated( presentationEmail, currentContext, ); }); ever(mailboxDashBoardController.threadDetailUIAction, (action) { if (action is UpdatedEmailKeywordsAction) { emailIdsPresentation [action.emailId] ?.keywords ?[action.updatedKeyword] = action.value; if (action.updatedKeyword == KeyWordIdentifierExtension.unsubscribeMail) { emailIdsPresentation [action.emailId] ?..emailHeader?.removeWhere((element) { return element.name == EmailProperty.headerUnsubscribeKey; }) ..listUnsubscribeHeader?.clear(); } } else if (action is EmailMovedAction) { handleEmailMovedAction(action); } else if (action is LoadThreadDetailAfterSelectedEmailAction) { _threadGetDebouncer.value = ( threadId: action.threadId, isSentMailbox: action.isSentMailbox, ); } else if (action is ReclaimMailViewKeyboardShortcutFocusAction) { refocusMailShortcutFocus(); } else if (action is ClearMailViewKeyboardShortcutFocusAction) { clearMailShortcutFocus(); } else if (action is ResyncThreadDetailWhenSettingChangedAction) { resyncThreadDetailWhenSettingChanged(); } // Reset [threadDetailUIAction] to original value mailboxDashBoardController.dispatchThreadDetailUIAction( ThreadDetailUIAction(), ); }); ever(mailboxDashBoardController.emailUIAction, (action) { if (action is RefreshThreadDetailAction) { mailboxDashBoardController.dispatchEmailUIAction(EmailUIAction()); handleRefreshThreadDetailAction( action, _getEmailIdsByThreadIdInteractor, ); } }); } bool _validateLoadThread(ThreadId? threadId) { return mailboxDashBoardController.selectedEmail.value?.threadId != null && threadId == mailboxDashBoardController.selectedEmail.value?.threadId && session != null && accountId != null && sentMailboxId != null && ownEmailAddress.isNotEmpty && networkConnected && isThreadDetailEnabled; } void reset() { emailIdsPresentation.clear(); emailsInThreadDetailInfo.clear(); scrollController?.dispose(); scrollController = null; currentExpandedEmailId.value = null; currentEmailLoaded.value = null; cachedEmailLoaded.clear(); _threadGetDebouncer.value = null; isDisplayAllAttachments = false; } @override void handleSuccessViewState(success) { if (success is GetThreadByIdSuccess) { handleGetEmailIdsByThreadIdSuccess(success); initializeThreadDetailEmails(success); } else if (success is GetEmailsByIdsSuccess) { handleGetEmailsByIdsSuccess(success); } else if (success is MarkAsEmailReadSuccess) { markCollapsedEmailReadSuccess(success); } else if (success is MarkAsMultipleEmailReadAllSuccess) { handleMarkMultipleEmailsReadSuccess( success, success.readActions, success.emailIds, ); } else if (success is MarkAsMultipleEmailReadHasSomeEmailFailure) { handleMarkMultipleEmailsReadSuccess( success, success.readActions, success.successEmailIds, ); } else if (success is CreateNewRuleFilterSuccess) { quickCreateRuleFromCollapsedEmailSuccess(success); } else if (success is AddALabelToAThreadSuccess) { handleAddLabelToThreadSuccess(success); } else if (success is RemoveALabelFromAThreadSuccess) { handleRemoveLabelFromThreadSuccess(success); } else { super.handleSuccessViewState(success); } } @override void handleFailureViewState(failure) { if (failure is GetThreadByIdFailure) { handleGetThreadByIdFailure(failure); } else if (failure is GetEmailsByIdsFailure) { if (failure.updateCurrentThreadDetail) return; showRetryToast(failure); } else if (failure is PrintEmailFailure) { if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, AppLocalizations.of(currentContext!).unknownError, ); } } else if (failure is MarkAsMultipleEmailReadFailure || failure is MarkAsStarMultipleEmailFailure) { toastManager.showMessageFailure(failure as FeatureFailure); } else if (failure is AddALabelToAThreadFailure) { handleAddLabelToThreadFailure(failure); } else if (failure is RemoveALabelFromAThreadFailure) { handleRemoveLabelFromThreadFailure(failure); } else { super.handleFailureViewState(failure); } } @override void onClose() { onKeyboardShortcutDispose(); super.onClose(); } }