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
@@ -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 {
+13
View File
@@ -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;
}
}
}
@@ -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<String> _removeCollapsedExpandedSignatureEffect({required String emailContent}) {
@override
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) {
try {
return _htmlDataSource.removeCollapsedExpandedSignatureEffect(emailContent: emailContent);
} catch (e) {
@@ -15,4 +15,6 @@ abstract class ComposerRepository {
Future<UploadAttachment> uploadAttachment(FileInfo fileInfo, Uri uploadUri, {CancelToken? cancelToken});
Future<String?> downloadImageAsBase64(String url, String cid, FileInfo fileInfo, {double? maxWidth, bool? compress});
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent});
}
@@ -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) {
@@ -329,6 +329,7 @@ class ComposerBindings extends BaseBindings {
Get.find<CreateNewAndSendEmailInteractor>(tag: composerId),
Get.find<CreateNewAndSaveEmailToDraftsInteractor>(tag: composerId),
Get.find<PrintEmailInteractor>(tag: composerId),
Get.find<ComposerRepository>(tag: composerId),
composerId: composerId,
composerArgs: composerArguments,
), tag: composerId);
File diff suppressed because it is too large Load Diff
@@ -127,9 +127,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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,
@@ -98,9 +98,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
base64: base64,
uploadError: uploadError
),
onInitialContentLoadComplete: controller.restoreCollapsibleButton,
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
)),
),
),
@@ -375,9 +369,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
base64: base64,
uploadError: uploadError
),
onInitialContentLoadComplete: controller.restoreCollapsibleButton,
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
);
}),
),
@@ -694,9 +682,7 @@ class ComposerView extends GetWidget<ComposerController> {
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<ComposerController> {
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<ComposerController> {
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<ComposerController> {
base64: base64,
uploadError: uploadError
),
onInitialContentLoadComplete: controller.restoreCollapsibleButton,
onInitialContentLoadComplete: controller.onInitialContentLoadCompleteWeb,
)),
),
),
@@ -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);
}
}
@@ -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,
];
}
@@ -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<String, dynamic> json) => _$SavedEmailDraftFromJson(json);
Map<String, dynamic> toJson() => _$SavedEmailDraftToJson(this);
String asString() => jsonEncode(toJson());
@override
List<Object?> get props => [
content,
subject,
// Prevent identical Set<EmailAddress>
{0: toRecipients},
{1: ccRecipients},
{2: bccRecipients},
{3: replyToRecipients},
toRecipients,
ccRecipients,
bccRecipients,
replyToRecipients,
attachments,
identity,
hasReadReceipt,
@@ -1,4 +0,0 @@
enum SignatureStatus {
editedAvailable,
editedApplied,
}
@@ -8,7 +8,6 @@ import 'package:model/email/email_action_type.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/editor_view_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/mobile/mobile_editor_widget.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/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -107,14 +106,14 @@ class MobileEditorView extends StatelessWidget with EditorViewMixin {
);
},
(success) {
if (success is TransformHtmlEmailContentLoading) {
if (success is GetEmailContentLoading) {
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else {
final emailContentQuoted = getEmailContentQuotedAsHtml(
locale: Localizations.localeOf(context),
appLocalizations: AppLocalizations.of(context),
emailContent: success is TransformHtmlEmailContentSuccess
? success.htmlContent
emailContent: success is GetEmailContentSuccess
? success.htmlEmailContent
: '',
emailActionType: arguments!.emailActionType,
presentationEmail: arguments!.presentationEmail!
@@ -7,11 +7,9 @@ import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:html_editor_enhanced/html_editor.dart';
import 'package:model/email/email_action_type.dart';
import 'package:tmail_ui_user/features/composer/domain/state/restore_email_inline_images_state.dart';
import 'package:tmail_ui_user/features/composer/presentation/view/editor_view_mixin.dart';
import 'package:tmail_ui_user/features/composer/presentation/widgets/web/web_editor_widget.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/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/utils/app_utils.dart';
@@ -115,12 +113,15 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
onInitialContentLoadComplete: onInitialContentLoadComplete,
),
(success) {
if (success is GetEmailContentLoading || success is RestoringEmailInlineImages) {
if (success is GetEmailContentLoading) {
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else {
var newContent = success is GetEmailContentSuccess
? success.htmlEmailContent
: HtmlExtension.editorStartTags;
var newContent = '';
if (success is GetEmailContentSuccess) {
newContent = success.htmlEmailContent;
} else if (success is GetEmailContentFromCacheSuccess) {
newContent = success.htmlEmailContent;
}
if (newContent.isEmpty) {
newContent = HtmlExtension.editorStartTags;
}
@@ -181,14 +182,14 @@ class WebEditorView extends StatelessWidget with EditorViewMixin {
);
},
(success) {
if (success is TransformHtmlEmailContentLoading) {
if (success is GetEmailContentLoading) {
return const CupertinoLoadingWidget(padding: EdgeInsets.all(16.0));
} else {
final emailContentQuoted = getEmailContentQuotedAsHtml(
locale: Localizations.localeOf(context),
appLocalizations: AppLocalizations.of(context),
emailContent: success is TransformHtmlEmailContentSuccess
? success.htmlContent
emailContent: success is GetEmailContentSuccess
? success.htmlEmailContent
: '',
emailActionType: arguments!.emailActionType,
presentationEmail: arguments!.presentationEmail!
@@ -190,23 +190,24 @@ class HtmlAnalyzer {
}
Future<String> removeCollapsedExpandedSignatureEffect({required String emailContent}) async {
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: BEFORE = $emailContent');
final document = parse(emailContent);
final signatureElements = document.querySelectorAll('div.tmail-signature');
await Future.wait(signatureElements.map((signatureTag) async {
final signatureChildren = signatureTag.children;
for (var child in signatureChildren) {
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: CHILD = ${child.outerHtml}');
if (child.attributes['class']?.contains('tmail-signature-button') == true) {
child.remove();
} else if (child.attributes['class']?.contains('tmail-signature-content') == true) {
signatureTag.innerHtml = child.innerHtml;
try {
final document = parse(emailContent);
final signatureElements = document.querySelectorAll('div.tmail-signature');
await Future.wait(signatureElements.map((signatureTag) async {
final signatureChildren = signatureTag.children;
for (var child in signatureChildren) {
if (child.attributes['class']?.contains('tmail-signature-button') == true) {
child.remove();
} else if (child.attributes['class']?.contains('tmail-signature-content') == true) {
signatureTag.innerHtml = child.innerHtml;
}
}
}
}));
final newContent = document.body?.innerHtml ?? emailContent;
log('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect: AFTER = $newContent');
return newContent;
}));
return document.body?.innerHtml ?? emailContent;
} catch (e) {
logError('HtmlAnalyzer::removeCollapsedExpandedSignatureEffect:Exception = $e');
return emailContent;
}
}
Future<(Attachment attachment, String taskId)?> _retrieveAttachmentFromUpload({
@@ -31,13 +31,13 @@ class GetEmailContentFromCacheSuccess extends UIState {
final String htmlEmailContent;
final List<Attachment>? attachments;
final List<Attachment>? inlineImages;
final Email? emailCurrent;
final Email emailCurrent;
GetEmailContentFromCacheSuccess({
required this.htmlEmailContent,
required this.emailCurrent,
this.attachments,
this.inlineImages,
this.emailCurrent
});
@override
@@ -619,7 +619,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) {
emailSupervisorController.popFirstEmailQueue();
}
emailSupervisorController.popEmailQueue(success.emailCurrent?.id);
emailSupervisorController.popEmailQueue(success.emailCurrent.id);
currentEmailLoaded.value = EmailLoaded(
htmlContent: success.htmlEmailContent,
@@ -629,7 +629,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
);
emailSupervisorController.pushEmailQueue(currentEmailLoaded.value!);
if (success.emailCurrent?.id == currentEmail?.id) {
if (success.emailCurrent.id == currentEmail?.id) {
attachments.value = success.attachments ?? [];
attachmentsViewState.value = {
for (var attachment in attachments.where((item) => item.blobId != null))
@@ -645,13 +645,13 @@ class SingleEmailController extends BaseController with AppLoaderMixin {
emailContents.value = success.htmlEmailContent;
}
final isShowMessageReadReceipt = success.emailCurrent?.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true;
final isShowMessageReadReceipt = success.emailCurrent.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true;
if (isShowMessageReadReceipt) {
_handleReadReceipt();
}
if (currentEmail?.isSubscribed == false && success.emailCurrent?.hasListUnsubscribe == true) {
_handleUnsubscribe(success.emailCurrent!.listUnsubscribe);
if (currentEmail?.isSubscribed == false && success.emailCurrent.hasListUnsubscribe == true) {
_handleUnsubscribe(success.emailCurrent.listUnsubscribe);
} else {
emailUnsubscribe.value = null;
}
@@ -35,6 +35,9 @@ class ComposerArguments extends RouterArguments {
final List<EmailAddress>? bcc;
final String? listPost;
final String? composerId;
final int? savedDraftHash;
final EmailActionType? savedActionType;
final EmailId? savedEmailDraftId;
ComposerArguments({
this.emailActionType = EmailActionType.compose,
@@ -60,6 +63,9 @@ class ComposerArguments extends RouterArguments {
this.listPost,
this.isMarkAsImportant,
this.composerId,
this.savedDraftHash,
this.savedActionType,
this.savedEmailDraftId,
});
factory ComposerArguments.fromSendingEmail(SendingEmail sendingEmail) =>
@@ -125,6 +131,9 @@ class ComposerArguments extends RouterArguments {
displayMode: composerCache.displayMode,
isMarkAsImportant: composerCache.isMarkAsImportant,
composerId: composerCache.composerId,
savedDraftHash: composerCache.draftHash,
savedActionType: composerCache.actionType,
savedEmailDraftId: composerCache.draftEmailId,
);
factory ComposerArguments.replyEmail({
@@ -246,6 +255,9 @@ class ComposerArguments extends RouterArguments {
bcc,
listPost,
composerId,
savedDraftHash,
savedActionType,
savedEmailDraftId,
];
ComposerArguments copyWith({
@@ -272,6 +284,9 @@ class ComposerArguments extends RouterArguments {
List<EmailAddress>? bcc,
String? listPost,
String? composerId,
int? savedDraftHash,
EmailActionType? savedActionType,
EmailId? savedEmailDraftId,
}) {
return ComposerArguments(
emailActionType: emailActionType ?? this.emailActionType,
@@ -297,6 +312,9 @@ class ComposerArguments extends RouterArguments {
bcc: bcc ?? this.bcc,
listPost: listPost ?? this.listPost,
composerId: composerId ?? this.composerId,
savedDraftHash: savedDraftHash ?? this.savedDraftHash,
savedActionType: savedActionType ?? this.savedActionType,
savedEmailDraftId: savedEmailDraftId ?? this.savedEmailDraftId,
);
}
}
@@ -1,11 +1,19 @@
import 'package:equatable/equatable.dart';
import 'package:jmap_dart_client/http/converter/email_id_nullable_converter.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:model/email/email_action_type.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/screen_display_mode.dart';
part 'composer_cache.g.dart';
@JsonSerializable(explicitToJson: true, includeIfNull: false)
@JsonSerializable(
explicitToJson: true,
includeIfNull: false,
converters: [
EmailIdNullableConverter(),
]
)
class ComposerCache with EquatableMixin {
final Email? email;
@@ -14,6 +22,9 @@ class ComposerCache with EquatableMixin {
final ScreenDisplayMode displayMode;
final int? composerIndex;
final String? composerId;
final int? draftHash;
final EmailActionType? actionType;
final EmailId? draftEmailId;
ComposerCache({
this.email,
@@ -22,6 +33,9 @@ class ComposerCache with EquatableMixin {
this.displayMode = ScreenDisplayMode.normal,
this.composerIndex,
this.composerId,
this.draftHash,
this.actionType,
this.draftEmailId,
});
factory ComposerCache.fromJson(Map<String, dynamic> json) => _$ComposerCacheFromJson(json);
@@ -36,5 +50,8 @@ class ComposerCache with EquatableMixin {
displayMode,
composerIndex,
composerId,
draftHash,
actionType,
draftEmailId,
];
}
+21
View File
@@ -1,12 +1,29 @@
import 'package:equatable/equatable.dart';
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/http/converter/id_nullable_converter.dart';
import 'package:jmap_dart_client/http/converter/media_type_nullable_converter.dart';
import 'package:jmap_dart_client/http/converter/part_id_nullable_converter.dart';
import 'package:jmap_dart_client/http/converter/unsigned_int_nullable_converter.dart';
import 'package:jmap_dart_client/jmap/account_id.dart';
import 'package:jmap_dart_client/jmap/core/id.dart';
import 'package:jmap_dart_client/jmap/core/unsigned_int.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:uri/uri.dart';
part 'attachment.g.dart';
@JsonSerializable(
includeIfNull: false,
explicitToJson: true,
converters: [
PartIdNullableConverter(),
IdNullableConverter(),
UnsignedIntNullableConverter(),
MediaTypeNullableConverter(),
],
)
class Attachment with EquatableMixin {
static const String eventICSSubtype = 'ics';
@@ -62,6 +79,10 @@ class Attachment with EquatableMixin {
}
}
factory Attachment.fromJson(Map<String, dynamic> json) => _$AttachmentFromJson(json);
Map<String, dynamic> toJson() => _$AttachmentToJson(this);
@override
List<Object?> get props => [
partId,
+2 -26
View File
@@ -1,10 +1,8 @@
import 'package:core/utils/string_convert.dart';
import 'package:equatable/equatable.dart';
import 'package:model/email/email_content_type.dart';
class EmailContent with EquatableMixin {
static const defaultHtmlTagsWrapContent = '<html><head></head><body></body></html>';
final String content;
final EmailContentType type;
@@ -23,28 +21,6 @@ extension EmailContentExtension on EmailContent {
.replaceAll('\n', '<br/>')
.replaceAll('\n\n', '<br/>');
}
final newContent = _getContentOriginal(content);
return newContent;
}
String _getContentOriginal(String content) {
if (content == EmailContent.defaultHtmlTagsWrapContent ||
content.trim().isEmpty) {
return '';
}
const firstTags = '<html><head></head><body>';
const latestTags = '</body></html>';
if (content.startsWith(firstTags) && content.endsWith(latestTags)) {
const firstIndex = firstTags.length;
final latestIndex = content.length - latestTags.length;
if (latestIndex > firstIndex) {
final contentOriginal = content.substring(firstIndex, latestIndex);
return contentOriginal;
}
}
return content;
return StringConvert.getContentOriginal(content);
}
}
File diff suppressed because it is too large Load Diff
@@ -26,9 +26,9 @@ void main() {
final props = savedEmailDraft.props;
// assert
expect(props[2], equals({0: savedEmailDraft.toRecipients}));
expect(props[3], equals({1: savedEmailDraft.ccRecipients}));
expect(props[4], equals({2: savedEmailDraft.bccRecipients}));
expect(props[2], equals(savedEmailDraft.toRecipients));
expect(props[3], equals(savedEmailDraft.ccRecipients));
expect(props[4], equals(savedEmailDraft.bccRecipients));
});
test(
@@ -123,11 +123,11 @@ void main() {
attachments: [],
hasReadReceipt: false
);
final hashCodeBeforeChange = savedEmailDraft.hashCode;
final hashCodeBeforeChange = savedEmailDraft.asString().hashCode;
// act
listToRecipients.add(EmailAddress('to name 2', 'to email 2'));
final hashCodeAfterChange = savedEmailDraft.hashCode;
final hashCodeAfterChange = savedEmailDraft.asString().hashCode;
// assert
expect(hashCodeBeforeChange, isNot(hashCodeAfterChange));
@@ -163,7 +163,10 @@ void main() {
);
// assert
expect(savedEmailDraft.hashCode, equals(savedEmailDraft2.hashCode));
expect(
savedEmailDraft.asString().hashCode,
equals(savedEmailDraft2.asString().hashCode),
);
});
});
}