From a756385546ff7a41da94d7aed8ff56b5a2b75fd3 Mon Sep 17 00:00:00 2001 From: dab246 Date: Fri, 11 Aug 2023 15:56:55 +0700 Subject: [PATCH] TF-2078 Display original email content when can not parse calendar event (cherry picked from commit 295c3caa684bf5c4279e3219a961b96498c01373) --- .../controller/single_email_controller.dart | 139 ++++---- .../email/presentation/email_view.dart | 331 ++++++------------ .../presentation/model/email_loaded.dart | 18 +- .../styles/email_attachments_styles.dart | 24 ++ .../styles/email_subject_styles.dart | 17 + .../styles/email_view_empty_styles.dart | 11 + .../widgets/email_attachments_widget.dart | 126 +++++++ ...uilder.dart => email_receiver_widget.dart} | 96 ++--- .../widgets/email_subject_widget.dart | 28 ++ .../widgets/email_view_empty_widget.dart | 23 ++ .../email_view_loading_bar_widget.dart | 2 +- ...formation_sender_and_receiver_builder.dart | 8 +- 12 files changed, 461 insertions(+), 362 deletions(-) create mode 100644 lib/features/email/presentation/styles/email_attachments_styles.dart create mode 100644 lib/features/email/presentation/styles/email_subject_styles.dart create mode 100644 lib/features/email/presentation/styles/email_view_empty_styles.dart create mode 100644 lib/features/email/presentation/widgets/email_attachments_widget.dart rename lib/features/email/presentation/widgets/{email_receiver_builder.dart => email_receiver_widget.dart} (79%) create mode 100644 lib/features/email/presentation/widgets/email_subject_widget.dart create mode 100644 lib/features/email/presentation/widgets/email_view_empty_widget.dart diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 9d86b3c1d..982b83f43 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -108,16 +108,15 @@ class SingleEmailController extends BaseController with AppLoaderMixin { SendReceiptToSenderInteractor? _sendReceiptToSenderInteractor; ParseCalendarEventInteractor? _parseCalendarEventInteractor; - final emailAddressExpandMode = ExpandMode.COLLAPSE.obs; - final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; final emailContents = RxnString(); final attachments = [].obs; final calendarEvent = Rxn(); final eventActions = [].obs; + final emailLoadedViewState = Rx>(Right(UIState.idle)); EmailId? _currentEmailId; Identity? _identitySelected; - String? initialEmailContents; + EmailLoaded? _currentEmailLoaded; final StreamController> _downloadProgressStateController = StreamController>.broadcast(); @@ -125,10 +124,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { PresentationEmail? get currentEmail => mailboxDashBoardController.selectedEmail.value; - bool get isDisplayFullEmailAddress => emailAddressExpandMode.value == ExpandMode.EXPAND; - - bool get isDisplayFullAttachments => attachmentsExpandMode.value == ExpandMode.EXPAND; - SingleEmailController( this._getEmailContentInteractor, this._markAsEmailReadInteractor, @@ -178,6 +173,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _sendReceiptToSenderSuccess(success); } else if (success is CreateNewRuleFilterSuccess) { _createNewRuleFilterSuccess(success); + } else if (success is ParseCalendarEventLoading) { + emailLoadedViewState.value = Right(success); } else if (success is ParseCalendarEventSuccess) { _handleParseCalendarEventSuccess(success); } @@ -194,6 +191,10 @@ class SingleEmailController extends BaseController with AppLoaderMixin { _exportAttachmentFailureAction(failure); } else if (failure is DownloadAttachmentForWebFailure) { _downloadAttachmentForWebFailureAction(failure); + } else if (failure is ParseCalendarEventFailure) { + _handleParseCalendarEventFailure(failure); + } else if (failure is GetEmailContentFailure) { + emailLoadedViewState.value = Left(failure); } } @@ -223,7 +224,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { log('SingleEmailController::_handleOpenEmailDetailedView(): email unselected'); return; } - dispatchState(Right(GetEmailContentLoading())); + emailLoadedViewState.value = Right(GetEmailContentLoading()); emailSupervisorController.updateNewCurrentListEmail(); _updateCurrentEmailId(selectedEmail.id); @@ -367,8 +368,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { if (emailLoaded != null) { consumeState(Stream.value(Right( GetEmailContentSuccess( - emailContent: emailLoaded.emailContent, - emailContentDisplayed: emailLoaded.emailContentDisplayed, + emailContent: emailLoaded.originalContent, + emailContentDisplayed: emailLoaded.displayedContent, attachments: emailLoaded.attachments, emailCurrent: emailLoaded.emailCurrent ) @@ -379,27 +380,32 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } void _getEmailContentOfflineSuccess(GetEmailContentFromCacheSuccess success) { + emailLoadedViewState.value = Right(success); if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) { emailSupervisorController.popFirstEmailQueue(); } emailSupervisorController.popEmailQueue(success.emailCurrent?.id); - emailSupervisorController.pushEmailQueue(EmailLoaded( - success.emailContent, - success.emailContent, - success.attachments.toList(), - success.emailCurrent, - )); + _currentEmailLoaded = EmailLoaded( + originalContent: success.emailContent, + displayedContent: success.emailContent, + attachments: List.of(success.attachments), + emailCurrent: success.emailCurrent, + ); + emailSupervisorController.pushEmailQueue(_currentEmailLoaded!); if (success.emailCurrent?.id == currentEmail?.id) { - emailContents.value = success.emailContent; - initialEmailContents = success.emailContent; attachments.value = success.attachments; - _loadCalendarEventAction( - blobIds: success.attachments.calendarEventBlobIds, - emailContents: success.emailContent - ); + if (_canParseCalendarEvent(blobIds: success.attachments.calendarEventBlobIds)) { + _parseCalendarEventAction( + accountId: mailboxDashBoardController.accountId.value!, + blobIds: success.attachments.calendarEventBlobIds, + emailContents: success.emailContent + ); + } else { + emailContents.value = success.emailContent; + } final isShowMessageReadReceipt = success.emailCurrent?.hasReadReceipt(mailboxDashBoardController.mapMailboxById) == true; if (isShowMessageReadReceipt) { @@ -409,36 +415,41 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } void _getEmailContentSuccess(GetEmailContentSuccess success) { + emailLoadedViewState.value = Right(success); if (emailSupervisorController.presentationEmailsLoaded.length > ThreadConstants.defaultLimit.value.toInt()) { emailSupervisorController.popFirstEmailQueue(); } emailSupervisorController.popEmailQueue(success.emailCurrent?.id); - emailSupervisorController.pushEmailQueue(EmailLoaded( - success.emailContent, - success.emailContentDisplayed, - success.attachments.toList(), - success.emailCurrent, - )); + _currentEmailLoaded = EmailLoaded( + originalContent: success.emailContent, + displayedContent: success.emailContentDisplayed, + attachments: List.of(success.attachments), + emailCurrent: success.emailCurrent, + ); + emailSupervisorController.pushEmailQueue(_currentEmailLoaded!); if (success.emailCurrent?.id == currentEmail?.id) { - emailContents.value = success.emailContentDisplayed; - initialEmailContents = success.emailContent; attachments.value = success.attachments; - _loadCalendarEventAction( - blobIds: success.attachments.calendarEventBlobIds, - emailContents: success.emailContent - ); + if (_canParseCalendarEvent(blobIds: success.attachments.calendarEventBlobIds)) { + _parseCalendarEventAction( + accountId: mailboxDashBoardController.accountId.value!, + blobIds: success.attachments.calendarEventBlobIds, + emailContents: success.emailContent + ); + } else { + emailContents.value = success.emailContentDisplayed; + } if (PlatformInfo.isMobile) { final detailedEmail = DetailedEmail( emailId: currentEmail!.id!, createdTime: currentEmail?.receivedAt?.value ?? DateTime.now(), - attachments: attachments, + attachments: success.attachments, headers: currentEmail?.emailHeader?.toSet(), keywords: currentEmail?.keywords, - htmlEmailContent: emailContents.value + htmlEmailContent: success.emailContentDisplayed ); _storeOpenedEmailAction( @@ -471,10 +482,8 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } void _resetToOriginalValue() { - attachmentsExpandMode.value = ExpandMode.COLLAPSE; - emailAddressExpandMode.value = ExpandMode.COLLAPSE; emailContents.value = null; - initialEmailContents = null; + _currentEmailLoaded = null; attachments.clear(); calendarEvent.value = null; eventActions.clear(); @@ -517,13 +526,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } - void toggleDisplayAttachmentsAction() { - final newExpandMode = attachmentsExpandMode.value == ExpandMode.COLLAPSE - ? ExpandMode.EXPAND - : ExpandMode.COLLAPSE; - attachmentsExpandMode.value = newExpandMode; - } - void downloadAttachments(BuildContext context, List attachments) async { final needRequestPermission = await _deviceManager.isNeedRequestStoragePermissionOnAndroid(); @@ -944,14 +946,6 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } - void expandEmailAddress() { - emailAddressExpandMode.value = ExpandMode.EXPAND; - } - - void collapseEmailAddress() { - emailAddressExpandMode.value = ExpandMode.COLLAPSE; - } - void openEmailAddressDialog(BuildContext context, EmailAddress emailAddress) { if (responsiveUtils.isScreenWithShortestSide(context)) { (EmailAddressBottomSheetBuilder(context, imagePaths, emailAddress) @@ -1171,7 +1165,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { final arguments = ComposerArguments( emailActionType: emailActionType, presentationEmail: mailboxDashBoardController.selectedEmail.value!, - emailContents: initialEmailContents, + emailContents: _currentEmailLoaded?.originalContent, attachments: emailActionType == EmailActionType.forward ? attachments : null, mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role ); @@ -1247,24 +1241,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } - void _loadCalendarEventAction({ - required Set blobIds, - required String emailContents - }) { - log('SingleEmailController::_loadCalendarEventAction:blobIds: $blobIds'); - if (_isCalendarEventSupported && - currentEmail?.hasCalendarEvent == true && - blobIds.isNotEmpty && - mailboxDashBoardController.accountId.value != null - ) { - _parseCalendarEventAction( - accountId: mailboxDashBoardController.accountId.value!, - blobIds: blobIds, - emailContents: emailContents - ); - } else { - logError('SingleEmailController::_loadCalendarEventAction: calendar event not supported'); - } + bool _canParseCalendarEvent({required Set blobIds}) { + return _isCalendarEventSupported && + currentEmail?.hasCalendarEvent == true && + blobIds.isNotEmpty && + _parseCalendarEventInteractor != null; } bool get _isCalendarEventSupported { @@ -1281,14 +1262,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { required String emailContents }) { log("SingleEmailController::_parseCalendarEventAction:blobIds: $blobIds"); - if (_parseCalendarEventInteractor != null) { - consumeState(_parseCalendarEventInteractor!.execute(accountId, blobIds, emailContents)); - } else { - logError("SingleEmailController::_parseCalendarEventAction: _parseCalendarEventInteractor is NULL"); - } + consumeState(_parseCalendarEventInteractor!.execute(accountId, blobIds, emailContents)); } void _handleParseCalendarEventSuccess(ParseCalendarEventSuccess success) { + emailLoadedViewState.value = Right(success); calendarEvent.value = success.calendarEventList.first; eventActions.value = success.eventActionList; if (PlatformInfo.isMobile) { @@ -1296,6 +1274,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } + void _handleParseCalendarEventFailure(ParseCalendarEventFailure failure) { + emailLoadedViewState.value = Left(failure); + emailContents.value = _currentEmailLoaded?.displayedContent; + } + void _enableScrollPageView() { emailSupervisorController.scrollPhysicsPageView.value = null; } diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 27d8a7865..530a9a975 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -9,11 +9,10 @@ import 'package:core/presentation/views/html_viewer/html_viewer_controller_for_w import 'package:core/utils/app_logger.dart'; import 'package:core/utils/direction_utils.dart'; import 'package:core/utils/platform_info.dart'; -import 'package:filesize/filesize.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; -import 'package:model/email/attachment.dart'; +import 'package:jmap_dart_client/jmap/mail/calendar/calendar_event.dart'; import 'package:model/email/email_action_type.dart'; import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/list_attachment_extension.dart'; @@ -21,19 +20,19 @@ import 'package:model/extensions/list_email_address_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; -import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; -import 'package:tmail_ui_user/features/base/widget/custom_scroll_behavior.dart'; import 'package:tmail_ui_user/features/base/widget/popup_item_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/calendar_event_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/styles/email_view_styles.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/app_bar_mail_widget_builder.dart'; -import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/bottom_bar_mail_widget_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_action_banner_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_detail_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/calendar_event/calendar_event_information_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_action_cupertino_action_sheet_action_builder.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/email_attachments_widget.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/email_subject_widget.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_empty_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_view_loading_bar_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/information_sender_and_receiver_builder.dart'; import 'package:tmail_ui_user/features/manage_account/presentation/extensions/vacation_response_extension.dart'; @@ -41,7 +40,7 @@ import 'package:tmail_ui_user/features/manage_account/presentation/vacation/widg import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class EmailView extends GetWidget with AppLoaderMixin { +class EmailView extends GetWidget { final responsiveUtils = Get.find(); final imagePaths = Get.find(); @@ -75,9 +74,24 @@ class EmailView extends GetWidget with AppLoaderMixin { margin: _getMarginEmailView(context), child: Obx(() { if (controller.currentEmail != null) { - return _buildEmailView(context, controller.currentEmail!); + return Column(children: [ + _buildAppBar(context, controller.currentEmail!), + _buildVacationNotificationMessage(context), + const Divider(color: AppColor.colorDividerHorizontal, height: 1), + Expanded( + child: Obx(() { + return controller.emailSupervisorController.supportedPageView.isTrue + ? _buildMultipleEmailView(controller.emailSupervisorController.currentListEmail) + : _buildSingleEmailView(context, controller.currentEmail!); + }), + ), + BottomBarMailWidgetBuilder( + controller.currentEmail!, + onPressEmailActionClick: controller.pressEmailAction + ), + ]); } else { - return _buildEmailViewEmpty(context); + return const EmailViewEmptyWidget(); } }) ) @@ -104,28 +118,6 @@ class EmailView extends GetWidget with AppLoaderMixin { } } - Widget _buildEmailViewEmpty(BuildContext context) { - return Center(child: _buildEmailEmpty(context)); - } - - Widget _buildEmailView(BuildContext context, PresentationEmail email) { - return Column(children: [ - _buildAppBar(context, email), - _buildVacationNotificationMessage(context), - const Divider(color: AppColor.colorDividerHorizontal, height: 1), - Expanded(child: Obx(() { - return controller.emailSupervisorController.supportedPageView.isTrue - ? _buildMultipleEmailView(controller.emailSupervisorController.currentListEmail) - : _buildSingleEmailView(context, email); - }), - ), - BottomBarMailWidgetBuilder( - email, - onPressEmailActionClick: controller.pressEmailAction - ), - ]); - } - Widget _buildMultipleEmailView(List listEmails) { return Obx( () => PageView.builder( @@ -139,10 +131,6 @@ class EmailView extends GetWidget with AppLoaderMixin { ); } - Widget _buildSingleEmailView(BuildContext context, PresentationEmail email) { - return _buildEmailBody(context, email); - } - bool _supportVerticalDivider(BuildContext context) { if (PlatformInfo.isWeb) { return responsiveUtils.isTabletLarge(context); @@ -228,224 +216,121 @@ class EmailView extends GetWidget with AppLoaderMixin { ]; } - Widget _buildEmailBody(BuildContext context, PresentationEmail email) { - if (PlatformInfo.isWeb && !email.hasCalendarEvent) { - return _buildEmailMessage(context, email); - } else { + Widget _buildSingleEmailView(BuildContext context, PresentationEmail presentationEmail) { + if (PlatformInfo.isMobile) { return SingleChildScrollView( physics : const ClampingScrollPhysics(), child: Container( - margin: EdgeInsets.zero, width: double.infinity, alignment: Alignment.center, - padding: EdgeInsets.zero, color: Colors.white, - child: _buildEmailMessage(context, email) + child: Obx(() => _buildEmailMessage( + context: context, + presentationEmail: presentationEmail, + calendarEvent: controller.calendarEvent.value + )) ) ); + } else { + return Obx(() { + final calendarEvent = controller.calendarEvent.value; + if (presentationEmail.hasCalendarEvent && calendarEvent != null) { + return SingleChildScrollView( + physics : const ClampingScrollPhysics(), + child: Container( + width: double.infinity, + alignment: Alignment.center, + color: Colors.white, + child: _buildEmailMessage( + context: context, + presentationEmail: presentationEmail, + calendarEvent: calendarEvent, + emailAddressSender: presentationEmail.listEmailAddressSender.getListAddress(), + ) + ) + ); + } else { + return _buildEmailMessage(context: context, presentationEmail: presentationEmail); + } + }); } } - Widget _buildEmailEmpty(BuildContext context) { - return Text( - AppLocalizations.of(context).no_mail_selected, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 25, color: AppColor.mailboxTextColor, fontWeight: FontWeight.bold)); - } - - Widget _buildEmailSubject(BuildContext context, PresentationEmail email) { - return Container( - color: Colors.white, - padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16), - child: SelectableText( - email.getEmailTitle(), - maxLines: PlatformInfo.isWeb ? 2 : null, - minLines: PlatformInfo.isWeb ? 1 : null, - cursorColor: AppColor.colorTextButton, - style: const TextStyle( - fontSize: 20, - color: AppColor.colorNameEmail, - fontWeight: FontWeight.w500) - )); - } - - Widget _buildEmailMessage(BuildContext context, PresentationEmail email) { + Widget _buildEmailMessage({ + required BuildContext context, + required PresentationEmail presentationEmail, + CalendarEvent? calendarEvent, + List? emailAddressSender, + }) { return LayoutBuilder( builder: (context, constraints) { return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - _buildEmailSubject(context, email), + EmailSubjectWidget(presentationEmail: presentationEmail), InformationSenderAndReceiverBuilder( controller: controller, - emailSelected: email, + emailSelected: presentationEmail, imagePaths: imagePaths, responsiveUtils: responsiveUtils, ), - _buildAttachments(context), + Obx(() { + final attachments = controller.attachments.listAttachmentsDisplayedOutSide; + if (attachments.isNotEmpty) { + return EmailAttachmentsWidget( + attachments: attachments, + onDownloadAttachmentActionCallback: (context, attachment) { + if (PlatformInfo.isWeb) { + controller.downloadAttachmentForWeb(context, attachment); + } else { + controller.exportAttachment(context, attachment); + } + }, + ); + } else { + return const SizedBox.shrink(); + } + }), Obx(() => EmailViewLoadingBarWidget( - viewState: controller.viewState.value, - selectedEmail: email + viewState: controller.emailLoadedViewState.value, + selectedEmail: presentationEmail )), - if (email.hasCalendarEvent) - Obx(() { - final calendarEvent = controller.calendarEvent.value; - final listEmailAddressSender = controller.currentEmail?.listEmailAddressSender.getListAddress() ?? []; - if (calendarEvent != null) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisSize: MainAxisSize.min, - children: [ - CalendarEventInformationWidget( - calendarEvent: calendarEvent, - eventActions: controller.eventActions, - onOpenComposerAction: controller.openNewComposerAction, - onOpenNewTabAction: controller.openNewTabAction, - ), - if (calendarEvent.getTitleEventAction(context, listEmailAddressSender).isNotEmpty) - CalendarEventActionBannerWidget( - calendarEvent: calendarEvent, - listEmailAddressSender: listEmailAddressSender - ), - CalendarEventDetailWidget( - calendarEvent: calendarEvent, - eventActions: controller.eventActions, - onOpenComposerAction: controller.openNewComposerAction, - onOpenNewTabAction: controller.openNewTabAction, - ), - ], - ); - } else { - return const SizedBox.shrink(); - } - }) + if (calendarEvent != null) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + CalendarEventInformationWidget( + calendarEvent: calendarEvent, + eventActions: controller.eventActions, + onOpenComposerAction: controller.openNewComposerAction, + onOpenNewTabAction: controller.openNewTabAction, + ), + if (calendarEvent.getTitleEventAction(context, emailAddressSender ?? []).isNotEmpty) + CalendarEventActionBannerWidget( + calendarEvent: calendarEvent, + listEmailAddressSender: emailAddressSender ?? [] + ), + CalendarEventDetailWidget( + calendarEvent: calendarEvent, + eventActions: controller.eventActions, + onOpenComposerAction: controller.openNewComposerAction, + onOpenNewTabAction: controller.openNewTabAction, + ), + ], + ) else - _buildEmailContent(context, constraints, email) + _buildEmailContentOnHtmlViewer(context, constraints, presentationEmail) ], ); }); } - Widget _buildAttachments(BuildContext context) { - return Obx(() { - final attachments = controller.attachments.listAttachmentsDisplayedOutSide; - return attachments.isNotEmpty - ? _buildAttachmentsBody(context, attachments) - : const SizedBox.shrink(); - }); - } - - Widget _buildAttachmentsBody(BuildContext context, List attachments) { - return Container( - color: Colors.white, - padding: const EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 16), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildAttachmentsHeader(context, attachments), - _buildAttachmentsList(context, attachments, controller.isDisplayFullAttachments) - ], - ), - ); - } - - Widget _buildAttachmentsHeader(BuildContext context, List attachments) { - return Container( - color: Colors.white, - child: Row( - children: [ - Expanded(child: Container( - margin: const EdgeInsets.symmetric(vertical: 4), - child: Row(children: [ - SvgPicture.asset(imagePaths.icAttachment, - width: 20, - height: 20, - colorFilter: AppColor.colorAttachmentIcon.asFilter(), - fit: BoxFit.fill), - const SizedBox(width: 5), - Expanded(child: Text( - AppLocalizations.of(context).titleHeaderAttachment( - attachments.length, - filesize(attachments.totalSize(), 1)), - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.normal, - color: AppColor.colorTitleHeaderAttachment))) - ]) - )), - if (attachments.length > 2) - Obx(() => Material( - color: Colors.transparent, - child: InkWell( - onTap: controller.toggleDisplayAttachmentsAction, - customBorder: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 12), - child: Text( - controller.isDisplayFullAttachments - ? AppLocalizations.of(context).hide - : AppLocalizations.of(context).showAll, - style: const TextStyle( - fontSize: 13, - color: AppColor.colorTextButton, - fontWeight: FontWeight.normal)), - ), - ), - )) - ], - ), - ); - } - - Widget _buildAttachmentsList( - BuildContext context, - List attachments, - bool isDisplayAll + Widget _buildEmailContentOnHtmlViewer( + BuildContext context, + BoxConstraints constraints, + PresentationEmail email ) { - return LayoutBuilder(builder: (context, constraints) { - if (isDisplayAll) { - return Wrap( - runSpacing: 12, - children: attachments - .map((attachment) => AttachmentFileTileBuilder( - attachment, - onDownloadAttachmentFileActionClick: (attachment) { - if (PlatformInfo.isWeb) { - controller.downloadAttachmentForWeb(context, attachment); - } else { - controller.exportAttachment(context, attachment); - } - })) - .toList()); - } else { - return Container( - height: 60, - color: Colors.transparent, - child: ScrollConfiguration( - behavior: CustomScrollBehavior(), - child: ListView.builder( - key: const Key('list_attachment_minimize_in_email'), - shrinkWrap: true, - scrollDirection: Axis.horizontal, - itemCount: attachments.length, - itemBuilder: (context, index) => - AttachmentFileTileBuilder( - attachments[index], - onDownloadAttachmentFileActionClick: (attachment) { - if (PlatformInfo.isWeb) { - controller.downloadAttachmentForWeb(context, attachment); - } else { - controller.exportAttachment(context, attachment); - } - } - ) - ), - ) - ); - } - }); - } - - Widget _buildEmailContent(BuildContext context, BoxConstraints constraints, PresentationEmail email) { - if(email.id != controller.currentEmail?.id) { + if (email.id != controller.currentEmail?.id) { return const SizedBox.shrink(); } return Obx(() { diff --git a/lib/features/email/presentation/model/email_loaded.dart b/lib/features/email/presentation/model/email_loaded.dart index 6fd774717..f7924ac64 100644 --- a/lib/features/email/presentation/model/email_loaded.dart +++ b/lib/features/email/presentation/model/email_loaded.dart @@ -3,22 +3,22 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/email/attachment.dart'; class EmailLoaded with EquatableMixin { - final String emailContent; - final String emailContentDisplayed; + final String originalContent; + final String displayedContent; final List attachments; final Email? emailCurrent; - EmailLoaded( - this.emailContent, - this.emailContentDisplayed, - this.attachments, + EmailLoaded({ + required this.originalContent, + required this.displayedContent, + required this.attachments, this.emailCurrent, - ); + }); @override List get props => [ - emailContent, - emailContentDisplayed, + originalContent, + displayedContent, attachments, emailCurrent ]; diff --git a/lib/features/email/presentation/styles/email_attachments_styles.dart b/lib/features/email/presentation/styles/email_attachments_styles.dart new file mode 100644 index 000000000..51a17130a --- /dev/null +++ b/lib/features/email/presentation/styles/email_attachments_styles.dart @@ -0,0 +1,24 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; + +class EmailAttachmentsStyles { + static const double headerTextSize = 15; + static const double headerIconSize = 20; + static const double headerSpace = 4; + static const double marginHeader = 6; + static const double buttonTextSize = 13; + static const double buttonBorderRadius = 8; + static const double listSpace = 12; + static const double listHeight = 60; + + static const Color headerTextColor = AppColor.colorTitleHeaderAttachment; + static const Color headerIconColor = AppColor.colorAttachmentIcon; + static const Color buttonTextColor = AppColor.primaryColor; + + static const FontWeight headerFontWeight = FontWeight.w400; + static const FontWeight buttonFontWeight = FontWeight.w400; + + static const EdgeInsetsGeometry padding = EdgeInsetsDirectional.symmetric(vertical: 12, horizontal: 16); + static const EdgeInsetsGeometry buttonPadding = EdgeInsets.symmetric(vertical: 8, horizontal: 12); +} \ No newline at end of file diff --git a/lib/features/email/presentation/styles/email_subject_styles.dart b/lib/features/email/presentation/styles/email_subject_styles.dart new file mode 100644 index 000000000..177944a34 --- /dev/null +++ b/lib/features/email/presentation/styles/email_subject_styles.dart @@ -0,0 +1,17 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/utils/platform_info.dart'; +import 'package:flutter/material.dart'; + +class EmailSubjectStyles { + static const double textSize = 20; + static const int? maxLines = PlatformInfo.isWeb ? 2 : null; + static const int? minLines = PlatformInfo.isWeb ? 1 : null; + + static const Color textColor = AppColor.colorNameEmail; + static const Color cursorColor = AppColor.colorTextButton; + + static const FontWeight fontWeight = FontWeight.w500; + + static const EdgeInsetsGeometry padding = EdgeInsets.symmetric(vertical: 12, horizontal: 16); +} \ No newline at end of file diff --git a/lib/features/email/presentation/styles/email_view_empty_styles.dart b/lib/features/email/presentation/styles/email_view_empty_styles.dart new file mode 100644 index 000000000..de4fb35eb --- /dev/null +++ b/lib/features/email/presentation/styles/email_view_empty_styles.dart @@ -0,0 +1,11 @@ + +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:flutter/material.dart'; + +class EmailViewEmptyStyles { + static const double textSize = 25; + + static const Color textColor = AppColor.mailboxTextColor; + + static const FontWeight fontWeight = FontWeight.bold; +} \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/email_attachments_widget.dart b/lib/features/email/presentation/widgets/email_attachments_widget.dart new file mode 100644 index 000000000..9f64efc3c --- /dev/null +++ b/lib/features/email/presentation/widgets/email_attachments_widget.dart @@ -0,0 +1,126 @@ +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:filesize/filesize.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:get/get.dart'; +import 'package:model/email/attachment.dart'; +import 'package:model/extensions/list_attachment_extension.dart'; +import 'package:tmail_ui_user/features/base/widget/custom_scroll_behavior.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/email_attachments_styles.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/attachment_file_tile_builder.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +typedef OnDownloadAttachmentActionCallback = Function(BuildContext context, Attachment attachment); + +class EmailAttachmentsWidget extends StatefulWidget { + + final List attachments; + final OnDownloadAttachmentActionCallback? onDownloadAttachmentActionCallback; + + const EmailAttachmentsWidget({ + super.key, + required this.attachments, + this.onDownloadAttachmentActionCallback + }); + + @override + State createState() => _EmailAttachmentsWidgetState(); +} + +class _EmailAttachmentsWidgetState extends State { + + final _imagePaths = Get.find(); + + bool _isDisplayAll = false; + + @override + Widget build(BuildContext context) { + return Padding( + padding: EmailAttachmentsStyles.padding, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Expanded( + child: Row( + children: [ + const SizedBox(width: EmailAttachmentsStyles.headerSpace), + SvgPicture.asset( + _imagePaths.icAttachment, + width: EmailAttachmentsStyles.headerIconSize, + height: EmailAttachmentsStyles.headerIconSize, + colorFilter: EmailAttachmentsStyles.headerIconColor.asFilter(), + fit: BoxFit.fill + ), + const SizedBox(width: EmailAttachmentsStyles.headerSpace), + Expanded( + child: Text( + AppLocalizations.of(context).titleHeaderAttachment( + widget.attachments.length, + filesize(widget.attachments.totalSize(), 1) + ), + style: const TextStyle( + fontSize: EmailAttachmentsStyles.headerTextSize, + fontWeight: EmailAttachmentsStyles.headerFontWeight, + color: EmailAttachmentsStyles.headerTextColor + ) + ) + ), + const SizedBox(width: EmailAttachmentsStyles.headerSpace), + ] + ) + ), + if (widget.attachments.length > 2) + TMailButtonWidget( + text: _isDisplayAll + ? AppLocalizations.of(context).hide + : AppLocalizations.of(context).showAll, + backgroundColor: Colors.transparent, + borderRadius: EmailAttachmentsStyles.buttonBorderRadius, + padding: EmailAttachmentsStyles.buttonPadding, + textStyle: const TextStyle( + fontSize: EmailAttachmentsStyles.buttonTextSize, + color: EmailAttachmentsStyles.buttonTextColor, + fontWeight: EmailAttachmentsStyles.buttonFontWeight + ), + onTapActionCallback: () => setState(() => _isDisplayAll = !_isDisplayAll), + ) + ], + ), + const SizedBox(height: EmailAttachmentsStyles.marginHeader), + if (_isDisplayAll) + Wrap( + runSpacing: EmailAttachmentsStyles.listSpace, + children: widget.attachments + .map((attachment) => AttachmentFileTileBuilder( + attachment, + onDownloadAttachmentFileActionClick: (attachment) => widget.onDownloadAttachmentActionCallback?.call(context, attachment) + )) + .toList() + ) + else + SizedBox( + height: EmailAttachmentsStyles.listHeight, + child: ScrollConfiguration( + behavior: CustomScrollBehavior(), + child: ListView.builder( + shrinkWrap: true, + scrollDirection: Axis.horizontal, + itemCount: widget.attachments.length, + itemBuilder: (context, index) { + return AttachmentFileTileBuilder( + widget.attachments[index], + onDownloadAttachmentFileActionClick: (attachment) => widget.onDownloadAttachmentActionCallback?.call(context, attachment) + ); + } + ), + ) + ), + ], + ), + ); + } +} diff --git a/lib/features/email/presentation/widgets/email_receiver_builder.dart b/lib/features/email/presentation/widgets/email_receiver_widget.dart similarity index 79% rename from lib/features/email/presentation/widgets/email_receiver_builder.dart rename to lib/features/email/presentation/widgets/email_receiver_widget.dart index b2e3fb4b3..3465bf8aa 100644 --- a/lib/features/email/presentation/widgets/email_receiver_builder.dart +++ b/lib/features/email/presentation/widgets/email_receiver_widget.dart @@ -15,46 +15,54 @@ import 'package:model/extensions/list_email_address_extension.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/base/widget/material_text_button.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/prefix_email_address_extension.dart'; -import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/utils/app_utils.dart'; -class EmailReceiverBuilder extends StatelessWidget { +typedef OnPreviewEmailAddressActionCallback = Function(BuildContext context, EmailAddress emailAddress); - static const double _maxSizeFullDisplayEmailAddressArrowDownButton = 30.0; +class EmailReceiverWidget extends StatefulWidget { final PresentationEmail emailSelected; - final SingleEmailController controller; - final ResponsiveUtils responsiveUtils; - final ImagePaths imagePaths; final double maxWidth; + final OnPreviewEmailAddressActionCallback? onPreviewEmailAddressActionCallback; - const EmailReceiverBuilder({ + const EmailReceiverWidget({ Key? key, required this.emailSelected, - required this.controller, - required this.responsiveUtils, - required this.imagePaths, this.maxWidth = 200, + this.onPreviewEmailAddressActionCallback, }) : super(key: key); + @override + State createState() => _EmailReceiverWidgetState(); +} + +class _EmailReceiverWidgetState extends State { + + static const double _maxSizeFullDisplayEmailAddressArrowDownButton = 30.0; + + final _imagePaths = Get.find(); + final _responsiveUtils = Get.find(); + + bool _isDisplayAll = false; + @override Widget build(BuildContext context) { - return Obx(() => Row( + return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded(child: Padding( - padding: EdgeInsets.only(top: controller.isDisplayFullEmailAddress + padding: EdgeInsets.only(top: _isDisplayAll ? DirectionUtils.isDirectionRTLByLanguage(context) ? 3 : 5.5 : 0), child: _buildEmailAddressOfReceiver( context, - emailSelected, - controller.isDisplayFullEmailAddress, - maxWidth + widget.emailSelected, + _isDisplayAll, + widget.maxWidth ), )), - if (controller.isDisplayFullEmailAddress) + if (_isDisplayAll) Padding( padding: EdgeInsets.symmetric( vertical: DirectionUtils.isDirectionRTLByLanguage(context) ? 0 : 6), @@ -62,12 +70,12 @@ class EmailReceiverBuilder extends StatelessWidget { padding: DirectionUtils.isDirectionRTLByLanguage(context) ? const EdgeInsets.symmetric(horizontal: 8, vertical: 4) : null, - onTap: controller.collapseEmailAddress, + onTap: () => setState(() => _isDisplayAll = false), label: AppLocalizations.of(context).hide, ) ) ] - )); + ); } Widget _buildEmailAddressOfReceiver( @@ -113,7 +121,27 @@ class EmailReceiverBuilder extends StatelessWidget { ] ), ), - _buildArrowDownButton() + Material( + color: Colors.transparent, + child: InkWell( + onTap: () => setState(() => _isDisplayAll = true), + customBorder: const CircleBorder(), + child: Container( + padding: const EdgeInsets.all(5), + color: Colors.transparent, + constraints: const BoxConstraints( + maxHeight: _maxSizeFullDisplayEmailAddressArrowDownButton, + maxWidth: _maxSizeFullDisplayEmailAddressArrowDownButton + ), + child: SvgPicture.asset( + _imagePaths.icChevronDown, + width: 16, + height: 16, + fit: BoxFit.fill + ), + ), + ), + ), ] ); } else { @@ -190,7 +218,7 @@ class EmailReceiverBuilder extends StatelessWidget { label: lastEmailAddress == emailAddress ? emailAddress.asString() : '${emailAddress.asString()},', - onTap: () => controller.openEmailAddressDialog(context, emailAddress), + onTap: () => widget.onPreviewEmailAddressActionCallback?.call(context, emailAddress), onLongPress: () { AppUtils.copyEmailAddressToClipboard(context, emailAddress.emailAddress); }, @@ -218,36 +246,12 @@ class EmailReceiverBuilder extends StatelessWidget { } double _getMaxWidthEmailAddressDisplayed(BuildContext context, double maxWidth) { - if (responsiveUtils.isPortraitMobile(context)) { + if (_responsiveUtils.isPortraitMobile(context)) { return maxWidth - _maxSizeFullDisplayEmailAddressArrowDownButton; - } else if (responsiveUtils.isWebDesktop(context)) { + } else if (_responsiveUtils.isWebDesktop(context)) { return maxWidth / 2; } else { return maxWidth * 3/4; } } - - Widget _buildArrowDownButton() { - return Material( - color: Colors.transparent, - child: InkWell( - onTap: controller.expandEmailAddress, - customBorder: const CircleBorder(), - child: Container( - padding: const EdgeInsets.all(5), - color: Colors.transparent, - constraints: const BoxConstraints( - maxHeight: _maxSizeFullDisplayEmailAddressArrowDownButton, - maxWidth: _maxSizeFullDisplayEmailAddressArrowDownButton - ), - child: SvgPicture.asset( - imagePaths.icChevronDown, - width: 16, - height: 16, - fit: BoxFit.fill - ), - ), - ), - ); - } } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/email_subject_widget.dart b/lib/features/email/presentation/widgets/email_subject_widget.dart new file mode 100644 index 000000000..e7611f963 --- /dev/null +++ b/lib/features/email/presentation/widgets/email_subject_widget.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; +import 'package:model/email/presentation_email.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/email_subject_styles.dart'; + +class EmailSubjectWidget extends StatelessWidget { + + final PresentationEmail presentationEmail; + + const EmailSubjectWidget({super.key, required this.presentationEmail}); + + @override + Widget build(BuildContext context) { + return Padding( + padding: EmailSubjectStyles.padding, + child: SelectableText( + presentationEmail.getEmailTitle(), + maxLines: EmailSubjectStyles.maxLines, + minLines: EmailSubjectStyles.minLines, + cursorColor: EmailSubjectStyles.cursorColor, + style: const TextStyle( + fontSize: EmailSubjectStyles.textSize, + color: EmailSubjectStyles.textColor, + fontWeight: EmailSubjectStyles.fontWeight + ) + ) + ); + } +} diff --git a/lib/features/email/presentation/widgets/email_view_empty_widget.dart b/lib/features/email/presentation/widgets/email_view_empty_widget.dart new file mode 100644 index 000000000..39f86e4a9 --- /dev/null +++ b/lib/features/email/presentation/widgets/email_view_empty_widget.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; +import 'package:tmail_ui_user/features/email/presentation/styles/email_view_empty_styles.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; + +class EmailViewEmptyWidget extends StatelessWidget { + + const EmailViewEmptyWidget({super.key}); + + @override + Widget build(BuildContext context) { + return Center( + child: Text( + AppLocalizations.of(context).no_mail_selected, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: EmailViewEmptyStyles.textSize, + color: EmailViewEmptyStyles.textColor, + fontWeight: EmailViewEmptyStyles.fontWeight + ) + ), + ); + } +} diff --git a/lib/features/email/presentation/widgets/email_view_loading_bar_widget.dart b/lib/features/email/presentation/widgets/email_view_loading_bar_widget.dart index 4d92cf478..644712178 100644 --- a/lib/features/email/presentation/widgets/email_view_loading_bar_widget.dart +++ b/lib/features/email/presentation/widgets/email_view_loading_bar_widget.dart @@ -23,7 +23,7 @@ class EmailViewLoadingBarWidget extends StatelessWidget { return viewState.fold( (failure) => const SizedBox.shrink(), (success) { - if (success is GetEmailContentLoading || (success is ParseCalendarEventLoading && selectedEmail.hasCalendarEvent)) { + if (success is GetEmailContentLoading || success is ParseCalendarEventLoading) { return const CupertinoLoadingWidget(); } else { return const SizedBox.shrink(); diff --git a/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart b/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart index 4a00793ea..152187a25 100644 --- a/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart +++ b/lib/features/email/presentation/widgets/information_sender_and_receiver_builder.dart @@ -6,7 +6,7 @@ import 'package:model/email/presentation_email.dart'; import 'package:model/extensions/presentation_email_extension.dart'; import 'package:tmail_ui_user/features/base/widget/email_avatar_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/controller/single_email_controller.dart'; -import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_builder.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/email_receiver_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/email_sender_builder.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/received_time_builder.dart'; @@ -56,12 +56,10 @@ class InformationSenderAndReceiverBuilder extends StatelessWidget { ReceivedTimeBuilder(emailSelected), ]), if (emailSelected.numberOfAllEmailAddress() > 0) - EmailReceiverBuilder( - controller: controller, + EmailReceiverWidget( emailSelected: emailSelected, - responsiveUtils: responsiveUtils, - imagePaths: imagePaths, maxWidth: constraints.maxWidth, + onPreviewEmailAddressActionCallback: controller.openEmailAddressDialog, ) ] ),