From f993eeb888f830764dab96112a95c07efd96bdc3 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 29 Apr 2022 15:22:11 +0700 Subject: [PATCH] TF-457 Display signature, bcc email address in composer --- .../dom/sigature_transformers.dart | 24 +++ .../html_transformer/html_transform.dart | 1 - .../message_content_transformer.dart | 1 - .../transform_configuration.dart | 2 + .../presentation/composer_controller.dart | 166 +++++++++++++++--- .../composer/presentation/composer_view.dart | 18 +- .../presentation/composer_view_web.dart | 54 +++--- .../mailbox_dashboard_controller.dart | 5 - pubspec.yaml | 5 +- 9 files changed, 206 insertions(+), 70 deletions(-) create mode 100644 core/lib/presentation/utils/html_transformer/dom/sigature_transformers.dart diff --git a/core/lib/presentation/utils/html_transformer/dom/sigature_transformers.dart b/core/lib/presentation/utils/html_transformer/dom/sigature_transformers.dart new file mode 100644 index 000000000..2b8fd1f46 --- /dev/null +++ b/core/lib/presentation/utils/html_transformer/dom/sigature_transformers.dart @@ -0,0 +1,24 @@ + +import 'package:core/data/network/dio_client.dart'; +import 'package:core/presentation/utils/html_transformer/base/dom_transformer.dart'; +import 'package:html/dom.dart'; + +class SignatureTransformer extends DomTransformer { + + const SignatureTransformer(); + + @override + Future process( + Document document, + String message, + { + Map? mapUrlDownloadCID, + DioClient? dioClient + } + ) async { + final signatureElements = document.querySelectorAll('div.tmail-signature'); + await Future.wait(signatureElements.map((element) async { + element.attributes['class'] = 'tmail-signature-blocked'; + })); + } +} \ No newline at end of file diff --git a/core/lib/presentation/utils/html_transformer/html_transform.dart b/core/lib/presentation/utils/html_transformer/html_transform.dart index 1ab5d8cc4..c85f308f0 100644 --- a/core/lib/presentation/utils/html_transformer/html_transform.dart +++ b/core/lib/presentation/utils/html_transformer/html_transform.dart @@ -1,6 +1,5 @@ import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/message_content_transformer.dart'; -import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:html/dom.dart'; class HtmlTransform { diff --git a/core/lib/presentation/utils/html_transformer/message_content_transformer.dart b/core/lib/presentation/utils/html_transformer/message_content_transformer.dart index e103d918a..875a9e131 100644 --- a/core/lib/presentation/utils/html_transformer/message_content_transformer.dart +++ b/core/lib/presentation/utils/html_transformer/message_content_transformer.dart @@ -1,5 +1,4 @@ import 'package:core/core.dart'; -import 'package:core/presentation/utils/html_transformer/transform_configuration.dart'; import 'package:html/dom.dart'; import 'package:html/parser.dart' show parse; diff --git a/core/lib/presentation/utils/html_transformer/transform_configuration.dart b/core/lib/presentation/utils/html_transformer/transform_configuration.dart index 63c841706..1f901818b 100644 --- a/core/lib/presentation/utils/html_transformer/transform_configuration.dart +++ b/core/lib/presentation/utils/html_transformer/transform_configuration.dart @@ -4,6 +4,7 @@ import 'package:core/presentation/utils/html_transformer/base/text_transformer.d import 'package:core/presentation/utils/html_transformer/dom/blockquoted_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/image_transformers.dart'; import 'package:core/presentation/utils/html_transformer/dom/script_transformers.dart'; +import 'package:core/presentation/utils/html_transformer/dom/sigature_transformers.dart'; /// Contains the configuration for all transformations. class TransformConfiguration { @@ -49,6 +50,7 @@ class TransformConfiguration { static const List standardDomTransformers = [ RemoveScriptTransformer(), + SignatureTransformer(), BlockQuotedTransformer(), ImageTransformer(), ]; diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 618b8bcd9..fe7a78d50 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -58,6 +58,7 @@ class ComposerController extends BaseController { final mailboxDashBoardController = Get.find(); final _appToast = Get.find(); final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); final expandModeAttachments = ExpandMode.COLLAPSE.obs; final composerArguments = Rxn(); @@ -92,7 +93,8 @@ class ComposerController extends BaseController { List listBccEmailAddress = []; ContactSuggestionSource _contactSuggestionSource = ContactSuggestionSource.localContact; HtmlEditorApi? htmlEditorApi; - final html_editor_browser.HtmlEditorController htmlControllerBrowser = html_editor_browser.HtmlEditorController(processNewLineAsBr: true); + final html_editor_browser.HtmlEditorController htmlControllerBrowser = + html_editor_browser.HtmlEditorController(processNewLineAsBr: true); final subjectEmailInputController = TextEditingController(); final toEmailAddressController = TextEditingController(); @@ -108,14 +110,21 @@ class ComposerController extends BaseController { void setSubjectEmail(String subject) => subjectEmail.value = subject; - Future _getEmailBodyText({bool onlyText = false}) async { + Future _getEmailBodyText({bool changedEmail = false}) async { if (kIsWeb) { return await htmlControllerBrowser.getText(); } else { - if (onlyText) { - return (await htmlEditorApi?.getText()) ?? ''; + if (changedEmail) { + return await htmlEditorApi?.getText() ?? ''; } else { - return (await htmlEditorApi?.getFullHtml()) ?? ''; + final content = await htmlEditorApi?.getText() ?? ''; + if (_isMobileApp && identitySelected.value?.textSignature?.value.isNotEmpty == true) { + final newContent = '$content


--

${identitySelected.value?.textSignature?.value}

'; + log('ComposerController::_generateEmail()_MOBILE: $newContent'); + return newContent; + } else { + return content; + } } } } @@ -328,9 +337,12 @@ class ComposerController extends BaseController { if (arguments.presentationEmail != null && userProfile != null) { final userEmailAddress = EmailAddress(null, userProfile.email); - final recipients = arguments.presentationEmail!.generateRecipientsEmailAddressForComposer(arguments.emailActionType, arguments.mailboxRole); + final recipients = arguments.presentationEmail!.generateRecipientsEmailAddressForComposer( + arguments.emailActionType, + arguments.mailboxRole); - if (arguments.mailboxRole == PresentationMailbox.roleSent || arguments.emailActionType == EmailActionType.edit) { + if (arguments.mailboxRole == PresentationMailbox.roleSent + || arguments.emailActionType == EmailActionType.edit) { listToEmailAddress = recipients.value1; listCcEmailAddress = recipients.value2; listBccEmailAddress = recipients.value3; @@ -362,7 +374,8 @@ class ComposerController extends BaseController { _updateStatusEmailSendButton(); } - void updateListEmailAddress(PrefixEmailAddress prefixEmailAddress, List newListEmailAddress) { + void updateListEmailAddress(PrefixEmailAddress prefixEmailAddress, + List newListEmailAddress) { switch(prefixEmailAddress) { case PrefixEmailAddress.to: listToEmailAddress = newListEmailAddress; @@ -395,22 +408,31 @@ class ComposerController extends BaseController { final headerEmailQuotedAsHtml = headerEmailQuoted != null ? headerEmailQuoted.addBlockTag('cite') : ''; final trustAsHtml = arguments.emailContents?.asHtmlString ?? ''; - final emailQuotedHtml = '


$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}'; + final emailQuotedHtml = '


$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}


'; return emailQuotedHtml; } - Future _generateEmail(Map mapDefaultMailboxId, UserProfile userProfile, {bool asDrafts = false}) async { + Future _generateEmail(Map mapDefaultMailboxId, + UserProfile userProfile, {bool asDrafts = false}) async { final generateEmailId = EmailId(Id(_uuid.v1())); final outboxMailboxId = mapDefaultMailboxId[PresentationMailbox.roleOutbox]; final draftMailboxId = mapDefaultMailboxId[PresentationMailbox.roleDrafts]; - final listFromEmailAddress = { - EmailAddress(null, userProfile.email) - }; + Set listFromEmailAddress = {EmailAddress(null, userProfile.email)}; + if (identitySelected.value?.email?.isNotEmpty == true) { + listFromEmailAddress = {EmailAddress( + identitySelected.value?.name, + identitySelected.value?.email)}; + } + Set listReplyToEmailAddress = {EmailAddress(null, userProfile.email)}; + if (identitySelected.value?.replyTo?.isNotEmpty == true) { + listReplyToEmailAddress = identitySelected.value!.replyTo!; + } final generatePartId = PartId(_uuid.v1()); final generateBlobId = Id(_uuid.v1()); - final emailBodyText = await _getEmailBodyText(); + var emailBodyText = await _getEmailBodyText(); + log('ComposerController::_generateEmail(): $emailBodyText'); final userAgent = await userAgentPlatform; return Email( @@ -420,7 +442,7 @@ class ComposerController extends BaseController { to: listToEmailAddress.toSet(), cc: listCcEmailAddress.toSet(), bcc: listBccEmailAddress.toSet(), - replyTo: listFromEmailAddress, + replyTo: listReplyToEmailAddress, keywords: asDrafts ? {KeyWordIdentifier.emailDraft : true} : null, subject: subjectEmail.value, htmlBody: { @@ -504,7 +526,8 @@ class ComposerController extends BaseController { if (!_validateAttachmentsSize()) { showConfirmDialogAction( context, - AppLocalizations.of(context).message_dialog_send_email_exceeds_maximum_size(filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), + AppLocalizations.of(context).message_dialog_send_email_exceeds_maximum_size( + filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), AppLocalizations.of(context).got_it, () => {}, title: AppLocalizations.of(context).sending_failed, @@ -521,7 +544,8 @@ class ComposerController extends BaseController { final session = mailboxDashBoardController.sessionCurrent; final mapDefaultMailboxId = mailboxDashBoardController.mapDefaultMailboxId; final userProfile = mailboxDashBoardController.userProfile.value; - if (arguments != null && session != null && mapDefaultMailboxId.isNotEmpty && userProfile != null) { + if (arguments != null && session != null && mapDefaultMailboxId.isNotEmpty + && userProfile != null) { _saveEmailAddress(); final email = await _generateEmail(mapDefaultMailboxId, userProfile); @@ -530,11 +554,11 @@ class ComposerController extends BaseController { final submissionCreateId = Id(_uuid.v1()); mailboxDashBoardController.consumeState(_sendEmailInteractor.execute( - accountId, - EmailRequest(email, submissionCreateId, mailboxIdSaved: sentMailboxId, - emailIdDestroyed: arguments.emailActionType == EmailActionType.edit - ? arguments.presentationEmail?.id - : null))); + accountId, + EmailRequest(email, submissionCreateId, mailboxIdSaved: sentMailboxId, + emailIdDestroyed: arguments.emailActionType == EmailActionType.edit + ? arguments.presentationEmail?.id + : null))); } if (kIsWeb) { @@ -564,7 +588,8 @@ class ComposerController extends BaseController { Future> getAutoCompleteSuggestion({String? word, bool? isAll}) async { if (_contactSuggestionSource == ContactSuggestionSource.all) { - return await _getAutoCompleteWithDeviceContactInteractor.execute(AutoCompletePattern(word: word, isAll: isAll)) + return await _getAutoCompleteWithDeviceContactInteractor + .execute(AutoCompletePattern(word: word, isAll: isAll)) .then((value) => value.fold( (failure) => [], (success) => success is GetAutoCompleteSuccess ? success.listEmailAddress : [])); @@ -610,7 +635,8 @@ class ComposerController extends BaseController { if (currentContext != null) { showConfirmDialogAction( currentContext!, - AppLocalizations.of(currentContext!).message_dialog_upload_attachments_exceeds_maximum_size(filesize(mailboxDashBoardController.maxSizeAttachmentsPerEmail?.value ?? 0, 0)), + 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, @@ -686,7 +712,8 @@ class ComposerController extends BaseController { } Future _isEmailChanged(BuildContext context, ComposerArguments arguments) async { - final newEmailBody = await _getEmailBodyText(onlyText: true); + final newEmailBody = (await _getEmailBodyText(changedEmail: true)) + .replaceAll('




', ''); log('ComposerController::_isEmailChanged(): newEmailBody: $newEmailBody'); var oldEmailBody = ''; final contentEmail = getContentEmail(context); @@ -747,9 +774,11 @@ class ComposerController extends BaseController { final oldEmail = arguments.presentationEmail; if (arguments.emailActionType == EmailActionType.edit && oldEmail != null) { - mailboxDashBoardController.consumeState(_updateEmailDraftsInteractor.execute(accountId, newEmail, oldEmail.id)); + mailboxDashBoardController.consumeState( + _updateEmailDraftsInteractor.execute(accountId, newEmail, oldEmail.id)); } else { - mailboxDashBoardController.consumeState(_saveEmailAsDraftsInteractor.execute(accountId, newEmail)); + mailboxDashBoardController.consumeState( + _saveEmailAsDraftsInteractor.execute(accountId, newEmail)); } } } @@ -821,7 +850,8 @@ class ComposerController extends BaseController { } void toggleDisplayAttachments() { - final newExpandMode = expandModeAttachments.value == ExpandMode.COLLAPSE ? ExpandMode.EXPAND : ExpandMode.COLLAPSE; + final newExpandMode = expandModeAttachments.value == ExpandMode.COLLAPSE + ? ExpandMode.EXPAND : ExpandMode.COLLAPSE; expandModeAttachments.value = newExpandMode; } @@ -912,7 +942,87 @@ class ComposerController extends BaseController { } void selectIdentity(Identity? newIdentity) { + final formerIdentity = identitySelected.value; identitySelected.value = newIdentity; + if (newIdentity != null) { + _applyIdentityForAllFieldComposer(formerIdentity, newIdentity); + FocusManager.instance.primaryFocus?.unfocus(); + } + } + + bool get _isMobileApp { + return !BuildUtils.isWeb + && currentContext != null + && _responsiveUtils.isMobile(currentContext!); + } + + void _applyIdentityForAllFieldComposer(Identity? formerIdentity, Identity newIdentity) { + if (formerIdentity != null) { + // Remove former identity + if (formerIdentity.bcc?.isNotEmpty == true) { + _removeBccEmailAddressFromFormerIdentity(formerIdentity.bcc!); + } + + if (!_isMobileApp) { + _removeSignature(); + } + } + + // Add new identity + if (newIdentity.bcc?.isNotEmpty == true) { + _applyBccEmailAddressFromIdentity(newIdentity.bcc!); + } + + if (!_isMobileApp) { + if (newIdentity.htmlSignature?.value.isNotEmpty == true) { + _applySignature(newIdentity.htmlSignature!); + } else if (newIdentity.textSignature?.value.isNotEmpty == true) { + _applySignature(newIdentity.textSignature!); + } + } + } + + void _applyBccEmailAddressFromIdentity(Set listEmailAddress) { + if (!listEmailAddressType.contains(PrefixEmailAddress.bcc)) { + listEmailAddressType.add(PrefixEmailAddress.bcc); + } + listBccEmailAddress = listEmailAddress.toList(); + toAddressExpandMode.value = ExpandMode.COLLAPSE; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + _updateStatusEmailSendButton(); + } + + void _removeBccEmailAddressFromFormerIdentity(Set listEmailAddress) { + listBccEmailAddress = listBccEmailAddress + .where((address) => !listEmailAddress.contains(address)) + .toList(); + if (listBccEmailAddress.isEmpty) { + listEmailAddressType.remove(PrefixEmailAddress.bcc); + } + toAddressExpandMode.value = ExpandMode.COLLAPSE; + ccAddressExpandMode.value = ExpandMode.COLLAPSE; + bccAddressExpandMode.value = ExpandMode.COLLAPSE; + _updateStatusEmailSendButton(); + } + + void _applySignature(Signature signature) { + final signatureAsHtml = '--

${signature.value}'; + log('ComposerController::_applySignature(): $signatureAsHtml'); + if (BuildUtils.isWeb) { + htmlControllerBrowser.insertSignature(signatureAsHtml); + } else { + htmlEditorApi?.insertSignature(signatureAsHtml); + } + } + + void _removeSignature() { + log('ComposerController::_removeSignature():'); + if (BuildUtils.isWeb) { + htmlControllerBrowser.removeSignature(); + } else { + htmlEditorApi?.removeSignature(); + } } void closeComposer() { diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart index ea2f27bf7..420a88f4c 100644 --- a/lib/features/composer/presentation/composer_view.dart +++ b/lib/features/composer/presentation/composer_view.dart @@ -89,7 +89,9 @@ class ComposerView extends GetWidget { borderRadius: const BorderRadius.all(Radius.circular(24)), child: SafeArea( child: Column(children: [ - Padding(padding: const EdgeInsets.symmetric(horizontal: 16), child: _buildAppBar(context, controller.isEnableEmailSendButton.value)), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: _buildAppBar(context, controller.isEnableEmailSendButton.value)), const Padding(padding: EdgeInsets.only(top: 8), child: Divider(color: AppColor.colorDividerComposer, height: 1)), Expanded(child: _buildBodyTablet(context)), const Divider(color: AppColor.colorDividerComposer, height: 1), @@ -119,12 +121,13 @@ class ComposerView extends GetWidget { controller.closeComposer(); }), Expanded(child: _buildTitleComposer(context)), - buildIconWeb( - icon: SvgPicture.asset( - isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendDisable, - fit: BoxFit.fill), - tooltip: AppLocalizations.of(context).send, - onTap: () => controller.sendEmailAction(context)), + if (responsiveUtils.isMobile(context)) + buildIconWeb( + icon: SvgPicture.asset( + isEnableSendButton ? imagePaths.icSendMobile : imagePaths.icSendDisable, + fit: BoxFit.fill), + tooltip: AppLocalizations.of(context).send, + onTap: () => controller.sendEmailAction(context)), ] ), ); @@ -481,6 +484,7 @@ class ComposerView extends GetWidget { return HtmlEditor( key: const Key('composer_editor'), minHeight: 550, + initialContent: '




', onCreated: (editorApi) => controller.htmlEditorApi = editorApi); } else { final message = controller.getContentEmail(context); diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index ef1443fb4..d4002264c 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -331,29 +331,31 @@ class ComposerView extends GetWidget { customButton: SvgPicture.asset(imagePaths.icEditIdentity), items: controller.listIdentities.map((item) => DropdownMenuItem( value: item, - child: Container( - alignment: Alignment.centerLeft, - padding: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: item == controller.identitySelected.value ? AppColor.colorBgMenuItemDropDownSelected : Colors.transparent), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - item.name ?? '', - style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - Text( - item.email ?? '', - style: const TextStyle(fontSize: 13, fontWeight: FontWeight.normal, color: AppColor.colorHintSearchBar), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ) - ] + child: PointerInterceptor( + child: Container( + alignment: Alignment.centerLeft, + padding: const EdgeInsets.symmetric(horizontal: 8), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: item == controller.identitySelected.value ? AppColor.colorBgMenuItemDropDownSelected : Colors.transparent), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + item.name ?? '', + style: const TextStyle(fontSize: 16, fontWeight: FontWeight.normal, color: Colors.black), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Text( + item.email ?? '', + style: const TextStyle(fontSize: 13, fontWeight: FontWeight.normal, color: AppColor.colorHintSearchBar), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ) + ] + ), ), ), )).toList(), @@ -516,7 +518,7 @@ class ComposerView extends GetWidget { Obx(() { if (controller.composerArguments.value != null) { if (controller.composerArguments.value?.emailActionType == EmailActionType.compose) { - final initContent = controller.textEditorWeb ?? ''; + final initContent = controller.textEditorWeb ?? '




'; return Expanded(child: Padding( padding: EdgeInsets.symmetric(horizontal: responsiveUtils.isMobile(context) ? 8 : 10), child: _buildEditor(context, initContent))); @@ -554,8 +556,8 @@ class ComposerView extends GetWidget { return html_editor_browser.HtmlEditor( key: const Key('composer_editor_web'), controller: controller.htmlControllerBrowser, - htmlEditorOptions: html_editor_browser.HtmlEditorOptions( - hint: AppLocalizations.of(context).hint_compose_email, + htmlEditorOptions: const html_editor_browser.HtmlEditorOptions( + hint: '', darkMode: false, ), blockQuotedContent: initContent, diff --git a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart index ba5ba924f..e366e48f0 100644 --- a/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart @@ -150,14 +150,9 @@ class MailboxDashBoardController extends ReloadableController { message: AppLocalizations.of(currentContext!).message_has_been_sent_successfully, icon: _imagePaths.icSendToast); } - clearState(); } else if (success is SaveEmailAsDraftsSuccess) { log('MailboxDashBoardController::onDone(): SaveEmailAsDraftsSuccess'); _saveEmailAsDraftsSuccess(success); - clearState(); - } else if (success is RemoveEmailDraftsSuccess - || success is UpdateEmailDraftsSuccess) { - clearState(); } else if (success is MoveToMailboxSuccess) { _moveToMailboxSuccess(success); } else if (success is DeleteEmailPermanentlySuccess) { diff --git a/pubspec.yaml b/pubspec.yaml index fabfbbf0a..c1521ff06 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -122,13 +122,14 @@ dependencies: # enough_html_editor: Compose email for Mobile & Tablet enough_html_editor: git: - url: https://github.com/Enough-Software/enough_html_editor.git + url: https://github.com/dab246/enough_html_editor.git + ref: improvements_for_use_tmail # html_editor_enhanced: Compose email for Web Browser html_editor_enhanced: git: url: https://github.com/dab246/html-editor-enhanced.git - ref: fix_display_content + ref: improvements_for_use_tmail # fk_user_agent fk_user_agent: 2.1.0