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
@@ -245,7 +245,6 @@ extension AppColor on Color {
static const blue400 = Color(0xFF80BDFF); static const blue400 = Color(0xFF80BDFF);
static const blue900 = Color(0xFF0F76E7); static const blue900 = Color(0xFF0F76E7);
static const m3Tertiary = Color(0xFF8C9CAF); static const m3Tertiary = Color(0xFF8C9CAF);
static const lightIconTertiary = Color(0xFFB8C1CC);
static const m3Neutral70 = Color(0xFFAEAAAE); static const m3Neutral70 = Color(0xFFAEAAAE);
static const m3Neutral90 = Color(0xFFE6E1E5); static const m3Neutral90 = Color(0xFFE6E1E5);
static const grayBackgroundColor = Color(0xFFF3F6F9); static const grayBackgroundColor = Color(0xFFF3F6F9);
@@ -48,6 +48,11 @@ class TransformConfiguration {
const RemoveCollapsedSignatureButtonTransformer(), const RemoveCollapsedSignatureButtonTransformer(),
]); ]);
factory TransformConfiguration.forReplyForwardEmptyEmail() => TransformConfiguration.fromDomTransformers([
...TransformConfiguration.forReplyForwardEmail().domTransformers,
const ImageTransformer(),
]);
factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create( factory TransformConfiguration.forDraftsEmail() => TransformConfiguration.create(
customDomTransformers: [const ImageTransformer()] customDomTransformers: [const ImageTransformer()]
); );
@@ -196,7 +196,7 @@ extension EmailActionTypeExtension on EmailActionType {
case EmailActionType.openInNewTab: case EmailActionType.openInNewTab:
return appLocalizations.openInNewTab; return appLocalizations.openInNewTab;
case EmailActionType.printAll: case EmailActionType.printAll:
return appLocalizations.printAll; return appLocalizations.print;
case EmailActionType.forward: case EmailActionType.forward:
return appLocalizations.forward; return appLocalizations.forward;
case EmailActionType.replyAll: case EmailActionType.replyAll:
@@ -27,78 +27,13 @@ extension SetupEmailContentExtension on ComposerController {
switch(currentEmailActionType) { switch(currentEmailActionType) {
case EmailActionType.editAsNewEmail: case EmailActionType.editAsNewEmail:
case EmailActionType.editDraft: case EmailActionType.editDraft:
final session = mailboxDashBoardController.sessionCurrent; await _getEmailContent(
final accountId = mailboxDashBoardController.accountId.value; arguments,
final emailId = arguments.presentationEmail?.id; transformConfiguration: TransformConfiguration.forEditDraftsEmail(),
if (session == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundSessionException()));
return;
}
if (accountId == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundAccountIdException()));
return;
}
if (emailId == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundEmailException()));
return;
}
final resultState = await getEmailContentInteractor.execute(
session,
accountId,
emailId,
mailboxDashBoardController.baseDownloadUrl,
TransformConfiguration.forEditDraftsEmail(),
additionalProperties: Properties({ additionalProperties: Properties({
IndividualHeaderIdentifier.identityHeader.value, IndividualHeaderIdentifier.identityHeader.value,
}), }),
).last; );
final uiState = resultState.fold((failure) => failure, (success) => success);
if (uiState is GetEmailContentSuccess) {
initAttachmentsAndInlineImages(
attachments: uiState.attachments,
inlineImages: uiState.inlineImages,
);
if (currentEmailActionType == EmailActionType.editDraft ||
currentEmailActionType == EmailActionType.editAsNewEmail) {
setupEmailRequestReadReceiptFlag(
uiState.emailCurrent!.hasRequestReadReceipt,
);
setupSelectedIdentityForEditDraft(
uiState.emailCurrent!.identityIdFromHeader,
);
}
emailContentsViewState.value = Right(uiState);
} else if (uiState is GetEmailContentFromCacheSuccess) {
initAttachmentsAndInlineImages(
attachments: uiState.attachments,
inlineImages: uiState.inlineImages,
);
if (currentEmailActionType == EmailActionType.editDraft ||
currentEmailActionType == EmailActionType.editAsNewEmail) {
setupEmailRequestReadReceiptFlag(
uiState.emailCurrent.hasRequestReadReceipt,
);
setupSelectedIdentityForEditDraft(
uiState.emailCurrent.identityIdFromHeader,
);
}
emailContentsViewState.value = Right(uiState);
} else if (uiState is GetEmailContentFailure) {
emailContentsViewState.value = Left(uiState);
consumeState(Stream.value(Left(uiState)));
} else {
emailContentsViewState.value = Right(LoadEmailContentCompleted());
}
break; break;
case EmailActionType.editSendingEmail: case EmailActionType.editSendingEmail:
final sendingEmail = arguments.sendingEmail; final sendingEmail = arguments.sendingEmail;
@@ -130,7 +65,10 @@ extension SetupEmailContentExtension on ComposerController {
case EmailActionType.replyAll: case EmailActionType.replyAll:
case EmailActionType.forward: case EmailActionType.forward:
if (arguments.emailContents?.trim().isNotEmpty != true) { if (arguments.emailContents?.trim().isNotEmpty != true) {
emailContentsViewState.value = Left(GetEmailContentFailure(EmptyEmailContentException())); await _getEmailContent(
arguments,
transformConfiguration: TransformConfiguration.forReplyForwardEmptyEmail(),
);
} else { } else {
final resultState = await transformHtmlEmailContentInteractor.execute( final resultState = await transformHtmlEmailContentInteractor.execute(
arguments.emailContents ?? '', arguments.emailContents ?? '',
@@ -224,4 +162,81 @@ extension SetupEmailContentExtension on ComposerController {
break; break;
} }
} }
Future<void> _getEmailContent(
ComposerArguments arguments, {
required TransformConfiguration transformConfiguration,
Properties? additionalProperties,
}) async {
final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value;
final emailId = arguments.presentationEmail?.id;
if (session == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundSessionException()));
return;
}
if (accountId == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundAccountIdException()));
return;
}
if (emailId == null) {
emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundEmailException()));
return;
}
final resultState = await getEmailContentInteractor.execute(
session,
accountId,
emailId,
mailboxDashBoardController.baseDownloadUrl,
transformConfiguration,
additionalProperties: additionalProperties,
).last;
final uiState = resultState.fold((failure) => failure, (success) => success);
if (uiState is GetEmailContentSuccess) {
initAttachmentsAndInlineImages(
attachments: uiState.attachments,
inlineImages: uiState.inlineImages,
);
if (currentEmailActionType == EmailActionType.editDraft ||
currentEmailActionType == EmailActionType.editAsNewEmail) {
setupEmailRequestReadReceiptFlag(
uiState.emailCurrent!.hasRequestReadReceipt,
);
setupSelectedIdentityForEditDraft(
uiState.emailCurrent!.identityIdFromHeader,
);
}
emailContentsViewState.value = Right(uiState);
} else if (uiState is GetEmailContentFromCacheSuccess) {
initAttachmentsAndInlineImages(
attachments: uiState.attachments,
inlineImages: uiState.inlineImages,
);
if (currentEmailActionType == EmailActionType.editDraft
|| currentEmailActionType == EmailActionType.editAsNewEmail) {
setupEmailRequestReadReceiptFlag(
uiState.emailCurrent.hasRequestReadReceipt,
);
setupSelectedIdentityForEditDraft(
uiState.emailCurrent.identityIdFromHeader,
);
}
emailContentsViewState.value = Right(uiState);
} else if (uiState is GetEmailContentFailure) {
emailContentsViewState.value = Left(uiState);
consumeState(Stream.value(Left(uiState)));
} else {
emailContentsViewState.value = Right(LoadEmailContentCompleted());
}
}
} }
@@ -1,9 +1,17 @@
import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/state/success.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
class UnsubscribeEmailLoading extends LoadingState {} class UnsubscribeEmailLoading extends LoadingState {}
class UnsubscribeEmailSuccess extends UIState {} class UnsubscribeEmailSuccess extends UIState {
UnsubscribeEmailSuccess(this.emailId);
final EmailId emailId;
@override
List<Object?> get props => [emailId];
}
class UnsubscribeEmailFailure extends FeatureFailure { class UnsubscribeEmailFailure extends FeatureFailure {
@@ -16,7 +16,7 @@ class UnsubscribeEmailInteractor {
try { try {
yield Right(UnsubscribeEmailLoading()); yield Right(UnsubscribeEmailLoading());
await emailRepository.unsubscribeMail(session, accountId, emailId); await emailRepository.unsubscribeMail(session, accountId, emailId);
yield Right(UnsubscribeEmailSuccess()); yield Right(UnsubscribeEmailSuccess(emailId));
} catch (e) { } catch (e) {
yield Left(UnsubscribeEmailFailure(exception: e)); yield Left(UnsubscribeEmailFailure(exception: e));
} }
@@ -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/mixin/app_loader_mixin.dart';
import 'package:tmail_ui_user/features/base/state/button_state.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/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/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/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/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/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/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_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_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.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/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/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_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/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/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/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/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/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/home/domain/extensions/session_extensions.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/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/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/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.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/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/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/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/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/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/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/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/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/error/capability_validator.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/app_localizations.dart';
import 'package:tmail_ui_user/main/localizations/localization_service.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/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/navigation_router.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.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/routes/route_utils.dart';
@@ -203,6 +197,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
Identity? _identitySelected; Identity? _identitySelected;
ButtonState? _printEmailButtonState; ButtonState? _printEmailButtonState;
final obxListeners = <Worker>[]; final obxListeners = <Worker>[];
late final EmailActionReactor emailActionReactor;
final StreamController<Either<Failure, Success>> _downloadProgressStateController = final StreamController<Either<Failure, Success>> _downloadProgressStateController =
StreamController<Either<Failure, Success>>.broadcast(); StreamController<Either<Failure, Success>>.broadcast();
@@ -254,6 +249,14 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_injectCalendarEventBindings(session, accountId); _injectCalendarEventBindings(session, accountId);
_registerObxStreamListener(); _registerObxStreamListener();
_listenDownloadAttachmentProgressState(); _listenDownloadAttachmentProgressState();
emailActionReactor = EmailActionReactor(
_markAsEmailReadInteractor,
_markAsStarEmailInteractor,
_createNewEmailRuleFilterInteractor,
_printEmailInteractor,
_getEmailContentInteractor,
_downloadAttachmentForWebInteractor,
);
super.onInit(); super.onInit();
} }
@@ -408,6 +411,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
obxListeners.add(ever(mailboxDashBoardController.viewState, (viewState) { obxListeners.add(ever(mailboxDashBoardController.viewState, (viewState) {
viewState.map((success) { viewState.map((success) {
if (success is UnsubscribeEmailSuccess) { if (success is UnsubscribeEmailSuccess) {
if (success.emailId != _currentEmailId) return;
emailUnsubscribe.value = null; emailUnsubscribe.value = null;
} }
}); });
@@ -766,13 +770,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
PresentationMailbox? getMailboxContain(PresentationEmail email) { PresentationMailbox? getMailboxContain(PresentationEmail email) {
if (mailboxDashBoardController.selectedMailbox.value == null) { return mailboxDashBoardController.getMailboxContain(email);
return email.findMailboxContain(mailboxDashBoardController.mapMailboxById);
} else {
return mailboxDashBoardController.searchController.isSearchEmailRunning
? email.findMailboxContain(mailboxDashBoardController.mapMailboxById)
: mailboxDashBoardController.selectedMailbox.value;
}
} }
void markAsEmailRead( void markAsEmailRead(
@@ -1200,81 +1198,28 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void moveToMailbox(BuildContext context, PresentationEmail email) async { void moveToMailbox(BuildContext context, PresentationEmail email) async {
final currentMailbox = getMailboxContain(email); if (session != null && accountId != null) {
final moveActionRequest = await emailActionReactor.moveToMailbox(
if (currentMailbox != null && accountId != null) { session!,
final arguments = DestinationPickerArguments(
accountId!, accountId!,
MailboxActions.moveEmail, email,
session, mapMailbox: mailboxDashBoardController.mapMailboxById,
mailboxIdSelected: currentMailbox.mailboxId selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
); );
if (!context.mounted || moveActionRequest == null) return;
final destinationMailbox = PlatformInfo.isWeb mailboxDashBoardController.moveToMailbox(
? await DialogRouter.pushGeneralDialog(routeName: AppRoutes.destinationPicker, arguments: arguments) session!,
: await push(AppRoutes.destinationPicker, arguments: arguments); accountId!,
moveActionRequest.moveRequest,
if (destinationMailbox != null && moveActionRequest.emailIdsWithReadStatus,
destinationMailbox is PresentationMailbox && );
session != null && if (_threadDetailController?.emailIdsPresentation.length == 1) {
context.mounted _threadDetailController?.closeThreadDetailAction(context);
) {
_dispatchMoveToAction(
context,
accountId!,
session!,
email,
currentMailbox,
destinationMailbox);
} }
} }
} }
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( void _moveToMailbox(
BuildContext context, BuildContext context,
Session session, Session session,
@@ -1282,7 +1227,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
MoveToMailboxRequest moveRequest, MoveToMailboxRequest moveRequest,
Map<EmailId, bool> emailIdsWithReadStatus, Map<EmailId, bool> emailIdsWithReadStatus,
) { ) {
closeEmailView(context: context);
consumeState(_moveToMailboxInteractor.execute( consumeState(_moveToMailboxInteractor.execute(
session, session,
accountId, accountId,
@@ -1332,94 +1276,71 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void moveToTrash(BuildContext context, PresentationEmail email) { void moveToTrash(BuildContext context, PresentationEmail email) {
final trashMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleTrash); if (session != null && accountId != null) {
final currentMailbox = getMailboxContain(email); final moveActionRequest = emailActionReactor.moveToTrash(
email,
if (session != null && accountId != null && currentMailbox != null && trashMailboxId != null) { mapMailbox: mailboxDashBoardController.mapMailboxById,
_moveToTrashAction( selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
context, isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!, session!,
accountId!, accountId!,
MoveToMailboxRequest( moveActionRequest.moveRequest,
{currentMailbox.id: [email.id!]}, moveActionRequest.emailIdsWithReadStatus,
trashMailboxId,
MoveAction.moving,
EmailActionType.moveToTrash),
{email.id!: email.hasRead},
); );
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) { void moveToSpam(BuildContext context, PresentationEmail email) {
final spamMailboxId = mailboxDashBoardController.spamMailboxId; if (session != null && accountId != null) {
final currentMailbox = getMailboxContain(email); final moveActionRequest = emailActionReactor.moveToSpam(
email,
if (session != null && accountId != null && currentMailbox != null && spamMailboxId != null) { mapMailbox: mailboxDashBoardController.mapMailboxById,
_moveToSpamAction( selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
context, isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!, session!,
accountId!, accountId!,
MoveToMailboxRequest( moveActionRequest.moveRequest,
{currentMailbox.id: [email.id!]}, moveActionRequest.emailIdsWithReadStatus,
spamMailboxId,
MoveAction.moving,
EmailActionType.moveToSpam),
{email.id!: email.hasRead},
); );
if (_threadDetailController?.emailIdsPresentation.length == 1) {
_threadDetailController?.closeThreadDetailAction(context);
}
} }
} }
void unSpam(BuildContext context, PresentationEmail email) { void unSpam(BuildContext context, PresentationEmail email) {
final spamMailboxId = mailboxDashBoardController.spamMailboxId; if (session != null && accountId != null) {
final inboxMailboxId = mailboxDashBoardController.getMailboxIdByRole(PresentationMailbox.roleInbox); final moveActionRequest = emailActionReactor.unSpam(
email,
if (session != null && accountId != null && spamMailboxId != null && inboxMailboxId != null) { mapMailbox: mailboxDashBoardController.mapMailboxById,
_moveToSpamAction( selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
context, isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (!context.mounted || moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!, session!,
accountId!, accountId!,
MoveToMailboxRequest( moveActionRequest.moveRequest,
{spamMailboxId: [email.id!]}, moveActionRequest.emailIdsWithReadStatus,
inboxMailboxId,
MoveAction.moving,
EmailActionType.unSpam),
{email.id!: email.hasRead},
); );
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( void markAsStarEmail(
PresentationEmail presentationEmail, PresentationEmail presentationEmail,
MarkStarAction markStarAction, MarkStarAction markStarAction,
@@ -1451,13 +1372,34 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) { void handleEmailAction(BuildContext context, PresentationEmail presentationEmail, EmailActionType actionType) {
switch(actionType) { switch(actionType) {
case EmailActionType.markAsUnread: case EmailActionType.markAsUnread:
markAsEmailRead(presentationEmail, ReadActions.markAsUnread, MarkReadAction.tap); if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsEmailRead(
session!,
accountId!,
presentationEmail,
readAction: ReadActions.markAsUnread,
));
}
break; break;
case EmailActionType.markAsStarred: case EmailActionType.markAsStarred:
markAsStarEmail(presentationEmail, MarkStarAction.markStar); if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsStarEmail(
session!,
accountId!,
presentationEmail,
markStarAction: MarkStarAction.markStar,
));
}
break; break;
case EmailActionType.unMarkAsStarred: case EmailActionType.unMarkAsStarred:
markAsStarEmail(presentationEmail, MarkStarAction.unMarkStar); if (session != null && accountId != null) {
consumeState(emailActionReactor.markAsStarEmail(
session!,
accountId!,
presentationEmail,
markStarAction: MarkStarAction.unMarkStar,
));
}
break; break;
case EmailActionType.moveToMailbox: case EmailActionType.moveToMailbox:
moveToMailbox(context, presentationEmail); moveToMailbox(context, presentationEmail);
@@ -1495,69 +1437,38 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
case EmailActionType.reply: case EmailActionType.reply:
_replyEmail(presentationEmail); _replyEmail(presentationEmail);
break; break;
case EmailActionType.replyAll:
case EmailActionType.replyToList:
case EmailActionType.forward:
pressEmailAction(actionType, presentationEmail);
break;
default: default:
break; break;
} }
} }
void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) { void openEmailAddressDialog(EmailAddress emailAddress) {
if (responsiveUtils.isScreenWithShortestSide(context)) { if (session == null || accountId == null) return;
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 copyEmailAddress(BuildContext context, EmailAddress emailAddress) { emailActionReactor.openEmailAddressDialog(
Clipboard.setData(ClipboardData(text: emailAddress.emailAddress)); session!,
appToast.showToastSuccessMessage( accountId!,
context, emailAddress: emailAddress,
AppLocalizations.of(context).email_address_copied_to_clipboard, 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 { Future<void> openMailToLink(Uri? uri) async {
if (uri == null) return; if (uri == null) return;
@@ -1577,34 +1488,18 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void deleteEmailPermanently(BuildContext context, PresentationEmail email) { void deleteEmailPermanently(BuildContext context, PresentationEmail email) {
if (responsiveUtils.isMobile(context)) { emailActionReactor.deleteEmailPermanently(
(ConfirmationDialogActionSheetBuilder(context) email,
..messageText(DeleteActionType.single.getContentDialog(context)) onDeleteEmailRequest: (email) {
..onCancelAction(AppLocalizations.of(context).cancel, () => popBack()) popBack();
..onConfirmAction(DeleteActionType.single.getConfirmActionName(context), () => _deleteEmailPermanentlyAction(context, email))) mailboxDashBoardController.deleteEmailPermanently(email);
.show(); if (_threadDetailController?.emailIdsPresentation.length == 1) {
} else { _threadDetailController?.closeThreadDetailAction(context);
Get.dialog( }
PointerInterceptor(child: ConfirmationDialogBuilder( },
key: const Key('confirm_dialog_delete_email_permanently'), responsiveUtils: responsiveUtils,
imagePath: imagePaths, imagePaths: 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);
} }
void _handleSendReceiptToSenderAction(BuildContext context) { void _handleSendReceiptToSenderAction(BuildContext context) {
@@ -1793,32 +1688,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
popBack(); popBack();
if (accountId != null && session != null) { if (accountId != null && session != null) {
final arguments = RulesFilterCreatorArguments( consumeState(emailActionReactor.quickCreateRule(
accountId!,
session!, session!,
emailAddress: emailAddress); accountId!,
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));
} }
} }
@@ -1929,70 +1803,30 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) { void _unsubscribeEmail(BuildContext context, PresentationEmail presentationEmail) {
showConfirmDialogAction( emailActionReactor.unsubscribeEmail(
context, presentationEmail,
'', emailUnsubscribe: emailUnsubscribe.value,
AppLocalizations.of(context).unsubscribe, onUnsubscribeByHttpsLink: mailboxDashBoardController.unsubscribeMail,
onConfirmAction: () { onUnsubscribeByMailtoLink: (emailId, navigationRouter) {
if (emailUnsubscribe.value?.httpLinks.isNotEmpty == true) { mailboxDashBoardController.openComposer(
_handleUnsubscribeMailByHttpsLink( ComposerArguments.fromUnsubscribeMailtoLink(
context: context, listEmailAddress: navigationRouter.listEmailAddress,
emailId: presentationEmail.id!, subject: navigationRouter.subject,
httpLinks: emailUnsubscribe.value!.httpLinks body: navigationRouter.body,
); previousEmailId: emailId,
} else if (emailUnsubscribe.value?.mailtoLinks.isNotEmpty == true) { )
_handleUnsubscribeMailByMailtoLink( );
context: context,
emailId: presentationEmail.id!,
mailtoLinks: emailUnsubscribe.value!.mailtoLinks
);
}
}, },
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) { 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) { void _printEmail(BuildContext context, PresentationEmail email) {
@@ -2003,40 +1837,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
_printEmailButtonState = ButtonState.disabled; _printEmailButtonState = ButtonState.disabled;
final locale = Localizations.localeOf(context); consumeState(emailActionReactor.printEmail(
final appLocalizations = AppLocalizations.of(context); email,
ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value,
consumeState( emailLoaded: currentEmailLoaded.value!,
_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,
)
)
);
} }
void _showMessageWhenStartingEmailPrinting() { void _showMessageWhenStartingEmailPrinting() {
@@ -2181,34 +1986,46 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
} }
void _downloadMessageAsEML(PresentationEmail presentationEmail) { void _downloadMessageAsEML(PresentationEmail presentationEmail) {
final emlAttachment = presentationEmail.createEMLAttachment(); if (accountId == null || session == null) return;
if (emlAttachment.blobId == null) {
consumeState(Stream.value(Left(DownloadAttachmentForWebFailure(exception: NotFoundEmailBlobIdException()))));
return;
}
downloadAttachmentForWeb(emlAttachment); consumeState(emailActionReactor.downloadMessageAsEML(
session!,
accountId!,
presentationEmail,
downloadProgressStateController: _downloadProgressStateController,
));
} }
void _editAsNewEmail(PresentationEmail presentationEmail) { void _editAsNewEmail(PresentationEmail presentationEmail) {
if (accountId == null || session == null) return; if (accountId == null || session == null) return;
mailboxDashBoardController.openComposer( emailActionReactor.editAsNewEmail(
ComposerArguments.editAsNewEmail(presentationEmail), presentationEmail,
onEditAsEmailRequest: (presentationEmail) {
mailboxDashBoardController.openComposer(
ComposerArguments.editAsNewEmail(presentationEmail),
);
},
); );
} }
void _replyEmail(PresentationEmail presentationEmail) { void _replyEmail(PresentationEmail presentationEmail) {
mailboxDashBoardController.openComposer( emailActionReactor.replyEmail(
ComposerArguments.replyEmail( presentationEmail,
presentationEmail: presentationEmail, emailLoaded: currentEmailLoaded.value,
content: currentEmailLoaded.value?.htmlContent ?? '', onReplyEmailRequest: (presentationEmail, emailLoaded) {
inlineImages: currentEmailLoaded.value?.inlineImages ?? [], mailboxDashBoardController.openComposer(
mailboxRole: presentationEmail.mailboxContain?.role, ComposerArguments.replyEmail(
messageId: currentEmailLoaded.value?.emailCurrent?.messageId, presentationEmail: presentationEmail,
references: currentEmailLoaded.value?.emailCurrent?.references, content: emailLoaded?.htmlContent,
listPost: currentEmailLoaded.value?.emailCurrent?.listPost, 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:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:model/email/email_action_type.dart'; import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.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/list_email_address_extension.dart';
import 'package:model/extensions/presentation_email_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:model/mailbox/presentation_mailbox.dart';
import 'package:pointer_interceptor/pointer_interceptor.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_expanded.dart';
import 'package:tmail_ui_user/features/base/widget/optional_scroll.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/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/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/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_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_detail_widget.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_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/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/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/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/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/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/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/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart';
class EmailView extends GetWidget<SingleEmailController> { class EmailView extends GetWidget<SingleEmailController> {
@@ -97,11 +88,15 @@ class EmailView extends GetWidget<SingleEmailController> {
isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning, isSearchActivated: controller.mailboxDashBoardController.searchController.isSearchEmailRunning,
onBackAction: () => controller.closeEmailView(context: context), onBackAction: () => controller.closeEmailView(context: context),
onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action), onEmailActionClick: (email, action) => controller.handleEmailAction(context, email, action),
onMoreActionClick: (presentationEmail, position) => _handleMoreEmailAction( onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
context: context, mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail, presentationEmail: presentationEmail,
position: position, 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, supportBackAction: !isInsideThreadDetailView,
appBarDecoration: isInsideThreadDetailView appBarDecoration: isInsideThreadDetailView
@@ -278,18 +273,38 @@ class EmailView extends GetWidget<SingleEmailController> {
sMimeStatus: controller.currentEmailLoaded.value?.sMimeStatus, sMimeStatus: controller.currentEmailLoaded.value?.sMimeStatus,
emailUnsubscribe: controller.emailUnsubscribe.value, emailUnsubscribe: controller.emailUnsubscribe.value,
maxBodyHeight: maxBodyHeight, maxBodyHeight: maxBodyHeight,
openEmailAddressDetailAction: controller.openEmailAddressDialog, openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType), onEmailActionClick: (presentationEmail, actionType) => controller.handleEmailAction(context, presentationEmail, actionType),
isInsideThreadDetailView: isInsideThreadDetailView, isInsideThreadDetailView: isInsideThreadDetailView,
emailLoaded: controller.currentEmailLoaded.value, emailLoaded: controller.currentEmailLoaded.value,
onMoreActionClick: (email, position) => _handleMoreEmailAction( onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
context: context, mailboxContain: controller.getMailboxContain(presentationEmail),
presentationEmail: email, presentationEmail: presentationEmail,
position: position, 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, onToggleThreadDetailCollapseExpand: onToggleThreadDetailCollapseExpand,
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand, onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
mailboxContain: presentationEmail.findMailboxContain(
controller.mailboxDashBoardController.mapMailboxById,
),
)), )),
Obx(() => MailUnsubscribedBanner( Obx(() => MailUnsubscribedBanner(
presentationEmail: controller.currentEmail, presentationEmail: controller.currentEmail,
@@ -340,7 +355,7 @@ class EmailView extends GetWidget<SingleEmailController> {
.ownEmailAddress .ownEmailAddress
.value, .value,
onMailtoAttendeesAction: controller.handleMailToAttendees, onMailtoAttendeesAction: controller.handleMailToAttendees,
openEmailAddressDetailAction: controller.openEmailAddressDialog, openEmailAddressDetailAction: (_, emailAddress) => controller.openEmailAddressDialog(emailAddress),
isFree: controller.isCalendarEventFree, isFree: controller.isCalendarEventFree,
listEmailAddressSender: emailAddressSender ?? [], listEmailAddressSender: emailAddressSender ?? [],
isPortraitMobile: controller isPortraitMobile: controller
@@ -496,113 +511,4 @@ class EmailView extends GetWidget<SingleEmailController> {
return usernameEvent.isNotEmpty && titleEvent.isNotEmpty; 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({ factory ComposerArguments.replyEmail({
required PresentationEmail presentationEmail, required PresentationEmail presentationEmail,
required String content, required String? content,
required List<Attachment> inlineImages, required List<Attachment>? inlineImages,
Role? mailboxRole, Role? mailboxRole,
MessageIdsHeaderValue? messageId, MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references, MessageIdsHeaderValue? references,
@@ -163,8 +163,8 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.replyToListEmail({ factory ComposerArguments.replyToListEmail({
required PresentationEmail presentationEmail, required PresentationEmail presentationEmail,
required String content, required String? content,
required List<Attachment> inlineImages, required List<Attachment>? inlineImages,
Role? mailboxRole, Role? mailboxRole,
MessageIdsHeaderValue? messageId, MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references, MessageIdsHeaderValue? references,
@@ -182,8 +182,8 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.replyAllEmail({ factory ComposerArguments.replyAllEmail({
required PresentationEmail presentationEmail, required PresentationEmail presentationEmail,
required String content, required String? content,
required List<Attachment> inlineImages, required List<Attachment>? inlineImages,
Role? mailboxRole, Role? mailboxRole,
MessageIdsHeaderValue? messageId, MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references, MessageIdsHeaderValue? references,
@@ -201,9 +201,9 @@ class ComposerArguments extends RouterArguments {
factory ComposerArguments.forwardEmail({ factory ComposerArguments.forwardEmail({
required PresentationEmail presentationEmail, required PresentationEmail presentationEmail,
required String content, required String? content,
required List<Attachment> attachments, required List<Attachment>? attachments,
required List<Attachment> inlineImages, required List<Attachment>? inlineImages,
MessageIdsHeaderValue? messageId, MessageIdsHeaderValue? messageId,
MessageIdsHeaderValue? references, MessageIdsHeaderValue? references,
}) => ComposerArguments( }) => 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 BoxDecoration? appBarDecoration;
final EmailLoaded? emailLoaded; final EmailLoaded? emailLoaded;
final bool isInsideThreadDetailView; final bool isInsideThreadDetailView;
final double? height;
final EdgeInsetsGeometry? iconPadding; final EdgeInsetsGeometry? iconPadding;
final EdgeInsetsGeometry? iconMargin; final EdgeInsetsGeometry? iconMargin;
final EdgeInsetsGeometry? padding; final EdgeInsetsGeometry? padding;
@@ -49,7 +48,6 @@ class EmailViewAppBarWidget extends StatelessWidget {
this.supportBackAction = true, this.supportBackAction = true,
this.appBarDecoration, this.appBarDecoration,
this.isInsideThreadDetailView = false, this.isInsideThreadDetailView = false,
this.height,
this.iconPadding, this.iconPadding,
this.iconMargin, this.iconMargin,
this.padding, this.padding,
@@ -59,9 +57,9 @@ class EmailViewAppBarWidget extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final child = LayoutBuilder(builder: (context, constraints) { final child = LayoutBuilder(builder: (context, constraints) {
return Container( return Container(
height: height ?? (PlatformInfo.isIOS height: PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils) ? EmailViewAppBarWidgetStyles.heightIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.height), : EmailViewAppBarWidgetStyles.height,
padding: padding ?? (PlatformInfo.isIOS padding: padding ?? (PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils) ? EmailViewAppBarWidgetStyles.paddingIOS(context, _responsiveUtils)
: EmailViewAppBarWidgetStyles.padding), : EmailViewAppBarWidgetStyles.padding),
@@ -248,7 +246,8 @@ class EmailViewAppBarWidget extends StatelessWidget {
}) { }) {
if (!isInsideThreadDetailView) { if (!isInsideThreadDetailView) {
return [ return [
_getPrintButton(appLocalizations), if (PlatformInfo.isWeb && PlatformInfo.isCanvasKit)
_getPrintButton(appLocalizations),
_getMoveEmailButton(appLocalizations), _getMoveEmailButton(appLocalizations),
_getMarkStarButton(appLocalizations), _getMarkStarButton(appLocalizations),
_getDeleteButton(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/utils/theme_utils.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/image/avatar_builder.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/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
import 'package:model/email/email_action_type.dart'; import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/presentation_email_extension.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/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_loaded.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_unsubscribe.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/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_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_sender_builder.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.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 bool showRecipients;
final VoidCallback? onToggleThreadDetailCollapseExpand; final VoidCallback? onToggleThreadDetailCollapseExpand;
final OnTapAvatarActionClick? onTapAvatarActionClick; final OnTapAvatarActionClick? onTapAvatarActionClick;
final PresentationMailbox? mailboxContain;
const InformationSenderAndReceiverBuilder({ const InformationSenderAndReceiverBuilder({
Key? key, Key? key,
@@ -53,6 +58,7 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
this.showRecipients = true, this.showRecipients = true,
this.onToggleThreadDetailCollapseExpand, this.onToggleThreadDetailCollapseExpand,
this.onTapAvatarActionClick, this.onTapAvatarActionClick,
this.mailboxContain,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -127,21 +133,30 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget {
padding: const EdgeInsetsDirectional.only(start: 16, top: 2), padding: const EdgeInsetsDirectional.only(start: 16, top: 2),
), ),
if (isInsideThreadDetailView) if (isInsideThreadDetailView)
EmailViewAppBarWidget( SizedBox(
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,
height: IconUtils.defaultIconSize, height: IconUtils.defaultIconSize,
iconPadding: EdgeInsets.zero, child: OverflowBox(
iconMargin: const EdgeInsetsDirectional.only(start: 16), maxHeight: PlatformInfo.isIOS
padding: const EdgeInsets.symmetric(horizontal: 8), ? 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,
),
),
), ),
], ],
), ),
@@ -151,10 +151,10 @@ class MailboxDashBoardBindings extends BaseBindings {
SendingQueueBindings().dependencies(); SendingQueueBindings().dependencies();
MailboxBindings().dependencies(); MailboxBindings().dependencies();
ThreadBindings().dependencies(); ThreadBindings().dependencies();
SearchEmailBindings().dependencies();
ThreadDetailBindings().dependencies();
EmailBindings().dependencies(); EmailBindings().dependencies();
SearchEmailBindings().dependencies();
QuotasBindings().dependencies(); QuotasBindings().dependencies();
ThreadDetailBindings().dependencies();
} }
@override @override
@@ -174,6 +174,7 @@ import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_multiple_e
import 'package:tmail_ui_user/features/thread/domain/usecases/mark_as_star_multiple_email_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/domain/usecases/move_multiple_email_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/thread/domain/usecases/move_multiple_email_to_mailbox_interactor.dart';
import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart'; import 'package:tmail_ui_user/features/thread/presentation/model/delete_action_type.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart'; import 'package:tmail_ui_user/main/deep_links/deep_link_data.dart';
import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart'; import 'package:tmail_ui_user/main/deep_links/deep_links_manager.dart';
import 'package:tmail_ui_user/main/deep_links/open_app_deep_link_data.dart'; import 'package:tmail_ui_user/main/deep_links/open_app_deep_link_data.dart';
@@ -252,6 +253,7 @@ class MailboxDashBoardController extends ReloadableController
final dashBoardAction = Rxn<UIAction>(); final dashBoardAction = Rxn<UIAction>();
final mailboxUIAction = Rxn<MailboxUIAction>(); final mailboxUIAction = Rxn<MailboxUIAction>();
final emailUIAction = Rxn<EmailUIAction>(); final emailUIAction = Rxn<EmailUIAction>();
final threadDetailUIAction = Rxn<ThreadDetailUIAction>();
final dashboardRoute = DashboardRoutes.waiting.obs; final dashboardRoute = DashboardRoutes.waiting.obs;
final currentSelectMode = SelectMode.INACTIVE.obs; final currentSelectMode = SelectMode.INACTIVE.obs;
final filterMessageOption = FilterMessageOption.all.obs; final filterMessageOption = FilterMessageOption.all.obs;
@@ -447,7 +449,7 @@ class MailboxDashBoardController extends ReloadableController
} else if (success is DeleteSendingEmailSuccess) { } else if (success is DeleteSendingEmailSuccess) {
getAllSendingEmails(); getAllSendingEmails();
} else if (success is UnsubscribeEmailSuccess) { } else if (success is UnsubscribeEmailSuccess) {
_handleUnsubscribeMailSuccess(); _handleUnsubscribeMailSuccess(success.emailId);
} else if (success is RestoreDeletedMessageSuccess) { } else if (success is RestoreDeletedMessageSuccess) {
_handleRestoreDeletedMessageSuccess(success.emailRecoveryAction.id!); _handleRestoreDeletedMessageSuccess(success.emailRecoveryAction.id!);
} else if (success is GetRestoredDeletedMessageSuccess) { } else if (success is GetRestoredDeletedMessageSuccess) {
@@ -1766,6 +1768,11 @@ class MailboxDashBoardController extends ReloadableController
emailUIAction.value = newAction; emailUIAction.value = newAction;
} }
void dispatchThreadDetailUIAction(ThreadDetailUIAction newAction) {
log('MailboxDashBoardController::dispatchThreadDetailUIAction():newAction: ${newAction.runtimeType}');
threadDetailUIAction.value = newAction;
}
void dispatchRoute(DashboardRoutes route) { void dispatchRoute(DashboardRoutes route) {
log('MailboxDashBoardController::dispatchRoute(): $route'); log('MailboxDashBoardController::dispatchRoute(): $route');
dashboardRoute.value = route; dashboardRoute.value = route;
@@ -2848,16 +2855,29 @@ class MailboxDashBoardController extends ReloadableController
} }
} }
void _handleUnsubscribeMailSuccess() { void _handleUnsubscribeMailSuccess(EmailId emailId) {
if (currentContext != null && currentOverlayContext != null) { if (currentContext != null && currentOverlayContext != null) {
appToast.showToastSuccessMessage( appToast.showToastSuccessMessage(
currentOverlayContext!, currentOverlayContext!,
AppLocalizations.of(currentContext!).unsubscribedFromThisMailingList); AppLocalizations.of(currentContext!).unsubscribedFromThisMailingList);
} }
final newEmail = selectedEmail.value?.updateKeywords({ dispatchThreadDetailUIAction(UpdatedEmailKeywordsAction(
emailId,
KeyWordIdentifierExtension.unsubscribeMail,
true,
));
// Reset threadDetailUIAction
dispatchThreadDetailUIAction(ThreadDetailUIAction());
final listEmail = searchController.isSearchEmailRunning
? listResultSearch
: emailsInCurrentMailbox;
var newEmailIndex = listEmail.indexWhere((email) => email.id == emailId);
if (newEmailIndex == -1) return;
listEmail[newEmailIndex] = listEmail[newEmailIndex].updateKeywords({
KeyWordIdentifierExtension.unsubscribeMail: true, KeyWordIdentifierExtension.unsubscribeMail: true,
}); });
setSelectedEmail(newEmail);
} }
void _replaceBrowserHistory({Uri? uri}) { void _replaceBrowserHistory({Uri? uri}) {
@@ -0,0 +1,16 @@
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/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
extension GetMailboxContainExtension on MailboxDashBoardController {
PresentationMailbox? getMailboxContain(PresentationEmail email) {
if (selectedMailbox.value == null) {
return email.findMailboxContain(mapMailboxById);
} else {
return searchController.isSearchEmailRunning
? email.findMailboxContain(mapMailboxById)
: selectedMailbox.value;
}
}
}
@@ -3,9 +3,9 @@ import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:model/email/mark_star_action.dart'; import 'package:model/email/mark_star_action.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:model/email/read_actions.dart'; import 'package:model/email/read_actions.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/dashboard_routes.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/action/thread_detail_ui_action.dart';
extension UpdateCurrentEmailsFlagsExtension on MailboxDashBoardController { extension UpdateCurrentEmailsFlagsExtension on MailboxDashBoardController {
void updateEmailFlagByEmailIds( void updateEmailFlagByEmailIds(
@@ -76,23 +76,25 @@ extension UpdateCurrentEmailsFlagsExtension on MailboxDashBoardController {
} }
void updateEmailAnswered(EmailId emailId) { void updateEmailAnswered(EmailId emailId) {
if (selectedEmail.value != null) { dispatchThreadDetailUIAction(UpdatedEmailKeywordsAction(
final newEmail = selectedEmail.value!.updateKeywords({ emailId,
KeyWordIdentifier.emailAnswered: true, KeyWordIdentifier.emailAnswered,
}); true,
setSelectedEmail(newEmail); ));
} // Reset threadDetailUIAction
dispatchThreadDetailUIAction(ThreadDetailUIAction());
updateEmailFlagByEmailIds([emailId], markAsAnswered: true); updateEmailFlagByEmailIds([emailId], markAsAnswered: true);
} }
void updateEmailForwarded(EmailId emailId) { void updateEmailForwarded(EmailId emailId) {
if (selectedEmail.value != null) { dispatchThreadDetailUIAction(UpdatedEmailKeywordsAction(
final newEmail = selectedEmail.value!.updateKeywords({ emailId,
KeyWordIdentifier.emailForwarded: true, KeyWordIdentifier.emailForwarded,
}); true,
setSelectedEmail(newEmail); ));
} // Reset threadDetailUIAction
dispatchThreadDetailUIAction(ThreadDetailUIAction());
updateEmailFlagByEmailIds([emailId], markAsForwarded: true); updateEmailFlagByEmailIds([emailId], markAsForwarded: true);
} }
@@ -0,0 +1,23 @@
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:tmail_ui_user/features/base/action/ui_action.dart';
class ThreadDetailUIAction extends UIAction {
@override
List<Object?> get props => [];
}
class UpdatedEmailKeywordsAction extends ThreadDetailUIAction {
UpdatedEmailKeywordsAction(
this.emailId,
this.updatedKeyword,
this.value,
);
final EmailId emailId;
final KeyWordIdentifier updatedKeyword;
final bool value;
@override
List<Object?> get props => [emailId, updatedKeyword, value];
}
@@ -10,10 +10,11 @@ extension CloseThreadDetailAction on ThreadDetailController {
} else { } else {
mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread); mailboxDashBoardController.dispatchRoute(DashboardRoutes.thread);
} }
for (var emailId in emailIdsPresentation.keys) { final cleanUpEmailIds = List.from(emailIdsPresentation.keys);
mailboxDashBoardController.dispatchEmailUIAction( for (var emailId in cleanUpEmailIds) {
CloseEmailInThreadDetailAction(emailId), mailboxDashBoardController
); ..dispatchEmailUIAction(CloseEmailInThreadDetailAction(emailId))
..dispatchEmailUIAction(EmailUIAction());
} }
reset(); reset();
@@ -1,12 +1,18 @@
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart'; import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:model/email/email_in_thread_status.dart'; import 'package:model/email/email_in_thread_status.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/get_mailbox_contain_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/load_more_thread_detail_emails.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_open_email_address_detail_action.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collape_expand.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/toggle_thread_detail_collape_expand.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_collapsed_email.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_load_more_circle.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension GetThreadDetailEmailViews on ThreadDetailController { extension GetThreadDetailEmailViews on ThreadDetailController {
List<Widget> getThreadDetailEmailViews() { List<Widget> getThreadDetailEmailViews() {
@@ -58,10 +64,37 @@ extension GetThreadDetailEmailViews on ThreadDetailController {
showSubject: isFirstEmailInThreadDetail, showSubject: isFirstEmailInThreadDetail,
imagePaths: imagePaths, imagePaths: imagePaths,
responsiveUtils: responsiveUtils, responsiveUtils: responsiveUtils,
mailboxContain: presentationEmail.mailboxContain, mailboxContain: presentationEmail.findMailboxContain(
mailboxDashBoardController.mapMailboxById,
),
emailLoaded: null, emailLoaded: null,
openEmailAddressDetailAction: (context, emailAddress) { onEmailActionClick: threadDetailOnEmailActionClick,
// TODO: Next PR onMoreActionClick: (presentationEmail, position) => emailActionReactor.handleMoreEmailAction(
mailboxContain: mailboxDashBoardController.getMailboxContain(presentationEmail),
presentationEmail: presentationEmail,
position: position,
responsiveUtils: responsiveUtils,
imagePaths: imagePaths,
username: session?.username,
handleEmailAction: threadDetailOnEmailActionClick,
additionalActions: [
EmailActionType.forward,
EmailActionType.replyAll,
EmailActionType.replyToList,
EmailActionType.printAll,
if (currentContext != null &&
responsiveUtils.isMobile(currentContext!))
EmailActionType.moveToMailbox,
if (!responsiveUtils.isDesktop(currentContext!)) ...[
EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash,
EmailActionType.deletePermanently,
],
],
),
openEmailAddressDetailAction: (_, emailAddress) {
openEmailAddressDetailAction(emailAddress);
}, },
onToggleThreadDetailCollapseExpand: () { onToggleThreadDetailCollapseExpand: () {
toggleThreadDetailCollapeExpand(presentationEmail); toggleThreadDetailCollapeExpand(presentationEmail);
@@ -0,0 +1,74 @@
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:tmail_ui_user/features/email/domain/state/download_attachment_for_web_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/model/download/download_task_state.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension HandleCollapsedEmailDownloadStates on ThreadDetailController {
void handleDownloadProgressState(Either<Failure, Success> state) {
state.fold(
(failure) => null,
(success) {
if (success is StartDownloadAttachmentForWeb &&
!success.previewerSupported
) {
mailboxDashBoardController.addDownloadTask(
DownloadTaskState(
taskId: success.taskId,
attachment: success.attachment,
onCancel: () => success.cancelToken?.cancel(),
),
);
if (currentOverlayContext != null &&
currentContext != null &&
!success.previewerSupported
) {
appToast.showToastMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).your_download_has_started,
leadingSVGIconColor: AppColor.primaryColor,
leadingSVGIcon: imagePaths.icDownload,
);
}
} else if (success is DownloadingAttachmentForWeb) {
mailboxDashBoardController.updateDownloadTask(
success.taskId,
(currentTask) {
final newTask = currentTask.copyWith(
progress: success.progress,
downloaded: success.downloaded,
total: success.total,
);
return newTask;
},
);
}
}
);
}
void handleDownloadSuccess(DownloadAttachmentForWebSuccess success) {
mailboxDashBoardController.deleteDownloadTask(success.taskId);
downloadManager.createAnchorElementDownloadFileWeb(
success.bytes,
success.attachment.generateFileName());
}
void handleDownloadFailure(DownloadAttachmentForWebFailure failure) {
if (failure.taskId != null) {
mailboxDashBoardController.deleteDownloadTask(failure.taskId!);
}
if (currentOverlayContext == null || currentContext == null) return;
final message = AppLocalizations.of(currentContext!).downloadMessageAsEMLFailed;
appToast.showToastErrorMessage(currentOverlayContext!, message);
}
}
@@ -25,7 +25,7 @@ extension InitializeThreadDetailEmails on ThreadDetailController {
properties: EmailUtils.getPropertiesForEmailGetMethod( properties: EmailUtils.getPropertiesForEmailGetMethod(
session!, session!,
accountId!, accountId!,
), ).union(additionalProperties),
)); ));
} }
} }
@@ -26,7 +26,7 @@ extension LoadMoreThreadDetailEmails on ThreadDetailController {
properties: EmailUtils.getPropertiesForEmailGetMethod( properties: EmailUtils.getPropertiesForEmailGetMethod(
session!, session!,
accountId!, accountId!,
), ).union(additionalProperties),
)); ));
} }
} }
@@ -0,0 +1,24 @@
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:model/email/mark_star_action.dart';
import 'package:model/extensions/presentation_email_extension.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
extension MarkCollapsedEmailStarSuccess on ThreadDetailController {
void markCollapsedEmailStarSuccess(MarkAsStarEmailSuccess success) {
final updatedMarkedEmail = emailIdsPresentation[success.emailId]
?.updateKeywords({
KeyWordIdentifier.emailFlagged: success.markStarAction == MarkStarAction.markStar,
});
if (updatedMarkedEmail == null) return;
if (mailboxDashBoardController.selectedEmail.value?.id == success.emailId) {
mailboxDashBoardController.updateEmailFlagByEmailIds(
[success.emailId],
markStarAction: success.markStarAction,
);
}
emailIdsPresentation[success.emailId] = updatedMarkedEmail;
}
}
@@ -0,0 +1,18 @@
import 'package:model/email/read_actions.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/update_current_emails_flags_extension.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/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension MarkCollapsedEmailReadSuccess on ThreadDetailController {
void markCollapsedEmailReadSuccess(MarkAsEmailReadSuccess success) {
mailboxDashBoardController.updateEmailFlagByEmailIds(
[success.emailId],
readAction: success.readActions,
);
if (success.readActions == ReadActions.markAsUnread) {
closeThreadDetailAction(currentContext);
}
}
}
@@ -0,0 +1,19 @@
import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension QuickCreateRuleFromCollapsedEmailSuccess on ThreadDetailController {
void quickCreateRuleFromCollapsedEmailSuccess(
CreateNewRuleFilterSuccess success,
) {
if (success.newListRules.isEmpty ||
currentContext == null ||
currentOverlayContext == null) return;
appToast.showToastSuccessMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).newFilterWasCreated,
);
}
}
@@ -0,0 +1,376 @@
import 'dart:async';
import 'package:core/presentation/utils/html_transformer/transform_configuration.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_extension.dart';
import 'package:model/extensions/session_extension.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/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension ThreadDetailOnEmailActionClick on ThreadDetailController {
void threadDetailOnEmailActionClick(
PresentationEmail presentationEmail,
EmailActionType emailActionType,
) {
switch(emailActionType) {
case EmailActionType.markAsRead:
_markRead(presentationEmail);
break;
case EmailActionType.markAsUnread:
_unRead(presentationEmail);
break;
case EmailActionType.markAsStarred:
_markStar(presentationEmail, MarkStarAction.markStar);
break;
case EmailActionType.unMarkAsStarred:
_markStar(presentationEmail, MarkStarAction.unMarkStar);
break;
case EmailActionType.moveToMailbox:
_moveEmail(presentationEmail);
break;
case EmailActionType.moveToTrash:
_trashEmail(presentationEmail);
break;
case EmailActionType.deletePermanently:
_deleteEmailPermanently(presentationEmail);
break;
case EmailActionType.moveToSpam:
_markEmailSpam(presentationEmail);
break;
case EmailActionType.unSpam:
_unSpamEmail(presentationEmail);
break;
case EmailActionType.createRule:
_quickCreateRule(presentationEmail);
break;
case EmailActionType.unsubscribe:
_unsubscribeEmail(presentationEmail);
break;
case EmailActionType.archiveMessage:
_archiveMessage(presentationEmail);
break;
case EmailActionType.printAll:
_printEmail(presentationEmail);
break;
case EmailActionType.downloadMessageAsEML:
_downloadMessageAsEML(presentationEmail);
break;
case EmailActionType.editAsNewEmail:
_editAsNewEmail(presentationEmail);
break;
case EmailActionType.reply:
_replyEmail(presentationEmail);
break;
case EmailActionType.replyAll:
_replyAll(presentationEmail);
break;
case EmailActionType.replyToList:
_replyToList(presentationEmail);
break;
case EmailActionType.forward:
_forward(presentationEmail);
break;
default:
break;
}
}
void _markRead(PresentationEmail presentationEmail) {
if (session == null || accountId == null) return;
consumeState(emailActionReactor.markAsEmailRead(
session!,
accountId!,
presentationEmail,
readAction: ReadActions.markAsRead,
));
}
void _unRead(PresentationEmail presentationEmail) {
if (session == null || accountId == null) return;
consumeState(emailActionReactor.markAsEmailRead(
session!,
accountId!,
presentationEmail,
readAction: ReadActions.markAsUnread,
));
}
void _markStar(
PresentationEmail presentationEmail,
MarkStarAction markStarAction,
) {
if (session == null || accountId == null) return;
consumeState(emailActionReactor.markAsStarEmail(
session!,
accountId!,
presentationEmail,
markStarAction: markStarAction,
));
}
Future<void> _moveEmail(PresentationEmail presentationEmail) async {
if (session == null || accountId == null) return;
final moveActionRequest = await emailActionReactor.moveToMailbox(
session!,
accountId!,
presentationEmail,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
);
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
}
void _trashEmail(PresentationEmail presentationEmail) {
if (session == null || accountId == null) return;
final moveActionRequest = emailActionReactor.moveToTrash(
presentationEmail,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
}
void _markEmailSpam(PresentationEmail presentationEmail) {
if (session == null || accountId == null) return;
final moveActionRequest = emailActionReactor.moveToSpam(
presentationEmail,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
}
void _unSpamEmail(PresentationEmail presentationEmail) {
if (session == null || accountId == null) return;
final moveActionRequest = emailActionReactor.unSpam(
presentationEmail,
mapMailbox: mailboxDashBoardController.mapMailboxById,
selectedMailbox: mailboxDashBoardController.selectedMailbox.value,
isSearchEmailRunning: mailboxDashBoardController.searchController.isSearchEmailRunning,
mapDefaultMailboxIdByRole: mailboxDashBoardController.mapDefaultMailboxIdByRole,
);
if (moveActionRequest == null) return;
mailboxDashBoardController.moveToMailbox(
session!,
accountId!,
moveActionRequest.moveRequest,
moveActionRequest.emailIdsWithReadStatus,
);
}
void _deleteEmailPermanently(PresentationEmail email) {
emailActionReactor.deleteEmailPermanently(
email,
onDeleteEmailRequest: (email) {
popBack();
mailboxDashBoardController.deleteEmailPermanently(email);
},
responsiveUtils: responsiveUtils,
imagePaths: imagePaths,
);
}
void _quickCreateRule(PresentationEmail presentationEmail) {
final emailAddress = presentationEmail.from?.first;
if (session == null || accountId == null || emailAddress == null) return;
consumeState(emailActionReactor.quickCreateRule(
session!,
accountId!,
emailAddress: emailAddress,
));
}
void _unsubscribeEmail(PresentationEmail presentationEmail) {
emailActionReactor.unsubscribeEmail(
presentationEmail,
emailUnsubscribe: EmailUtils.parsingUnsubscribe(
presentationEmail.listUnsubscribe ?? '',
),
onUnsubscribeByHttpsLink: mailboxDashBoardController.unsubscribeMail,
onUnsubscribeByMailtoLink: (emailId, navigationRouter) {
mailboxDashBoardController.openComposer(
ComposerArguments.fromUnsubscribeMailtoLink(
listEmailAddress: navigationRouter.listEmailAddress,
subject: navigationRouter.subject,
body: navigationRouter.body,
previousEmailId: emailId,
)
);
},
);
}
void _archiveMessage(PresentationEmail presentationEmail) {
if (currentContext == null) return;
emailActionReactor.archiveMessage(
presentationEmail,
onArchiveEmailRequest: (presentationEmail) {
mailboxDashBoardController.archiveMessage(currentContext!, presentationEmail);
},
);
}
void _printEmail(PresentationEmail presentationEmail) {
consumeState(emailActionReactor.printEmail(
presentationEmail,
ownEmailAddress: mailboxDashBoardController.ownEmailAddress.value,
emailLoaded: null,
session: session,
accountId: accountId,
baseDownloadUrl: session?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl),
transformConfiguration: TransformConfiguration.forPreviewEmailOnWeb(),
onGetEmailContentFailure: _showUnknownErrorToast,
));
}
void _downloadMessageAsEML(PresentationEmail presentationEmail) {
if (accountId == null || session == null) return;
consumeState(emailActionReactor.downloadMessageAsEML(
session!,
accountId!,
presentationEmail,
downloadProgressStateController: downloadProgressState,
));
}
void _editAsNewEmail(PresentationEmail presentationEmail) {
if (accountId == null || session == null) return;
emailActionReactor.editAsNewEmail(
presentationEmail,
onEditAsEmailRequest: (presentationEmail) {
mailboxDashBoardController.openComposer(
ComposerArguments.editAsNewEmail(presentationEmail),
);
},
);
}
void _replyEmail(PresentationEmail presentationEmail) {
emailActionReactor.replyEmail(
presentationEmail,
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,
)
);
},
emailLoaded: null,
);
}
void _replyAll(PresentationEmail presentationEmail) {
emailActionReactor.replyAll(
presentationEmail,
onReplyAllRequest: (presentationEmail, emailLoaded) {
mailboxDashBoardController.openComposer(
ComposerArguments.replyAllEmail(
presentationEmail: presentationEmail,
content: emailLoaded?.htmlContent,
inlineImages: emailLoaded?.inlineImages,
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: emailLoaded?.emailCurrent?.messageId,
references: emailLoaded?.emailCurrent?.references,
listPost: emailLoaded?.emailCurrent?.listPost,
)
);
},
emailLoaded: null,
);
}
void _replyToList(PresentationEmail presentationEmail) {
emailActionReactor.replyToList(
presentationEmail,
onReplyToListRequest: (presentationEmail, emailLoaded) {
mailboxDashBoardController.openComposer(
ComposerArguments.replyToListEmail(
presentationEmail: presentationEmail,
content: emailLoaded?.htmlContent,
inlineImages: emailLoaded?.inlineImages,
mailboxRole: presentationEmail.mailboxContain?.role,
messageId: emailLoaded?.emailCurrent?.messageId,
references: emailLoaded?.emailCurrent?.references,
listPost: emailLoaded?.emailCurrent?.listPost,
)
);
},
emailLoaded: null,
);
}
void _forward(PresentationEmail presentationEmail) {
emailActionReactor.forward(
presentationEmail,
onForwardRequest: (presentationEmail, emailLoaded) {
mailboxDashBoardController.openComposer(
ComposerArguments.forwardEmail(
presentationEmail: presentationEmail,
content: emailLoaded?.htmlContent,
attachments: emailLoaded?.attachments,
inlineImages: emailLoaded?.inlineImages,
messageId: emailLoaded?.emailCurrent?.messageId,
references: emailLoaded?.emailCurrent?.references,
)
);
},
emailLoaded: null,
);
}
_showUnknownErrorToast() {
if (currentOverlayContext != null && currentContext != null) {
appToast.showToastErrorMessage(
currentOverlayContext!,
AppLocalizations.of(currentContext!).unknownError,
);
}
}
}
@@ -0,0 +1,29 @@
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
extension ThreadDetailOpenEmailAddressDetailAction on ThreadDetailController {
void openEmailAddressDetailAction(EmailAddress emailAddress) {
if (session == null || accountId == null) return;
emailActionReactor.openEmailAddressDialog(
session!,
accountId!,
emailAddress: emailAddress,
responsiveUtils: responsiveUtils,
imagePaths: imagePaths,
appToast: appToast,
onComposeEmailFromEmailAddressRequest: (emailAddress) {
popBack();
mailboxDashBoardController.openComposer(
ComposerArguments.fromEmailAddress(emailAddress),
);
},
onQuickCreateRuleRequest: (quickCreateRuleStream) {
consumeState(quickCreateRuleStream);
},
);
}
}
@@ -2,6 +2,11 @@ import 'package:core/data/model/source_type/data_source_type.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart'; import 'package:jmap_dart_client/http/http_client.dart';
import 'package:tmail_ui_user/features/base/base_bindings.dart'; import 'package:tmail_ui_user/features/base/base_bindings.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/thread_detail/data/data_source/thread_detail_data_source.dart'; import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_data_source.dart';
import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart'; import 'package:tmail_ui_user/features/thread_detail/data/data_source/thread_detail_remote_data_source_impl.dart';
import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart'; import 'package:tmail_ui_user/features/thread_detail/data/network/thread_detail_api.dart';
@@ -25,6 +30,11 @@ class ThreadDetailBindings extends BaseBindings {
Get.put(ThreadDetailController( Get.put(ThreadDetailController(
Get.find<GetThreadByIdInteractor>(), Get.find<GetThreadByIdInteractor>(),
Get.find<GetEmailsByIdsInteractor>(), Get.find<GetEmailsByIdsInteractor>(),
Get.find<MarkAsEmailReadInteractor>(),
Get.find<MarkAsStarEmailInteractor>(),
Get.find<PrintEmailInteractor>(),
Get.find<GetEmailContentInteractor>(),
Get.find<DownloadAttachmentForWebInteractor>(),
)); ));
} }
@@ -1,43 +1,88 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'dart:async';
import 'package:core/data/network/download/download_manager.dart';
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/core/session/session.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/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: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/email/presentation_email.dart';
import 'package:model/extensions/keyword_identifier_extension.dart';
import 'package:model/extensions/session_extension.dart'; import 'package:model/extensions/session_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.dart'; import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.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/mark_as_email_read_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_star_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/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/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/search/email/presentation/search_email_controller.dart'; import 'package:tmail_ui_user/features/search/email/presentation/search_email_controller.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/state/get_thread_by_id_state.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_emails_by_ids_state.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/domain/usecases/get_thread_by_id_interactor.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_emails_by_ids_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/close_thread_detail_action.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/handle_get_email_ids_by_thread_id_success.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_emails_by_ids_success.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/initialize_thread_detail_emails.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/handle_collapsed_email_download_states.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/mark_collapsed_email_star_success.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';
class ThreadDetailController extends BaseController { class ThreadDetailController extends BaseController {
final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor; final GetThreadByIdInteractor _getEmailIdsByThreadIdInteractor;
final GetEmailsByIdsInteractor getEmailsByIdsInteractor; final GetEmailsByIdsInteractor getEmailsByIdsInteractor;
final MarkAsEmailReadInteractor _markAsEmailReadInteractor;
final MarkAsStarEmailInteractor _markAsStarEmailInteractor;
final PrintEmailInteractor _printEmailInteractor;
final GetEmailContentInteractor _getEmailContentInteractor;
final DownloadAttachmentForWebInteractor _downloadAttachmentForWebInteractor;
ThreadDetailController( ThreadDetailController(
this._getEmailIdsByThreadIdInteractor, this._getEmailIdsByThreadIdInteractor,
this.getEmailsByIdsInteractor, this.getEmailsByIdsInteractor,
this._markAsEmailReadInteractor,
this._markAsStarEmailInteractor,
this._printEmailInteractor,
this._getEmailContentInteractor,
this._downloadAttachmentForWebInteractor,
); );
final emailIdsPresentation = <EmailId, PresentationEmail?>{}.obs; final emailIdsPresentation = <EmailId, PresentationEmail?>{}.obs;
final currentExpandedEmailId = Rxn<EmailId>(); final currentExpandedEmailId = Rxn<EmailId>();
final currentEmailLoaded = Rxn<EmailLoaded>(); final currentEmailLoaded = Rxn<EmailLoaded>();
late final EmailActionReactor emailActionReactor;
final additionalProperties = Properties({
IndividualHeaderIdentifier.listPostHeader.value,
IndividualHeaderIdentifier.listUnsubscribeHeader.value,
});
final mailboxDashBoardController = Get.find<MailboxDashBoardController>(); final mailboxDashBoardController = Get.find<MailboxDashBoardController>();
final searchEmailController = Get.find<SearchEmailController>(); final searchEmailController = Get.find<SearchEmailController>();
final downloadManager = Get.find<DownloadManager>();
final downloadProgressState = StreamController<Either<Failure, Success>>();
ScrollController? scrollController; ScrollController? scrollController;
CreateNewEmailRuleFilterInteractor? _createNewEmailRuleFilterInteractor;
AccountId? get accountId => mailboxDashBoardController.accountId.value; AccountId? get accountId => mailboxDashBoardController.accountId.value;
Session? get session => mailboxDashBoardController.sessionCurrent; Session? get session => mailboxDashBoardController.sessionCurrent;
@@ -67,6 +112,17 @@ class ThreadDetailController extends BaseController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
injectRuleFilterBindings(session, accountId);
_createNewEmailRuleFilterInteractor = getBinding<CreateNewEmailRuleFilterInteractor>();
emailActionReactor = EmailActionReactor(
_markAsEmailReadInteractor,
_markAsStarEmailInteractor,
_createNewEmailRuleFilterInteractor,
_printEmailInteractor,
_getEmailContentInteractor,
_downloadAttachmentForWebInteractor,
);
downloadProgressState.stream.listen(handleDownloadProgressState);
ever(mailboxDashBoardController.selectedEmail, (presentationEmail) { ever(mailboxDashBoardController.selectedEmail, (presentationEmail) {
if (presentationEmail?.threadId == null) { if (presentationEmail?.threadId == null) {
closeThreadDetailAction(currentContext); closeThreadDetailAction(currentContext);
@@ -86,6 +142,22 @@ class ThreadDetailController extends BaseController {
)); ));
} }
}); });
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();
}
}
});
} }
void reset() { void reset() {
@@ -103,6 +175,14 @@ class ThreadDetailController extends BaseController {
initializeThreadDetailEmails(); initializeThreadDetailEmails();
} else if (success is GetEmailsByIdsSuccess) { } else if (success is GetEmailsByIdsSuccess) {
handleGetEmailsByIdsSuccess(success); handleGetEmailsByIdsSuccess(success);
} else if (success is MarkAsEmailReadSuccess) {
markCollapsedEmailReadSuccess(success);
} else if (success is MarkAsStarEmailSuccess) {
markCollapsedEmailStarSuccess(success);
} else if (success is CreateNewRuleFilterSuccess) {
quickCreateRuleFromCollapsedEmailSuccess(success);
} else if (success is DownloadAttachmentForWebSuccess) {
handleDownloadSuccess(success);
} else { } else {
super.handleSuccessViewState(success); super.handleSuccessViewState(success);
} }
@@ -118,6 +198,10 @@ class ThreadDetailController extends BaseController {
showRetryToast(failure); showRetryToast(failure);
return; return;
} }
if (failure is DownloadAttachmentForWebFailure) {
handleDownloadFailure(failure);
return;
}
super.handleFailureViewState(failure); super.handleFailureViewState(failure);
} }
} }
@@ -4,18 +4,19 @@ import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:model/extensions/session_extension.dart'; import 'package:model/extensions/session_extension.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart'; import 'package:tmail_ui_user/features/email/presentation/action/email_ui_action.dart';
import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart';
import 'package:tmail_ui_user/features/email/presentation/email_view.dart'; import 'package:tmail_ui_user/features/email/presentation/email_view.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_bottom_bar_widget.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/get_mailbox_contain_extension.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/state/get_thread_by_id_state.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/close_thread_detail_action.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_loading_view.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_detail_loading_view.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_details_email_views.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/extension/get_thread_details_email_views.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/extension/thread_detail_on_email_action_click.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/thread_detail_controller.dart';
import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_app_bar.dart'; import 'package:tmail_ui_user/features/thread_detail/presentation/widgets/thread_detail_app_bar.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
class ThreadDetailView extends GetWidget<ThreadDetailController> { class ThreadDetailView extends GetWidget<ThreadDetailController> {
const ThreadDetailView({super.key}); const ThreadDetailView({super.key});
@@ -39,15 +40,29 @@ class ThreadDetailView extends GetWidget<ThreadDetailController> {
lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull, lastEmailOfThread: controller.emailIdsPresentation.values.lastOrNull,
ownUserName: controller.session?.getOwnEmailAddress() ?? '', ownUserName: controller.session?.getOwnEmailAddress() ?? '',
mailboxContain: _getMailboxContain(), mailboxContain: _getMailboxContain(),
emailLoaded: getBinding<SingleEmailController>( onEmailActionClick: controller.threadDetailOnEmailActionClick,
tag: controller.emailIdsPresentation.keys.lastOrNull?.id.value onMoreActionClick: (presentationEmail, position) => controller.emailActionReactor.handleMoreEmailAction(
)?.currentEmailLoaded.value, mailboxContain: controller.mailboxDashBoardController.getMailboxContain(presentationEmail),
onEmailActionClick: (email, action) { presentationEmail: presentationEmail,
// TODO: Next PR position: position,
}, responsiveUtils: controller.responsiveUtils,
onMoreActionClick: (p0, p1) { imagePaths: controller.imagePaths,
// TODO: Next PR username: controller.session?.username,
}, handleEmailAction: controller.threadDetailOnEmailActionClick,
additionalActions: [
if (controller.responsiveUtils.isMobile(context)) ...[
EmailActionType.forward,
EmailActionType.replyAll,
EmailActionType.replyToList,
],
EmailActionType.markAsStarred,
EmailActionType.unMarkAsStarred,
EmailActionType.moveToTrash,
EmailActionType.deletePermanently,
EmailActionType.printAll,
EmailActionType.moveToMailbox,
],
),
); );
}), }),
Obx(() => controller.getThreadDetailLoadingView( Obx(() => controller.getThreadDetailLoadingView(
@@ -5,10 +5,8 @@ import 'package:core/utils/platform_info.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:model/email/email_action_type.dart'; import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart'; import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/email_extension.dart';
import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:tmail_ui_user/features/email/presentation/model/email_loaded.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/styles/email_view_app_bar_widget_styles.dart';
import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart'; import 'package:tmail_ui_user/features/email/presentation/utils/email_utils.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_app_bar_widget.dart';
@@ -28,7 +26,6 @@ class ThreadDetailAppBar extends StatelessWidget {
this.optionWidgets = const [], this.optionWidgets = const [],
this.onEmailActionClick, this.onEmailActionClick,
this.onMoreActionClick, this.onMoreActionClick,
this.emailLoaded,
}); });
final ResponsiveUtils responsiveUtils; final ResponsiveUtils responsiveUtils;
@@ -41,16 +38,31 @@ class ThreadDetailAppBar extends StatelessWidget {
final List<Widget> optionWidgets; final List<Widget> optionWidgets;
final OnEmailActionClick? onEmailActionClick; final OnEmailActionClick? onEmailActionClick;
final OnMoreActionClick? onMoreActionClick; final OnMoreActionClick? onMoreActionClick;
final EmailLoaded? emailLoaded;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isReplyToListEnabled = EmailUtils.isReplyToListEnabled( final isReplyToListEnabled = EmailUtils.isReplyToListEnabled(
emailLoaded?.emailCurrent?.listPost ?? '', lastEmailOfThread?.listPost ?? '',
); );
final child = LayoutBuilder( final child = LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
Widget backButton = EmailViewBackButton(
imagePaths: imagePaths,
onBackAction: () => closeThreadDetailAction(context),
mailboxContain: mailboxContain,
isSearchActivated: isSearchRunning,
maxWidth: constraints.maxWidth,
);
if (responsiveUtils.isMobile(context)) {
backButton = Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: backButton,
),
);
}
return Container( return Container(
height: PlatformInfo.isIOS height: PlatformInfo.isIOS
? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils) ? EmailViewAppBarWidgetStyles.heightIOS(context, responsiveUtils)
@@ -76,20 +88,7 @@ class ThreadDetailAppBar extends StatelessWidget {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end,
children: [ children: [
if (_supportDisplayMailboxNameTitle(context)) if (_supportDisplayMailboxNameTitle(context)) backButton,
Expanded(
child: Align(
alignment: Alignment.centerLeft,
child: EmailViewBackButton(
imagePaths: imagePaths,
onBackAction: () => closeThreadDetailAction(context),
mailboxContain: mailboxContain,
isSearchActivated: isSearchRunning,
maxWidth: constraints.maxWidth,
),
),
),
...optionWidgets,
if (lastEmailOfThread != null) ...[ if (lastEmailOfThread != null) ...[
TMailButtonWidget.fromIcon( TMailButtonWidget.fromIcon(
icon: imagePaths.icReply, icon: imagePaths.icReply,
@@ -102,60 +101,45 @@ class ThreadDetailAppBar extends StatelessWidget {
EmailActionType.reply, EmailActionType.reply,
), ),
), ),
if (lastEmailOfThread!.getCountMailAddressWithoutMe(ownUserName) > 1) if (!responsiveUtils.isMobile(context)) ...[
if (lastEmailOfThread!.getCountMailAddressWithoutMe(ownUserName) > 1)
TMailButtonWidget.fromIcon(
icon: imagePaths.icReplyAll,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).reply_all,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!,
EmailActionType.replyAll,
),
),
if (isReplyToListEnabled)
TMailButtonWidget.fromIcon(
icon: imagePaths.icReply,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).replyToList,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!,
EmailActionType.replyToList,
),
),
TMailButtonWidget.fromIcon( TMailButtonWidget.fromIcon(
icon: imagePaths.icReplyAll, icon: imagePaths.icForward,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize, iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor, iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).reply_all, tooltipMessage: AppLocalizations.of(context).forward,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call( onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!, lastEmailOfThread!,
EmailActionType.replyAll, EmailActionType.forward,
), ),
), ),
if (isReplyToListEnabled) ],
TMailButtonWidget.fromIcon( if (!responsiveUtils.isMobile(context)) const Spacer(),
icon: imagePaths.icReply, ...optionWidgets,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).replyToList,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!,
EmailActionType.replyToList,
),
),
TMailButtonWidget.fromIcon(
icon: imagePaths.icForward,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: EmailViewAppBarWidgetStyles.iconColor,
tooltipMessage: AppLocalizations.of(context).forward,
backgroundColor: Colors.transparent,
onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!,
EmailActionType.forward,
),
),
TMailButtonWidget.fromIcon(
icon: lastEmailOfThread!.hasStarred
? imagePaths.icStar
: imagePaths.icUnStar,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
iconColor: lastEmailOfThread!.hasStarred
? null
: EmailViewAppBarWidgetStyles.iconColor,
backgroundColor: Colors.transparent,
tooltipMessage: lastEmailOfThread!.hasStarred
? AppLocalizations.of(context).not_starred
: AppLocalizations.of(context).mark_as_starred,
onTapActionCallback: () => onEmailActionClick?.call(
lastEmailOfThread!,
lastEmailOfThread!.hasStarred
? EmailActionType.unMarkAsStarred
: EmailActionType.markAsStarred
),
),
TMailButtonWidget.fromIcon( TMailButtonWidget.fromIcon(
icon: imagePaths.icMoreVertical, icon: imagePaths.icMoreVertical,
iconSize: EmailViewAppBarWidgetStyles.buttonIconSize, iconSize: EmailViewAppBarWidgetStyles.buttonIconSize,
@@ -169,7 +153,7 @@ class ThreadDetailAppBar extends StatelessWidget {
? (position) => onMoreActionClick?.call(lastEmailOfThread!, position) ? (position) => onMoreActionClick?.call(lastEmailOfThread!, position)
: null, : null,
), ),
], ] else const Spacer(),
], ],
), ),
); );
@@ -72,6 +72,7 @@ class ThreadDetailCollapsedEmail extends StatelessWidget {
openEmailAddressDetailAction: openEmailAddressDetailAction, openEmailAddressDetailAction: openEmailAddressDetailAction,
showRecipients: false, showRecipients: false,
onTapAvatarActionClick: onToggleThreadDetailCollapseExpand, onTapAvatarActionClick: onToggleThreadDetailCollapseExpand,
mailboxContain: mailboxContain,
), ),
Padding( Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
+17
View File
@@ -17,6 +17,7 @@ import 'package:model/email/email_in_thread_status.dart';
import 'package:model/email/mail_priority_header.dart'; import 'package:model/email/mail_priority_header.dart';
import 'package:model/extensions/email_address_extension.dart'; import 'package:model/extensions/email_address_extension.dart';
import 'package:model/extensions/keyword_identifier_extension.dart'; import 'package:model/extensions/keyword_identifier_extension.dart';
import 'package:model/extensions/list_email_header_extension.dart';
import 'package:model/extensions/media_type_nullable_extension.dart'; import 'package:model/extensions/media_type_nullable_extension.dart';
import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:model/mailbox/presentation_mailbox.dart';
import 'package:model/mailbox/select_mode.dart'; import 'package:model/mailbox/select_mode.dart';
@@ -50,6 +51,8 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
final Map<IndividualHeaderIdentifier, String?>? xPriorityHeader; final Map<IndividualHeaderIdentifier, String?>? xPriorityHeader;
final Map<IndividualHeaderIdentifier, String?>? importanceHeader; final Map<IndividualHeaderIdentifier, String?>? importanceHeader;
final Map<IndividualHeaderIdentifier, String?>? priorityHeader; final Map<IndividualHeaderIdentifier, String?>? priorityHeader;
final Map<IndividualHeaderIdentifier, String?>? listPostHeader;
final Map<IndividualHeaderIdentifier, String?>? listUnsubscribeHeader;
final EmailInThreadStatus? emailInThreadStatus; final EmailInThreadStatus? emailInThreadStatus;
PresentationEmail({ PresentationEmail({
@@ -79,6 +82,8 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
this.xPriorityHeader, this.xPriorityHeader,
this.importanceHeader, this.importanceHeader,
this.priorityHeader, this.priorityHeader,
this.listPostHeader,
this.listUnsubscribeHeader,
this.emailInThreadStatus, this.emailInThreadStatus,
}); });
@@ -141,6 +146,12 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
bool get hasCalendarEvent => headerCalendarEvent?[IndividualHeaderIdentifier.headerCalendarEvent]?.isNotEmpty == true; bool get hasCalendarEvent => headerCalendarEvent?[IndividualHeaderIdentifier.headerCalendarEvent]?.isNotEmpty == true;
String? get listPost => emailHeader?.toSet().listPost?.trim()
?? listPostHeader?[IndividualHeaderIdentifier.listPostHeader]?.trim();
String? get listUnsubscribe => emailHeader?.toSet().listUnsubscribe?.trim()
?? listUnsubscribeHeader?[IndividualHeaderIdentifier.listUnsubscribeHeader]?.trim();
bool get isMarkAsImportant { bool get isMarkAsImportant {
final xPriority = xPriorityHeader?[IndividualHeaderIdentifier.xPriorityHeader] final xPriority = xPriorityHeader?[IndividualHeaderIdentifier.xPriorityHeader]
?.trim() ?.trim()
@@ -201,6 +212,8 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
xPriorityHeader, xPriorityHeader,
importanceHeader, importanceHeader,
priorityHeader, priorityHeader,
listPostHeader,
listUnsubscribeHeader,
emailInThreadStatus, emailInThreadStatus,
]; ];
@@ -231,6 +244,8 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
Map<IndividualHeaderIdentifier, String?>? xPriorityHeader, Map<IndividualHeaderIdentifier, String?>? xPriorityHeader,
Map<IndividualHeaderIdentifier, String?>? importanceHeader, Map<IndividualHeaderIdentifier, String?>? importanceHeader,
Map<IndividualHeaderIdentifier, String?>? priorityHeader, Map<IndividualHeaderIdentifier, String?>? priorityHeader,
Map<IndividualHeaderIdentifier, String?>? listPostHeader,
Map<IndividualHeaderIdentifier, String?>? listUnsubscribeHeader,
EmailInThreadStatus? emailInThreadStatus, EmailInThreadStatus? emailInThreadStatus,
}) { }) {
return PresentationEmail( return PresentationEmail(
@@ -260,6 +275,8 @@ class PresentationEmail with EquatableMixin, SearchSnippetMixin {
xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader, xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader,
importanceHeader: importanceHeader ?? this.importanceHeader, importanceHeader: importanceHeader ?? this.importanceHeader,
priorityHeader: priorityHeader ?? this.priorityHeader, priorityHeader: priorityHeader ?? this.priorityHeader,
listPostHeader: listPostHeader ?? this.listPostHeader,
listUnsubscribeHeader: listUnsubscribeHeader ?? this.listUnsubscribeHeader,
emailInThreadStatus: emailInThreadStatus ?? this.emailInThreadStatus, emailInThreadStatus: emailInThreadStatus ?? this.emailInThreadStatus,
); );
} }
+18 -2
View File
@@ -33,7 +33,9 @@ extension EmailExtension on Email {
bool get withAttachments => hasAttachment == true; bool get withAttachments => hasAttachment == true;
String get listUnsubscribe => headers.listUnsubscribe; String get listUnsubscribe => headers.listUnsubscribe
?? listUnsubscribeHeader?[IndividualHeaderIdentifier.listUnsubscribeHeader]
?? '';
bool get hasRequestReadReceipt => headers.readReceiptHasBeenRequested || bool get hasRequestReadReceipt => headers.readReceiptHasBeenRequested ||
headerMdn?.containsKey(IndividualHeaderIdentifier.headerMdn) == true; headerMdn?.containsKey(IndividualHeaderIdentifier.headerMdn) == true;
@@ -42,7 +44,9 @@ extension EmailExtension on Email {
String get sMimeStatusHeaderParsed => sMimeStatusHeader?[IndividualHeaderIdentifier.sMimeStatusHeader]?.trim() ?? ''; String get sMimeStatusHeaderParsed => sMimeStatusHeader?[IndividualHeaderIdentifier.sMimeStatusHeader]?.trim() ?? '';
String get listPost => headers.listPost.trim(); String get listPost => headers.listPost?.trim()
?? listPostHeader?[IndividualHeaderIdentifier.listPostHeader]?.trim()
?? '';
IdentityId? get identityIdFromHeader { IdentityId? get identityIdFromHeader {
final rawIdentityId = identityHeader?[IndividualHeaderIdentifier.identityHeader]; final rawIdentityId = identityHeader?[IndividualHeaderIdentifier.identityHeader];
@@ -125,6 +129,8 @@ extension EmailExtension on Email {
xPriorityHeader: xPriorityHeader, xPriorityHeader: xPriorityHeader,
importanceHeader: importanceHeader, importanceHeader: importanceHeader,
priorityHeader: priorityHeader, priorityHeader: priorityHeader,
listPostHeader: listPostHeader,
listUnsubscribeHeader: listUnsubscribeHeader,
) )
..searchSnippetSubject = searchSnippetSubject ..searchSnippetSubject = searchSnippetSubject
..searchSnippetPreview = searchSnippetPreview; ..searchSnippetPreview = searchSnippetPreview;
@@ -158,6 +164,12 @@ extension EmailExtension on Email {
priorityHeader: updatedProperties.contain(IndividualHeaderIdentifier.priorityHeader.value) priorityHeader: updatedProperties.contain(IndividualHeaderIdentifier.priorityHeader.value)
? newEmail.priorityHeader ? newEmail.priorityHeader
: priorityHeader, : priorityHeader,
listPostHeader: updatedProperties.contain(IndividualHeaderIdentifier.listPostHeader.value)
? newEmail.listPostHeader
: listPostHeader,
listUnsubscribeHeader: updatedProperties.contain(IndividualHeaderIdentifier.listUnsubscribeHeader.value)
? newEmail.listUnsubscribeHeader
: listUnsubscribeHeader,
); );
} }
@@ -246,6 +258,8 @@ extension EmailExtension on Email {
Map<IndividualHeaderIdentifier, String?>? xPriorityHeader, Map<IndividualHeaderIdentifier, String?>? xPriorityHeader,
Map<IndividualHeaderIdentifier, String?>? importanceHeader, Map<IndividualHeaderIdentifier, String?>? importanceHeader,
Map<IndividualHeaderIdentifier, String?>? priorityHeader, Map<IndividualHeaderIdentifier, String?>? priorityHeader,
Map<IndividualHeaderIdentifier, String?>? listPostHeader,
Map<IndividualHeaderIdentifier, String?>? listUnsubscribeHeader,
}) { }) {
return Email( return Email(
id: id ?? this.id, id: id ?? this.id,
@@ -283,6 +297,8 @@ extension EmailExtension on Email {
xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader, xPriorityHeader: xPriorityHeader ?? this.xPriorityHeader,
importanceHeader: importanceHeader ?? this.importanceHeader, importanceHeader: importanceHeader ?? this.importanceHeader,
priorityHeader: priorityHeader ?? this.priorityHeader, priorityHeader: priorityHeader ?? this.priorityHeader,
listPostHeader: listPostHeader ?? this.listPostHeader,
listUnsubscribeHeader: listUnsubscribeHeader ?? this.listUnsubscribeHeader,
); );
} }
} }
@@ -14,9 +14,9 @@ extension ListEmailHeaderExtension on Set<EmailHeader>? {
} }
} }
String get listUnsubscribe { String? get listUnsubscribe {
final listUnsubscribe = this?.firstWhereOrNull((header) => header.name == EmailProperty.headerUnsubscribeKey); final listUnsubscribe = this?.firstWhereOrNull((header) => header.name == EmailProperty.headerUnsubscribeKey);
return listUnsubscribe?.value ?? ''; return listUnsubscribe?.value;
} }
String get sMimeStatus { String get sMimeStatus {
@@ -25,8 +25,8 @@ extension ListEmailHeaderExtension on Set<EmailHeader>? {
return sMimeStatus?.value.trim() ?? ''; return sMimeStatus?.value.trim() ?? '';
} }
String get listPost { String? get listPost {
final listPost = this?.firstWhereOrNull((header) => header.name == EmailProperty.headerListPostKey); final listPost = this?.firstWhereOrNull((header) => header.name == EmailProperty.headerListPostKey);
return listPost?.value ?? ''; return listPost?.value;
} }
} }
@@ -111,6 +111,9 @@ extension PresentationEmailExtension on PresentationEmail {
xPriorityHeader: xPriorityHeader, xPriorityHeader: xPriorityHeader,
importanceHeader: importanceHeader, importanceHeader: importanceHeader,
priorityHeader: priorityHeader, priorityHeader: priorityHeader,
listPostHeader: listPostHeader,
listUnsubscribeHeader: listUnsubscribeHeader,
threadId: threadId,
); );
} }
@@ -2,6 +2,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:mockito/annotations.dart'; import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
@@ -40,6 +41,7 @@ void main() {
Map.fromEntries(emailIds.map((emailId) => MapEntry(emailId, null))).obs, Map.fromEntries(emailIds.map((emailId) => MapEntry(emailId, null))).obs,
); );
when(threadDetailController.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor); when(threadDetailController.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(threadDetailController.additionalProperties).thenReturn(Properties.empty());
when(getEmailsByIdsInteractor.execute( when(getEmailsByIdsInteractor.execute(
any, any,
any, any,
@@ -1,6 +1,7 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:mockito/annotations.dart'; import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart'; import 'package:mockito/mockito.dart';
@@ -37,6 +38,7 @@ void main() {
when(controller.session).thenReturn(SessionFixtures.aliceSession); when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId); when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor); when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.additionalProperties).thenReturn(Properties.empty());
// Act // Act
controller.loadMoreThreadDetailEmails(); controller.loadMoreThreadDetailEmails();
@@ -86,6 +88,7 @@ void main() {
when(controller.session).thenReturn(SessionFixtures.aliceSession); when(controller.session).thenReturn(SessionFixtures.aliceSession);
when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId); when(controller.accountId).thenReturn(AccountFixtures.aliceAccountId);
when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor); when(controller.getEmailsByIdsInteractor).thenReturn(getEmailsByIdsInteractor);
when(controller.additionalProperties).thenReturn(Properties.empty());
// Act // Act
controller.loadMoreThreadDetailEmails(); controller.loadMoreThreadDetailEmails();