Hot fix after some reload minimized composer should be lost body

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2025-03-31 10:01:25 +07:00
committed by Dat H. Pham
parent 89a3743eaf
commit 4e0e92c35b
34 changed files with 1144 additions and 1902 deletions
@@ -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<void> 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');
}
}
}
@@ -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<Attachment>? attachments;
List<Attachment>? 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<SharedMediaFile> 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),
);
}
}
@@ -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<void> 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;
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
@@ -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();
}
}
@@ -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<void> 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();
}
}
}
@@ -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;
}
}
@@ -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;
}
}
}
@@ -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<void> setupListIdentities(ComposerArguments arguments) async {
final identities = arguments.identities ?? [];
if (identities.isEmpty) {
await getAllIdentitiesAsSynchronize();
} else {
listFromIdentities.value = identities;
}
}
Future<void> 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)));
}
}
}
@@ -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<void> 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<void> 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);
}
}