diff --git a/core/lib/presentation/utils/html_transformer/dom/hide_draft_signature_transformer.dart b/core/lib/presentation/utils/html_transformer/dom/hide_draft_signature_transformer.dart
index 572cbc221..3eb9ab930 100644
--- a/core/lib/presentation/utils/html_transformer/dom/hide_draft_signature_transformer.dart
+++ b/core/lib/presentation/utils/html_transformer/dom/hide_draft_signature_transformer.dart
@@ -19,6 +19,8 @@ class HideDraftSignatureTransformer extends DomTransformer {
final currentStyle = signature.attributes['style']?.trim();
if (currentStyle == null) {
signature.attributes['style'] = 'display: none;';
+ } else if (currentStyle.contains('display: block;') == true) {
+ signature.attributes['style'] = currentStyle.replaceFirst('display: block;', 'display: none;');
} else if (currentStyle.endsWith(';')) {
signature.attributes['style'] = '$currentStyle display: none;';
} else {
diff --git a/core/lib/utils/string_convert.dart b/core/lib/utils/string_convert.dart
index 6b87a44d5..6da89081a 100644
--- a/core/lib/utils/string_convert.dart
+++ b/core/lib/utils/string_convert.dart
@@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:core/utils/app_logger.dart';
import 'package:core/domain/exceptions/string_exception.dart';
+import 'package:html/parser.dart';
import 'package:http_parser/http_parser.dart';
class StringConvert {
@@ -107,4 +108,16 @@ class StringConvert {
return null;
}
}
+
+ static String getContentOriginal(String content) {
+ try {
+ final emailDocument = parse(content);
+ final contentOriginal = emailDocument.body?.innerHtml ?? content;
+ log('StringConvert::getContentOriginal:contentOriginal = $contentOriginal');
+ return contentOriginal;
+ } catch (e) {
+ logError('StringConvert::getContentOriginal:Exception = $e');
+ return content;
+ }
+ }
}
diff --git a/lib/features/composer/data/repository/composer_repository_impl.dart b/lib/features/composer/data/repository/composer_repository_impl.dart
index 6cfc4f016..ba5160561 100644
--- a/lib/features/composer/data/repository/composer_repository_impl.dart
+++ b/lib/features/composer/data/repository/composer_repository_impl.dart
@@ -63,7 +63,7 @@ class ComposerRepositoryImpl extends ComposerRepository {
emailContent = tupleContentInlineAttachments.value1;
emailAttachments.addAll(tupleContentInlineAttachments.value2);
- emailContent = await _removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
+ emailContent = await removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
final userAgent = await _applicationManager.generateApplicationUserAgent();
final emailBodyPartId = PartId(_uuid.v1());
@@ -104,7 +104,8 @@ class ComposerRepositoryImpl extends ComposerRepository {
}
}
- Future _removeCollapsedExpandedSignatureEffect({required String emailContent}) {
+ @override
+ Future removeCollapsedExpandedSignatureEffect({required String emailContent}) {
try {
return _htmlDataSource.removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
} catch (e) {
diff --git a/lib/features/composer/domain/repository/composer_repository.dart b/lib/features/composer/domain/repository/composer_repository.dart
index 767104011..5a28d848d 100644
--- a/lib/features/composer/domain/repository/composer_repository.dart
+++ b/lib/features/composer/domain/repository/composer_repository.dart
@@ -15,4 +15,6 @@ abstract class ComposerRepository {
Future uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken});
Future downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress});
+
+ Future removeCollapsedExpandedSignatureEffect({required String emailContent});
}
\ No newline at end of file
diff --git a/lib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart b/lib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart
index 7e11038a5..27ea726c6 100644
--- a/lib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart
+++ b/lib/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart
@@ -39,6 +39,9 @@ class SaveComposerCacheOnWebInteractor {
displayMode: createEmailRequest.displayMode,
composerIndex: createEmailRequest.composerIndex,
composerId: createEmailRequest.composerId,
+ draftHash: createEmailRequest.savedDraftHash,
+ actionType: createEmailRequest.savedActionType,
+ draftEmailId: createEmailRequest.draftsEmailId,
));
return Right(SaveComposerCacheSuccess());
} catch (exception) {
diff --git a/lib/features/composer/presentation/composer_bindings.dart b/lib/features/composer/presentation/composer_bindings.dart
index 7864fe746..dee60c914 100644
--- a/lib/features/composer/presentation/composer_bindings.dart
+++ b/lib/features/composer/presentation/composer_bindings.dart
@@ -329,6 +329,7 @@ class ComposerBindings extends BaseBindings {
Get.find(tag: composerId),
Get.find(tag: composerId),
Get.find(tag: composerId),
+ Get.find(tag: composerId),
composerId: composerId,
composerArgs: composerArguments,
), tag: composerId);
diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart
index 2aaf68de1..5e07cd079 100644
--- a/lib/features/composer/presentation/composer_controller.dart
+++ b/lib/features/composer/presentation/composer_controller.dart
@@ -2,7 +2,6 @@
import 'dart:async';
import 'dart:math';
-import 'package:collection/collection.dart';
import 'package:core/core.dart';
import 'package:custom_pop_up_menu/custom_pop_up_menu.dart';
import 'package:dartz/dartz.dart';
@@ -15,21 +14,16 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
-import 'package:html/parser.dart';
import 'package:html_editor_enhanced/html_editor.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/identities/identity.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/email/individual_header_identifier.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:pointer_interceptor/pointer_interceptor.dart';
-import 'package:receive_sharing_intent/receive_sharing_intent.dart';
import 'package:rich_text_composer/rich_text_composer.dart';
-import 'package:server_settings/server_settings/tmail_server_settings_extension.dart';
import 'package:super_tag_editor/tag_editor.dart';
import 'package:tmail_ui_user/features/base/base_controller.dart';
import 'package:tmail_ui_user/features/base/before_reconnect_handler.dart';
@@ -39,9 +33,9 @@ import 'package:tmail_ui_user/features/base/state/base_ui_state.dart';
import 'package:tmail_ui_user/features/base/state/button_state.dart';
import 'package:tmail_ui_user/features/composer/domain/exceptions/compose_email_exception.dart';
import 'package:tmail_ui_user/features/composer/domain/model/contact_suggestion_source.dart';
+import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/composer/domain/state/download_image_as_base64_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/generate_email_state.dart';
-import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart';
import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart';
@@ -63,12 +57,20 @@ import 'package:tmail_ui_user/features/composer/presentation/extensions/get_outb
import 'package:tmail_ui_user/features/composer/presentation/extensions/get_sent_mailbox_id_for_composer_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/handle_message_failure_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart';
-import 'package:tmail_ui_user/features/composer/presentation/extensions/list_shared_media_file_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/sanitize_signature_in_email_content_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_attachments_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_content_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_important_flag_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_other_components_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_recipients_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_subject_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_list_identities_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_selected_identity_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/update_screen_display_mode_extension.dart';
import 'package:tmail_ui_user/features/composer/presentation/mixin/drag_drog_file_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/saved_email_draft.dart';
-import 'package:tmail_ui_user/features/composer/presentation/model/signature_status.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/inline_image.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
@@ -77,16 +79,13 @@ import 'package:tmail_ui_user/features/composer/presentation/view/editor_view_mi
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/from_composer_bottom_sheet_builder.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/saving_message_dialog_view.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/sending_message_dialog_view.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/print_email_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/extensions/presentation_email_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/home/data/exceptions/session_exceptions.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_by_id_on_web_interactor.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/open_and_close_composer_extension.dart';
@@ -96,8 +95,6 @@ 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'
if (dart.library.html) 'package:tmail_ui_user/features/network_connection/presentation/web_network_connection_controller.dart';
-import 'package:tmail_ui_user/features/sending_queue/domain/model/sending_email.dart';
-import 'package:tmail_ui_user/features/server_settings/domain/state/get_server_setting_state.dart';
import 'package:tmail_ui_user/features/server_settings/domain/usecases/get_server_setting_interactor.dart';
import 'package:tmail_ui_user/features/upload/domain/exceptions/pick_file_exception.dart';
import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
@@ -110,10 +107,10 @@ import 'package:tmail_ui_user/features/upload/domain/state/local_image_picker_st
import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart';
import 'package:tmail_ui_user/features/upload/domain/usecases/local_image_picker_interactor.dart';
import 'package:tmail_ui_user/features/upload/presentation/controller/upload_controller.dart';
-import 'package:tmail_ui_user/main/exceptions/remote_exception.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/universal_import/html_stub.dart' as html;
+import 'package:tmail_ui_user/main/utils/app_config.dart';
class ComposerController extends BaseController
with DragDropFileMixin, AutoCompleteResultMixin, EditorViewMixin
@@ -156,13 +153,14 @@ class ComposerController extends BaseController
final CreateNewAndSendEmailInteractor _createNewAndSendEmailInteractor;
final CreateNewAndSaveEmailToDraftsInteractor _createNewAndSaveEmailToDraftsInteractor;
final PrintEmailInteractor printEmailInteractor;
+ final ComposerRepository _composerRepository;
final String? composerId;
final ComposerArguments? composerArgs;
GetAllAutoCompleteInteractor? _getAllAutoCompleteInteractor;
GetAutoCompleteInteractor? _getAutoCompleteInteractor;
GetDeviceContactSuggestionsInteractor? _getDeviceContactSuggestionsInteractor;
- RestoreEmailInlineImagesInteractor? _restoreEmailInlineImagesInteractor;
+ RestoreEmailInlineImagesInteractor? restoreEmailInlineImagesInteractor;
List listToEmailAddress = [];
List listCcEmailAddress = [];
@@ -214,24 +212,31 @@ class ComposerController extends BaseController
List initialAttachments = [];
String? _textEditorWeb;
double? maxWithEditor;
- EmailId? _emailIdEditing;
+ EmailId? emailIdEditing;
bool isAttachmentCollapsed = false;
ButtonState _closeComposerButtonState = ButtonState.enabled;
ButtonState _saveToDraftButtonState = ButtonState.enabled;
ButtonState _sendButtonState = ButtonState.enabled;
ButtonState printDraftButtonState = ButtonState.enabled;
- SignatureStatus _identityContentOnOpenPolicy = SignatureStatus.editedAvailable;
int? _savedEmailDraftHash;
- bool _restoringSignatureButton = false;
+ bool restoringSignatureButton = false;
+ bool synchronizeInitDraftHash = false;
GlobalKey? responsiveContainerKey;
- Worker? emailContentViewStateWorker;
-
- @visibleForTesting
- bool get restoringSignatureButton => _restoringSignatureButton;
+ EmailActionType? currentEmailActionType;
+ EmailActionType? savedActionType;
+ int minInputLengthAutocomplete = AppConfig.defaultMinInputLengthAutocomplete;
@visibleForTesting
int? get savedEmailDraftHash => _savedEmailDraftHash;
+ GetEmailContentInteractor get getEmailContentInteractor => _getEmailContentInteractor;
+
+ GetServerSettingInteractor get getServerSettingInteractor => _getServerSettingInteractor;
+
+ GetAllIdentitiesInteractor get getAllIdentitiesInteractor => _getAllIdentitiesInteractor;
+
+ TransformHtmlEmailContentInteractor get transformHtmlEmailContentInteractor => _transformHtmlEmailContentInteractor;
+
late Worker uploadInlineImageWorker;
late Worker dashboardViewStateWorker;
late bool _isEmailBodyLoaded;
@@ -250,6 +255,7 @@ class ComposerController extends BaseController
this._createNewAndSendEmailInteractor,
this._createNewAndSaveEmailToDraftsInteractor,
this.printEmailInteractor,
+ this._composerRepository,
{
this.composerId,
this.composerArgs,
@@ -262,6 +268,7 @@ class ComposerController extends BaseController
if (PlatformInfo.isWeb) {
responsiveContainerKey = GlobalKey();
richTextWebController = getBinding(tag: composerId);
+ restoreEmailInlineImagesInteractor = getBinding(tag: composerId);
menuMoreOptionController = CustomPopupMenuController();
} else {
richTextMobileTabletController = getBinding(tag: composerId);
@@ -270,6 +277,7 @@ class ComposerController extends BaseController
scrollControllerEmailAddress.addListener(_scrollControllerEmailAddressListener);
_listenStreamEvent();
_beforeReconnectManager.addListener(onBeforeReconnect);
+ _injectBinding();
}
@override
@@ -277,7 +285,7 @@ class ComposerController extends BaseController
if (PlatformInfo.isWeb) {
_triggerBrowserEventListener();
}
- _initEmail();
+ setupComposer();
if (PlatformInfo.isMobile) {
Future.delayed(const Duration(milliseconds: 500), _checkContactPermission);
}
@@ -287,6 +295,12 @@ class ComposerController extends BaseController
@override
void onClose() {
_textEditorWeb = null;
+ savedActionType = null;
+ _savedEmailDraftHash = null;
+ currentEmailActionType = null;
+ emailIdEditing = null;
+ maxWithEditor = null;
+ initialAttachments.clear();
dispatchState(Right(UIClosedState()));
composerArguments.value = null;
emailContentsViewState.value = Right(UIClosedState());
@@ -304,12 +318,9 @@ class ComposerController extends BaseController
responsiveContainerKey = null;
menuMoreOptionController?.dispose();
menuMoreOptionController = null;
- emailContentViewStateWorker?.dispose();
- emailContentViewStateWorker = null;
} else {
richTextMobileTabletController = null;
}
- _identityContentOnOpenPolicy = SignatureStatus.editedAvailable;
super.onClose();
}
@@ -352,20 +363,10 @@ class ComposerController extends BaseController
@override
void handleSuccessViewState(Success success) {
- super.handleSuccessViewState(success);
- if (success is GetEmailContentLoading ||
- success is TransformHtmlEmailContentLoading ||
- success is TransformHtmlEmailContentSuccess ||
- success is RestoringEmailInlineImages) {
- emailContentsViewState.value = Right(success);
- } else if (success is LocalFilePickerSuccess) {
+ if (success is LocalFilePickerSuccess) {
_handlePickFileSuccess(success);
} else if (success is LocalImagePickerSuccess) {
_handlePickImageSuccess(success);
- } else if (success is GetEmailContentSuccess) {
- _getEmailContentSuccess(success);
- } else if (success is GetEmailContentFromCacheSuccess) {
- _getEmailContentOffLineSuccess(success);
} else if (success is GetAllIdentitiesSuccess) {
_handleGetAllIdentitiesSuccess(success);
} else if (success is DownloadImageAsBase64Success) {
@@ -376,49 +377,22 @@ class ComposerController extends BaseController
richTextMobileTabletController?.insertImage(inlineImage);
}
maxWithEditor = null;
- } else if (success is GetServerSettingSuccess) {
- hasRequestReadReceipt.value = success.settingOption.isAlwaysReadReceipts;
- _initEmailDraftHash();
- } else if (success is RestoreEmailInlineImagesSuccess) {
- _updateEditorContent(success);
+ } else {
+ super.handleSuccessViewState(success);
}
}
- void _updateEditorContent(RestoreEmailInlineImagesSuccess success) {
- richTextWebController?.editorController.setText(success.emailContent);
- consumeState(Stream.value(Right(GetEmailContentSuccess(htmlEmailContent: success.emailContent))));
- }
-
@override
void handleFailureViewState(Failure failure) {
- super.handleFailureViewState(failure);
if (failure is LocalFilePickerFailure) {
_handlePickFileFailure(failure);
} else if (failure is LocalImagePickerFailure) {
_handlePickImageFailure(failure);
- } else if (failure is GetEmailContentFailure) {
- _handleGetEmailContentFailure(failure);
- } else if (failure is TransformHtmlEmailContentFailure
- || failure is RestoreEmailInlineImagesFailure) {
- emailContentsViewState.value = Left(failure);
- } else if (failure is GetAllIdentitiesFailure) {
- if (identitySelected.value == null) {
- _autoFocusFieldWhenLauncher();
- }
- } else if (failure is GetServerSettingFailure) {
- hasRequestReadReceipt.value = false;
- _initEmailDraftHash();
+ } else {
+ super.handleFailureViewState(failure);
}
}
- @override
- void handleUrgentExceptionOnMobile({Failure? failure, Exception? exception}) {
- if (failure is GetAllIdentitiesFailure && exception is! BadCredentialsException) {
- _handleGetAllIdentitiesFailure();
- }
- super.handleUrgentExceptionOnMobile(failure: failure, exception: exception);
- }
-
@override
Future onUnloadBrowserListener(html.Event event) async {
final username = mailboxDashBoardController.sessionCurrent?.username;
@@ -442,39 +416,6 @@ class ComposerController extends BaseController
}
});
});
-
- if (PlatformInfo.isWeb) {
- emailContentViewStateWorker = ever(emailContentsViewState, (state) {
- state?.fold((_) => null, (success) {
- if (success is GetEmailContentSuccess) {
- onChangeTextEditorWeb(success.htmlEmailContent);
- } else if (success is TransformHtmlEmailContentSuccess) {
- final arguments = composerArguments.value;
- if (arguments == null ||
- currentContext == null ||
- arguments.presentationEmail == null) return;
-
- final emailActionType = arguments.emailActionType;
-
- if (emailActionType == EmailActionType.reply ||
- emailActionType == EmailActionType.replyAll ||
- emailActionType == EmailActionType.replyToList ||
- emailActionType == EmailActionType.forward) {
-
- final emailContentQuoted = getEmailContentQuotedAsHtml(
- locale: Localizations.localeOf(currentContext!),
- appLocalizations: AppLocalizations.of(currentContext!),
- emailContent: success.htmlContent,
- emailActionType: arguments.emailActionType,
- presentationEmail: arguments.presentationEmail!
- );
-
- onChangeTextEditorWeb(emailContentQuoted);
- }
- }
- });
- });
- }
}
void _triggerBrowserEventListener() {
@@ -514,7 +455,7 @@ class ComposerController extends BaseController
Future _saveComposerCacheOnWebAction() async {
autoCreateEmailTag();
- final createEmailRequest = await _generateCreateEmailRequest();
+ final createEmailRequest = await _generateCreateEmailRequestToSaveAsCache();
if (createEmailRequest == null) return;
await _saveComposerCacheOnWebInteractor.execute(
@@ -532,7 +473,7 @@ class ComposerController extends BaseController
}
}
- Future _generateCreateEmailRequest() async {
+ Future _generateCreateEmailRequestToSaveAsCache() async {
final arguments = composerArguments.value;
final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value;
@@ -542,7 +483,12 @@ class ComposerController extends BaseController
return null;
}
- final emailContent = await getContentInEditor();
+ String emailContent = await getContentInEditor();
+ if (currentEmailActionType == EmailActionType.compose) {
+ emailContent = await _composerRepository.removeCollapsedExpandedSignatureEffect(
+ emailContent: emailContent,
+ );
+ }
final uploadUri = _getUploadUriFromSession(session, accountId);
final composerIndex = composerId != null
@@ -578,6 +524,9 @@ class ComposerController extends BaseController
uploadUri: uploadUri,
composerIndex: composerIndex,
composerId: composerId,
+ savedDraftHash: arguments.savedDraftHash ?? _savedEmailDraftHash,
+ savedActionType: savedActionType ?? currentEmailActionType,
+ savedEmailDraftId: emailIdEditing,
);
}
@@ -646,206 +595,47 @@ class ComposerController extends BaseController
);
}
- void onLoadCompletedMobileEditorAction(HtmlEditorApi editorApi, WebUri? url) async {
+ Future onLoadCompletedMobileEditorAction(HtmlEditorApi editorApi, WebUri? url) async {
_isEmailBodyLoaded = true;
- if (identitySelected.value == null) {
- _getAllIdentities();
- } else {
- await _selectIdentity(identitySelected.value);
- _autoFocusFieldWhenLauncher();
- }
+ await setupSelectedIdentity();
+ _autoFocusFieldWhenLauncher();
}
- void _initEmail() {
+ void _injectBinding() {
+ injectAutoCompleteBindings(
+ mailboxDashBoardController.sessionCurrent,
+ mailboxDashBoardController.accountId.value,
+ );
+ }
+
+ Future setupComposer() async {
_isEmailBodyLoaded = false;
- final arguments = PlatformInfo.isWeb
- ? composerArgs
- : Get.arguments;
- if (arguments is ComposerArguments) {
- composerArguments.value = arguments;
+ final arguments = PlatformInfo.isWeb ? composerArgs : Get.arguments;
- _initIdentities(arguments);
+ if (arguments is! ComposerArguments) return;
- injectAutoCompleteBindings(
- mailboxDashBoardController.sessionCurrent,
- mailboxDashBoardController.accountId.value
- );
+ composerArguments.value = arguments;
+ currentEmailActionType = arguments.emailActionType;
+ savedActionType = arguments.savedActionType;
+ emailIdEditing = arguments.savedEmailDraftId;
+ emailContentsViewState.value = Right(GetEmailContentLoading());
- switch(arguments.emailActionType) {
- case EmailActionType.editAsNewEmail:
- _initEmailAddress(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.editAsNewEmail
- );
- _initSubjectEmail(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.editAsNewEmail
- );
- _getEmailContentOfEmailDrafts(
- emailId: arguments.presentationEmail!.id!,
- );
- isMarkAsImportant.value = arguments.presentationEmail!.isMarkAsImportant;
- break;
- case EmailActionType.editDraft:
- _initEmailAddress(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.editDraft
- );
- _initSubjectEmail(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.editDraft
- );
- _getEmailContentOfEmailDrafts(
- emailId: arguments.presentationEmail!.id!,
- );
- _emailIdEditing = arguments.presentationEmail!.id!;
- isMarkAsImportant.value = arguments.presentationEmail!.isMarkAsImportant;
- break;
- case EmailActionType.editSendingEmail:
- _initEmailAddress(
- presentationEmail: arguments.sendingEmail!.presentationEmail,
- actionType: EmailActionType.editSendingEmail
- );
- _initSubjectEmail(
- presentationEmail: arguments.sendingEmail!.presentationEmail,
- actionType: EmailActionType.editSendingEmail
- );
- final allAttachments = arguments.sendingEmail!.email.allAttachments;
- _initAttachmentsAndInlineImages(
- attachments: allAttachments.getListAttachmentsDisplayedOutside(
- arguments.sendingEmail!.email.htmlBodyAttachments),
- inlineImages: allAttachments.listAttachmentsDisplayedInContent);
+ setupEmailSubject(arguments);
+ setupEmailRecipients(arguments);
+ setupEmailImportantFlag(arguments);
+ setupEmailAttachments(arguments);
+ setupEmailOtherComponents(arguments);
+ setupEmailRequestReadReceiptFlag(arguments);
- _getEmailContentFromSendingEmail(arguments.sendingEmail!);
- _emailIdEditing = arguments.sendingEmail!.presentationEmail.id!;
- break;
- case EmailActionType.composeFromContentShared:
- _getEmailContentFromContentShared(arguments.emailContents!);
- break;
- case EmailActionType.composeFromFileShared:
- _addAttachmentFromFileShare(arguments.listSharedMediaFile!);
- break;
- case EmailActionType.composeFromEmailAddress:
- listToEmailAddress.addAll(arguments.listEmailAddress ?? []);
- isInitialRecipient.value = true;
- toAddressExpandMode.value = ExpandMode.COLLAPSE;
- updateStatusEmailSendButton();
- break;
- case EmailActionType.composeFromMailtoUri:
- if (arguments.subject != null) {
- setSubjectEmail(arguments.subject!);
- subjectEmailInputController.text = arguments.subject!;
- }
- if (arguments.listEmailAddress?.isNotEmpty == true) {
- listToEmailAddress.addAll(arguments.listEmailAddress!);
- isInitialRecipient.value = true;
- toAddressExpandMode.value = ExpandMode.COLLAPSE;
- }
- if (arguments.cc?.isNotEmpty == true) {
- listCcEmailAddress = arguments.cc!;
- ccRecipientState.value = PrefixRecipientState.enabled;
- ccAddressExpandMode.value = ExpandMode.COLLAPSE;
- }
- if (arguments.bcc?.isNotEmpty == true) {
- bccRecipientState.value = PrefixRecipientState.enabled;
- bccAddressExpandMode.value = ExpandMode.COLLAPSE;
- listBccEmailAddress = arguments.bcc!;
- }
- _getEmailContentFromMailtoUri(arguments.body ?? '');
- updateStatusEmailSendButton();
- break;
- case EmailActionType.reply:
- case EmailActionType.replyToList:
- case EmailActionType.replyAll:
- log('ComposerController::_initEmail:listPost = ${arguments.listPost}');
- _initEmailAddress(
- presentationEmail: arguments.presentationEmail!,
- actionType: arguments.emailActionType,
- listPost: arguments.listPost,
- );
- _initSubjectEmail(
- presentationEmail: arguments.presentationEmail!,
- actionType: arguments.emailActionType
- );
- _initAttachmentsAndInlineImages(
- attachments: arguments.attachments,
- inlineImages: arguments.inlineImages);
+ await setupListIdentities(arguments);
+ await setupEmailContent(arguments);
- _transformHtmlEmailContent(arguments.emailContents);
- break;
- case EmailActionType.forward:
- _initSubjectEmail(
- presentationEmail: arguments.presentationEmail!,
- actionType: arguments.emailActionType
- );
- _initAttachmentsAndInlineImages(
- attachments: arguments.attachments,
- inlineImages: arguments.inlineImages);
- _transformHtmlEmailContent(arguments.emailContents);
- break;
- case EmailActionType.reopenComposerBrowser:
- if (!PlatformInfo.isWeb) return;
-
- screenDisplayMode.value = arguments.displayMode;
- isMarkAsImportant.value = arguments.isMarkAsImportant ?? false;
-
- _initEmailAddress(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.reopenComposerBrowser
- );
- _initSubjectEmail(
- presentationEmail: arguments.presentationEmail!,
- actionType: EmailActionType.reopenComposerBrowser
- );
- _initAttachmentsAndInlineImages(
- attachments: arguments.attachments,
- inlineImages: arguments.inlineImages);
-
- final accountId = mailboxDashBoardController.accountId.value;
- dynamic downloadUrl;
- try {
- downloadUrl = mailboxDashBoardController.sessionCurrent
- ?.getDownloadUrl(jmapUrl: dynamicUrlInterceptors.jmapUrl);
- } catch (e) {
- logError('ComposerController::_initEmail(): $e');
- downloadUrl = null;
- }
-
- if (accountId == null || downloadUrl == null) return;
- _getEmailContentFromSessionStorageBrowser(
- htmlContent: arguments.emailContents ?? '',
- inlineImages: arguments.inlineImages ?? [],
- accountId: accountId,
- downloadUrl: downloadUrl
- );
- break;
- case EmailActionType.composeFromUnsubscribeMailtoLink:
- if (arguments.subject != null) {
- setSubjectEmail(arguments.subject!);
- subjectEmailInputController.text = arguments.subject!;
- }
- if (arguments.listEmailAddress?.isNotEmpty == true) {
- listToEmailAddress.addAll(arguments.listEmailAddress!);
- isInitialRecipient.value = true;
- toAddressExpandMode.value = ExpandMode.COLLAPSE;
- }
- _getEmailContentFromUnsubscribeMailtoLink(arguments.body ?? '');
- updateStatusEmailSendButton();
- break;
- default:
- break;
- }
-
- if (composerArguments.value?.emailActionType == EmailActionType.reopenComposerBrowser) {
- log('ComposerController::_initEmail: hasRequestReadReceipt = ${arguments.hasRequestReadReceipt}');
- hasRequestReadReceipt.value = arguments.hasRequestReadReceipt ?? false;
- } else if (composerArguments.value?.emailActionType != EmailActionType.editDraft) {
- _getServerSetting();
- }
+ if (screenDisplayMode.value.isNotContentVisible()) {
+ await setupSelectedIdentityWithoutApplySignature();
}
}
- void _initSubjectEmail({
+ void initSubjectEmail({
required PresentationEmail presentationEmail,
required EmailActionType actionType
}) {
@@ -855,7 +645,7 @@ class ComposerController extends BaseController
subjectEmailInputController.text = newSubject;
}
- void _initAttachmentsAndInlineImages({
+ void initAttachmentsAndInlineImages({
List? attachments,
List? inlineImages
}) {
@@ -868,28 +658,6 @@ class ComposerController extends BaseController
}
}
- Identity? _selectIdentityFromId(IdentityId? identityId) {
- if (identityId == null) return null;
-
- return listFromIdentities.firstWhereOrNull(
- (identity) => identity.id == identityId);
- }
-
- Future _initIdentities(ComposerArguments composerArguments) async {
- listFromIdentities.value = composerArguments.identities ?? [];
- final selectedIdentityFromId = _selectIdentityFromId(
- composerArguments.selectedIdentityId);
- if (listFromIdentities.isEmpty) {
- _getAllIdentities();
- } else if (selectedIdentityFromId != null) {
- await _selectIdentity(selectedIdentityFromId);
- _initEmailDraftHash();
- } else if (composerArguments.identities?.isNotEmpty == true) {
- await _selectIdentity(composerArguments.identities!.first);
- _initEmailDraftHash();
- }
- }
-
void _getAllIdentities() {
log('ComposerController::_getAllIdentities: Fetch again identity !');
final accountId = mailboxDashBoardController.accountId.value;
@@ -899,27 +667,14 @@ class ComposerController extends BaseController
}
}
- void _handleGetAllIdentitiesSuccess(GetAllIdentitiesSuccess success) async {
+ void _handleGetAllIdentitiesSuccess(GetAllIdentitiesSuccess success) {
final listIdentitiesMayDeleted = success.identities?.toListMayDeleted() ?? [];
if (listIdentitiesMayDeleted.isNotEmpty) {
listFromIdentities.value = listIdentitiesMayDeleted;
-
- if (identitySelected.value == null) {
- final selectedIdentityFromId = _selectIdentityFromId(
- composerArguments.value?.selectedIdentityId);
- if (selectedIdentityFromId != null) {
- await _selectIdentity(selectedIdentityFromId);
- _initEmailDraftHash();
- } else {
- await _selectIdentity(listIdentitiesMayDeleted.firstOrNull);
- _initEmailDraftHash();
- }
- }
}
- _autoFocusFieldWhenLauncher();
}
- void _initEmailAddress({
+ void initEmailAddress({
required PresentationEmail presentationEmail,
required EmailActionType actionType,
String? listPost,
@@ -1322,19 +1077,18 @@ class ComposerController extends BaseController
void _handlePickFileSuccess(LocalFilePickerSuccess success) {
uploadController.validateTotalSizeAttachmentsBeforeUpload(
totalSizePreparedFiles: success.pickedFiles.totalSize,
- onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: success.pickedFiles)
+ onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: success.pickedFiles)
);
}
void _handlePickImageSuccess(LocalImagePickerSuccess success) {
uploadController.validateTotalSizeInlineAttachmentsBeforeUpload(
totalSizePreparedFiles: success.fileInfo.fileSize,
- onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: [success.fileInfo.withInline()])
+ onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: [success.fileInfo.withInline()])
);
}
-
- void _uploadAttachmentsAction({required List pickedFiles}) {
+ void uploadAttachmentsAction({required List pickedFiles}) {
final session = mailboxDashBoardController.sessionCurrent;
final accountId = mailboxDashBoardController.accountId.value;
if (session != null && accountId != null) {
@@ -1345,11 +1099,11 @@ class ComposerController extends BaseController
uploadUri: uploadUri,
);
} catch (e) {
- log('ComposerController::_uploadAttachmentsAction: $e');
+ logError('ComposerController::uploadAttachmentsAction: $e');
uploadController.consumeState(Stream.value(Left(UploadAttachmentFailure(e, pickedFiles[0]))));
}
} else {
- log('ComposerController::_uploadAttachmentsAction: SESSION OR ACCOUNT_ID is NULL');
+ logError('ComposerController::uploadAttachmentsAction: SESSION OR ACCOUNT_ID is NULL');
}
}
@@ -1359,12 +1113,16 @@ class ComposerController extends BaseController
Future _validateEmailChange() async {
final newDraftHash = await _hashDraftEmail();
-
+ log('ComposerController::_validateEmailChange:newDraftHash = $newDraftHash | _savedEmailDraftHash = $_savedEmailDraftHash');
return _savedEmailDraftHash != newDraftHash;
}
Future _hashDraftEmail() async {
- final emailContent = await getContentInEditor();
+ String emailContent = await getContentInEditor();
+
+ emailContent = await _composerRepository.removeCollapsedExpandedSignatureEffect(
+ emailContent: emailContent,
+ );
final savedEmailDraft = SavedEmailDraft(
subject: subjectEmail.value ?? '',
@@ -1378,28 +1136,30 @@ class ComposerController extends BaseController
hasReadReceipt: hasRequestReadReceipt.value,
isMarkAsImportant: isMarkAsImportant.value,
);
-
- return savedEmailDraft.hashCode;
+ final draftAsString = savedEmailDraft.asString();
+ final draftAsHasCode = draftAsString.hashCode;
+ log('ComposerController::_hashDraftEmail:draftAsString = $draftAsString | draftAsHasCode = $draftAsHasCode');
+ return draftAsHasCode;
}
- int get emptyDraftEmailHash => SavedEmailDraft.empty().hashCode;
-
Future _updateSavedEmailDraftHash() async {
_savedEmailDraftHash = await _hashDraftEmail();
}
- Future _initEmailDraftHash() async {
- final draftEmailHash = await _hashDraftEmail();
+ Future initEmailDraftHash() async {
+ final currentDraftHash = await _hashDraftEmail();
- isEmailChanged.value = draftEmailHash != emptyDraftEmailHash;
+ final oldSavedDraftHash = composerArguments.value?.savedDraftHash;
- final emailActionType = composerArguments.value?.emailActionType;
- if (emailActionType == EmailActionType.compose
- || emailActionType == EmailActionType.editDraft) {
- _savedEmailDraftHash = draftEmailHash;
- } else if (emailActionType == EmailActionType.reopenComposerBrowser) {
- _savedEmailDraftHash = emptyDraftEmailHash;
+ if (currentEmailActionType == EmailActionType.compose ||
+ currentEmailActionType == EmailActionType.editDraft) {
+ _savedEmailDraftHash = currentDraftHash;
+ } else if (currentEmailActionType == EmailActionType.reopenComposerBrowser) {
+ _savedEmailDraftHash = oldSavedDraftHash;
}
+ log('ComposerController::initEmailDraftHash:oldSavedDraftHash = $oldSavedDraftHash | currentDraftHash = $currentDraftHash | _savedEmailDraftHash = $_savedEmailDraftHash');
+
+ isEmailChanged.value = currentDraftHash != _savedEmailDraftHash;
}
void handleClickSaveAsDraftsButton(BuildContext context) async {
@@ -1433,18 +1193,18 @@ class ComposerController extends BaseController
arguments: arguments,
emailContent: emailContent,
uploadUri: uploadUri,
- draftEmailId: _emailIdEditing,
+ draftEmailId: emailIdEditing,
cancelToken: cancelToken
);
if (resultState is SaveEmailAsDraftsSuccess) {
_saveToDraftButtonState = ButtonState.enabled;
- _emailIdEditing = resultState.emailId;
+ emailIdEditing = resultState.emailId;
mailboxDashBoardController.consumeState(Stream.value(Right(resultState)));
_updateSavedEmailDraftHash();
} else if (resultState is UpdateEmailDraftsSuccess) {
_saveToDraftButtonState = ButtonState.enabled;
- _emailIdEditing = resultState.emailId;
+ emailIdEditing = resultState.emailId;
mailboxDashBoardController.consumeState(Stream.value(Right(resultState)));
_updateSavedEmailDraftHash();
} else if ((resultState is SaveEmailAsDraftsFailure && resultState.exception is SavingEmailToDraftsCanceledException) ||
@@ -1473,158 +1233,6 @@ class ComposerController extends BaseController
}
}
- void _addAttachmentFromFileShare(List listSharedMediaFile) {
- final listFileInfo = listSharedMediaFile.toListFileInfo(isShared: true);
-
- final tupleListFileInfo = partition(listFileInfo, (fileInfo) => fileInfo.isInline == true);
- final listAttachments = tupleListFileInfo.value2;
-
- uploadController.validateTotalSizeAttachmentsBeforeUpload(
- totalSizePreparedFiles: listFileInfo.totalSize,
- totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
- onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
- );
- }
-
- void _getEmailContentFromSendingEmail(SendingEmail sendingEmail) {
- consumeState(Stream.value(
- Right(GetEmailContentSuccess(
- htmlEmailContent: sendingEmail.presentationEmail.emailContentList.asHtmlString,
- emailCurrent: sendingEmail.email
- ))
- ));
- }
-
- void _getEmailContentFromSessionStorageBrowser({
- required String htmlContent,
- required List inlineImages,
- required AccountId accountId,
- required String downloadUrl
- }) {
- _restoreEmailInlineImagesInteractor = getBinding(tag: composerId);
- if (_restoreEmailInlineImagesInteractor == null) return;
- consumeState(_restoreEmailInlineImagesInteractor!.execute(
- htmlContent: htmlContent,
- transformConfiguration: TransformConfiguration.forRestoreEmail(),
- mapUrlDownloadCID: inlineImages.toMapCidImageDownloadUrl(
- accountId: accountId,
- downloadUrl: downloadUrl)));
- }
-
- void _getEmailContentFromContentShared(String content) {
- consumeState(Stream.value(Right(GetEmailContentSuccess(htmlEmailContent: content))));
- }
-
- void _getEmailContentFromMailtoUri(String content) {
- log('ComposerController::_getEmailContentFromMailtoUri:content: $content');
- consumeState(Stream.value(Right(GetEmailContentSuccess(htmlEmailContent: content))));
- }
-
- void _getEmailContentFromUnsubscribeMailtoLink(String content) {
- log('ComposerController::_getEmailContentFromUnsubscribeMailtoLink:content: $content');
- consumeState(Stream.value(Right(GetEmailContentSuccess(htmlEmailContent: content))));
- }
-
- void _getEmailContentOfEmailDrafts({required EmailId emailId}) {
- final session = mailboxDashBoardController.sessionCurrent;
- final accountId = mailboxDashBoardController.accountId.value;
-
- if (session == null || accountId == null) {
- consumeState(Stream.value(Left(GetEmailContentFailure(NotFoundSessionException()))));
- return;
- }
-
- try {
- consumeState(_getEmailContentInteractor.execute(
- session,
- accountId,
- emailId,
- mailboxDashBoardController.baseDownloadUrl,
- TransformConfiguration.forEditDraftsEmail(),
- additionalProperties: Properties({
- IndividualHeaderIdentifier.identityHeader.value}),
- ));
- } catch (e) {
- logError('ComposerController::_handleUploadInlineSuccess(): $e');
- consumeState(Stream.value(Left(GetEmailContentFailure(e))));
- }
- }
-
- void _getEmailContentOffLineSuccess(GetEmailContentFromCacheSuccess success) {
- _initAttachmentsAndInlineImages(
- attachments: success.attachments,
- inlineImages: success.inlineImages);
- emailContentsViewState.value = Right(success);
-
- if (composerArguments.value?.emailActionType == EmailActionType.editDraft) {
- _setUpRequestReadReceiptForDraftEmail(success.emailCurrent);
- }
- }
-
- void _getEmailContentSuccess(GetEmailContentSuccess success) {
- _initAttachmentsAndInlineImages(
- attachments: success.attachments,
- inlineImages: success.inlineImages);
- emailContentsViewState.value = Right(success);
-
- if (composerArguments.value?.emailActionType == EmailActionType.editDraft) {
- _setUpRequestReadReceiptForDraftEmail(success.emailCurrent);
- _restoreIdentityFromHeader(success.emailCurrent);
- }
- }
-
- void _restoreIdentityFromHeader(Email? email) {
- final identityIdFromHeader = email?.identityIdFromHeader;
- if (identityIdFromHeader == null) return;
- final selectedIdentityFromHeader = _selectIdentityFromId(identityIdFromHeader);
- if (selectedIdentityFromHeader == null) return;
- identitySelected.value = selectedIdentityFromHeader;
-
- _initEmailDraftHash();
- }
-
- Future restoreCollapsibleButton(String? emailContent) async {
- try {
- if (emailContent == null) return;
- final emailDocument = parse(emailContent);
-
- final existedSignatureButton = emailDocument.querySelector(
- 'button.tmail-signature-button');
- if (existedSignatureButton != null) return;
-
- final signature = emailDocument.querySelector('div.tmail-signature');
- if (signature == null) return;
- _restoringSignatureButton = true;
- await _applySignature(signature.innerHtml);
- } catch (e) {
- logError('ComposerController::_restoreCollapsibleButton: $e');
- }
- }
-
- 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.editDraft) {
- return arguments.presentationEmail?.firstEmailAddressInFrom ?? '';
- } else {
- return mailboxDashBoardController.sessionCurrent?.getOwnEmailAddress() ?? '';
- }
- }
- return '';
- }
-
void clearFocus(BuildContext context) {
log('ComposerController::clearFocus:');
if (PlatformInfo.isMobile) {
@@ -1804,24 +1412,11 @@ class ComposerController extends BaseController
}
}
- Future _selectIdentity(Identity? newIdentity) async {
+ Future selectIdentity(Identity? newIdentity) async {
final formerIdentity = identitySelected.value;
identitySelected.value = newIdentity;
if (newIdentity == null) return;
-
- final emailActionType = composerArguments.value?.emailActionType;
- switch (emailActionType) {
- case EmailActionType.reopenComposerBrowser:
- case EmailActionType.editDraft:
- if (_identityContentOnOpenPolicy == SignatureStatus.editedAvailable) {
- _identityContentOnOpenPolicy = SignatureStatus.editedApplied;
- } else {
- await _applyIdentityForAllFieldComposer(formerIdentity, newIdentity);
- }
- break;
- default:
- await _applyIdentityForAllFieldComposer(formerIdentity, newIdentity);
- }
+ await _applyIdentityForAllFieldComposer(formerIdentity, newIdentity);
}
Future _applyIdentityForAllFieldComposer(
@@ -1840,7 +1435,7 @@ class ComposerController extends BaseController
}
if (newIdentity.signatureAsString.isNotEmpty == true) {
- await _applySignature(newIdentity.signatureAsString.asSignatureHtml());
+ await applySignature(newIdentity.signatureAsString.asSignatureHtml());
}
if (PlatformInfo.isMobile) {
@@ -1877,7 +1472,7 @@ class ComposerController extends BaseController
updateStatusEmailSendButton();
}
- Future _applySignature(String signature) async {
+ Future applySignature(String signature) async {
if (PlatformInfo.isWeb) {
richTextWebController?.editorController.insertSignature(signature);
} else {
@@ -1886,7 +1481,6 @@ class ComposerController extends BaseController
}
Future _removeSignature() async {
- log('ComposerController::_removeSignature():');
if (PlatformInfo.isWeb) {
richTextWebController?.editorController.removeSignature();
} else {
@@ -2049,22 +1643,19 @@ class ComposerController extends BaseController
return false;
}
- void handleInitHtmlEditorWeb(String initContent) async {
+ void handleInitHtmlEditorWeb(String initContent) {
if (_isEmailBodyLoaded) return;
- log('ComposerController::handleInitHtmlEditorWeb:');
_isEmailBodyLoaded = true;
richTextWebController?.editorController.setFullScreen();
richTextWebController?.editorController.setOnDragDropEvent();
- onChangeTextEditorWeb(initContent);
richTextWebController?.setEnableCodeView();
- if (identitySelected.value == null) {
- _getAllIdentities();
- } else {
- if (composerArguments.value?.emailActionType != EmailActionType.editDraft) {
- await _selectIdentity(identitySelected.value);
- }
- _autoFocusFieldWhenLauncher();
- }
+ setTextEditorWeb(initContent);
+ }
+
+ Future onInitialContentLoadCompleteWeb(String? initContent) async {
+ await restoreCollapsibleSignatureButton(initContent);
+ await setupSelectedIdentity();
+ _autoFocusFieldWhenLauncher();
}
void handleOnFocusHtmlEditorWeb() {
@@ -2119,21 +1710,17 @@ class ComposerController extends BaseController
String? get textEditorWeb => _textEditorWeb;
+ void setTextEditorWeb(String value) => _textEditorWeb = value;
+
HtmlEditorApi? get htmlEditorApi => richTextMobileTabletController?.htmlEditorApi;
void onChangeTextEditorWeb(String? text) {
_textEditorWeb = text;
- _initEmailDraftHashAfterSignatureButtonRestored(text);
- }
- void _initEmailDraftHashAfterSignatureButtonRestored(String? emailContent) {
- if (!_restoringSignatureButton) return;
- final emailDocument = parse(emailContent);
- final signatureButton = emailDocument.querySelector('button.tmail-signature-button');
- if (signatureButton == null) return;
-
- _restoringSignatureButton = false;
- _initEmailDraftHash();
+ if (restoringSignatureButton ||
+ (currentEmailActionType == EmailActionType.compose && !synchronizeInitDraftHash)) {
+ synchronizeInitEmailDraftHash(text);
+ }
}
void setSubjectEmail(String subject) => subjectEmail.value = subject;
@@ -2146,18 +1733,8 @@ class ComposerController extends BaseController
);
}
- Future _handleGetAllIdentitiesFailure() async {
- if (composerArguments.value?.emailActionType == EmailActionType.editSendingEmail) {
- final signatureContent = await htmlEditorApi?.getSignatureContent();
- log('ComposerController::_handleGetAllIdentitiesFailure:signatureContent: $signatureContent');
- if (signatureContent?.isNotEmpty == true) {
- await _applySignature(signatureContent!);
- }
- }
- }
-
Future onChangeIdentity(Identity? newIdentity) async {
- await _selectIdentity(newIdentity);
+ await selectIdentity(newIdentity);
}
void _searchIdentities(String searchText) {
@@ -2197,7 +1774,7 @@ class ComposerController extends BaseController
_closeComposerButtonState = ButtonState.disabled;
- if (composerArguments.value == null || !_isEmailBodyLoaded) {
+ if (_validateCloseComposerWithoutSave()) {
log('ComposerController::handleClickCloseComposer: ARGUMENTS is NULL or EMAIL NOT LOADED');
_closeComposerButtonState = ButtonState.enabled;
clearFocus(context);
@@ -2220,6 +1797,18 @@ class ComposerController extends BaseController
}
}
+ bool _validateCloseComposerWithoutSave() {
+ if (composerArguments.value == null) return true;
+
+ if (PlatformInfo.isWeb &&
+ !_isEmailBodyLoaded &&
+ !screenDisplayMode.value.isNotContentVisible()) return true;
+
+ if (PlatformInfo.isMobile && !_isEmailBodyLoaded) return true;
+
+ return false;
+ }
+
Future _showConfirmDialogSaveMessage(BuildContext context) async {
await showConfirmDialogAction(
context,
@@ -2254,13 +1843,6 @@ class ComposerController extends BaseController
);
}
- void _getServerSetting() {
- final accountId = mailboxDashBoardController.accountId.value;
- if (accountId != null) {
- consumeState(_getServerSettingInteractor.execute(accountId));
- }
- }
-
void handleOnDragEnterHtmlEditorWeb(List? types) {
if (types.validateFilesTransfer) {
mailboxDashBoardController.localFileDraggableAppState.value = DraggableAppState.active;
@@ -2293,7 +1875,7 @@ class ComposerController extends BaseController
uploadController.validateTotalSizeAttachmentsBeforeUpload(
totalSizePreparedFiles: listFileInfo.totalSize,
totalSizePreparedFilesWithDispositionAttachment: listFileInfo.listAttachmentFiles.totalSize,
- onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
+ onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: listFileInfo)
);
}
@@ -2351,10 +1933,10 @@ class ComposerController extends BaseController
}
EmailId? getDraftEmailId() {
- if (_emailIdEditing != null &&
- _emailIdEditing != composerArguments.value!.presentationEmail?.id) {
- return _emailIdEditing;
- } else if (composerArguments.value!.emailActionType == EmailActionType.editDraft) {
+ if (emailIdEditing != null &&
+ emailIdEditing != composerArguments.value!.presentationEmail?.id) {
+ return emailIdEditing;
+ } else if (currentEmailActionType == EmailActionType.editDraft) {
return composerArguments.value!.presentationEmail?.id;
} else {
return null;
@@ -2456,22 +2038,6 @@ class ComposerController extends BaseController
replyToRecipientState.value = isEnabled ? PrefixRecipientState.disabled : PrefixRecipientState.enabled;
}
- void _handleGetEmailContentFailure(GetEmailContentFailure failure) {
- emailContentsViewState.value = Left(failure);
- if (composerArguments.value?.emailActionType == EmailActionType.editDraft) {
- _getServerSetting();
- }
- }
-
- void _setUpRequestReadReceiptForDraftEmail(Email? email) {
- if (email?.hasRequestReadReceipt == true) {
- hasRequestReadReceipt.value = true;
- _initEmailDraftHash();
- } else {
- _getServerSetting();
- }
- }
-
@override
Future onBeforeReconnect() async {
if (mailboxDashBoardController.accountId.value != null &&
@@ -2504,7 +2070,7 @@ class ComposerController extends BaseController
uploadController.validateTotalSizeAttachmentsBeforeUpload(
totalSizePreparedFiles: listFileInfo.totalSize,
- onValidationSuccess: () => _uploadAttachmentsAction(pickedFiles: listFileInfo)
+ onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: listFileInfo)
);
}
@@ -2519,4 +2085,8 @@ class ComposerController extends BaseController
context,
AppLocalizations.of(context).thisImageCannotBePastedIntoTheEditor);
}
+
+ void onCompleteSetupComposer() {
+ initEmailDraftHash();
+ }
}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/composer_view.dart b/lib/features/composer/presentation/composer_view.dart
index fbd703b71..357ef5700 100644
--- a/lib/features/composer/presentation/composer_view.dart
+++ b/lib/features/composer/presentation/composer_view.dart
@@ -127,9 +127,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listToEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
fromState: controller.fromRecipientState.value,
ccState: controller.ccRecipientState.value,
bccState: controller.bccRecipientState.value,
@@ -157,9 +155,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listCcEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.ccAddressExpandMode.value,
controller: controller.ccEmailAddressController,
focusNode: controller.ccAddressFocusNode,
@@ -186,9 +182,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listBccEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.bccAddressExpandMode.value,
controller: controller.bccEmailAddressController,
focusNode: controller.bccAddressFocusNode,
@@ -320,9 +314,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listToEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
fromState: controller.fromRecipientState.value,
ccState: controller.ccRecipientState.value,
bccState: controller.bccRecipientState.value,
@@ -349,9 +341,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listCcEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.ccAddressExpandMode.value,
controller: controller.ccEmailAddressController,
focusNode: controller.ccAddressFocusNode,
@@ -373,9 +363,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listBccEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.bccAddressExpandMode.value,
controller: controller.bccEmailAddressController,
focusNode: controller.bccAddressFocusNode,
diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart
index ef3e060e2..726292564 100644
--- a/lib/features/composer/presentation/composer_view_web.dart
+++ b/lib/features/composer/presentation/composer_view_web.dart
@@ -98,9 +98,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listToEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
fromState: controller.fromRecipientState.value,
ccState: controller.ccRecipientState.value,
bccState: controller.bccRecipientState.value,
@@ -129,9 +127,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listCcEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.ccAddressExpandMode.value,
controller: controller.ccEmailAddressController,
focusNode: controller.ccAddressFocusNode,
@@ -156,9 +152,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listBccEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.bccAddressExpandMode.value,
controller: controller.bccEmailAddressController,
focusNode: controller.bccAddressFocusNode,
@@ -248,7 +242,7 @@ class ComposerView extends GetWidget {
base64: base64,
uploadError: uploadError
),
- onInitialContentLoadComplete: controller.restoreCollapsibleButton,
+ onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
)),
),
),
@@ -375,9 +369,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listToEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
fromState: controller.fromRecipientState.value,
ccState: controller.ccRecipientState.value,
bccState: controller.bccRecipientState.value,
@@ -406,9 +398,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listCcEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.ccAddressExpandMode.value,
controller: controller.ccEmailAddressController,
focusNode: controller.ccAddressFocusNode,
@@ -433,9 +423,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listBccEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.bccAddressExpandMode.value,
controller: controller.bccEmailAddressController,
focusNode: controller.bccAddressFocusNode,
@@ -539,7 +527,7 @@ class ComposerView extends GetWidget {
base64: base64,
uploadError: uploadError
),
- onInitialContentLoadComplete: controller.restoreCollapsibleButton,
+ onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
);
}),
),
@@ -694,9 +682,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listToEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
fromState: controller.fromRecipientState.value,
ccState: controller.ccRecipientState.value,
bccState: controller.bccRecipientState.value,
@@ -725,9 +711,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listCcEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.ccAddressExpandMode.value,
controller: controller.ccEmailAddressController,
focusNode: controller.ccAddressFocusNode,
@@ -752,9 +736,7 @@ class ComposerView extends GetWidget {
listEmailAddress: controller.listBccEmailAddress,
imagePaths: controller.imagePaths,
maxWidth: constraints.maxWidth,
- minInputLengthAutocomplete: controller
- .mailboxDashBoardController
- .minInputLengthAutocomplete,
+ minInputLengthAutocomplete: controller.minInputLengthAutocomplete,
expandMode: controller.bccAddressExpandMode.value,
controller: controller.bccEmailAddressController,
focusNode: controller.bccAddressFocusNode,
@@ -857,7 +839,7 @@ class ComposerView extends GetWidget {
base64: base64,
uploadError: uploadError
),
- onInitialContentLoadComplete: controller.restoreCollapsibleButton,
+ onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
)),
),
),
diff --git a/lib/features/composer/presentation/extensions/sanitize_signature_in_email_content_extension.dart b/lib/features/composer/presentation/extensions/sanitize_signature_in_email_content_extension.dart
new file mode 100644
index 000000000..039c12b9d
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/sanitize_signature_in_email_content_extension.dart
@@ -0,0 +1,39 @@
+
+import 'package:html/parser.dart';
+import 'package:rich_text_composer/views/commons/logger.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+
+extension SanitizeSignatureInEmailContentExtension on ComposerController {
+
+ Future restoreCollapsibleSignatureButton(String? emailContent) async {
+ try {
+ if (emailContent == null) return;
+
+ final emailDocument = parse(emailContent);
+ final existedSignatureButton = emailDocument.querySelector('button.tmail-signature-button');
+ if (existedSignatureButton != null) return;
+
+ final signature = emailDocument.querySelector('div.tmail-signature');
+ if (signature == null) return;
+
+ restoringSignatureButton = true;
+ await applySignature(signature.innerHtml);
+ } catch (e) {
+ logError('SanitizeSignatureInEmailContentExtension::restoreCollapsibleSignatureButton:Exception = $e');
+ }
+ }
+
+ void synchronizeInitEmailDraftHash(String? emailContent) {
+ try {
+ final emailDocument = parse(emailContent);
+ final signatureButton = emailDocument.querySelector('button.tmail-signature-button');
+ if (signatureButton == null) return;
+
+ restoringSignatureButton = false;
+ synchronizeInitDraftHash = true;
+ initEmailDraftHash();
+ } catch (e) {
+ logError('SanitizeSignatureInEmailContentExtension::synchronizeInitEmailDraftHash:Exception = $e');
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_attachments_extension.dart b/lib/features/composer/presentation/extensions/setup_email_attachments_extension.dart
new file mode 100644
index 000000000..f75a3cf2f
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_attachments_extension.dart
@@ -0,0 +1,67 @@
+
+import 'package:core/utils/list_utils.dart';
+import 'package:model/email/attachment.dart';
+import 'package:model/email/email_action_type.dart';
+import 'package:model/extensions/email_extension.dart';
+import 'package:model/extensions/list_attachment_extension.dart';
+import 'package:receive_sharing_intent/receive_sharing_intent.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/list_shared_media_file_extension.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+import 'package:tmail_ui_user/features/upload/domain/extensions/list_file_info_extension.dart';
+
+extension SetupEmailAttachmentsExtension on ComposerController {
+
+ void setupEmailAttachments(ComposerArguments arguments) {
+ List? attachments;
+ List? inlineImages;
+
+ switch(currentEmailActionType) {
+ case EmailActionType.editSendingEmail:
+ final sendingEmail = arguments.sendingEmail;
+ final allAttachments = sendingEmail?.email.allAttachments;
+ attachments = allAttachments?.getListAttachmentsDisplayedOutside(
+ sendingEmail?.email.htmlBodyAttachments ?? [],
+ );
+ inlineImages = allAttachments?.listAttachmentsDisplayedInContent;
+ break;
+ case EmailActionType.composeFromFileShared:
+ _uploadAttachmentFromFileShare(arguments.listSharedMediaFile!);
+ break;
+ case EmailActionType.reply:
+ case EmailActionType.replyToList:
+ case EmailActionType.replyAll:
+ case EmailActionType.forward:
+ attachments = arguments.attachments;
+ inlineImages = arguments.inlineImages;
+ break;
+ case EmailActionType.reopenComposerBrowser:
+ attachments = arguments.attachments;
+ inlineImages = arguments.inlineImages;
+ break;
+ default:
+ break;
+ }
+
+ initAttachmentsAndInlineImages(
+ attachments: attachments,
+ inlineImages: inlineImages,
+ );
+ }
+
+ void _uploadAttachmentFromFileShare(List listSharedMediaFile) {
+ final listFileInfo = listSharedMediaFile.toListFileInfo(isShared: true);
+
+ final tupleListFileInfo = partition(
+ listFileInfo,
+ (fileInfo) => fileInfo.isInline == true,
+ );
+ final listAttachments = tupleListFileInfo.value2;
+
+ uploadController.validateTotalSizeAttachmentsBeforeUpload(
+ totalSizePreparedFiles: listFileInfo.totalSize,
+ totalSizePreparedFilesWithDispositionAttachment: listAttachments.totalSize,
+ onValidationSuccess: () => uploadAttachmentsAction(pickedFiles: listFileInfo),
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_content_extension.dart b/lib/features/composer/presentation/extensions/setup_email_content_extension.dart
new file mode 100644
index 000000000..10fc4b6af
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_content_extension.dart
@@ -0,0 +1,223 @@
+
+import 'package:core/presentation/state/success.dart';
+import 'package:core/presentation/utils/html_transformer/transform_configuration.dart';
+import 'package:core/utils/platform_info.dart';
+import 'package:dartz/dartz.dart';
+import 'package:flutter/material.dart';
+import 'package:jmap_dart_client/jmap/core/properties/properties.dart';
+import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
+import 'package:model/email/email_action_type.dart';
+import 'package:model/extensions/email_extension.dart';
+import 'package:model/extensions/list_attachment_extension.dart';
+import 'package:model/extensions/list_email_content_extension.dart';
+import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_for_edit_draft_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/setup_selected_identity_extension.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/presentation/model/composer_arguments.dart';
+import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart';
+import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
+import 'package:tmail_ui_user/main/routes/route_navigation.dart';
+
+extension SetupEmailContentExtension on ComposerController {
+
+ Future setupEmailContent(ComposerArguments arguments) async {
+ switch(currentEmailActionType) {
+ case EmailActionType.editAsNewEmail:
+ case EmailActionType.editDraft:
+ 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.forEditDraftsEmail(),
+ additionalProperties: Properties({
+ 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) {
+ setupEmailRequestReadReceiptFlagForEditDraft(
+ 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) {
+ setupEmailRequestReadReceiptFlagForEditDraft(
+ uiState.emailCurrent.hasRequestReadReceipt,
+ );
+ }
+
+ emailContentsViewState.value = Right(uiState);
+ } else if (uiState is GetEmailContentFailure) {
+ emailContentsViewState.value = Left(uiState);
+ consumeState(Stream.value(Left(uiState)));
+ } else {
+ emailContentsViewState.value = Right(UIState.idle);
+ }
+ break;
+ case EmailActionType.editSendingEmail:
+ final sendingEmail = arguments.sendingEmail;
+ final successState = GetEmailContentSuccess(
+ htmlEmailContent: sendingEmail?.presentationEmail.emailContentList.asHtmlString ?? '',
+ emailCurrent: arguments.sendingEmail?.email,
+ );
+ if (PlatformInfo.isWeb) {
+ setTextEditorWeb(successState.htmlEmailContent);
+ }
+ emailContentsViewState.value = Right(successState);
+ break;
+ case EmailActionType.composeFromContentShared:
+ final successState = GetEmailContentSuccess(htmlEmailContent: arguments.emailContents ?? '');
+ if (PlatformInfo.isWeb) {
+ setTextEditorWeb(successState.htmlEmailContent);
+ }
+ emailContentsViewState.value = Right(successState);
+ break;
+ case EmailActionType.composeFromMailtoUri:
+ final successState = GetEmailContentSuccess(htmlEmailContent: arguments.body ?? '');
+ if (PlatformInfo.isWeb) {
+ setTextEditorWeb(successState.htmlEmailContent);
+ }
+ emailContentsViewState.value = Right(successState);
+ break;
+ case EmailActionType.reply:
+ case EmailActionType.replyToList:
+ case EmailActionType.replyAll:
+ case EmailActionType.forward:
+ if (arguments.emailContents?.trim().isNotEmpty != true) {
+ emailContentsViewState.value = Left(GetEmailContentFailure(EmptyEmailContentException()));
+ } else {
+ final resultState = await transformHtmlEmailContentInteractor.execute(
+ arguments.emailContents ?? '',
+ TransformConfiguration.forReplyForwardEmail(),
+ ).last;
+
+ final uiState = resultState.fold((failure) => failure, (success) => success);
+
+ if (uiState is TransformHtmlEmailContentSuccess) {
+ final emailContent = uiState.htmlContent;
+
+ if (PlatformInfo.isWeb &&
+ currentContext != null &&
+ arguments.presentationEmail != null) {
+ final emailContentQuoted = getEmailContentQuotedAsHtml(
+ locale: Localizations.localeOf(currentContext!),
+ appLocalizations: AppLocalizations.of(currentContext!),
+ emailContent: emailContent,
+ emailActionType: currentEmailActionType!,
+ presentationEmail: arguments.presentationEmail!,
+ );
+
+ setTextEditorWeb(emailContentQuoted);
+ }
+
+ final successState = GetEmailContentSuccess(htmlEmailContent: emailContent);
+ emailContentsViewState.value = Right(successState);
+ } else if (uiState is TransformHtmlEmailContentFailure) {
+ emailContentsViewState.value = Left(GetEmailContentFailure(uiState.exception));
+ consumeState(Stream.value(Left(GetEmailContentFailure(uiState.exception))));
+ } else {
+ emailContentsViewState.value = Right(UIState.idle);
+ }
+ }
+ break;
+ case EmailActionType.reopenComposerBrowser:
+ final inlineImages = arguments.inlineImages ?? [];
+ final content = arguments.emailContents ?? '';
+ final displayMode = arguments.displayMode;
+
+ if (content.trim().isEmpty) {
+ emailContentsViewState.value = Left(GetEmailContentFailure(EmptyEmailContentException()));
+ } else if (displayMode.isNotContentVisible()) {
+ final successState = GetEmailContentSuccess(htmlEmailContent: content);
+ setTextEditorWeb(content);
+ emailContentsViewState.value = Right(successState);
+ } else {
+ final accountId = mailboxDashBoardController.accountId.value;
+ final downloadUrl = mailboxDashBoardController.baseDownloadUrl;
+
+ if (restoreEmailInlineImagesInteractor == null ||
+ accountId == null ||
+ downloadUrl.isEmpty) {
+ emailContentsViewState.value = Left(GetEmailContentFailure(NotFoundAccountIdException()));
+ return;
+ }
+
+ final resultState = await restoreEmailInlineImagesInteractor!.execute(
+ htmlContent: content,
+ transformConfiguration: TransformConfiguration.forRestoreEmail(),
+ mapUrlDownloadCID: inlineImages.toMapCidImageDownloadUrl(
+ accountId: accountId,
+ downloadUrl: downloadUrl,
+ ),
+ ).last;
+
+ final uiState = resultState.fold((failure) => failure, (success) => success);
+
+ if (uiState is RestoreEmailInlineImagesSuccess) {
+ final emailContent = uiState.emailContent;
+ final successState = GetEmailContentSuccess(htmlEmailContent: emailContent);
+ setTextEditorWeb(emailContent);
+ emailContentsViewState.value = Right(successState);
+ } else if (uiState is RestoreEmailInlineImagesFailure) {
+ emailContentsViewState.value = Left(GetEmailContentFailure(uiState.exception));
+ consumeState(Stream.value(Left(GetEmailContentFailure(uiState.exception))));
+ } else {
+ emailContentsViewState.value = Right(UIState.idle);
+ }
+ }
+ break;
+ case EmailActionType.composeFromUnsubscribeMailtoLink:
+ final successState = GetEmailContentSuccess(htmlEmailContent: arguments.body ?? '');
+ if (PlatformInfo.isWeb) {
+ setTextEditorWeb(successState.htmlEmailContent);
+ }
+ emailContentsViewState.value = Right(successState);
+ break;
+ default:
+ emailContentsViewState.value = Right(UIState.idle);
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_important_flag_extension.dart b/lib/features/composer/presentation/extensions/setup_email_important_flag_extension.dart
new file mode 100644
index 000000000..c3921306b
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_important_flag_extension.dart
@@ -0,0 +1,21 @@
+
+import 'package:model/email/email_action_type.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+
+extension SetupEmailImportantFlagExtension on ComposerController {
+
+ void setupEmailImportantFlag(ComposerArguments arguments) {
+ switch(currentEmailActionType) {
+ case EmailActionType.editAsNewEmail:
+ case EmailActionType.editDraft:
+ isMarkAsImportant.value = arguments.presentationEmail?.isMarkAsImportant ?? false;
+ break;
+ case EmailActionType.reopenComposerBrowser:
+ isMarkAsImportant.value = arguments.isMarkAsImportant ?? false;
+ break;
+ default:
+ break;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_other_components_extension.dart b/lib/features/composer/presentation/extensions/setup_email_other_components_extension.dart
new file mode 100644
index 000000000..ab321761b
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_other_components_extension.dart
@@ -0,0 +1,25 @@
+
+import 'package:model/email/email_action_type.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+
+extension SetupEmailOtherComponentsExtension on ComposerController {
+
+ void setupEmailOtherComponents(ComposerArguments arguments) {
+ switch(currentEmailActionType) {
+ case EmailActionType.editDraft:
+ emailIdEditing = arguments.presentationEmail?.id;
+ break;
+ case EmailActionType.editSendingEmail:
+ emailIdEditing = arguments.sendingEmail?.presentationEmail.id;
+ break;
+ case EmailActionType.reopenComposerBrowser:
+ screenDisplayMode.value = arguments.displayMode;
+ break;
+ default:
+ break;
+ }
+
+ minInputLengthAutocomplete = mailboxDashBoardController.minInputLengthAutocomplete;
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_recipients_extension.dart b/lib/features/composer/presentation/extensions/setup_email_recipients_extension.dart
new file mode 100644
index 000000000..03dc63bca
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_recipients_extension.dart
@@ -0,0 +1,73 @@
+
+import 'package:model/email/email_action_type.dart';
+import 'package:model/mailbox/expand_mode.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/composer/presentation/model/prefix_recipient_state.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+
+extension SetupEmailRecipientsExtension on ComposerController {
+
+ void setupEmailRecipients(ComposerArguments arguments) {
+ switch(currentEmailActionType) {
+ case EmailActionType.editAsNewEmail:
+ case EmailActionType.editDraft:
+ case EmailActionType.reopenComposerBrowser:
+ initEmailAddress(
+ presentationEmail: arguments.presentationEmail!,
+ actionType: currentEmailActionType!,
+ );
+ break;
+ case EmailActionType.editSendingEmail:
+ initEmailAddress(
+ presentationEmail: arguments.sendingEmail!.presentationEmail,
+ actionType: currentEmailActionType!,
+ );
+ break;
+ case EmailActionType.composeFromEmailAddress:
+ case EmailActionType.composeFromUnsubscribeMailtoLink:
+ final emailAddressOfTo = arguments.listEmailAddress ?? [];
+ if (emailAddressOfTo.isNotEmpty) {
+ listToEmailAddress.addAll(emailAddressOfTo);
+ isInitialRecipient.value = true;
+ toAddressExpandMode.value = ExpandMode.COLLAPSE;
+ }
+ break;
+ case EmailActionType.composeFromMailtoUri:
+ final emailAddressOfTo = arguments.listEmailAddress ?? [];
+ final emailAddressOfCc = arguments.cc ?? [];
+ final emailAddressOfBc = arguments.bcc ?? [];
+
+ if (emailAddressOfTo.isNotEmpty) {
+ listToEmailAddress.addAll(emailAddressOfTo);
+ isInitialRecipient.value = true;
+ toAddressExpandMode.value = ExpandMode.COLLAPSE;
+ }
+
+ if (emailAddressOfCc.isNotEmpty) {
+ listCcEmailAddress = emailAddressOfCc;
+ ccRecipientState.value = PrefixRecipientState.enabled;
+ ccAddressExpandMode.value = ExpandMode.COLLAPSE;
+ }
+
+ if (emailAddressOfBc.isNotEmpty) {
+ listBccEmailAddress = emailAddressOfBc;
+ bccRecipientState.value = PrefixRecipientState.enabled;
+ bccAddressExpandMode.value = ExpandMode.COLLAPSE;
+ }
+ break;
+ case EmailActionType.reply:
+ case EmailActionType.replyToList:
+ case EmailActionType.replyAll:
+ initEmailAddress(
+ presentationEmail: arguments.presentationEmail!,
+ actionType: currentEmailActionType!,
+ listPost: arguments.listPost,
+ );
+ break;
+ default:
+ break;
+ }
+
+ updateStatusEmailSendButton();
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dart b/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dart
new file mode 100644
index 000000000..44a62ea11
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_extension.dart
@@ -0,0 +1,31 @@
+
+import 'package:model/email/email_action_type.dart';
+import 'package:server_settings/server_settings/tmail_server_settings_extension.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+import 'package:tmail_ui_user/features/server_settings/domain/state/get_server_setting_state.dart';
+
+extension SetupEmailRequestReadReceiptFlagExtension on ComposerController {
+
+ void setupEmailRequestReadReceiptFlag(ComposerArguments arguments) {
+ if (currentEmailActionType == EmailActionType.reopenComposerBrowser) {
+ hasRequestReadReceipt.value = arguments.hasRequestReadReceipt ?? false;
+ } else if (currentEmailActionType != EmailActionType.editDraft) {
+ getServerSetting();
+ }
+ }
+
+ Future getServerSetting() async {
+ final accountId = mailboxDashBoardController.accountId.value;
+ if (accountId == null) return;
+
+ final resultState = await getServerSettingInteractor.execute(accountId).last;
+
+ final uiState = resultState.fold((failure) => failure, (success) => success);
+
+ if (uiState is GetServerSettingSuccess) {
+ hasRequestReadReceipt.value = uiState.settingOption.isAlwaysReadReceipts;
+ initEmailDraftHash();
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_for_edit_draft_extension.dart b/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_for_edit_draft_extension.dart
new file mode 100644
index 000000000..ae0eeb169
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_request_read_receipt_flag_for_edit_draft_extension.dart
@@ -0,0 +1,9 @@
+
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+
+extension SetupEmailRequestReadReceiptFlagExtension on ComposerController {
+
+ void setupEmailRequestReadReceiptFlagForEditDraft(bool isRequestReadReceipt) {
+ hasRequestReadReceipt.value = isRequestReadReceipt;
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_email_subject_extension.dart b/lib/features/composer/presentation/extensions/setup_email_subject_extension.dart
new file mode 100644
index 000000000..ff5a8c505
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_email_subject_extension.dart
@@ -0,0 +1,44 @@
+
+import 'package:model/email/email_action_type.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+import 'package:tmail_ui_user/main/routes/route_navigation.dart';
+
+extension SetupEmailSubjectExtension on ComposerController {
+
+ void setupEmailSubject(ComposerArguments arguments) {
+ String subject = '';
+
+ switch(currentEmailActionType!) {
+ case EmailActionType.editAsNewEmail:
+ case EmailActionType.editDraft:
+ case EmailActionType.reply:
+ case EmailActionType.replyToList:
+ case EmailActionType.replyAll:
+ case EmailActionType.forward:
+ case EmailActionType.reopenComposerBrowser:
+ subject = arguments.presentationEmail!.getEmailTitle().trim();
+ break;
+ case EmailActionType.editSendingEmail:
+ subject = arguments.sendingEmail!.presentationEmail.getEmailTitle().trim();
+ break;
+ case EmailActionType.composeFromMailtoUri:
+ case EmailActionType.composeFromUnsubscribeMailtoLink:
+ subject = arguments.subject ?? '';
+ break;
+ default:
+ break;
+ }
+
+ final newSubject = currentEmailActionType!.getSubjectComposer(
+ currentContext,
+ subject,
+ );
+
+ if (newSubject.isNotEmpty) {
+ setSubjectEmail(subject);
+ subjectEmailInputController.text = subject;
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_list_identities_extension.dart b/lib/features/composer/presentation/extensions/setup_list_identities_extension.dart
new file mode 100644
index 000000000..1326cf30d
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_list_identities_extension.dart
@@ -0,0 +1,41 @@
+
+import 'package:dartz/dartz.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+import 'package:tmail_ui_user/features/composer/presentation/extensions/list_identities_extension.dart';
+import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
+import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
+
+extension SetupListIdentitiesExtension on ComposerController {
+
+ Future setupListIdentities(ComposerArguments arguments) async {
+ final identities = arguments.identities ?? [];
+ if (identities.isEmpty) {
+ await getAllIdentitiesAsSynchronize();
+ } else {
+ listFromIdentities.value = identities;
+ }
+ }
+
+ Future getAllIdentitiesAsSynchronize() async {
+ final accountId = mailboxDashBoardController.accountId.value;
+ final session = mailboxDashBoardController.sessionCurrent;
+
+ if (accountId == null || session == null) return;
+
+ final resultState = await getAllIdentitiesInteractor.execute(
+ session,
+ accountId,
+ ).last;
+
+ final uiState = resultState.fold((failure) => failure, (success) => success);
+
+ if (uiState is GetAllIdentitiesSuccess) {
+ final identitiesMayDeleted = uiState.identities?.toListMayDeleted() ?? [];
+ if (identitiesMayDeleted.isNotEmpty) {
+ listFromIdentities.value = identitiesMayDeleted;
+ }
+ } else if (uiState is GetAllIdentitiesFailure) {
+ consumeState(Stream.value(Left(uiState)));
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/extensions/setup_selected_identity_extension.dart b/lib/features/composer/presentation/extensions/setup_selected_identity_extension.dart
new file mode 100644
index 000000000..5203ec22c
--- /dev/null
+++ b/lib/features/composer/presentation/extensions/setup_selected_identity_extension.dart
@@ -0,0 +1,72 @@
+
+import 'package:collection/collection.dart';
+import 'package:core/core.dart';
+import 'package:jmap_dart_client/jmap/identities/identity.dart';
+import 'package:model/email/email_action_type.dart';
+import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
+
+extension SetupSelectedIdentityExtension on ComposerController {
+
+ Future setupSelectedIdentity() async {
+ if (identitySelected.value != null) {
+ if (PlatformInfo.isMobile && currentEmailActionType == EmailActionType.editDraft) {
+ await selectIdentity(identitySelected.value);
+ onCompleteSetupComposer();
+ }
+ return;
+ }
+
+ if (listFromIdentities.isEmpty &&
+ currentEmailActionType == EmailActionType.editSendingEmail) {
+ final signatureContent = await htmlEditorApi?.getSignatureContent();
+ if (signatureContent?.trim().isNotEmpty == true) {
+ await applySignature(signatureContent!);
+ }
+ }
+
+ if (listFromIdentities.isNotEmpty) {
+ final currentIdentity = _findIdentityById(
+ composerArguments.value?.selectedIdentityId,
+ ) ?? listFromIdentities.first;
+
+ if (currentEmailActionType == EmailActionType.editDraft ||
+ currentEmailActionType == EmailActionType.reopenComposerBrowser &&
+ savedActionType == EmailActionType.editDraft) {
+ identitySelected.value = currentIdentity;
+ } else if (currentEmailActionType == EmailActionType.editAsNewEmail) {
+ identitySelected.value = currentIdentity;
+ await selectIdentity(currentIdentity);
+ } else {
+ await selectIdentity(currentIdentity);
+ }
+ }
+
+ onCompleteSetupComposer();
+ }
+
+ Future setupSelectedIdentityWithoutApplySignature() async {
+ if (identitySelected.value == null && listFromIdentities.isNotEmpty) {
+ final currentIdentity = _findIdentityById(
+ composerArguments.value?.selectedIdentityId,
+ ) ?? listFromIdentities.first;
+
+ identitySelected.value = currentIdentity;
+ }
+
+ onCompleteSetupComposer();
+ }
+
+ void setupSelectedIdentityForEditDraft(IdentityId? identityId) {
+ if (identityId == null) return;
+
+ if (listFromIdentities.isNotEmpty) {
+ final currentIdentity = _findIdentityById(identityId);
+ identitySelected.value = currentIdentity;
+ }
+ }
+
+ Identity? _findIdentityById(IdentityId? identityId) {
+ return listFromIdentities
+ .firstWhereOrNull((identity) => identity.id == identityId);
+ }
+}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/model/create_email_request.dart b/lib/features/composer/presentation/model/create_email_request.dart
index a4236833d..2ffb786d5 100644
--- a/lib/features/composer/presentation/model/create_email_request.dart
+++ b/lib/features/composer/presentation/model/create_email_request.dart
@@ -41,6 +41,9 @@ class CreateEmailRequest with EquatableMixin {
final Uri? uploadUri;
final int? composerIndex;
final String? composerId;
+ final int? savedDraftHash;
+ final EmailActionType? savedActionType;
+ final EmailId? savedEmailDraftId;
CreateEmailRequest({
required this.session,
@@ -71,6 +74,9 @@ class CreateEmailRequest with EquatableMixin {
this.uploadUri,
this.composerIndex,
this.composerId,
+ this.savedDraftHash,
+ this.savedActionType,
+ this.savedEmailDraftId,
});
@override
@@ -103,5 +109,8 @@ class CreateEmailRequest with EquatableMixin {
uploadUri,
composerIndex,
composerId,
+ savedDraftHash,
+ savedActionType,
+ savedEmailDraftId,
];
}
\ No newline at end of file
diff --git a/lib/features/composer/presentation/model/saved_email_draft.dart b/lib/features/composer/presentation/model/saved_email_draft.dart
index a0ed39604..ec7b7845d 100644
--- a/lib/features/composer/presentation/model/saved_email_draft.dart
+++ b/lib/features/composer/presentation/model/saved_email_draft.dart
@@ -1,8 +1,14 @@
+import 'dart:convert';
+
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/jmap/identities/identity.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_address.dart';
+import 'package:json_annotation/json_annotation.dart';
import 'package:model/email/attachment.dart';
+part 'saved_email_draft.g.dart';
+
+@JsonSerializable(includeIfNull: false, explicitToJson: true)
class SavedEmailDraft with EquatableMixin {
final String content;
final String subject;
@@ -28,29 +34,20 @@ class SavedEmailDraft with EquatableMixin {
this.isMarkAsImportant = false,
});
- factory SavedEmailDraft.empty() {
- return SavedEmailDraft(
- subject: '',
- content: '',
- toRecipients: {},
- ccRecipients: {},
- bccRecipients: {},
- replyToRecipients: {},
- attachments: [],
- identity: null,
- hasReadReceipt: false,
- );
- }
+ factory SavedEmailDraft.fromJson(Map json) => _$SavedEmailDraftFromJson(json);
+
+ Map toJson() => _$SavedEmailDraftToJson(this);
+
+ String asString() => jsonEncode(toJson());
@override
List