TF-2667 Handle save message to cache when reload page

This commit is contained in:
dab246
2024-03-06 03:42:45 +07:00
committed by Dat H. Pham
parent b58a0179f9
commit ae6341532c
9 changed files with 72 additions and 229 deletions
@@ -22,7 +22,6 @@ class ConfirmDialogBuilder {
TextStyle? _styleTitle;
TextStyle? _styleContent;
double? _radiusButton;
double? heightButton;
EdgeInsetsGeometry? _paddingTitle;
EdgeInsets? _paddingContent;
EdgeInsets? _paddingButton;
@@ -46,7 +45,6 @@ class ConfirmDialogBuilder {
{
this.showAsBottomSheet = false,
this.listTextSpan,
this.heightButton,
this.maxWith = double.infinity,
}
);
@@ -240,7 +238,6 @@ class ConfirmDialogBuilder {
name: _cancelText,
bgColor: _colorCancelButton,
radius: _radiusButton,
height: heightButton,
textStyle: _styleTextCancelButton,
action: _onCancelButtonAction)),
if (_confirmText.isNotEmpty && _cancelText.isNotEmpty) const SizedBox(width: 16),
@@ -249,7 +246,6 @@ class ConfirmDialogBuilder {
name: _confirmText,
bgColor: _colorConfirmButton,
radius: _radiusButton,
height: heightButton,
textStyle: _styleTextConfirmButton,
action: _onConfirmButtonAction))
]
@@ -264,12 +260,10 @@ class ConfirmDialogBuilder {
TextStyle? textStyle,
Color? bgColor,
double? radius,
double? height,
Function? action
}) {
return SizedBox(
width: double.infinity,
height: height ?? 48,
child: ElevatedButton(
onPressed: () => action?.call(),
style: ButtonStyle(
@@ -281,8 +275,7 @@ class ConfirmDialogBuilder {
borderRadius: BorderRadius.circular(radius ?? 8),
side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton),
)),
padding: MaterialStateProperty.resolveWith<EdgeInsets>(
(Set<MaterialState> states) => const EdgeInsets.symmetric(horizontal: 16)),
padding: MaterialStateProperty.resolveWith<EdgeInsets>((Set<MaterialState> states) => const EdgeInsets.all(8)),
elevation: MaterialStateProperty.resolveWith<double>((Set<MaterialState> states) => 0)),
child: Text(name ?? '',
textAlign: TextAlign.center,
@@ -38,7 +38,7 @@ mixin MessageDialogActionMixin {
if (alignCenter) {
return await Get.dialog(
PointerInterceptor(
child: (ConfirmDialogBuilder(imagePaths, listTextSpan: listTextSpan, heightButton: 44)
child: (ConfirmDialogBuilder(imagePaths, listTextSpan: listTextSpan)
..key(const Key('confirm_dialog_action'))
..title(title ?? '')
..content(message)
@@ -0,0 +1,27 @@
import 'package:core/presentation/state/failure.dart';
import 'package:core/presentation/state/success.dart';
import 'package:dartz/dartz.dart';
import 'package:tmail_ui_user/features/composer/domain/repository/composer_repository.dart';
import 'package:tmail_ui_user/features/composer/presentation/model/create_email_request.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/save_composer_cache_state.dart';
class SaveComposerCacheOnWebInteractor {
final ComposerCacheRepository _composerCacheRepository;
final ComposerRepository _composerRepository;
SaveComposerCacheOnWebInteractor(
this._composerCacheRepository,
this._composerRepository,
);
Future<Either<Failure, Success>> execute(CreateEmailRequest createEmailRequest) async {
try {
final emailCreated = await _composerRepository.generateEmail(createEmailRequest);
_composerCacheRepository.saveComposerCacheOnWeb(emailCreated);
return Right(SaveComposerCacheSuccess());
} catch (exception) {
return Left(SaveComposerCacheFailure(exception));
}
}
}
@@ -13,6 +13,7 @@ import 'package:tmail_ui_user/features/composer/domain/repository/contact_reposi
import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_save_email_to_drafts_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/create_new_and_send_email_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_as_base64_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/upload_attachment_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
@@ -43,7 +44,6 @@ import 'package:tmail_ui_user/features/mailbox/data/repository/mailbox_repositor
import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/get_all_identities_interactor.dart';
import 'package:tmail_ui_user/features/manage_account/presentation/profiles/identities/identity_interactors_bindings.dart';
import 'package:tmail_ui_user/features/offline_mode/manager/new_email_cache_manager.dart';
@@ -191,7 +191,10 @@ class ComposerBindings extends BaseBindings {
Get.lazyPut(() => UploadAttachmentInteractor(Get.find<ComposerRepository>()));
Get.lazyPut(() => GetEmailContentInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(
Get.find<ComposerCacheRepository>(),
Get.find<ComposerRepository>(),
));
Get.lazyPut(() => DownloadImageAsBase64Interactor(Get.find<ComposerRepository>()));
Get.lazyPut(() => TransformHtmlEmailContentInteractor(Get.find<EmailRepository>()));
Get.lazyPut(() => GetAlwaysReadReceiptSettingInteractor(Get.find<ServerSettingsRepository>()));
@@ -16,15 +16,9 @@ import 'package:flutter/services.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:get/get.dart';
import 'package:html_editor_enhanced/html_editor.dart' as web_html_editor;
import 'package:http_parser/http_parser.dart';
import 'package:jmap_dart_client/jmap/core/user_name.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/email_body_part.dart';
import 'package:jmap_dart_client/jmap/mail/email/email_body_value.dart';
import 'package:jmap_dart_client/jmap/mail/email/individual_header_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart';
import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart';
import 'package:model/model.dart';
import 'package:permission_handler/permission_handler.dart';
@@ -49,6 +43,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/download_image_a
import 'package:tmail_ui_user/features/composer/domain/usecases/get_all_autocomplete_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_autocomplete_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/get_device_contact_suggestions_interactor.dart';
import 'package:tmail_ui_user/features/composer/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_mobile_tablet_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/controller/rich_text_web_controller.dart';
import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart';
@@ -72,7 +67,6 @@ import 'package:tmail_ui_user/features/email/domain/usecases/get_email_content_i
import 'package:tmail_ui_user/features/email/domain/usecases/transform_html_email_content_interactor.dart';
import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_composer_cache_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/model/draggable_app_state.dart';
import 'package:tmail_ui_user/features/manage_account/domain/state/get_all_identities_state.dart';
@@ -343,14 +337,43 @@ class ComposerController extends BaseController with DragDropFileMixin {
}
void _listenBrowserTabRefresh() {
_subscriptionOnBeforeUnload = html.window.onBeforeUnload.listen((event) async {
_removeComposerCacheOnWebInteractor.execute();
if (mailboxDashBoardController.sessionCurrent != null) {
final draftEmail = await _generateEmail(
currentContext!,
mailboxDashBoardController.sessionCurrent!.username);
_saveComposerCacheOnWebInteractor.execute(draftEmail);
_subscriptionOnBeforeUnload = html.window.onBeforeUnload.listen((event) async {
await _removeComposerCacheOnWebInteractor.execute();
if (composerArguments.value == null ||
mailboxDashBoardController.sessionCurrent == null ||
mailboxDashBoardController.accountId.value == null
) {
log('ComposerController::_listenBrowserTabRefresh: SESSION or ACCOUNT_ID or ARGUMENTS is NULL');
return;
}
final emailContent = await _getContentInEditor();
await _saveComposerCacheOnWebInteractor.execute(CreateEmailRequest(
session: mailboxDashBoardController.sessionCurrent!,
accountId: mailboxDashBoardController.accountId.value!,
emailActionType: composerArguments.value!.emailActionType,
subject: subjectEmail.value ?? '',
emailContent: emailContent,
fromSender: composerArguments.value!.presentationEmail?.from ?? {},
toRecipients: listToEmailAddress.toSet(),
ccRecipients: listCcEmailAddress.toSet(),
bccRecipients: listBccEmailAddress.toSet(),
isRequestReadReceipt: hasRequestReadReceipt.value,
identity: identitySelected.value,
attachments: uploadController.attachmentsUploaded,
inlineAttachments: uploadController.mapInlineAttachments,
outboxMailboxId: mailboxDashBoardController.outboxMailbox?.mailboxId,
sentMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleSent],
draftsMailboxId: mailboxDashBoardController.mapDefaultMailboxIdByRole[PresentationMailbox.roleDrafts],
draftsEmailId: _getDraftEmailId(),
answerForwardEmailId: composerArguments.value!.presentationEmail?.id,
unsubscribeEmailId: composerArguments.value!.previousEmailId,
messageId: composerArguments.value!.messageId,
references: composerArguments.value!.references,
emailSendingQueue: composerArguments.value!.sendingEmail
));
});
_subscriptionOnDragEnter = html.window.onDragEnter.listen((event) {
@@ -666,128 +689,6 @@ class ComposerController extends BaseController with DragDropFileMixin {
}
}
Future<Email> _generateEmail(
BuildContext context,
UserName userName,
{
bool asDrafts = false,
MailboxId? draftMailboxId,
MailboxId? outboxMailboxId,
ComposerArguments? arguments,
}
) async {
Set<EmailAddress> listFromEmailAddress = {EmailAddress(null, userName.value)};
if (identitySelected.value?.email?.isNotEmpty == true) {
listFromEmailAddress = {
EmailAddress(
identitySelected.value?.name,
identitySelected.value?.email
)
};
}
Set<EmailAddress> listReplyToEmailAddress = {EmailAddress(null, userName.value)};
if (identitySelected.value?.replyTo?.isNotEmpty == true) {
listReplyToEmailAddress = identitySelected.value!.replyTo!;
}
final attachments = <EmailBodyPart>{};
attachments.addAll(uploadController.generateAttachments() ?? []);
var emailBodyText = await _getEmailBodyText(context, asDrafts: asDrafts);
if (uploadController.mapInlineAttachments.isNotEmpty) {
final mapContents = await _getMapContent(emailBodyText);
emailBodyText = mapContents.value1;
final listInlineAttachment = mapContents.value2;
final listInlineEmailBodyPart = listInlineAttachment
.map((attachment) => attachment.toEmailBodyPart(charset: 'base64'))
.toSet();
attachments.addAll(listInlineEmailBodyPart);
}
final userAgent = await applicationManager.getUserAgent();
log('ComposerController::_generateEmail(): userAgent: $userAgent');
Map<MailboxId, bool> mailboxIds = {};
if (asDrafts && draftMailboxId != null) {
mailboxIds[draftMailboxId] = true;
}
if (outboxMailboxId != null) {
mailboxIds[outboxMailboxId] = true;
}
Map<KeyWordIdentifier, bool>? mapKeywords = {};
if (asDrafts) {
mapKeywords[KeyWordIdentifier.emailDraft] = true;
mapKeywords[KeyWordIdentifier.emailSeen] = true;
}
final inReplyTo = _generateInReplyTo(arguments);
final references = _generateReferences(arguments);
final generatePartId = PartId(uuid.v1());
return Email(
mailboxIds: mailboxIds.isNotEmpty ? mailboxIds : null,
from: listFromEmailAddress,
to: listToEmailAddress.toSet(),
cc: listCcEmailAddress.toSet(),
bcc: listBccEmailAddress.toSet(),
replyTo: listReplyToEmailAddress,
inReplyTo: inReplyTo,
references: references,
keywords: mapKeywords.isNotEmpty ? mapKeywords : null,
subject: subjectEmail.value,
htmlBody: {
EmailBodyPart(
partId: generatePartId,
type: MediaType.parse('text/html')
)},
bodyValues: {
generatePartId: EmailBodyValue(emailBodyText, false, false)
},
attachments: attachments.isNotEmpty ? attachments : null,
headerMdn: hasRequestReadReceipt.value ? { IndividualHeaderIdentifier.headerMdn: getEmailAddressSender() } : {},
);
}
MessageIdsHeaderValue? _generateInReplyTo(ComposerArguments? arguments) {
if (arguments?.emailActionType == EmailActionType.reply ||
arguments?.emailActionType == EmailActionType.replyAll) {
return arguments?.messageId;
}
return null;
}
MessageIdsHeaderValue? _generateReferences(ComposerArguments? arguments) {
if (arguments?.emailActionType == EmailActionType.reply ||
arguments?.emailActionType == EmailActionType.replyAll ||
arguments?.emailActionType == EmailActionType.forward) {
Set<String> ids = {};
if (arguments?.messageId?.ids.isNotEmpty == true) {
ids.addAll(arguments!.messageId!.ids);
}
if (arguments?.references?.ids.isNotEmpty == true) {
ids.addAll(arguments!.references!.ids);
}
if (ids.isNotEmpty) {
return MessageIdsHeaderValue(ids);
}
}
return null;
}
Future<Tuple2<String, List<Attachment>>> _getMapContent(String emailBodyText) async {
if (kIsWeb) {
return await richTextWebController.refactorContentHasInlineImage(
emailBodyText,
uploadController.mapInlineAttachments);
} else {
return await richTextMobileTabletController.refactorContentHasInlineImage(
emailBodyText,
uploadController.mapInlineAttachments);
}
}
void handleClickSendButton(BuildContext context) async {
if (_sendButtonState == ButtonState.disabled) {
log('ComposerController::handleClickSendButton: SENDING EMAIL');
@@ -1897,32 +1798,6 @@ class ComposerController extends BaseController with DragDropFileMixin {
void setSubjectEmail(String subject) => subjectEmail.value = subject;
Future<String> _getEmailBodyText(BuildContext context, {bool asDrafts = false}) async {
var contentHtml = '';
if (PlatformInfo.isWeb) {
if (responsiveUtils.isDesktop(context) &&
screenDisplayMode.value == ScreenDisplayMode.minimize) {
contentHtml = _textEditorWeb ?? '';
} else {
if (asDrafts) {
contentHtml = await richTextWebController.editorController.getText();
} else {
contentHtml = await richTextWebController.editorController.getTextWithSignatureContent();
}
}
} else {
if (asDrafts) {
contentHtml = (await htmlEditorApi?.getText()) ?? '';
} else {
contentHtml = (await htmlEditorApi?.getTextWithSignatureContent()) ?? '';
}
}
final newContentHtml = contentHtml.removeEditorStartTag();
return newContentHtml;
}
void removeDraggableEmailAddress(DraggableEmailAddress draggableEmailAddress) {
log('ComposerController::removeDraggableEmailAddress: $draggableEmailAddress');
switch(draggableEmailAddress.prefix) {
@@ -1,12 +1,7 @@
import 'package:collection/collection.dart';
import 'package:core/presentation/views/dialog/color_picker_dialog_builder.dart';
import 'package:core/utils/app_logger.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:html/parser.dart' show parse;
import 'package:model/email/attachment.dart';
import 'package:tmail_ui_user/main/localizations/app_localizations.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
@@ -37,32 +32,4 @@ abstract class BaseRichTextController extends GetxController {
}
).show();
}
Future<Tuple2<String, List<Attachment>>> refactorContentHasInlineImage(
String emailContent,
Map<String, Attachment> mapInlineAttachments
) async {
final document = parse(emailContent);
final listImgTag = document.querySelectorAll('img[src^="data:image/"][id^="cid:"]');
final listInlineAttachment = await Future.wait(listImgTag.map((imgTag) async {
final idImg = imgTag.attributes['id'];
final cid = idImg!.replaceFirst('cid:', '').trim();
log('BaseRichTextController::refactorContentHasInlineImage(): $cid');
imgTag.attributes['src'] = 'cid:$cid';
imgTag.attributes.remove('id');
return cid;
})).then((listCid) {
log('BaseRichTextController::refactorContentHasInlineImage(): $listCid');
final listInlineAttachment = listCid
.whereNotNull()
.map((cid) => mapInlineAttachments[cid])
.whereNotNull()
.toList();
return listInlineAttachment;
});
final newContent = document.body?.innerHtml ?? emailContent;
log('BaseRichTextController::refactorContentHasInlineImage(): $newContent');
log('BaseRichTextController::refactorContentHasInlineImage(): listInlineAttachment: $listInlineAttachment');
return Tuple2(newContent, listInlineAttachment);
}
}
@@ -8,7 +8,7 @@ class RemoveComposerCacheOnWebInteractor {
RemoveComposerCacheOnWebInteractor(this.composerCacheRepository);
Either<Failure, Success> execute() {
Future<Either<Failure, Success>> execute() async {
try {
composerCacheRepository.removeComposerCacheOnWeb();
return Right(RemoveComposerCacheSuccess());
@@ -1,20 +0,0 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:jmap_dart_client/jmap/mail/email/email.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/repository/composer_cache_repository.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/state/save_composer_cache_state.dart';
class SaveComposerCacheOnWebInteractor {
final ComposerCacheRepository composerCacheRepository;
SaveComposerCacheOnWebInteractor(this.composerCacheRepository);
Either<Failure, Success> execute(Email email) {
try {
composerCacheRepository.saveComposerCacheOnWeb(email);
return Right(SaveComposerCacheSuccess());
} catch (exception) {
return Left(SaveComposerCacheFailure(exception));
}
}
}
@@ -69,7 +69,6 @@ import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/get_unr
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/quick_search_email_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/remove_email_drafts_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_composer_cache_on_web_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/save_recent_search_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_last_time_dismissed_spam_reported_interactor.dart';
import 'package:tmail_ui_user/features/mailbox_dashboard/domain/usecases/store_spam_report_state_interactor.dart';
@@ -276,7 +275,6 @@ class MailboxDashBoardBindings extends BaseBindings {
Get.find<EmailRepository>())
);
Get.lazyPut(() => GetComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => SaveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => RemoveComposerCacheOnWebInteractor(Get.find<ComposerCacheRepository>()));
Get.lazyPut(() => MarkAsEmailReadInteractor(
Get.find<EmailRepository>(),