diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 9f1903e01..8777e53b8 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -1273,7 +1273,7 @@ class ComposerController extends BaseController if (composerArguments.value?.emailActionType == EmailActionType.compose || composerArguments.value?.emailActionType == EmailActionType.editDraft) { - _savedEmailDraftHash = await _hashDraftEmail(); + _savedEmailDraftHash = draftEmailHash; } } diff --git a/lib/features/composer/presentation/composer_view_web.dart b/lib/features/composer/presentation/composer_view_web.dart index 0535d5cb8..1f002e1b9 100644 --- a/lib/features/composer/presentation/composer_view_web.dart +++ b/lib/features/composer/presentation/composer_view_web.dart @@ -519,13 +519,18 @@ class ComposerView extends GetWidget { openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, attachFileAction: () => controller.openFilePickerByType(context, FileType.any), insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - showCodeViewAction: controller.richTextWebController!.toggleCodeView, deleteComposerAction: () => controller.handleClickDeleteComposer(context), saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), sendMessageAction: () => controller.handleClickSendButton(context), - requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), isPrintDraftEnabled: controller.isEmailChanged.isTrue, - onPrintDraftAction: () => controller.printDraft(context), + openContextMenuAction: (position) { + controller.openPopupMenuAction( + context, + position, + _createMoreOptionPopupItemsOnDesktop(context), + radius: ComposerStyle.popupMenuRadius, + ); + }, )), ], ), @@ -801,13 +806,18 @@ class ComposerView extends GetWidget { openRichToolbarAction: controller.richTextWebController!.toggleFormattingOptions, attachFileAction: () => controller.openFilePickerByType(context, FileType.any), insertImageAction: () => controller.insertImage(context, constraints.maxWidth), - showCodeViewAction: controller.richTextWebController!.toggleCodeView, deleteComposerAction: () => controller.handleClickDeleteComposer(context), saveToDraftAction: () => controller.handleClickSaveAsDraftsButton(context), sendMessageAction: () => controller.handleClickSendButton(context), - requestReadReceiptAction: () => controller.toggleRequestReadReceipt(context), isPrintDraftEnabled: controller.isEmailChanged.isTrue, - onPrintDraftAction: () => controller.printDraft(context), + openContextMenuAction: (position) { + controller.openPopupMenuAction( + context, + position, + _createMoreOptionPopupItemsOnDesktop(context), + radius: ComposerStyle.popupMenuRadius, + ); + }, )), ], ), @@ -936,6 +946,7 @@ class ComposerView extends GetWidget { child: PopupItemWidget( controller.imagePaths.icDeleteMailbox, AppLocalizations.of(context).delete, + colorIcon: ComposerStyle.popupItemIconColor, styleName: ComposerStyle.popupItemTextStyle, padding: ComposerStyle.popupItemPadding, onCallbackAction: () { @@ -946,4 +957,56 @@ class ComposerView extends GetWidget { ), ]; } + + List _createMoreOptionPopupItemsOnDesktop(BuildContext context) { + return [ + PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupItemWidget( + controller.imagePaths.icStyleCodeView, + AppLocalizations.of(context).embedCode, + styleName: ComposerStyle.popupItemTextStyle, + colorIcon: ComposerStyle.popupItemIconColor, + padding: ComposerStyle.popupItemPadding, + selectedIcon: controller.imagePaths.icFilterSelected, + isSelected: controller.richTextWebController?.codeViewEnabled, + onCallbackAction: () { + popBack(); + controller.richTextWebController?.toggleCodeView(); + }, + ), + ), + PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupItemWidget( + controller.imagePaths.icReadReceipt, + AppLocalizations.of(context).requestReadReceipt, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + colorIcon: ComposerStyle.popupItemIconColor, + selectedIcon: controller.imagePaths.icFilterSelected, + isSelected: controller.hasRequestReadReceipt.value, + onCallbackAction: () { + popBack(); + controller.toggleRequestReadReceipt(context); + }, + ), + ), + if (controller.isEmailChanged.isTrue) + PopupMenuItem( + padding: EdgeInsets.zero, + child: PopupItemWidget( + controller.imagePaths.icPrinter, + AppLocalizations.of(context).print, + colorIcon: ComposerStyle.popupItemIconColor, + styleName: ComposerStyle.popupItemTextStyle, + padding: ComposerStyle.popupItemPadding, + onCallbackAction: () { + popBack(); + controller.printDraft(context); + }, + ), + ), + ]; + } } \ No newline at end of file diff --git a/lib/features/composer/presentation/extensions/composer_print_draft_extension.dart b/lib/features/composer/presentation/extensions/composer_print_draft_extension.dart index e560df517..a8f85471f 100644 --- a/lib/features/composer/presentation/extensions/composer_print_draft_extension.dart +++ b/lib/features/composer/presentation/extensions/composer_print_draft_extension.dart @@ -4,13 +4,14 @@ import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/utils/app_logger.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:model/extensions/identity_extension.dart'; import 'package:model/extensions/list_email_address_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/base/state/button_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_controller.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draft_email_print.dart'; import 'package:tmail_ui_user/features/composer/presentation/widgets/web/print_draft_dialog_view.dart'; -import 'package:tmail_ui_user/features/email/domain/model/email_print.dart'; import 'package:tmail_ui_user/features/email/domain/state/print_email_state.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; @@ -62,10 +63,10 @@ extension ComposerPrintDraftExtension on ComposerController { final childWidget = PointerInterceptor( child: PrintDraftDialogView( - emailPrint: EmailPrint( + emailPrint: DraftEmailPrint( appName: appLocalizations.app_name, userName: mailboxDashBoardController.userEmail, - attachments: uploadController.attachmentsUploaded, + attachments: uploadController.allAttachmentsUploaded, emailContent: emailContent, locale: locale.toLanguageTag(), fromPrefix: appLocalizations.from_email_address_prefix, @@ -77,9 +78,7 @@ extension ComposerPrintDraftExtension on ComposerController { toAddress: listToEmailAddress.toSet().toEscapeHtmlStringUseCommaSeparator(), ccAddress: listCcEmailAddress.toSet().toEscapeHtmlStringUseCommaSeparator(), bccAddress: listBccEmailAddress.toSet().toEscapeHtmlStringUseCommaSeparator(), - sender: composerArguments.value!.presentationEmail?.from?.isNotEmpty == true - ? composerArguments.value!.presentationEmail?.from!.first - : null, + sender: identitySelected.value?.toEmailAddress(), receiveTime: presentationEmail?.getReceivedAt( locale.toLanguageTag(), pattern: presentationEmail.receivedAt diff --git a/lib/features/composer/presentation/model/draft_email_print.dart b/lib/features/composer/presentation/model/draft_email_print.dart new file mode 100644 index 000000000..9dbd3ca41 --- /dev/null +++ b/lib/features/composer/presentation/model/draft_email_print.dart @@ -0,0 +1,25 @@ + +import 'package:tmail_ui_user/features/email/domain/model/email_print.dart'; + +class DraftEmailPrint extends EmailPrint { + DraftEmailPrint({ + required super.appName, + required super.userName, + required super.emailContent, + required super.locale, + required super.fromPrefix, + required super.toPrefix, + required super.ccPrefix, + required super.bccPrefix, + required super.replyToPrefix, + required super.titleAttachment, + required super.receiveTime, + super.attachments, + super.toAddress, + super.ccAddress, + super.bccAddress, + super.replyToAddress, + super.sender, + super.subject, + }); +} \ No newline at end of file diff --git a/lib/features/composer/presentation/styles/composer_style.dart b/lib/features/composer/presentation/styles/composer_style.dart index 17f1e8285..6c0a88d7f 100644 --- a/lib/features/composer/presentation/styles/composer_style.dart +++ b/lib/features/composer/presentation/styles/composer_style.dart @@ -14,7 +14,7 @@ class ComposerStyle { static const Color backgroundEditorColor = Colors.white; static const Color richToolbarColor = Colors.white; static const Color mobileBackgroundColor = Colors.white; - static const Color popupItemIconColor = AppColor.primaryColor; + static const Color popupItemIconColor = AppColor.steelGrayA540; static const EdgeInsetsGeometry richToolbarPadding = EdgeInsetsDirectional.symmetric(horizontal: 24, vertical: 8); static const EdgeInsetsGeometry desktopRecipientPadding = EdgeInsetsDirectional.only(end: 24); diff --git a/lib/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart b/lib/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart index ef82023e2..fe34fefdb 100644 --- a/lib/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart +++ b/lib/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart @@ -13,10 +13,10 @@ class BottomBarComposerWidgetStyle { static const double height = 60; static const Color backgroundColor = Colors.white; - static const Color iconColor = AppColor.colorRichButtonComposer; - static const Color sendButtonBackgroundColor = AppColor.primaryColor; - static const Color selectedBackgroundColor = AppColor.colorSelected; - static const Color selectedIconColor = AppColor.primaryColor; + static const Color iconColor = AppColor.steelGrayA540; + static const Color sendButtonBackgroundColor = AppColor.blue700; + static const Color selectedIconColor = AppColor.blue700; + static const Color disabledIconColor = AppColor.colorRichButtonComposer; static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(horizontal: 32); static const EdgeInsetsGeometry iconPadding = EdgeInsetsDirectional.all(5); diff --git a/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart b/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart index 7de92e121..d3bff3aa0 100644 --- a/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart +++ b/lib/features/composer/presentation/widgets/web/bottom_bar_composer_widget.dart @@ -1,8 +1,8 @@ -import 'package:core/presentation/extensions/color_extension.dart'; import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/views/button/tmail_button_widget.dart'; import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/composer/presentation/styles/web/bottom_bar_composer_widget_style.dart'; +import 'package:tmail_ui_user/features/composer/presentation/widgets/web/mobile_responsive_app_bar_composer_widget.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; class BottomBarComposerWidget extends StatelessWidget { @@ -15,12 +15,10 @@ class BottomBarComposerWidget extends StatelessWidget { final VoidCallback openRichToolbarAction; final VoidCallback attachFileAction; final VoidCallback insertImageAction; - final VoidCallback showCodeViewAction; final VoidCallback deleteComposerAction; final VoidCallback saveToDraftAction; final VoidCallback sendMessageAction; - final VoidCallback requestReadReceiptAction; - final VoidCallback onPrintDraftAction; + final OnOpenContextMenuAction openContextMenuAction; const BottomBarComposerWidget({ super.key, @@ -32,12 +30,10 @@ class BottomBarComposerWidget extends StatelessWidget { required this.openRichToolbarAction, required this.attachFileAction, required this.insertImageAction, - required this.showCodeViewAction, required this.deleteComposerAction, required this.saveToDraftAction, required this.sendMessageAction, - required this.requestReadReceiptAction, - required this.onPrintDraftAction, + required this.openContextMenuAction, }); @override @@ -52,9 +48,7 @@ class BottomBarComposerWidget extends StatelessWidget { icon: imagePaths.icRichToolbar, borderRadius: BottomBarComposerWidgetStyle.iconRadius, padding: BottomBarComposerWidgetStyle.richTextIconPadding, - backgroundColor: isFormattingOptionsEnabled - ? BottomBarComposerWidgetStyle.selectedBackgroundColor - : Colors.transparent, + backgroundColor: Colors.transparent, iconSize: BottomBarComposerWidgetStyle.richTextIconSize, iconColor: isFormattingOptionsEnabled ? BottomBarComposerWidgetStyle.selectedIconColor @@ -78,7 +72,9 @@ class BottomBarComposerWidget extends StatelessWidget { absorbing: isCodeViewEnabled, child: TMailButtonWidget.fromIcon( icon: imagePaths.icInsertImage, - iconColor: BottomBarComposerWidgetStyle.iconColor, + iconColor: isCodeViewEnabled + ? BottomBarComposerWidgetStyle.disabledIconColor + : BottomBarComposerWidgetStyle.iconColor, borderRadius: BottomBarComposerWidgetStyle.iconRadius, backgroundColor: Colors.transparent, padding: BottomBarComposerWidgetStyle.iconPadding, @@ -89,18 +85,12 @@ class BottomBarComposerWidget extends StatelessWidget { ), const SizedBox(width: BottomBarComposerWidgetStyle.space), TMailButtonWidget.fromIcon( - icon: imagePaths.icStyleCodeView, - iconColor: isCodeViewEnabled - ? BottomBarComposerWidgetStyle.selectedIconColor - : BottomBarComposerWidgetStyle.iconColor, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - backgroundColor: isCodeViewEnabled - ? BottomBarComposerWidgetStyle.selectedBackgroundColor - : Colors.transparent, - padding: BottomBarComposerWidgetStyle.iconPadding, + icon: imagePaths.icMore, + iconColor: BottomBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, iconSize: BottomBarComposerWidgetStyle.iconSize, - tooltipMessage: AppLocalizations.of(context).embedCode, - onTapActionCallback: showCodeViewAction, + tooltipMessage: AppLocalizations.of(context).more, + onTapActionAtPositionCallback: openContextMenuAction, ), const Spacer(), TMailButtonWidget.fromIcon( @@ -108,42 +98,19 @@ class BottomBarComposerWidget extends StatelessWidget { borderRadius: BottomBarComposerWidgetStyle.iconRadius, padding: BottomBarComposerWidgetStyle.iconPadding, iconSize: BottomBarComposerWidgetStyle.iconSize, + iconColor: BottomBarComposerWidgetStyle.iconColor, + backgroundColor: Colors.transparent, tooltipMessage: AppLocalizations.of(context).delete, onTapActionCallback: deleteComposerAction, ), const SizedBox(width: BottomBarComposerWidgetStyle.space), - TMailButtonWidget.fromIcon( - icon: imagePaths.icReadReceipt, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - iconColor: hasReadReceipt - ? BottomBarComposerWidgetStyle.selectedIconColor - : BottomBarComposerWidgetStyle.iconColor, - tooltipMessage: hasReadReceipt - ? AppLocalizations.of(context).turnOffRequestReadReceipt - : AppLocalizations.of(context).turnOnRequestReadReceipt, - onTapActionCallback: requestReadReceiptAction, - ), - const SizedBox(width: BottomBarComposerWidgetStyle.space), - if (isPrintDraftEnabled) - TMailButtonWidget.fromIcon( - icon: imagePaths.icPrinter, - borderRadius: BottomBarComposerWidgetStyle.iconRadius, - padding: BottomBarComposerWidgetStyle.iconPadding, - iconSize: BottomBarComposerWidgetStyle.iconSize, - iconColor: AppColor.steelGrayA540, - margin: const EdgeInsetsDirectional.only( - end: BottomBarComposerWidgetStyle.space, - ), - tooltipMessage: AppLocalizations.of(context).print, - onTapActionCallback: onPrintDraftAction, - ), TMailButtonWidget.fromIcon( icon: imagePaths.icSaveToDraft, borderRadius: BottomBarComposerWidgetStyle.iconRadius, padding: BottomBarComposerWidgetStyle.iconPadding, iconSize: BottomBarComposerWidgetStyle.iconSize, + backgroundColor: Colors.transparent, + iconColor: BottomBarComposerWidgetStyle.iconColor, tooltipMessage: AppLocalizations.of(context).saveAsDraft, onTapActionCallback: saveToDraftAction, ), diff --git a/lib/features/email/data/datasource_impl/print_file_datasource_impl.dart b/lib/features/email/data/datasource_impl/print_file_datasource_impl.dart index a85ffdfb6..7cc6fdae6 100644 --- a/lib/features/email/data/datasource_impl/print_file_datasource_impl.dart +++ b/lib/features/email/data/datasource_impl/print_file_datasource_impl.dart @@ -7,6 +7,7 @@ import 'package:core/utils/file_utils.dart'; import 'package:core/utils/print_utils.dart'; import 'package:filesize/filesize.dart'; import 'package:model/email/attachment.dart'; +import 'package:tmail_ui_user/features/composer/presentation/model/draft_email_print.dart'; import 'package:tmail_ui_user/features/email/data/datasource/print_file_datasource.dart'; import 'package:tmail_ui_user/features/email/data/local/html_analyzer.dart'; import 'package:tmail_ui_user/features/email/domain/model/email_print.dart'; @@ -32,8 +33,7 @@ class PrintFileDataSourceImpl extends PrintFileDataSource { @override Future printEmail(EmailPrint emailPrint) { return Future.sync(() async { - final emailContentEscaped = await _transformHtmlEmailContent( - emailPrint.emailContent); + final emailContentEscaped = await _transformHtmlEmailContent(emailPrint); final List listPrintAttachment = []; @@ -72,16 +72,23 @@ class PrintFileDataSourceImpl extends PrintFileDataSource { }).catchError(_exceptionThrower.throwException); } - Future _transformHtmlEmailContent(String emailContent) async { + Future _transformHtmlEmailContent(EmailPrint emailPrint) async { try { - final htmlContentTransformed = await _htmlAnalyzer.transformHtmlEmailContent( - emailContent, - TransformConfiguration.forPrintEmail(), - ); + if (emailPrint.emailContent.isEmpty) return ''; + + final htmlContentTransformed = emailPrint is DraftEmailPrint + ? await _htmlAnalyzer.removeCollapsedExpandedSignatureEffect( + emailContent: emailPrint.emailContent, + ) + : await _htmlAnalyzer.transformHtmlEmailContent( + emailPrint.emailContent, + TransformConfiguration.forPrintEmail(), + ); + return htmlContentTransformed; } catch (e) { logError('PrintFileDataSourceImpl::_transformHtmlEmailContent: Exception: $e'); - return emailContent; + return ''; } } } \ No newline at end of file diff --git a/lib/features/upload/presentation/controller/upload_controller.dart b/lib/features/upload/presentation/controller/upload_controller.dart index a0c75b612..f7d63ee9e 100644 --- a/lib/features/upload/presentation/controller/upload_controller.dart +++ b/lib/features/upload/presentation/controller/upload_controller.dart @@ -471,6 +471,11 @@ class UploadController extends BaseController { return mapInlineAttachments; } + List get allAttachmentsUploaded => [ + ...attachmentsUploaded, + ...inlineAttachmentsUploaded, + ]; + void _handleUploadAttachmentFailure(UploadAttachmentFailure failure) { if (currentContext != null && currentOverlayContext != null) { appToast.showToastErrorMessage( diff --git a/model/lib/extensions/identity_extension.dart b/model/lib/extensions/identity_extension.dart index 983654f5c..530f8446f 100644 --- a/model/lib/extensions/identity_extension.dart +++ b/model/lib/extensions/identity_extension.dart @@ -6,6 +6,8 @@ import 'package:model/identity/identity_request_dto.dart'; extension IdentityExtension on Identity { EmailAddress toEmailAddressNoName() => EmailAddress(null, email); + EmailAddress toEmailAddress() => EmailAddress(name, email); + IdentityRequestDto toIdentityRequest() => IdentityRequestDto( name: name, replyTo: replyTo, diff --git a/test/features/composer/presentation/composer_controller_test.dart b/test/features/composer/presentation/composer_controller_test.dart index 6baca38a0..f23ec0d1b 100644 --- a/test/features/composer/presentation/composer_controller_test.dart +++ b/test/features/composer/presentation/composer_controller_test.dart @@ -1272,7 +1272,6 @@ void main() { composerController?.handleSuccessViewState(state); // assert - verifyNever(mockHtmlEditorApi.getText()); expect(composerController?.savedEmailDraftHash, isNull); }); @@ -1292,7 +1291,6 @@ void main() { composerController?.handleFailureViewState(state); // assert - verifyNever(mockHtmlEditorApi.getText()); expect(composerController?.savedEmailDraftHash, isNull); });