TF-3715 Thread Detail create resuable actions

TF-3715 Thread Detail refactor actions for expanded email view

TF-3715 Thread Detail apply actions for collapsed email view

TF-3715 Thread Detail fix unit tests

TF-3715 Thread Detail add await for _getEmailContent in composer

TF-3715 Thread Detail remove unused emailLoaded in ThreadDetailAppBar

TF-3715 Thread Detail hide print action when isMobile

TF-3715 Thread Detail allow mark as read in EmailActionReactor

TF-3715 Thread Detail fix loadMoreThreadDetailEmails unit test

TF-3715 Thread Detail fix action small tap region

TF-3715 Thread Detail fix cannot permanent delete email

TF-3715 Thread Detail prevent close when move email

TF-3715 Thread Detail fix refresh when mark star selected collapsed email

TF-3715 Thread Detail update print action display logic

TF-3715 Thread Detail fix duplicate mark star in app bar

TF-3715 Thread Detail create new transformer for reply forward empty content email

TF-3715 Thread Detail fix email not load after open multiple times

TF-3715 Thread Detail fix close when move expanded email

TF-3715 Thread Detail fix duplicate and missing actions

TF-3715 Thread Detail restore editAsNewEmail action to _getEmailContent in setupEmailContent

TF-3715 Thread Detail update print action name

TF-3715 Thread Detail handle move and delete email with 1 item in thread detail

TF-3715 Thread Detail fix tap outside to close context menu

TF-3715 Thread Detail fix create rule and compose from recipient info

TF-3715 Thread Detail handle move and delete email with 1 item in thread detail

TF-3715 Thread Detail Fix cannot create email rule

TF-3715 Thread Detail Fix crash composer

TF-3715 Thread Detail Fix cannot unsubscribe

TF-3715 Thread Detail Fix duplicate actions on single email

TF-3715 Thread Detail Fix refresh view when reply/forward

TF-3715 Thread Detail Revert old logic to _getEmailContent

TF-3715 Thread Detail Update specific cases for UpdatedEmailKeywordsAction

TF-3715 Thread Detail Fix concurrent modification during iteration

TF-3715 Thread Detail Fix unsubscribe for all thread detail emails

TF-3715 Thread Detail Remove unnecessary Future in reactor

TF-3715 Thread Detail Remove dependency from SingleEmailController

TF-3715 Thread Detail Apply individual list post and unsubscribe header

TF-3715 Thread Detail Fix unsubscribe collapsed email

TF-3715 Thread Detail Conditionally inject rule filter interactor
This commit is contained in:
DatDang
2025-05-16 15:37:07 +07:00
committed by Dat H. Pham
parent 421fa321ba
commit 8a2ea62987
38 changed files with 2135 additions and 715 deletions
@@ -43,11 +43,9 @@ import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/base/state/button_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart';
import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart';
import 'package:tmail_ui_user/features/email/domain/model/email_print.dart';
import 'package:tmail_ui_user/features/email/domain/model/event_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_action.dart';
@@ -110,39 +108,35 @@ import 'package:tmail_ui_user/features/email/presentation/model/composer_argumen
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/email/presentation/model/eml_previewer.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_action_reactor/email_action_reactor.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_list/attachment_list_bottom_sheet_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_list/attachment_list_dialog_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_dialog_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/html_attachment_previewer.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/pdf_viewer/pdf_viewer.dart';
import 'package:tmail_ui_user/features/email_previewer/email_previewer_dialog_view.dart';
import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.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/get_mailbox_contain_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/download/download_task_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.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/state/get_all_identities_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/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/datetime_extension.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/close_thread_detail_action.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/focus_thread_detail_expanded_email.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/error/capability_validator.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/localizations/localization_service.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/dialog_router.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';
@@ -203,6 +197,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
Identity? _identitySelected;
ButtonState? _printEmailButtonState;
final obxListeners = <Worker>[];
late final EmailActionReactor emailActionReactor;
final StreamController<Either<Failure, Success>> _downloadProgressStateController =
StreamController<Either<Failure, Success>>.broadcast();
@@ -254,6 +249,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_injectCalendarEventBindings(session, accountId);
_registerObxStreamListener();
_listenDownloadAttachmentProgressState();
emailActionReactor = EmailActionReactor(
_markAsEmailReadInteractor,
_markAsStarEmailInteractor,
_createNewEmailRuleFilterInteractor,
_printEmailInteractor,
_getEmailContentInteractor,
_downloadAttachmentForWebInteractor,
);
super.onInit();
}
@@ -408,6 +411,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
obxListeners.add(ever(mailboxDashBoardController.viewState, (viewState) {
viewState.map((success) {
if (success is UnsubscribeEmailSuccess) {
if (success.emailId != _currentEmailId) return;
emailUnsubscribe.value = null;
}
});
@@ -766,13 +770,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
PresentationMailbox? getMailboxContain(PresentationEmail email) {
if (mailboxDashBoardController.selectedMailbox.value == null) {
return email.findMailboxContain(mailboxDashBoardController.mapMailboxById);
} else {
return mailboxDashBoardController.searchController.isSearchEmailRunning
? email.findMailboxContain(mailboxDashBoardController.mapMailboxById)
: mailboxDashBoardController.selectedMailbox.value;
}
return mailboxDashBoardController.getMailboxContain(email);
}
void markAsEmailRead(
@@ -1200,81 +1198,28 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void moveToMailbox(BuildContext context, PresentationEmail email) async {
final currentMailbox = getMailboxContain(email);
if (currentMailbox != null && accountId != null) {
final arguments = DestinationPickerArguments(
if (session != null && accountId != null) {
final moveActionRequest = await emailActionReactor.moveToMailbox(
session!,
accountId!,
MailboxActions.moveEmail,
session,
mailboxIdSelected: currentMailbox.mailboxId
email,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
);
final destinationMailbox = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments)
: await push(AppRoutes.destinationPicker, arguments: arguments);
if (destinationMailbox != null &&
destinationMailbox is PresentationMailbox &&
session != null &&
context.mounted
) {
_dispatchMoveToAction(
context,
accountId!,
session!,
email,
currentMailbox,
destinationMailbox);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
}
}
void _dispatchMoveToAction(
BuildContext context,
AccountId accountId,
Session session,
PresentationEmail emailSelected,
PresentationMailbox currentMailbox,
PresentationMailbox destinationMailbox
) {
if (destinationMailbox.isTrash) {
_moveToTrashAction(
context,
session,
accountId,
MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToTrash),
{emailSelected.id!: emailSelected.hasRead});
} else if (destinationMailbox.isSpam) {
_moveToSpamAction(
context,
session,
accountId,
MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToSpam),
{emailSelected.id!: emailSelected.hasRead});
} else {
_moveToMailbox(
context,
session,
accountId,
MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToMailbox,
destinationPath: destinationMailbox.mailboxPath),
{emailSelected.id!: emailSelected.hasRead});
}
}
void _moveToMailbox(
BuildContext context,
Session session,
@@ -1282,7 +1227,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus,
) {
closeEmailView(context: context);
consumeState(_moveToMailboxInteractor.execute(
session,
accountId,
@@ -1332,94 +1276,71 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void moveToTrash(BuildContext context, PresentationEmail email) {
final trashMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleTrash);
final currentMailbox = getMailboxContain(email);
if (session != null && accountId != null && currentMailbox != null && trashMailboxId != null) {
_moveToTrashAction(
context,
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.moveToTrash(
email,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
MoveToMailboxRequest(
{currentMailbox.id: [email.id!]},
trashMailboxId,
MoveAction.moving,
EmailActionType.moveToTrash),
{email.id!: email.hasRead},
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
}
}
void _moveToTrashAction(
BuildContext context,
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus,
) {
closeEmailView(context: context);
mailboxDashBoardController.moveToMailbox(
session,
accountId,
moveRequest,
emailIdsWithReadStatus,
);
}
void moveToSpam(BuildContext context, PresentationEmail email) {
final spamMailboxId = mailboxDashBoardController.spamMailboxId;
final currentMailbox = getMailboxContain(email);
if (session != null && accountId != null && currentMailbox != null && spamMailboxId != null) {
_moveToSpamAction(
context,
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.moveToSpam(
email,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
MoveToMailboxRequest(
{currentMailbox.id: [email.id!]},
spamMailboxId,
MoveAction.moving,
EmailActionType.moveToSpam),
{email.id!: email.hasRead},
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
}
}
void unSpam(BuildContext context, PresentationEmail email) {
final spamMailboxId = mailboxDashBoardController.spamMailboxId;
final inboxMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleInbox);
if (session != null && accountId != null && spamMailboxId != null && inboxMailboxId != null) {
_moveToSpamAction(
context,
if (session != null && accountId != null) {
final moveActionRequest = emailActionReactor.unSpam(
email,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
MoveToMailboxRequest(
{spamMailboxId: [email.id!]},
inboxMailboxId,
MoveAction.moving,
EmailActionType.unSpam),
{email.id!: email.hasRead},
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
}
}
void _moveToSpamAction(
BuildContext context,
Session session,
AccountId accountId,
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus,
) {
closeEmailView(context: context);
mailboxDashBoardController.moveToMailbox(
session,
accountId,
moveRequest,
emailIdsWithReadStatus,
);
}
void markAsStarEmail(
PresentationEmail presentationEmail,
MarkStarAction markStarAction,
@@ -1451,13 +1372,34 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) {
switch(actionType) {
case EmailActionType.markAsUnread:
markAsEmailRead(presentationEmail, ReadActions.markAsUnread, MarkReadAction.tap);
if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsEmailRead(
session!,
accountId!,
presentationEmail,
readAction: ReadActions.markAsUnread,
));
}
break;
case EmailActionType.markAsStarred:
markAsStarEmail(presentationEmail, MarkStarAction.markStar);
if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsStarEmail(
session!,
accountId!,
presentationEmail,
markStarAction: MarkStarAction.markStar,
));
}
break;
case EmailActionType.unMarkAsStarred:
markAsStarEmail(presentationEmail, MarkStarAction.unMarkStar);
if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsStarEmail(
session!,
accountId!,
presentationEmail,
markStarAction: MarkStarAction.unMarkStar,
));
}
break;
case EmailActionType.moveToMailbox:
moveToMailbox(context, presentationEmail);
@@ -1495,69 +1437,38 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
case EmailActionType.reply:
_replyEmail(presentationEmail);
break;
case EmailActionType.replyAll:
case EmailActionType.replyToList:
case EmailActionType.forward:
pressEmailAction(actionType, presentationEmail);
break;
default:
break;
}
}
void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) {
if (responsiveUtils.isScreenWithShortestSide(context)) {
Get.bottomSheet(
PointerInterceptor(
child: EmailAddressBottomSheetBuilder(
imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) =>
copyEmailAddress(context, emailAddress),
onComposeEmailAction: composeEmailFromEmailAddress,
onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
quickCreatingRule(context, emailAddress),
),
),
useRootNavigator: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.only(
topStart: Radius.circular(16.0),
topEnd: Radius.circular(16.0),
),
),
isScrollControlled: true,
enableDrag: false,
backgroundColor: Colors.transparent,
);
} else {
Get.dialog(
PointerInterceptor(
child: EmailAddressDialogBuilder(
imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) =>
copyEmailAddress(context, emailAddress),
onComposeEmailAction: composeEmailFromEmailAddress,
onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
quickCreatingRule(context, emailAddress),
),
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
}
void openEmailAddressDialog(EmailAddress emailAddress) {
if (session == null || accountId == null) return;
void copyEmailAddress(BuildContext context, EmailAddress emailAddress) {
Clipboard.setData(ClipboardData(text: emailAddress.emailAddress));
appToast.showToastSuccessMessage(
context,
AppLocalizations.of(context).email_address_copied_to_clipboard,
emailActionReactor.openEmailAddressDialog(
session!,
accountId!,
emailAddress: emailAddress,
responsiveUtils: responsiveUtils,
imagePaths: imagePaths,
appToast: appToast,
onComposeEmailFromEmailAddressRequest: (emailAddress) {
popBack();
mailboxDashBoardController.openComposer(
ComposerArguments.fromEmailAddress(emailAddress),
);
},
onQuickCreateRuleRequest: (quickCreateRuleStream) {
consumeState(quickCreateRuleStream);
},
);
}
void composeEmailFromEmailAddress(EmailAddress emailAddress) {
popBack();
mailboxDashBoardController.openComposer(ComposerArguments.fromEmailAddress(emailAddress));
}
Future<void> openMailToLink(Uri? uri) async {
if (uri == null) return;
@@ -1577,34 +1488,18 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void deleteEmailPermanently(BuildContext context, PresentationEmail email) {
if (responsiveUtils.isMobile(context)) {
(ConfirmationDialogActionSheetBuilder(context)
..messageText(DeleteActionType.single.getContentDialog(context))
..onCancelAction(AppLocalizations.of(context).cancel, () => popBack())
..onConfirmAction(DeleteActionType.single.getConfirmActionName(context), () => _deleteEmailPermanentlyAction(context, email)))
.show();
} else {
Get.dialog(
PointerInterceptor(child: ConfirmationDialogBuilder(
key: const Key('confirm_dialog_delete_email_permanently'),
imagePath: imagePaths,
title: DeleteActionType.single.getTitleDialog(context),
textContent: DeleteActionType.single.getContentDialog(context),
confirmText: DeleteActionType.single.getConfirmActionName(context),
cancelText: AppLocalizations.of(context).cancel,
onConfirmButtonAction: () => _deleteEmailPermanentlyAction(context, email),
onCancelButtonAction: popBack,
onCloseButtonAction: popBack,
)),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
}
void _deleteEmailPermanentlyAction(BuildContext context, PresentationEmail email) {
popBack();
closeEmailView(context: context);
mailboxDashBoardController.deleteEmailPermanently(email);
emailActionReactor.deleteEmailPermanently(
email,
onDeleteEmailRequest: (email) {
popBack();
mailboxDashBoardController.deleteEmailPermanently(email);
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
},
responsiveUtils: responsiveUtils,
imagePaths: imagePaths,
);
}
void _handleSendReceiptToSenderAction(BuildContext context) {
@@ -1793,32 +1688,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
popBack();
if (accountId != null && session != null) {
final arguments = RulesFilterCreatorArguments(
accountId!,
consumeState(emailActionReactor.quickCreateRule(
session!,
emailAddress: emailAddress);
final newRuleFilterRequest = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.rulesFilterCreator, arguments: arguments)
: await push(AppRoutes.rulesFilterCreator, arguments: arguments);
if (newRuleFilterRequest is CreateNewEmailRuleFilterRequest) {
_createNewRuleFilterAction(accountId!, newRuleFilterRequest);
}
}
}
void _createNewRuleFilterAction(
AccountId accountId,
CreateNewEmailRuleFilterRequest ruleFilterRequest
) async {
try {
_createNewEmailRuleFilterInteractor = Get.find<CreateNewEmailRuleFilterInteractor>();
} catch (e) {
logError('SingleEmailController::onInit(): ${e.toString()}');
}
if (_createNewEmailRuleFilterInteractor != null) {
consumeState(_createNewEmailRuleFilterInteractor!.execute(accountId, ruleFilterRequest));
accountId!,
emailAddress: emailAddress,
));
}
}
@@ -1929,70 +1803,30 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) {
showConfirmDialogAction(
context,
'',
AppLocalizations.of(context).unsubscribe,
onConfirmAction: () {
if (emailUnsubscribe.value?.httpLinks.isNotEmpty == true) {
_handleUnsubscribeMailByHttpsLink(
context: context,
emailId: presentationEmail.id!,
httpLinks: emailUnsubscribe.value!.httpLinks
);
} else if (emailUnsubscribe.value?.mailtoLinks.isNotEmpty == true) {
_handleUnsubscribeMailByMailtoLink(
context: context,
emailId: presentationEmail.id!,
mailtoLinks: emailUnsubscribe.value!.mailtoLinks
);
}
emailActionReactor.unsubscribeEmail(
presentationEmail,
emailUnsubscribe: emailUnsubscribe.value,
onUnsubscribeByHttpsLink: mailboxDashBoardController.unsubscribeMail,
onUnsubscribeByMailtoLink: (emailId, navigationRouter) {
mailboxDashBoardController.openComposer(
ComposerArguments.fromUnsubscribeMailtoLink(
listEmailAddress: navigationRouter.listEmailAddress,
subject: navigationRouter.subject,
body: navigationRouter.body,
previousEmailId: emailId,
)
);
},
showAsBottomSheet: true,
dialogMargin: MediaQuery.paddingOf(context).add(const EdgeInsets.only(bottom: 12)),
title: AppLocalizations.of(context).unsubscribeMail,
listTextSpan: [
TextSpan(text: AppLocalizations.of(context).unsubscribeMailDialogMessage),
TextSpan(
text: ' ${presentationEmail.getSenderName()}',
style: ThemeUtils.textStyleM3BodyMedium1.copyWith(
fontWeight: FontWeight.w700,
),
),
const TextSpan(text: ' ?'),
]
);
}
void _handleUnsubscribeMailByHttpsLink({
required BuildContext context,
required EmailId emailId,
required List<String> httpLinks
}) {
log('SingleEmailController::_handleUnsubscribeMailByHttpsLink:httpLinks: $httpLinks');
mailboxDashBoardController.unsubscribeMail(emailId);
AppUtils.launchLink(httpLinks.first);
}
void _handleUnsubscribeMailByMailtoLink({
required BuildContext context,
required EmailId emailId,
required List<String> mailtoLinks
}) {
log('SingleEmailController::_handleUnsubscribeMailByMailtoLink:mailtoLinks: $mailtoLinks');
final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink(mailtoLinks.first);
mailboxDashBoardController.openComposer(
ComposerArguments.fromUnsubscribeMailtoLink(
listEmailAddress: navigationRouter.listEmailAddress,
subject: navigationRouter.subject,
body: navigationRouter.body,
previousEmailId: emailId,
)
);
}
void archiveMessage(BuildContext context, PresentationEmail email) {
mailboxDashBoardController.archiveMessage(context, email);
emailActionReactor.archiveMessage(
email,
onArchiveEmailRequest: (presentationEmail) {
mailboxDashBoardController.archiveMessage(context, presentationEmail);
},
);
}
void _printEmail(BuildContext context, PresentationEmail email) {
@@ -2003,40 +1837,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_printEmailButtonState = ButtonState.disabled;
final locale = Localizations.localeOf(context);
final appLocalizations = AppLocalizations.of(context);
consumeState(
_printEmailInteractor.execute(
EmailPrint(
appName: appLocalizations.app_name,
userName: mailboxDashBoardController.ownEmailAddress.value,
attachments: currentEmailLoaded.value!.attachments,
emailContent: currentEmailLoaded.value!.htmlContent,
fromPrefix: appLocalizations.from_email_address_prefix,
toPrefix: appLocalizations.to_email_address_prefix,
ccPrefix: appLocalizations.cc_email_address_prefix,
bccPrefix: appLocalizations.bcc_email_address_prefix,
replyToPrefix: appLocalizations.replyToEmailAddressPrefix,
titleAttachment: appLocalizations.attachments.toLowerCase(),
toAddress: email.to?.listEmailAddressToString(isFullEmailAddress: true),
ccAddress: email.cc?.listEmailAddressToString(isFullEmailAddress: true),
bccAddress: email.bcc?.listEmailAddressToString(isFullEmailAddress: true),
replyToAddress: email.replyTo?.listEmailAddressToString(isFullEmailAddress: true),
sender: email.from?.isNotEmpty == true
? email.from!.first
: null,
receiveTime: email.getReceivedAt(
locale.toLanguageTag(),
pattern: email.receivedAt
?.value
.toLocal()
.toPatternForPrinting(locale.toLanguageTag()),
),
subject: email.subject,
)
)
);
consumeState(emailActionReactor.printEmail(
email,
ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value,
emailLoaded: currentEmailLoaded.value!,
));
}
void _showMessageWhenStartingEmailPrinting() {
@@ -2181,34 +1986,46 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
}
void _downloadMessageAsEML(PresentationEmail presentationEmail) {
final emlAttachment = presentationEmail.createEMLAttachment();
if (emlAttachment.blobId == null) {
consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(exception: NotFoundEmailBlobIdException()))));
return;
}
if (accountId == null || session == null) return;
downloadAttachmentForWeb(emlAttachment);
consumeState(emailActionReactor.downloadMessageAsEML(
session!,
accountId!,
presentationEmail,
downloadProgressStateController: _downloadProgressStateController,
));
}
void _editAsNewEmail(PresentationEmail presentationEmail) {
if (accountId == null || session == null) return;
mailboxDashBoardController.openComposer(
ComposerArguments.editAsNewEmail(presentationEmail),
emailActionReactor.editAsNewEmail(
presentationEmail,
onEditAsEmailRequest: (presentationEmail) {
mailboxDashBoardController.openComposer(
ComposerArguments.editAsNewEmail(presentationEmail),
);
},
);
}
void _replyEmail(PresentationEmail presentationEmail) {
mailboxDashBoardController.openComposer(
ComposerArguments.replyEmail(
presentationEmail: presentationEmail,
content: currentEmailLoaded.value?.htmlContent ?? '',
inlineImages: currentEmailLoaded.value?.inlineImages ?? [],
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: currentEmailLoaded.value?.emailCurrent?.messageId,
references: currentEmailLoaded.value?.emailCurrent?.references,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost,
)
emailActionReactor.replyEmail(
presentationEmail,
emailLoaded: currentEmailLoaded.value,
onReplyEmailRequest: (presentationEmail, emailLoaded) {
mailboxDashBoardController.openComposer(
ComposerArguments.replyEmail(
presentationEmail: presentationEmail,
content: emailLoaded?.htmlContent,
inlineImages: emailLoaded?.inlineImages,
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: emailLoaded?.emailCurrent?.messageId,
references: emailLoaded?.emailCurrent?.references,
listPost: emailLoaded?.emailCurrent?.listPost,
)
);
},
);
}
+33 -127
View File
@@ -11,22 +11,15 @@ import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/email_extension.dart';
import 'package:model/extensions/list_email_address_extension.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/widget/optional_expanded.dart';
import 'package:tmail_ui_user/features/base/widget/optional_scroll.dart';
import 'package:tmail_ui_user/features/base/widget/popup_menu/popup_menu_item_action_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/model/context_item_email_action.dart';
import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_action_banner_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_detail_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart';
@@ -39,12 +32,10 @@ import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_loa
import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/mail_unsubscribed_banner.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/view_entire_message_with_message_clipped_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/verify_display_overlay_view_on_iframe_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widgets/vacation_notification_message_widget.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
class EmailView extends GetWidget<SingleEmailController> {
@@ -97,11 +88,15 @@ class EmailView extends GetWidget<SingleEmailController> {
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction(
context: context,
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail,
position: position,
emailLoaded: controller.currentEmailLoaded.value,
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
username: controller.session?.username,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
additionalActions: [],
),
supportBackAction: !isInsideThreadDetailView,
appBarDecoration: isInsideThreadDetailView
@@ -278,18 +273,38 @@ class EmailView extends GetWidget<SingleEmailController> {
sMimeStatus: controller.currentEmailLoaded.value?.sMimeStatus,
emailUnsubscribe: controller.emailUnsubscribe.value,
maxBodyHeight: maxBodyHeight,
openEmailAddressDetailAction: controller.openEmailAddressDialog,
openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
isInsideThreadDetailView: isInsideThreadDetailView,
emailLoaded: controller.currentEmailLoaded.value,
onMoreActionClick: (email, position) => _handleMoreEmailAction(
context: context,
presentationEmail: email,
onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail,
position: position,
emailLoaded: controller.currentEmailLoaded.value,
responsiveUtils: controller.responsiveUtils,
imagePaths: controller.imagePaths,
username: controller.session?.username,
handleEmailAction: (email, action) => controller.handleEmailAction(context, email, action),
additionalActions: [
EmailActionType.forward,
EmailActionType.replyAll,
EmailActionType.replyToList,
EmailActionType.printAll,
if (controller.responsiveUtils.isMobile(context))
EmailActionType.moveToMailbox,
if (!controller.responsiveUtils.isDesktop(context)) ...[
EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash,
EmailActionType.deletePermanently,
],
],
),
onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
mailboxContain: presentationEmail.findMailboxContain(
controller.mailboxDashBoardController.mapMailboxById,
),
)),
Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail,
@@ -340,7 +355,7 @@ class EmailView extends GetWidget<SingleEmailController> {
.ownEmailAddress
.value,
onMailtoAttendeesAction: controller.handleMailToAttendees,
openEmailAddressDetailAction: controller.openEmailAddressDialog,
openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
isFree: controller.isCalendarEventFree,
listEmailAddressSender: emailAddressSender ?? [],
isPortraitMobile: controller
@@ -496,113 +511,4 @@ class EmailView extends GetWidget<SingleEmailController> {
return usernameEvent.isNotEmpty && titleEvent.isNotEmpty;
}
bool canDeletePermanently(PresentationEmail email) {
return email.mailboxContain?.isTrash
?? email.mailboxContain?.isSpam
?? false;
}
void _handleMoreEmailAction({
required BuildContext context,
required PresentationEmail presentationEmail,
RelativeRect? position,
EmailLoaded? emailLoaded,
}) {
final mailboxContain = controller.getMailboxContain(presentationEmail);
final isChildOfTeamMailboxes = mailboxContain?.isChildOfTeamMailboxes ?? false;
final isSpam = mailboxContain?.isSpam ?? false;
final isArchive = mailboxContain?.isArchive ?? false;
final isTemplates = mailboxContain?.isTemplates ?? false;
final isUnsubscribeEnabled = !presentationEmail.isSubscribed &&
controller.emailUnsubscribe.value != null;
final isCreateRuleEnabled = presentationEmail.from?.isNotEmpty == true;
final isDownloadMessageAsEMLEnabled =
PlatformInfo.isWeb && PlatformInfo.isCanvasKit;
final emailActions = [
if (isInsideThreadDetailView) ...[
EmailActionType.forward,
if (presentationEmail.getCountMailAddressWithoutMe(controller.session?.username.value ?? '') > 1)
EmailActionType.replyAll,
if (EmailUtils.isReplyToListEnabled(emailLoaded?.emailCurrent?.listPost ?? ''))
EmailActionType.replyToList,
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
EmailActionType.printAll,
if (controller.responsiveUtils.isMobile(context))
EmailActionType.moveToMailbox,
if (!controller.responsiveUtils.isDesktop(context)) ...[
presentationEmail.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred,
canDeletePermanently(presentationEmail)
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
],
],
EmailActionType.markAsUnread,
if (!isChildOfTeamMailboxes)
isSpam ? EmailActionType.unSpam : EmailActionType.moveToSpam,
if (isCreateRuleEnabled)
EmailActionType.createRule,
if (isUnsubscribeEnabled)
EmailActionType.unsubscribe,
if (!isArchive)
EmailActionType.archiveMessage,
if (isDownloadMessageAsEMLEnabled)
EmailActionType.downloadMessageAsEML,
if (!isTemplates)
EmailActionType.editAsNewEmail,
];
if (position == null) {
final contextMenuActions = emailActions
.map((action) => ContextItemEmailAction(
action,
AppLocalizations.of(context),
controller.imagePaths,
))
.toList();
controller.mailboxDashBoardController.openBottomSheetContextMenu(
context: context,
itemActions: contextMenuActions,
onContextMenuActionClick: (menuAction) {
popBack();
controller.handleEmailAction(
context,
presentationEmail,
menuAction.action,
);
},
);
} else {
final popupMenuItems = emailActions.map((actionType) {
return PopupMenuItem(
padding: EdgeInsets.zero,
child: PopupMenuItemActionWidget(
menuAction: PopupMenuItemEmailAction(
actionType,
AppLocalizations.of(context),
controller.imagePaths,
),
menuActionClick: (menuAction) {
popBack();
controller.handleEmailAction(
context,
presentationEmail,
menuAction.action,
);
},
),
);
}).toList();
controller.mailboxDashBoardController.openPopupMenu(
context,
position,
popupMenuItems,
);
}
}
}
@@ -144,8 +144,8 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.replyEmail({
required PresentationEmail presentationEmail,
required String content,
required List<Attachment> inlineImages,
required String? content,
required List<Attachment>? inlineImages,
Role? mailboxRole,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
@@ -163,8 +163,8 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.replyToListEmail({
required PresentationEmail presentationEmail,
required String content,
required List<Attachment> inlineImages,
required String? content,
required List<Attachment>? inlineImages,
Role? mailboxRole,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
@@ -182,8 +182,8 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.replyAllEmail({
required PresentationEmail presentationEmail,
required String content,
required List<Attachment> inlineImages,
required String? content,
required List<Attachment>? inlineImages,
Role? mailboxRole,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
@@ -201,9 +201,9 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.forwardEmail({
required PresentationEmail presentationEmail,
required String content,
required List<Attachment> attachments,
required List<Attachment> inlineImages,
required String? content,
required List<Attachment>? attachments,
required List<Attachment>? inlineImages,
MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references,
}) => ComposerArguments(
@@ -0,0 +1,886 @@
import 'dart:async';
import 'package:core/data/network/config/dynamic_url_interceptors.dart';
import 'package:core/domain/extensions/datetime_extension.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart';
import 'package:core/presentation/views/dialog/confirmation_dialog_builder.dart';
import 'package:core/utils/app_logger.dart';
import 'package:core/utils/platform_info.dart';
import 'package:dartz/dartz.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.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/user_name.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/download/download_task_id.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/mark_star_action.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/email/read_actions.dart';
import 'package:model/extensions/email_address_extension.dart';
import 'package:model/extensions/list_email_address_extension.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/extensions/presentation_mailbox_extension.dart';
import 'package:model/extensions/session_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/mixin/message_dialog_action_mixin.dart';
import 'package:tmail_ui_user/features/base/mixin/popup_context_menu_action_mixin.dart';
import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/model/email_print.dart';
import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_action.dart';
import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/download_attachment_for_web_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/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/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_bottom_sheet_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_address_dialog_builder.dart';
import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart';
import 'package:tmail_ui_user/features/manage_account/domain/model/create_new_email_rule_filter_request.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/create_new_email_rule_filter_interactor.dart';
import 'package:tmail_ui_user/features/rules_filter_creator/presentation/model/rules_filter_creator_arguments.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/dialog_router.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_utils.dart';
import 'package:uuid/uuid.dart';
class EmailActionReactor with MessageDialogActionMixin, PopupContextMenuActionMixin {
const EmailActionReactor(
this._markAsEmailReadInteractor,
this._markAsStarEmailInteractor,
this._createNewEmailRuleFilterInteractor,
this._printEmailInteractor,
this._getEmailContentInteractor,
this._downloadAttachmentForWebInteractor,
);
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
final MarkAsStarEmailInteractor _markAsStarEmailInteractor;
final CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
final PrintEmailInteractor _printEmailInteractor;
final GetEmailContentInteractor _getEmailContentInteractor;
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
Stream<Either<Failure, Success>> markAsEmailRead(
Session session,
AccountId accountId,
PresentationEmail presentationEmail, {
required ReadActions readAction,
}) {
return _markAsEmailReadInteractor.execute(
session,
accountId,
presentationEmail.id!,
readAction,
MarkReadAction.tap,
presentationEmail.mailboxContain?.mailboxId,
);
}
Stream<Either<Failure, Success>> markAsStarEmail(
Session session,
AccountId accountId,
PresentationEmail presentationEmail, {
required MarkStarAction markStarAction,
}) {
return _markAsStarEmailInteractor.execute(
session,
accountId,
presentationEmail.id!,
markStarAction,
);
}
Future<({
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus
})?> moveToMailbox(
Session session,
AccountId accountId,
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
required PresentationMailbox? selectedMailbox,
required bool isSearchEmailRunning,
}) async {
final moveRequest = await _moveToMailbox(
session,
accountId,
presentationEmail,
mapMailbox: mapMailbox,
isSearchEmailRunning: isSearchEmailRunning,
selectedMailbox: selectedMailbox,
);
if (moveRequest == null) return null;
return (
moveRequest: moveRequest.moveRequest,
emailIdsWithReadStatus: moveRequest.emailIdsWithReadStatus,
);
}
({
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus
})? moveToTrash(
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
required PresentationMailbox? selectedMailbox,
required bool isSearchEmailRunning,
required Map<Role, MailboxId> mapDefaultMailboxIdByRole,
}) {
final trashMailboxId = mapDefaultMailboxIdByRole[
PresentationMailbox.roleTrash
];
final currentMailbox = _getMailboxContain(
presentationEmail,
mapMailbox: mapMailbox,
isSearchEmailRunning: isSearchEmailRunning,
selectedMailbox: selectedMailbox,
);
if (trashMailboxId == null || currentMailbox == null) return null;
return (
moveRequest: MoveToMailboxRequest(
{currentMailbox.id: [presentationEmail.id!]},
trashMailboxId,
MoveAction.moving,
EmailActionType.moveToTrash),
emailIdsWithReadStatus: {presentationEmail.id!: presentationEmail.hasRead},
);
}
void deleteEmailPermanently(
PresentationEmail presentationEmail, {
required void Function(PresentationEmail email) onDeleteEmailRequest,
required ResponsiveUtils responsiveUtils,
required ImagePaths imagePaths,
}) async {
if (currentContext == null || !currentContext!.mounted) return;
if (responsiveUtils.isMobile(currentContext!)) {
await (ConfirmationDialogActionSheetBuilder(currentContext!)
..messageText(DeleteActionType.single.getContentDialog(currentContext!))
..onCancelAction(AppLocalizations.of(currentContext!).cancel, () => popBack())
..onConfirmAction(
DeleteActionType.single.getConfirmActionName(currentContext!),
() => onDeleteEmailRequest.call(presentationEmail)
)
).show();
} else {
await Get.dialog(
PointerInterceptor(child: ConfirmationDialogBuilder(
key: const Key('confirm_dialog_delete_email_permanently'),
imagePath: imagePaths,
title: DeleteActionType.single.getTitleDialog(currentContext!),
textContent: DeleteActionType.single.getContentDialog(currentContext!),
cancelText: DeleteActionType.single.getConfirmActionName(currentContext!),
confirmText: AppLocalizations.of(currentContext!).cancel,
onCancelButtonAction: () => onDeleteEmailRequest.call(presentationEmail),
onConfirmButtonAction: popBack,
onCloseButtonAction: popBack,
)),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
}
({
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus
})? moveToSpam(
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
required PresentationMailbox? selectedMailbox,
required bool isSearchEmailRunning,
required Map<Role, MailboxId> mapDefaultMailboxIdByRole,
}) {
final spamMailboxId = _spamMailboxId(mapDefaultMailboxIdByRole);
final currentMailbox = _getMailboxContain(
presentationEmail,
mapMailbox: mapMailbox,
isSearchEmailRunning: isSearchEmailRunning,
selectedMailbox: selectedMailbox,
);
if (spamMailboxId == null || currentMailbox == null) return null;
return (
moveRequest: MoveToMailboxRequest(
{currentMailbox.id: [presentationEmail.id!]},
spamMailboxId,
MoveAction.moving,
EmailActionType.moveToSpam),
emailIdsWithReadStatus: {presentationEmail.id!: presentationEmail.hasRead},
);
}
({
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus
})? unSpam(
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
required PresentationMailbox? selectedMailbox,
required bool isSearchEmailRunning,
required Map<Role, MailboxId> mapDefaultMailboxIdByRole,
}) {
final spamMailboxId = _spamMailboxId(mapDefaultMailboxIdByRole);
final inboxMailboxId = mapDefaultMailboxIdByRole[
PresentationMailbox.roleInbox
];
if (spamMailboxId == null || inboxMailboxId == null) return null;
return (
moveRequest: MoveToMailboxRequest(
{spamMailboxId: [presentationEmail.id!]},
inboxMailboxId,
MoveAction.moving,
EmailActionType.unSpam
),
emailIdsWithReadStatus: {presentationEmail.id!: presentationEmail.hasRead},
);
}
MailboxId? _spamMailboxId(
Map<Role, MailboxId> mapDefaultMailboxIdByRole
) {
return mapDefaultMailboxIdByRole[
PresentationMailbox.roleSpam
] ?? mapDefaultMailboxIdByRole[
PresentationMailbox.roleJunk
];
}
Stream<Either<Failure, Success>> quickCreateRule(
Session session,
AccountId accountId, {
required EmailAddress? emailAddress,
}) async* {
Get.back();
final arguments = RulesFilterCreatorArguments(
accountId,
session,
emailAddress: emailAddress,
);
final newRuleFilterRequest = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(
routeName: AppRoutes.rulesFilterCreator,
arguments: arguments,
)
: await push(AppRoutes.rulesFilterCreator, arguments: arguments);
if (newRuleFilterRequest is! CreateNewEmailRuleFilterRequest) return;
yield* _createNewEmailRuleFilterInteractor?.execute(
accountId,
newRuleFilterRequest
) ?? const Stream.empty();
}
Future<void> unsubscribeEmail(
PresentationEmail presentationEmail, {
required EmailUnsubscribe? emailUnsubscribe,
required void Function(EmailId emailId) onUnsubscribeByHttpsLink,
required void Function(
EmailId emailId,
NavigationRouter navigationRouter,
) onUnsubscribeByMailtoLink,
}) async {
if (currentContext == null || !currentContext!.mounted) return;
await showConfirmDialogAction(
currentContext!,
'',
AppLocalizations.of(currentContext!).unsubscribe,
onConfirmAction: () {
if (emailUnsubscribe?.httpLinks.isNotEmpty == true) {
onUnsubscribeByHttpsLink(presentationEmail.id!);
AppUtils.launchLink(emailUnsubscribe!.httpLinks.first);
} else if (emailUnsubscribe?.mailtoLinks.isNotEmpty == true) {
final navigationRouter = RouteUtils.generateNavigationRouterFromMailtoLink(
emailUnsubscribe!.mailtoLinks.first,
);
onUnsubscribeByMailtoLink(presentationEmail.id!, navigationRouter);
}
},
showAsBottomSheet: true,
title: AppLocalizations.of(currentContext!).unsubscribeMail,
listTextSpan: [
TextSpan(text: AppLocalizations.of(currentContext!).unsubscribeMailDialogMessage),
TextSpan(
text: ' ${presentationEmail.getSenderName()}',
style: ThemeUtils.textStyleBodyBody2(
color: AppColor.steelGray400,
fontWeight: FontWeight.w700,
),
),
const TextSpan(text: ' ?'),
]
);
}
void archiveMessage(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
) onArchiveEmailRequest,
}) => onArchiveEmailRequest(presentationEmail);
Stream<Either<Failure, Success>> printEmail(
PresentationEmail presentationEmail, {
required String ownEmailAddress,
required EmailLoaded? emailLoaded,
VoidCallback? onGetEmailContentFailure,
Session? session,
AccountId? accountId,
String? baseDownloadUrl,
TransformConfiguration? transformConfiguration,
}) async* {
emailLoaded ??= await _getEmailLoaded(
session,
accountId,
presentationEmail.id,
baseDownloadUrl,
transformConfiguration,
);
if (emailLoaded == null) {
onGetEmailContentFailure?.call();
return;
}
if (currentContext == null || !currentContext!.mounted) return;
final locale = Localizations.localeOf(currentContext!);
final appLocalizations = AppLocalizations.of(currentContext!);
yield* _printEmailInteractor.execute(
EmailPrint(
appName: appLocalizations.app_name,
userName: ownEmailAddress,
attachments: emailLoaded.attachments,
emailContent: emailLoaded.htmlContent,
fromPrefix: appLocalizations.from_email_address_prefix,
toPrefix: appLocalizations.to_email_address_prefix,
ccPrefix: appLocalizations.cc_email_address_prefix,
bccPrefix: appLocalizations.bcc_email_address_prefix,
replyToPrefix: appLocalizations.replyToEmailAddressPrefix,
titleAttachment: appLocalizations.attachments.toLowerCase(),
toAddress: presentationEmail.to?.listEmailAddressToString(isFullEmailAddress: true),
ccAddress: presentationEmail.cc?.listEmailAddressToString(isFullEmailAddress: true),
bccAddress: presentationEmail.bcc?.listEmailAddressToString(isFullEmailAddress: true),
replyToAddress: presentationEmail.replyTo?.listEmailAddressToString(isFullEmailAddress: true),
sender: presentationEmail.from?.isNotEmpty == true
? presentationEmail.from!.first
: null,
receiveTime: presentationEmail.getReceivedAt(
locale.toLanguageTag(),
pattern: presentationEmail.receivedAt
?.value
.toLocal()
.toPatternForPrinting(locale.toLanguageTag()),
),
subject: presentationEmail.subject,
)
);
}
Stream<Either<Failure, Success>> downloadMessageAsEML(
Session session,
AccountId accountId,
PresentationEmail presentationEmail, {
required StreamController<Either<Failure, Success>> downloadProgressStateController,
}) async* {
final emlAttachment = presentationEmail.createEMLAttachment();
if (emlAttachment.blobId == null) {
yield* Stream.value(Left(DownloadAttachmentForWebFailure(
exception: NotFoundEmailBlobIdException(),
)));
return;
}
final generateTaskId = DownloadTaskId(const Uuid().v4());
try {
final baseDownloadUrl = session.getDownloadUrl(
jmapUrl: getBinding<DynamicUrlInterceptors>()?.jmapUrl,
);
final cancelToken = CancelToken();
yield* _downloadAttachmentForWebInteractor.execute(
generateTaskId,
emlAttachment,
accountId,
baseDownloadUrl,
downloadProgressStateController,
cancelToken: cancelToken,
previewerSupported: false,
);
} catch (e) {
logError('SingleEmailController::downloadAttachmentForWeb(): $e');
yield* Stream.value(Left(DownloadAttachmentForWebFailure(
attachment: emlAttachment,
taskId: generateTaskId,
exception: e,
)));
}
}
void editAsNewEmail(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
) onEditAsEmailRequest,
}) => onEditAsEmailRequest(presentationEmail);
void replyEmail(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
EmailLoaded? emailLoaded,
) onReplyEmailRequest,
required EmailLoaded? emailLoaded,
}) {
onReplyEmailRequest(presentationEmail, emailLoaded);
}
void replyAll(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
EmailLoaded? emailLoaded,
) onReplyAllRequest,
required EmailLoaded? emailLoaded,
}) {
onReplyAllRequest(presentationEmail, emailLoaded);
}
void replyToList(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
EmailLoaded? emailLoaded,
) onReplyToListRequest,
required EmailLoaded? emailLoaded,
}) {
onReplyToListRequest(presentationEmail, emailLoaded);
}
void forward(
PresentationEmail presentationEmail, {
required void Function(
PresentationEmail presentationEmail,
EmailLoaded? emailLoaded,
) onForwardRequest,
required EmailLoaded? emailLoaded,
}) {
onForwardRequest(presentationEmail, emailLoaded);
}
void handleMoreEmailAction({
required PresentationEmail presentationEmail,
required PresentationMailbox? mailboxContain,
required RelativeRect? position,
required ResponsiveUtils responsiveUtils,
required ImagePaths imagePaths,
required UserName? username,
required void Function(
PresentationEmail presentationEmail,
EmailActionType action,
) handleEmailAction,
required List<EmailActionType> additionalActions,
}) {
if (currentContext == null) return;
final moreActions = [
if (additionalActions.contains(EmailActionType.forward))
EmailActionType.forward,
if (presentationEmail.getCountMailAddressWithoutMe(username?.value ?? '') > 1 &&
additionalActions.contains(EmailActionType.replyAll))
EmailActionType.replyAll,
if (EmailUtils.isReplyToListEnabled(presentationEmail.listPost ?? '') &&
additionalActions.contains(EmailActionType.replyToList))
EmailActionType.replyToList,
if (PlatformInfo.isWeb &&
PlatformInfo.isCanvasKit &&
additionalActions.contains(EmailActionType.printAll))
EmailActionType.printAll,
if (additionalActions.contains(EmailActionType.moveToMailbox))
EmailActionType.moveToMailbox,
if (additionalActions.contains(EmailActionType.markAsStarred) &&
additionalActions.contains(EmailActionType.unMarkAsStarred))
presentationEmail.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred,
if (additionalActions.contains(EmailActionType.moveToTrash) &&
additionalActions.contains(EmailActionType.deletePermanently))
_canDeletePermanently(presentationEmail)
? EmailActionType.deletePermanently
: EmailActionType.moveToTrash,
EmailActionType.markAsUnread,
if (mailboxContain?.isChildOfTeamMailboxes == false)
if (mailboxContain?.isSpam == true)
EmailActionType.unSpam
else
EmailActionType.moveToSpam,
if (presentationEmail.from?.isNotEmpty == true)
EmailActionType.createRule,
if (!presentationEmail.isSubscribed && presentationEmail.listUnsubscribe != null)
EmailActionType.unsubscribe,
if (mailboxContain?.isArchive == false)
EmailActionType.archiveMessage,
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
EmailActionType.downloadMessageAsEML,
EmailActionType.editAsNewEmail,
];
if (position == null) {
openContextMenuAction(
currentContext!,
_emailActionMoreActionTile(
currentContext!,
presentationEmail,
moreActions,
responsiveUtils,
imagePaths,
handleEmailAction: handleEmailAction,
)
);
} else {
openPopupMenuAction(
currentContext!,
position,
_popupMenuEmailActionTile(
currentContext!,
presentationEmail,
moreActions,
imagePaths,
handleEmailAction: handleEmailAction,
)
);
}
}
bool _canDeletePermanently(PresentationEmail email) {
return email.mailboxContain?.isTrash
?? email.mailboxContain?.isSpam
?? false;
}
List<Widget> _emailActionMoreActionTile(
BuildContext context,
PresentationEmail presentationEmail,
List<EmailActionType> actionTypes,
ResponsiveUtils responsiveUtils,
ImagePaths imagePaths, {
required void Function(
PresentationEmail presentationEmail,
EmailActionType action,
) handleEmailAction
}) {
return actionTypes.map((action) {
return (EmailActionCupertinoActionSheetActionBuilder(
Key('${action.name}_action'),
SvgPicture.asset(
action.getIcon(imagePaths),
width: 24,
height: 24,
fit: BoxFit.fill,
colorFilter: AppColor.colorTextButton.asFilter()
),
action.getTitle(AppLocalizations.of(context)),
presentationEmail,
iconLeftPadding: responsiveUtils.isScreenWithShortestSide(context)
? const EdgeInsetsDirectional.only(start: 12, end: 16)
: const EdgeInsetsDirectional.only(end: 12),
iconRightPadding: responsiveUtils.isScreenWithShortestSide(context)
? const EdgeInsetsDirectional.only(end: 12)
: EdgeInsets.zero
)
..onActionClick((presentationEmail) {
popBack();
handleEmailAction(presentationEmail, action);
})).build();
}).toList();
}
List<PopupMenuEntry> _popupMenuEmailActionTile(
BuildContext context,
PresentationEmail presentationEmail,
List<EmailActionType> actionTypes,
ImagePaths imagePaths, {
required void Function(
PresentationEmail presentationEmail,
EmailActionType action,
) handleEmailAction,
}) {
return actionTypes.map((action) {
return PopupMenuItem(
key: Key('${action.name}_action'),
padding: EdgeInsets.zero,
child: PopupItemWidget(
iconAction: action.getIcon(imagePaths),
nameAction: action.getTitle(AppLocalizations.of(context)),
colorIcon: AppColor.colorTextButton,
padding: const EdgeInsetsDirectional.only(start: 12),
styleName: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 16,
color: Colors.black
),
onCallbackAction: () {
popBack();
handleEmailAction(presentationEmail, action);
}
)
);
}).toList();
}
void openEmailAddressDialog(
Session session,
AccountId accountId, {
required EmailAddress emailAddress,
required ResponsiveUtils responsiveUtils,
required ImagePaths imagePaths,
required AppToast appToast,
required void Function(EmailAddress emailAddress) onComposeEmailFromEmailAddressRequest,
required void Function(Stream<Either<Failure, Success>> quickCreateRuleStream) onQuickCreateRuleRequest,
}) {
if (currentContext?.mounted != true) return;
if (responsiveUtils.isScreenWithShortestSide(currentContext!)) {
Get.bottomSheet(
PointerInterceptor(
child: EmailAddressBottomSheetBuilder(
imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) =>
_copyEmailAddress(currentContext!, emailAddress, appToast),
onComposeEmailAction: (emailAddress) =>
onComposeEmailFromEmailAddressRequest(emailAddress),
onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
onQuickCreateRuleRequest(
quickCreateRule(session, accountId, emailAddress: emailAddress),
),
),
),
useRootNavigator: true,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadiusDirectional.only(
topStart: Radius.circular(16.0),
topEnd: Radius.circular(16.0),
),
),
isScrollControlled: true,
enableDrag: false,
backgroundColor: Colors.transparent,
);
} else {
Get.dialog(
PointerInterceptor(
child: EmailAddressDialogBuilder(
imagePaths: imagePaths,
emailAddress: emailAddress,
onCloseDialogAction: popBack,
onCopyEmailAddressAction: (emailAddress) =>
_copyEmailAddress(currentContext!, emailAddress, appToast),
onComposeEmailAction: (emailAddress) =>
onComposeEmailFromEmailAddressRequest(emailAddress),
onQuickCreatingRuleEmailDialogAction: (emailAddress) =>
onQuickCreateRuleRequest(
quickCreateRule(session, accountId, emailAddress: emailAddress),
),
),
),
barrierColor: AppColor.colorDefaultCupertinoActionSheet,
);
}
}
void _copyEmailAddress(
BuildContext context,
EmailAddress emailAddress,
AppToast appToast,
) {
Clipboard.setData(ClipboardData(text: emailAddress.emailAddress));
appToast.showToastSuccessMessage(
context,
AppLocalizations.of(context).email_address_copied_to_clipboard,
);
}
Future<({
Map<EmailId, bool> emailIdsWithReadStatus,
MoveToMailboxRequest moveRequest,
})?> _moveToMailbox(
Session session,
AccountId accountId,
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
PresentationMailbox? selectedMailbox,
bool isSearchEmailRunning = false,
}) async {
final currentMailbox = _getMailboxContain(
presentationEmail,
mapMailbox: mapMailbox,
selectedMailbox: selectedMailbox,
isSearchEmailRunning: isSearchEmailRunning,
);
if (currentMailbox == null) return null;
final arguments = DestinationPickerArguments(
accountId,
MailboxActions.moveEmail,
session,
mailboxIdSelected: currentMailbox.mailboxId
);
final destinationMailbox = PlatformInfo.isWeb
? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments)
: await push(AppRoutes.destinationPicker, arguments: arguments);
if (destinationMailbox is PresentationMailbox) {
return _generateMoveMailboxRequest(
accountId,
session,
presentationEmail,
currentMailbox,
destinationMailbox,
);
}
return null;
}
PresentationMailbox? _getMailboxContain(
PresentationEmail presentationEmail, {
required Map<MailboxId, PresentationMailbox> mapMailbox,
PresentationMailbox? selectedMailbox,
bool isSearchEmailRunning = false,
}) {
if (selectedMailbox == null || isSearchEmailRunning) {
return presentationEmail.findMailboxContain(mapMailbox);
}
return selectedMailbox;
}
({
MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus,
}) _generateMoveMailboxRequest(
AccountId accountId,
Session session,
PresentationEmail emailSelected,
PresentationMailbox currentMailbox,
PresentationMailbox destinationMailbox
) {
if (destinationMailbox.isTrash) {
return (
moveRequest: MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToTrash,
),
emailIdsWithReadStatus: {emailSelected.id!: emailSelected.hasRead},
);
} else if (destinationMailbox.isSpam) {
return (
moveRequest: MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToSpam,
),
emailIdsWithReadStatus: {emailSelected.id!: emailSelected.hasRead},
);
} else {
return (
moveRequest: MoveToMailboxRequest(
{currentMailbox.id: [emailSelected.id!]},
destinationMailbox.id,
MoveAction.moving,
EmailActionType.moveToMailbox,
destinationPath: destinationMailbox.mailboxPath),
emailIdsWithReadStatus: {emailSelected.id!: emailSelected.hasRead},
);
}
}
Future<EmailLoaded?> _getEmailLoaded(
Session? session,
AccountId? accountId,
EmailId? emailId,
String? baseDownloadUrl,
TransformConfiguration? transformConfiguration,
) {
if (session == null ||
accountId == null ||
emailId == null ||
baseDownloadUrl == null ||
transformConfiguration == null
) {
return Future.value(null);
}
return _getEmailContentInteractor
.execute(
session,
accountId,
emailId,
baseDownloadUrl,
transformConfiguration,
)
.last
.then(
(value) {
return value.fold(
(failure) => null,
(success) => success is GetEmailContentSuccess
? EmailLoaded(
htmlContent: success.htmlEmailContent,
attachments: List.of(success.attachments ?? []),
inlineImages: List.of(success.inlineImages ?? []),
emailCurrent: success.emailCurrent,
)
: null,
);
},
onError: (error) {
logError('EmailActionReactor::_getEmailLoaded(): error: $error');
return null;
},
);
}
}
@@ -31,7 +31,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
final BoxDecoration? appBarDecoration;
final EmailLoaded? emailLoaded;
final bool isInsideThreadDetailView;
final double? height;
final EdgeInsetsGeometry? iconPadding;
final EdgeInsetsGeometry? iconMargin;
final EdgeInsetsGeometry? padding;
@@ -49,7 +48,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
this.supportBackAction = true,
this.appBarDecoration,
this.isInsideThreadDetailView = false,
this.height,
this.iconPadding,
this.iconMargin,
this.padding,
@@ -59,9 +57,9 @@ class EmailViewAppBarWidget extends StatelessWidget {
Widget build(BuildContext context) {
final child = LayoutBuilder(builder: (context, constraints) {
return Container(
height: height ?? (PlatformInfo.isIOS
height: PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.height),
: EmailViewAppBarWidgetStyles.height,
padding: padding ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.padding),
@@ -248,7 +246,8 @@ class EmailViewAppBarWidget extends StatelessWidget {
}) {
if (!isInsideThreadDetailView) {
return [
_getPrintButton(appLocalizations),
if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
_getPrintButton(appLocalizations),
_getMoveEmailButton(appLocalizations),
_getMarkStarButton(appLocalizations),
_getDeleteButton(appLocalizations),
@@ -5,15 +5,19 @@ import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/theme_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/image/avatar_builder.dart';
import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/widget/email_avatar_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.dart';
import 'package:tmail_ui_user/features/email/presentation/model/smime_signature_status.dart';
import 'package:tmail_ui_user/features/email/presentation/styles/email_view_app_bar_widget_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_sender_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart';
@@ -36,6 +40,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
final bool showRecipients;
final VoidCallback? onToggleThreadDetailCollapseExpand;
final OnTapAvatarActionClick? onTapAvatarActionClick;
final PresentationMailbox? mailboxContain;
const InformationSenderAndReceiverBuilder({
Key? key,
@@ -53,6 +58,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
this.showRecipients = true,
this.onToggleThreadDetailCollapseExpand,
this.onTapAvatarActionClick,
this.mailboxContain,
}) : super(key: key);
@override
@@ -127,21 +133,30 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
),
if (isInsideThreadDetailView)
EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: emailSelected,
isSearchActivated: false,
onBackAction: () {},
onEmailActionClick: onEmailActionClick,
onMoreActionClick: onMoreActionClick,
supportBackAction: false,
appBarDecoration: const BoxDecoration(),
emailLoaded: emailLoaded,
isInsideThreadDetailView: isInsideThreadDetailView,
SizedBox(
height: IconUtils.defaultIconSize,
iconPadding: EdgeInsets.zero,
iconMargin: const EdgeInsetsDirectional.only(start: 16),
padding: const EdgeInsets.symmetric(horizontal: 8),
child: OverflowBox(
maxHeight: PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils)
: EmailViewAppBarWidgetStyles.height,
fit: OverflowBoxFit.deferToChild,
child: EmailViewAppBarWidget(
key: const Key('email_view_app_bar_widget'),
presentationEmail: emailSelected,
mailboxContain: mailboxContain,
isSearchActivated: false,
onBackAction: () {},
onEmailActionClick: onEmailActionClick,
onMoreActionClick: onMoreActionClick,
supportBackAction: false,
appBarDecoration: const BoxDecoration(),
emailLoaded: emailLoaded,
isInsideThreadDetailView: isInsideThreadDetailView,
iconPadding: const EdgeInsets.all(8),
iconMargin: EdgeInsets.zero,
padding: EdgeInsets.zero,
),
),
),
],
),