TF-1961 Transform html email content in composer

(cherry picked from commit f5470c130744242195b265a0ea4aad044ad8e740)
This commit is contained in:
dab246
2023-08-18 19:42:04 +07:00
committed by Dat Vu
parent e06d35f1e1
commit 8eed49fd73
45 changed files with 1139 additions and 644 deletions
@@ -5,6 +5,7 @@ import 'package:core/presentation/utils/app_toast.dart';
import 'package:core/presentation/utils/responsive_utils.dart';
import 'package:core/presentation/utils/style_utils.dart';
import 'package:core/presentation/views/button/icon_button_web.dart';
import 'package:core/presentation/views/button/tmail_button_widget.dart';
import 'package:core/presentation/views/list/sliver_grid_delegate_fixed_height.dart';
import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:core/presentation/views/text/text_overflow_builder.dart';
@@ -321,22 +322,17 @@ abstract class BaseComposerView extends GetWidget<ComposerController>
)
),
const Spacer(),
Material(
type: MaterialType.circle,
color: Colors.transparent,
child: TextButton(
onPressed: controller.toggleDisplayAttachments,
child: Text(
expandModeAttachment == ExpandMode.EXPAND
? AppLocalizations.of(context).hide
: '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})',
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 12,
color: AppColor.colorTextButton
)
)
)
TMailButtonWidget.fromText(
text: expandModeAttachment == ExpandMode.EXPAND
? AppLocalizations.of(context).hide
: '${AppLocalizations.of(context).showAll} (${uploadFilesState.length})',
onTapActionCallback: controller.toggleDisplayAttachments,
textStyle: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 12,
color: AppColor.colorTextButton
),
backgroundColor: Colors.transparent,
)
],
);
@@ -29,6 +29,7 @@ import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/email/data/repository/email_repository_impl.dart';
import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/mailbox_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource/state_datasource.dart';
import 'package:tmail_ui_user/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart';
@@ -163,6 +164,7 @@ class ComposerBindings extends BaseBindings {
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find<ComposerRepository>()));
Get.lazyPut(() => TransformHtmlEmailContentInteractor(Get.find<EmailRepository>()));
IdentityInteractorsBindings().dependencies();
}
@@ -184,6 +186,7 @@ class ComposerBindings extends BaseBindings {
Get.find<SaveComposerCacheOnWebInteractor>(),
Get.find<RichTextWebController>(),
Get.find<DownloadImageAsBase64Interactor>(),
Get.find<TransformHtmlEmailContentInteractor>(),
));
}
@@ -48,8 +48,11 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/email_ac
import 'package:tmail_ui_user/features/composer/presentation/model/image_source.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/email/domain/exceptions/email_exceptions.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
@@ -60,6 +63,7 @@ import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_id
import 'package:tmail_ui_user/features/manage_account/presentation/extensions/identity_extension.dart';
import 'package:tmail_ui_user/features/network_connection/presentation/network_connection_controller.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/extensions/sending_email_extension.dart';
import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_action_type.dart';
import 'package:tmail_ui_user/features/sending_queue/presentation/model/sending_email_arguments.dart';
import 'package:tmail_ui_user/features/upload/domain/model/upload_task_id.dart';
@@ -95,7 +99,7 @@ class ComposerController extends BaseController {
final bccAddressExpandMode = ExpandMode.EXPAND.obs;
final identitySelected = Rxn<Identity>();
final listIdentities = <Identity>[].obs;
final emailContentsViewState = Rx<Either<Failure, Success>>(Right(UIState.idle));
final emailContentsViewState = Rxn<Either<Failure, Success>>();
final hasRequestReadReceipt = false.obs;
final LocalFilePickerInteractor _localFilePickerInteractor;
@@ -109,6 +113,7 @@ class ComposerController extends BaseController {
final SaveComposerCacheOnWebInteractor _saveComposerCacheOnWebInteractor;
final RichTextWebController richTextWebController;
final DownloadImageAsBase64Interactor _downloadImageAsBase64Interactor;
final TransformHtmlEmailContentInteractor _transformHtmlEmailContentInteractor;
GetAutoCompleteWithDeviceContactInteractor? _getAutoCompleteWithDeviceContactInteractor;
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
@@ -145,7 +150,6 @@ class ComposerController extends BaseController {
List<Attachment> initialAttachments = <Attachment>[];
String? _textEditorWeb;
String? _initTextEditor;
String? _emailContents;
double? maxWithEditor;
late Worker uploadInlineImageWorker;
@@ -201,6 +205,7 @@ class ComposerController extends BaseController {
this._saveComposerCacheOnWebInteractor,
this.richTextWebController,
this._downloadImageAsBase64Interactor,
this._transformHtmlEmailContentInteractor,
);
@override
@@ -264,7 +269,9 @@ class ComposerController extends BaseController {
@override
void handleSuccessViewState(Success success) {
super.handleSuccessViewState(success);
if (success is GetEmailContentLoading) {
if (success is GetEmailContentLoading ||
success is TransformHtmlEmailContentLoading ||
success is TransformHtmlEmailContentSuccess) {
emailContentsViewState.value = Right(success);
} else if (success is LocalFilePickerSuccess) {
_pickFileSuccess(success);
@@ -299,7 +306,8 @@ class ComposerController extends BaseController {
super.handleFailureViewState(failure);
if (failure is LocalFilePickerFailure || failure is LocalFilePickerCancel) {
_pickFileFailure(failure);
} else if (failure is GetEmailContentFailure) {
} else if (failure is GetEmailContentFailure ||
failure is TransformHtmlEmailContentFailure) {
emailContentsViewState.value = Left(failure);
}
}
@@ -387,39 +395,105 @@ class ComposerController extends BaseController {
: Get.arguments;
if (arguments is ComposerArguments) {
composerArguments.value = arguments;
injectAutoCompleteBindings(
mailboxDashBoardController.sessionCurrent,
mailboxDashBoardController.accountId.value);
mailboxDashBoardController.sessionCurrent,
mailboxDashBoardController.accountId.value
);
if (arguments.emailActionType == EmailActionType.edit) {
_getEmailContentAction(arguments);
switch(arguments.emailActionType) {
case EmailActionType.editDraft:
_initEmailAddress(
presentationEmail: arguments.presentationEmail!,
actionType: EmailActionType.editDraft
);
_initSubjectEmail(
presentationEmail: arguments.presentationEmail!,
actionType: EmailActionType.editDraft
);
_getEmailContentFromEmailId(
emailId: arguments.presentationEmail!.id!,
isDraftEmail: arguments.presentationEmail!.isDraft
);
break;
case EmailActionType.editSendingEmail:
_initEmailAddress(
presentationEmail: arguments.sendingEmail!.presentationEmail,
actionType: EmailActionType.editSendingEmail
);
_initSubjectEmail(
presentationEmail: arguments.sendingEmail!.presentationEmail,
actionType: EmailActionType.editSendingEmail
);
_getEmailContentFromSendingEmail(arguments.sendingEmail!);
break;
case EmailActionType.composeFromContentShared:
_getEmailContentFromContentShared(arguments.emailContents!);
break;
case EmailActionType.composeFromFileShared:
_addAttachmentFromFileShare(arguments.listSharedMediaFile!);
break;
case EmailActionType.composeFromEmailAddress:
listToEmailAddress.add(arguments.emailAddress!);
isInitialRecipient.value = true;
toAddressExpandMode.value = ExpandMode.COLLAPSE;
_updateStatusEmailSendButton();
break;
case EmailActionType.reply:
case EmailActionType.replyAll:
_initEmailAddress(
presentationEmail: arguments.presentationEmail!,
actionType: arguments.emailActionType,
mailboxRole: arguments.presentationEmail!.mailboxContain?.role ?? mailboxDashBoardController.selectedMailbox.value?.role
);
_initSubjectEmail(
presentationEmail: arguments.presentationEmail!,
actionType: arguments.emailActionType
);
_transformHtmlEmailContent(arguments.emailContents);
break;
case EmailActionType.forward:
_initSubjectEmail(
presentationEmail: arguments.presentationEmail!,
actionType: arguments.emailActionType
);
_initAttachments(arguments.attachments ?? []);
_transformHtmlEmailContent(arguments.emailContents);
break;
case EmailActionType.reopenComposerBrowser:
_initEmailAddress(
presentationEmail: arguments.presentationEmail!,
actionType: EmailActionType.reopenComposerBrowser
);
_initSubjectEmail(
presentationEmail: arguments.presentationEmail!,
actionType: EmailActionType.reopenComposerBrowser
);
_initAttachments(arguments.attachments ?? []);
_getEmailContentFromSessionStorageBrowser(arguments.emailContents!);
break;
default:
break;
}
_initEmailAddress(arguments);
_initSubjectEmail(arguments);
_initAttachments(arguments);
}
_autoFocusFieldWhenLauncher();
}
void _initSubjectEmail(ComposerArguments arguments) {
if (currentContext != null) {
final subjectEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
final newSubject = arguments.emailActionType.getSubjectComposer(currentContext!, subjectEmail);
setSubjectEmail(newSubject);
subjectEmailInputController.text = newSubject;
}
void _initSubjectEmail({
required PresentationEmail presentationEmail,
required EmailActionType actionType
}) {
final subjectEmail = presentationEmail.getEmailTitle().trim();
final newSubject = actionType.getSubjectComposer(currentContext, subjectEmail);
setSubjectEmail(newSubject);
subjectEmailInputController.text = newSubject;
}
void _initAttachments(ComposerArguments arguments) {
if (arguments.attachments?.isNotEmpty == true) {
initialAttachments = arguments.attachments!;
uploadController.initializeUploadAttachments(
arguments.attachments!.listAttachmentsDisplayedOutSide);
}
if (PlatformInfo.isWeb) {
expandModeAttachments.value = ExpandMode.EXPAND;
void _initAttachments(List<Attachment> attachments) {
if (attachments.isNotEmpty) {
initialAttachments = attachments;
uploadController.initializeUploadAttachments(attachments.listAttachmentsDisplayedOutSide);
}
}
@@ -438,7 +512,6 @@ class ComposerController extends BaseController {
.toList();
if (listIdentities.isNotEmpty) {
_initTextEditor = null;
await selectIdentity(listIdentities.first);
}
}
@@ -446,113 +519,49 @@ class ComposerController extends BaseController {
_autoFocusFieldWhenLauncher();
}
String? _getHeaderEmailQuoted(BuildContext context, ComposerArguments arguments) {
final presentationEmail = arguments.presentationEmail;
if (presentationEmail != null) {
final locale = Localizations.localeOf(context).toLanguageTag();
log('ComposerController::_getHeaderEmailQuoted(): emailActionType: ${arguments.emailActionType}');
switch(arguments.emailActionType) {
case EmailActionType.reply:
case EmailActionType.replyAll:
final receivedAt = presentationEmail.receivedAt;
final emailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
return AppLocalizations.of(context).header_email_quoted(
receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale),
emailAddress);
case EmailActionType.forward:
var headerQuoted = '------- ${AppLocalizations.of(context).forwarded_message} -------'.addNewLineTag();
final subject = presentationEmail.subject ?? '';
final receivedAt = presentationEmail.receivedAt;
final fromEmailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
final toEmailAddress = presentationEmail.to.listEmailAddressToString(isFullEmailAddress: true);
final ccEmailAddress = presentationEmail.cc.listEmailAddressToString(isFullEmailAddress: true);
final bccEmailAddress = presentationEmail.bcc.listEmailAddressToString(isFullEmailAddress: true);
if (subject.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).subject_email}: ')
.append(subject)
.addNewLineTag();
}
if (receivedAt != null) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).date}: ')
.append(receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale))
.addNewLineTag();
}
if (fromEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).from_email_address_prefix}: ')
.append(fromEmailAddress)
.addNewLineTag();
}
if (toEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).to_email_address_prefix}: ')
.append(toEmailAddress)
.addNewLineTag();
}
if (ccEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).cc_email_address_prefix}: ')
.append(ccEmailAddress)
.addNewLineTag();
}
if (bccEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).bcc_email_address_prefix}: ')
.append(bccEmailAddress)
.addNewLineTag();
}
return headerQuoted;
default:
return null;
}
}
return null;
}
void _initEmailAddress(ComposerArguments arguments) {
void _initEmailAddress({
required PresentationEmail presentationEmail,
required EmailActionType actionType,
Role? mailboxRole,
}) {
final recipients = presentationEmail.generateRecipientsEmailAddressForComposer(
emailActionType: actionType,
mailboxRole: mailboxRole
);
log('ComposerController::_initEmailAddress:recipients: $recipients');
final userProfile = mailboxDashBoardController.userProfile.value;
if (arguments.presentationEmail != null && userProfile != null) {
final userEmailAddress = EmailAddress(null, userProfile.email);
final recipients = arguments.presentationEmail!.generateRecipientsEmailAddressForComposer(
arguments.emailActionType,
arguments.mailboxRole);
final isSender = arguments.presentationEmail!.from.asList().every((element) => element.email == userEmailAddress.email);
if (userProfile != null) {
final isSender = presentationEmail.from.asList().every((element) => element.email == userProfile.email);
if (isSender) {
listToEmailAddress = List.from(recipients.value1);
listCcEmailAddress = List.from(recipients.value2);
listBccEmailAddress = List.from(recipients.value3);
listToEmailAddress = List.from(recipients.value1.toSet());
listCcEmailAddress = List.from(recipients.value2.toSet());
listBccEmailAddress = List.from(recipients.value3.toSet());
} else {
listToEmailAddress = List.from(recipients.value1.toSet().filterEmailAddress(userEmailAddress));
listCcEmailAddress = List.from(recipients.value2.toSet().filterEmailAddress(userEmailAddress));
listBccEmailAddress = List.from(recipients.value3.toSet().filterEmailAddress(userEmailAddress));
listToEmailAddress = List.from(recipients.value1.toSet().filterEmailAddress(userProfile.email));
listCcEmailAddress = List.from(recipients.value2.toSet().filterEmailAddress(userProfile.email));
listBccEmailAddress = List.from(recipients.value3.toSet().filterEmailAddress(userProfile.email));
}
} else {
listToEmailAddress = List.from(recipients.value1.toSet());
listCcEmailAddress = List.from(recipients.value2.toSet());
listBccEmailAddress = List.from(recipients.value3.toSet());
}
if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) {
isInitialRecipient.value = true;
toAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (listCcEmailAddress.isNotEmpty) {
listEmailAddressType.add(PrefixEmailAddress.cc);
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (listBccEmailAddress.isNotEmpty) {
listEmailAddressType.add(PrefixEmailAddress.bcc);
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
}
} else if (arguments.emailAddress != null) {
listToEmailAddress.add(arguments.emailAddress!);
if (listToEmailAddress.isNotEmpty || listCcEmailAddress.isNotEmpty || listBccEmailAddress.isNotEmpty) {
isInitialRecipient.value = true;
toAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (listCcEmailAddress.isNotEmpty) {
listEmailAddressType.add(PrefixEmailAddress.cc);
ccAddressExpandMode.value = ExpandMode.COLLAPSE;
}
if (listBccEmailAddress.isNotEmpty) {
listEmailAddressType.add(PrefixEmailAddress.bcc);
bccAddressExpandMode.value = ExpandMode.COLLAPSE;
}
_updateStatusEmailSendButton();
}
@@ -586,13 +595,21 @@ class ComposerController extends BaseController {
}
}
String getEmailContentQuotedAsHtml(BuildContext context, ComposerArguments arguments) {
final headerEmailQuoted = _getHeaderEmailQuoted(context, arguments);
String getEmailContentQuotedAsHtml({
required BuildContext context,
required String emailContent,
required EmailActionType emailActionType,
required PresentationEmail presentationEmail,
}) {
final headerEmailQuoted = emailActionType.getHeaderEmailQuoted(
context: context,
presentationEmail: presentationEmail
);
log('ComposerController::getEmailContentQuotedAsHtml(): headerEmailQuoted: $headerEmailQuoted');
final headerEmailQuotedAsHtml = headerEmailQuoted != null ? headerEmailQuoted.addBlockTag('cite') : '';
final trustAsHtml = arguments.emailContents ?? '';
final emailQuotedHtml = '${HtmlExtension.editorStartTags}$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}';
final headerEmailQuotedAsHtml = headerEmailQuoted != null
? headerEmailQuoted.addCiteTag()
: '';
final emailQuotedHtml = '${HtmlExtension.editorStartTags}$headerEmailQuotedAsHtml${emailContent.addBlockQuoteTag()}';
return emailQuotedHtml;
}
@@ -789,13 +806,13 @@ class ComposerController extends BaseController {
if (arguments != null && accountId != null && userProfile != null && session != null) {
final createdEmail = await _generateEmail(context, userProfile, outboxMailboxId: outboxMailboxId);
final emailRequest = arguments.sendingEmail != null
final emailRequest = arguments.emailActionType == EmailActionType.editSendingEmail
? arguments.sendingEmail!.toEmailRequest(newEmail: createdEmail)
: EmailRequest(
email: createdEmail,
sentMailboxId: sentMailboxId,
identityId: identitySelected.value?.id,
emailIdDestroyed: arguments.emailActionType == EmailActionType.edit
emailIdDestroyed: arguments.emailActionType == EmailActionType.editDraft
? arguments.presentationEmail?.id
: null,
emailIdAnsweredOrForwarded: arguments.presentationEmail?.id,
@@ -952,36 +969,40 @@ class ComposerController extends BaseController {
uploadController.deleteFileUploaded(uploadId);
}
Future<bool> _isEmailChanged(
BuildContext context,
ComposerArguments arguments,
) async {
Future<bool> _isEmailChanged({
required BuildContext context,
required EmailActionType emailActionType,
PresentationEmail? presentationEmail,
Role? mailboxRole,
}) async {
final newEmailBody = await _getEmailBodyText(context, changedEmail: true);
log('ComposerController::_isEmailChanged(): newEmailBody: $newEmailBody');
var oldEmailBody = _initTextEditor ?? '';
final oldEmailBody = _initTextEditor ?? '';
log('ComposerController::_isEmailChanged(): oldEmailBody: $oldEmailBody');
final isEmailBodyChanged = !oldEmailBody.trim().isSame(newEmailBody.trim());
log('ComposerController::_isEmailChanged(): isEmailBodyChanged: $isEmailBodyChanged');
final newEmailSubject = subjectEmail.value ?? '';
final titleEmail = arguments.presentationEmail?.getEmailTitle().trim() ?? '';
final oldEmailSubject = arguments.emailActionType == EmailActionType.edit ? titleEmail : '';
final oldEmailSubject = emailActionType == EmailActionType.editDraft
? presentationEmail?.getEmailTitle().trim() ?? ''
: '';
final isEmailSubjectChanged = !oldEmailSubject.trim().isSame(newEmailSubject.trim());
final recipients = arguments.presentationEmail
?.generateRecipientsEmailAddressForComposer(arguments.emailActionType, arguments.mailboxRole)
?? const Tuple3(<EmailAddress>[], <EmailAddress>[], <EmailAddress>[]);
final recipients = presentationEmail
?.generateRecipientsEmailAddressForComposer(
emailActionType: emailActionType,
mailboxRole: mailboxRole
) ?? const Tuple3(<EmailAddress>[], <EmailAddress>[], <EmailAddress>[]);
final newToEmailAddress = listToEmailAddress;
final oldToEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value1 : [];
final oldToEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value1 : [];
final isToEmailAddressChanged = !oldToEmailAddress.isSame(newToEmailAddress);
final newCcEmailAddress = listCcEmailAddress;
final oldCcEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value2 : [];
final oldCcEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value2 : [];
final isCcEmailAddressChanged = !oldCcEmailAddress.isSame(newCcEmailAddress);
final newBccEmailAddress = listBccEmailAddress;
final oldBccEmailAddress = arguments.emailActionType == EmailActionType.edit ? recipients.value3 : [];
final oldBccEmailAddress = emailActionType == EmailActionType.editDraft ? recipients.value3 : [];
final isBccEmailAddressChanged = !oldBccEmailAddress.isSame(newBccEmailAddress);
final isAttachmentsChanged = !initialAttachments.isSame(uploadController.attachmentsUploaded.toList());
@@ -999,36 +1020,60 @@ class ComposerController extends BaseController {
clearFocusEditor(context);
final arguments = composerArguments.value;
final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts];
final userProfile = mailboxDashBoardController.userProfile.value;
final accountId = mailboxDashBoardController.accountId.value;
final session = mailboxDashBoardController.sessionCurrent;
if (arguments != null && userProfile != null && accountId != null && session != null) {
final isChanged = await _isEmailChanged(context, arguments);
if (isChanged && context.mounted) {
final newEmail = await _generateEmail(
context,
userProfile,
asDrafts: true,
draftMailboxId: draftMailboxId);
final oldEmail = arguments.presentationEmail;
if (arguments.emailActionType == EmailActionType.edit && oldEmail != null && oldEmail.id != null) {
mailboxDashBoardController.consumeState(
_updateEmailDraftsInteractor.execute(
session,
accountId,
newEmail,
oldEmail.id!
)
);
} else {
mailboxDashBoardController.consumeState(_saveEmailAsDraftsInteractor.execute(session, accountId, newEmail));
}
uploadController.clearInlineFileUploaded();
if (arguments == null ||
userProfile == null ||
session == null ||
accountId == null
) {
if (PlatformInfo.isWeb) {
mailboxDashBoardController.closeComposerOverlay();
} else {
if (canPop) popBack();
}
return;
}
final isChanged = await _isEmailChanged(
context: context,
emailActionType: arguments.emailActionType,
presentationEmail: arguments.presentationEmail,
mailboxRole: arguments.mailboxRole
);
if (isChanged && context.mounted) {
final draftMailboxId = mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts];
final newEmail = await _generateEmail(
context,
userProfile,
asDrafts: true,
draftMailboxId: draftMailboxId
);
if (arguments.emailActionType == EmailActionType.editDraft) {
mailboxDashBoardController.consumeState(
_updateEmailDraftsInteractor.execute(
session,
accountId,
newEmail,
arguments.presentationEmail!.id!
)
);
} else {
mailboxDashBoardController.consumeState(
_saveEmailAsDraftsInteractor.execute(
session,
accountId,
newEmail
)
);
}
uploadController.clearInlineFileUploaded();
}
if (PlatformInfo.isWeb) {
@@ -1087,90 +1132,109 @@ class ComposerController extends BaseController {
return listFileInfo;
}
void _getEmailContentAction(ComposerArguments arguments) async {
final listSharedMediaFile = arguments.listSharedMediaFile;
if (listSharedMediaFile != null && listSharedMediaFile.isNotEmpty) {
final listImageSharedMediaFile = listSharedMediaFile.where((element) => element.type == SharedMediaType.IMAGE);
final listFileAttachmentSharedMediaFile = listSharedMediaFile.where((element) => element.type != SharedMediaType.IMAGE);
if (listImageSharedMediaFile.isNotEmpty) {
final listInlineImage = covertListSharedMediaFileToInlineImage(arguments.listSharedMediaFile!);
for (var e in listInlineImage) {
_uploadInlineAttachmentsAction(e.fileInfo!);
}
void _addAttachmentFromFileShare(List<SharedMediaFile> listSharedMediaFile) {
final listImageSharedMediaFile = listSharedMediaFile.where((element) => element.type == SharedMediaType.IMAGE);
final listFileAttachmentSharedMediaFile = listSharedMediaFile.where((element) => element.type != SharedMediaType.IMAGE);
if (listImageSharedMediaFile.isNotEmpty) {
final listInlineImage = covertListSharedMediaFileToInlineImage(listSharedMediaFile);
for (var e in listInlineImage) {
_uploadInlineAttachmentsAction(e.fileInfo!);
}
if (listFileAttachmentSharedMediaFile.isNotEmpty) {
final listFile = covertListSharedMediaFileToFileInfo(arguments.listSharedMediaFile!);
if (uploadController.hasEnoughMaxAttachmentSize(listFiles: listFile)) {
_uploadAttachmentsAction(listFile);
} else {
if (currentContext != null) {
showConfirmDialogAction(
currentContext!,
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)),
AppLocalizations.of(currentContext!).got_it,
onConfirmAction: () => {},
title: AppLocalizations.of(currentContext!).maximum_files_size,
hasCancelButton: false,
);
}
}
if (listFileAttachmentSharedMediaFile.isNotEmpty) {
final listFile = covertListSharedMediaFileToFileInfo(listSharedMediaFile);
if (uploadController.hasEnoughMaxAttachmentSize(listFiles: listFile)) {
_uploadAttachmentsAction(listFile);
} else {
if (currentContext != null) {
showConfirmDialogAction(
currentContext!,
AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(
filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)),
AppLocalizations.of(currentContext!).got_it,
title: AppLocalizations.of(currentContext!).maximum_files_size,
hasCancelButton: false,
);
}
}
}
}
if (arguments.emailContents != null && arguments.emailContents!.isNotEmpty && arguments.sendingEmail != null) {
_emailContents = arguments.emailContents;
emailContentsViewState.value = Right(
GetEmailContentSuccess(
emailContent: _emailContents!,
emailContentDisplayed: '',
attachments: [],
emailCurrent: arguments.presentationEmail?.toEmail()
)
);
} else {
final session = mailboxDashBoardController.sessionCurrent;
final baseDownloadUrl = mailboxDashBoardController.sessionCurrent?.getDownloadUrl(jmapUrl: _dynamicUrlInterceptors.jmapUrl);
final accountId = mailboxDashBoardController.sessionCurrent?.accounts.keys.first;
final emailId = arguments.presentationEmail?.id;
if (session != null && emailId != null && baseDownloadUrl != null && accountId != null) {
consumeState(_getEmailContentInteractor.execute(
session,
accountId,
emailId,
baseDownloadUrl,
composeEmail: true,
draftsEmail: arguments.presentationEmail?.isDraft ?? false
));
void _getEmailContentFromSendingEmail(SendingEmail sendingEmail) {
consumeState(Stream.value(
Right(GetEmailContentSuccess(
htmlEmailContent: sendingEmail.presentationEmail.emailContentList.asHtmlString,
attachments: sendingEmail.email.allAttachments,
emailCurrent: sendingEmail.email
))
));
}
void _getEmailContentFromSessionStorageBrowser(String content) {
consumeState(Stream.value(
Right(GetEmailContentSuccess(
htmlEmailContent: content,
attachments: [],
))
));
}
void _getEmailContentFromContentShared(String content) {
consumeState(Stream.value(
Right(GetEmailContentSuccess(
htmlEmailContent: content,
attachments: [],
))
));
}
void _getEmailContentFromEmailId({required EmailId emailId, bool isDraftEmail = false}) {
final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value;
if (session != null && accountId != null) {
TransformConfiguration transformConfiguration = TransformConfiguration.standardConfiguration;
if (isDraftEmail) {
transformConfiguration = TransformConfiguration.forDraftsEmail();
} else if (PlatformInfo.isWeb) {
transformConfiguration = TransformConfiguration.forComposeEmailPlatformWeb();
}
consumeState(_getEmailContentInteractor.execute(
session,
accountId,
emailId,
mailboxDashBoardController.baseDownloadUrl,
transformConfiguration
));
}
}
void _getEmailContentOffLineSuccess(GetEmailContentFromCacheSuccess success) {
if (success.attachments.isNotEmpty) {
initialAttachments = success.attachments;
uploadController.initializeUploadAttachments(
success.attachments.listAttachmentsDisplayedOutSide);
}
_initAttachments(success.attachments);
emailContentsViewState.value = Right(success);
_emailContents = success.emailContent;
}
void _getEmailContentSuccess(GetEmailContentSuccess success) {
if (success.attachments.isNotEmpty) {
initialAttachments = success.attachments;
uploadController.initializeUploadAttachments(
success.attachments.listAttachmentsDisplayedOutSide);
}
_initAttachments(success.attachments);
emailContentsViewState.value = Right(success);
_emailContents = success.emailContent;
}
void _transformHtmlEmailContent(String? emailContent) {
emailContentsViewState(Right(TransformHtmlEmailContentLoading()));
if (emailContent?.isEmpty == true) {
consumeState(Stream.value(Left(TransformHtmlEmailContentFailure(EmptyEmailContentException()))));
} else {
consumeState(_transformHtmlEmailContentInteractor.execute(
emailContent!,
TransformConfiguration.forReplyForwardEmail()
));
}
}
String getEmailAddressSender() {
final arguments = composerArguments.value;
if (arguments != null) {
if (arguments.emailActionType == EmailActionType.edit) {
if (arguments.emailActionType == EmailActionType.editDraft) {
return arguments.presentationEmail?.from?.first.emailAddress ?? '';
} else {
return mailboxDashBoardController.userProfile.value?.email ?? '';
@@ -15,9 +15,11 @@ import 'package:get/get.dart';
import 'package:model/email/email_action_type.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
import 'package:rich_text_composer/views/widgets/rich_text_keyboard_toolbar.dart';
import 'package:tmail_ui_user/features/base/widget/cupertino_loading_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/base_composer_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -276,38 +278,96 @@ class ComposerView extends BaseComposerView {
switch(argsComposer.emailActionType) {
case EmailActionType.compose:
case EmailActionType.composeFromEmailAddress:
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
case EmailActionType.edit:
return controller.emailContentsViewState.value.fold(
(failure) => _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags),
case EmailActionType.composeFromFileShared:
return _buildHtmlEditor(
context,
initialContent: HtmlExtension.editorStartTags
);
case EmailActionType.editDraft:
case EmailActionType.editSendingEmail:
case EmailActionType.composeFromContentShared:
case EmailActionType.reopenComposerBrowser:
final emailContentsViewState = controller.emailContentsViewState.value;
if (emailContentsViewState == null) {
return const SizedBox.shrink();
}
return emailContentsViewState.fold(
(failure) => _buildHtmlEditor(
context,
initialContent: HtmlExtension.editorStartTags
),
(success) {
if (success is GetEmailContentLoading) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: loadingWidget,
);
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else if (success is GetEmailContentSuccess) {
var contentHtml = success.emailContent;
var contentHtml = success.htmlEmailContent;
if (contentHtml.isEmpty == true) {
contentHtml = HtmlExtension.editorStartTags;
}
return _buildHtmlEditor(context, initialContent: contentHtml);
return _buildHtmlEditor(
context,
initialContent: contentHtml
);
} else {
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
return _buildHtmlEditor(
context,
initialContent: HtmlExtension.editorStartTags
);
}
});
case EmailActionType.reply:
case EmailActionType.replyAll:
case EmailActionType.forward:
var contentHtml = controller.getEmailContentQuotedAsHtml(
context,
argsComposer);
if (contentHtml.isEmpty == true) {
contentHtml = HtmlExtension.editorStartTags;
final emailContentsViewState = controller.emailContentsViewState.value;
if (emailContentsViewState == null) {
return const SizedBox.shrink();
}
return _buildHtmlEditor(context, initialContent: contentHtml);
return emailContentsViewState.fold(
(failure) {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: '',
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
initialContent: emailContentQuoted
);
},
(success) {
if (success is TransformHtmlEmailContentLoading) {
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else if (success is TransformHtmlEmailContentSuccess) {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: success.htmlContent,
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
initialContent: emailContentQuoted
);
} else {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: '',
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
initialContent: emailContentQuoted
);
}
}
);
default:
return _buildHtmlEditor(context, initialContent: HtmlExtension.editorStartTags);
return _buildHtmlEditor(
context,
initialContent: HtmlExtension.editorStartTags
);
}
});
}
@@ -10,12 +10,14 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:model/email/email_action_type.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
import 'package:tmail_ui_user/features/base/widget/cupertino_loading_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/base_composer_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
import 'package:tmail_ui_user/features/composer/presentation/styles/composer_style.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/email_editor_widget.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/toolbar_rich_text_builder.dart';
import 'package:tmail_ui_user/features/email/domain/state/get_email_content_state.dart';
import 'package:tmail_ui_user/features/email/domain/state/transform_html_email_content_state.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -385,46 +387,96 @@ class ComposerView extends BaseComposerView {
switch(argsComposer.emailActionType) {
case EmailActionType.compose:
case EmailActionType.composeFromEmailAddress:
case EmailActionType.composeFromFileShared:
return _buildHtmlEditor(
context,
currentTextEditor ?? HtmlExtension.editorStartTags);
case EmailActionType.edit:
return controller.emailContentsViewState.value.fold(
context,
currentTextEditor ?? HtmlExtension.editorStartTags
);
case EmailActionType.editDraft:
case EmailActionType.editSendingEmail:
case EmailActionType.composeFromContentShared:
case EmailActionType.reopenComposerBrowser:
final emailContentsViewState = controller.emailContentsViewState.value;
if (emailContentsViewState == null) {
return const SizedBox.shrink();
}
return emailContentsViewState.fold(
(failure) => _buildHtmlEditor(
context,
currentTextEditor ?? HtmlExtension.editorStartTags),
context,
currentTextEditor ?? HtmlExtension.editorStartTags
),
(success) {
if (success is GetEmailContentLoading) {
return Padding(
padding: const EdgeInsets.all(16.0),
child: loadingWidget,
);
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else if (success is GetEmailContentSuccess) {
var contentHtml = success.emailContent;
var contentHtml = success.htmlEmailContent;
if (contentHtml.isEmpty == true) {
contentHtml = HtmlExtension.editorStartTags;
}
return _buildHtmlEditor(context, currentTextEditor ?? contentHtml);
return _buildHtmlEditor(
context,
currentTextEditor ?? contentHtml
);
} else {
return _buildHtmlEditor(
context,
currentTextEditor ?? HtmlExtension.editorStartTags);
currentTextEditor ?? HtmlExtension.editorStartTags
);
}
});
case EmailActionType.reply:
case EmailActionType.replyAll:
case EmailActionType.forward:
var contentHtml = controller.getEmailContentQuotedAsHtml(
context,
argsComposer);
if (contentHtml.isEmpty == true) {
contentHtml = HtmlExtension.editorStartTags;
final emailContentsViewState = controller.emailContentsViewState.value;
if (emailContentsViewState == null) {
return const SizedBox.shrink();
}
return _buildHtmlEditor(context, currentTextEditor ?? contentHtml);
return emailContentsViewState.fold(
(failure) {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: '',
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
currentTextEditor ?? emailContentQuoted
);
},
(success) {
if (success is TransformHtmlEmailContentLoading) {
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else if (success is TransformHtmlEmailContentSuccess) {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: success.htmlContent,
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
currentTextEditor ?? emailContentQuoted
);
} else {
final emailContentQuoted = controller.getEmailContentQuotedAsHtml(
context: context,
emailContent: '',
emailActionType: argsComposer.emailActionType,
presentationEmail: argsComposer.presentationEmail!
);
return _buildHtmlEditor(
context,
currentTextEditor ?? emailContentQuoted
);
}
}
);
default:
return _buildHtmlEditor(
context,
currentTextEditor ?? HtmlExtension.editorStartTags);
context,
currentTextEditor ?? HtmlExtension.editorStartTags
);
}
});
}
@@ -1,25 +1,35 @@
import 'package:core/presentation/extensions/html_extension.dart';
import 'package:flutter/cupertino.dart';
import 'package:model/email/email_action_type.dart';
import 'package:model/email/presentation_email.dart';
import 'package:model/extensions/list_email_address_extension.dart';
import 'package:model/extensions/utc_date_extension.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
extension EmailActionTypeExtension on EmailActionType {
String getSubjectComposer(BuildContext context, String subject) {
String getSubjectComposer(BuildContext? context, String subject) {
switch(this) {
case EmailActionType.reply:
case EmailActionType.replyAll:
if (subject.toLowerCase().startsWith('re:')) {
return subject;
} else {
return '${AppLocalizations.of(context).prefix_reply_email} $subject';
return context != null
? '${AppLocalizations.of(context).prefix_reply_email} $subject'
: subject;
}
case EmailActionType.forward:
if (subject.toLowerCase().startsWith('fwd:')) {
return subject;
} else {
return '${AppLocalizations.of(context).prefix_forward_email} $subject';
return context != null
? '${AppLocalizations.of(context).prefix_forward_email} $subject'
: subject;
}
case EmailActionType.edit:
case EmailActionType.editDraft:
case EmailActionType.editSendingEmail:
case EmailActionType.reopenComposerBrowser:
return subject;
default:
return '';
@@ -40,4 +50,71 @@ extension EmailActionTypeExtension on EmailActionType {
return '';
}
}
String? getHeaderEmailQuoted({
required BuildContext context,
required PresentationEmail presentationEmail
}) {
final locale = Localizations.localeOf(context).toLanguageTag();
switch(this) {
case EmailActionType.reply:
case EmailActionType.replyAll:
final receivedAt = presentationEmail.receivedAt;
final emailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
return AppLocalizations.of(context).header_email_quoted(
receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale),
emailAddress
);
case EmailActionType.forward:
var headerQuoted = '------- ${AppLocalizations.of(context).forwarded_message} -------'.addNewLineTag();
final subject = presentationEmail.subject ?? '';
final receivedAt = presentationEmail.receivedAt;
final fromEmailAddress = presentationEmail.from.listEmailAddressToString(isFullEmailAddress: true);
final toEmailAddress = presentationEmail.to.listEmailAddressToString(isFullEmailAddress: true);
final ccEmailAddress = presentationEmail.cc.listEmailAddressToString(isFullEmailAddress: true);
final bccEmailAddress = presentationEmail.bcc.listEmailAddressToString(isFullEmailAddress: true);
if (subject.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).subject_email}: ')
.append(subject)
.addNewLineTag();
}
if (receivedAt != null) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).date}: ')
.append(receivedAt.formatDateToLocal(pattern: 'MMM d, y h:mm a', locale: locale))
.addNewLineTag();
}
if (fromEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).from_email_address_prefix}: ')
.append(fromEmailAddress)
.addNewLineTag();
}
if (toEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).to_email_address_prefix}: ')
.append(toEmailAddress)
.addNewLineTag();
}
if (ccEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).cc_email_address_prefix}: ')
.append(ccEmailAddress)
.addNewLineTag();
}
if (bccEmailAddress.isNotEmpty) {
headerQuoted = headerQuoted
.append('${AppLocalizations.of(context).bcc_email_address_prefix}: ')
.append(bccEmailAddress)
.addNewLineTag();
}
return headerQuoted;
default:
return null;
}
}
}
@@ -99,7 +99,7 @@ class ComposerStyle {
if (responsiveUtils.isPortraitMobile(context) || responsiveUtils.isLandscapeMobile(context)) {
return const EdgeInsetsDirectional.symmetric(horizontal: 16);
} else {
return const EdgeInsetsDirectional.only(start: 88, end: 48);
return const EdgeInsetsDirectional.only(start: 88, end: 48, top: 8);
}
}