diff --git a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart index 16f8eb740..74973d8aa 100644 --- a/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart +++ b/core/lib/presentation/views/html_viewer/html_content_viewer_widget.dart @@ -1,4 +1,3 @@ -import 'package:core/core.dart'; import 'package:core/presentation/utils/html_transformer/html_transform.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; @@ -109,15 +108,7 @@ class _HtmlContentViewState extends State { @override Widget build(BuildContext context) { if (_isGenerating) { - return Padding( - padding: const EdgeInsets.all(16.0), - child: Center( - child: SizedBox( - width: 30, - height: 30, - child: CircularProgressIndicator(color: AppColor.primaryColor)), - ), - ); + return SizedBox.shrink(); } if (widget.blockExternalImages != _wereExternalImagesBlocked) { _generateHtml(widget.blockExternalImages); diff --git a/lib/features/composer/presentation/composer_controller.dart b/lib/features/composer/presentation/composer_controller.dart index 152b5b499..0c2104877 100644 --- a/lib/features/composer/presentation/composer_controller.dart +++ b/lib/features/composer/presentation/composer_controller.dart @@ -10,7 +10,6 @@ import 'package:flutter/cupertino.dart'; import 'package:get/get.dart'; import 'package:http_parser/http_parser.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; -import 'package:jmap_dart_client/jmap/core/session/session.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'; @@ -30,9 +29,7 @@ import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_inter import 'package:tmail_ui_user/features/composer/domain/usecases/upload_mutiple_attachment_interactor.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/constants/email_constants.dart'; -import 'package:tmail_ui_user/features/email/presentation/extensions/email_content_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/message_content.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/features/upload/domain/state/local_file_picker_state.dart'; import 'package:tmail_ui_user/features/upload/domain/usecases/local_file_picker_interactor.dart'; @@ -157,19 +154,6 @@ class ComposerController extends BaseController { return null; } - Tuple3, List, Session>? getContentEmailQuoted() { - if (composerArguments.value != null) { - final emailContent = composerArguments.value!.emailContent; - final session = composerArguments.value!.session; - if (emailContent != null) { - final messageContents = emailContent.getListMessageContent(); - final attachmentsInline = emailContent.getListAttachmentInline(); - return Tuple3(messageContents, attachmentsInline, session); - } - } - return null; - } - void expandEmailAddressAction() { final newExpandMode = expandMode.value == ExpandMode.COLLAPSE ? ExpandMode.EXPAND : ExpandMode.COLLAPSE; expandMode.value = newExpandMode; @@ -229,7 +213,6 @@ class ComposerController extends BaseController { String _getBodyEmailQuotedAsHtml(BuildContext context, HtmlMessagePurifier htmlMessagePurifier) { final headerEmailQuoted = getHeaderEmailQuoted(Localizations.localeOf(context).toLanguageTag()); - final contentEmail = getContentEmailQuoted(); final headerEmailQuotedAsHtml = headerEmailQuoted != null ? AppLocalizations.of(context).header_email_quoted(headerEmailQuoted.value1, headerEmailQuoted.value2) @@ -237,9 +220,12 @@ class ComposerController extends BaseController { : ''; var trustAsHtml = ''; - if (contentEmail != null && contentEmail.value1.isNotEmpty) { - final messageContent = contentEmail.value1.first; - trustAsHtml = messageContent.content; + + if (composerArguments.value != null) { + final emailContents = composerArguments.value!.emailContents; + if (emailContents != null && emailContents.isNotEmpty) { + trustAsHtml = emailContents.first.content; + } } final emailQuotedHtml = '


$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}
'; @@ -281,7 +267,7 @@ class ComposerController extends BaseController { Set _generateAttachments() { return attachments.map((attachment) => - attachment.toEmailBodyPart(Attachment.dispositionAttachment)).toSet(); + attachment.toEmailBodyPart(ContentDisposition.attachment.value)).toSet(); } void sendEmailAction(BuildContext context) async { diff --git a/lib/features/email/domain/state/get_email_content_state.dart b/lib/features/email/domain/state/get_email_content_state.dart index 91669e611..2726c39a8 100644 --- a/lib/features/email/domain/state/get_email_content_state.dart +++ b/lib/features/email/domain/state/get_email_content_state.dart @@ -1,13 +1,14 @@ import 'package:core/core.dart'; -import 'package:model/email/email_content.dart'; +import 'package:model/model.dart'; class GetEmailContentSuccess extends UIState { - final EmailContent? emailContent; + final List emailContents; + final List attachments; - GetEmailContentSuccess(this.emailContent); + GetEmailContentSuccess(this.emailContents, this.attachments); @override - List get props => [emailContent]; + List get props => [emailContents, attachments]; } class GetEmailContentFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/get_email_content_interactor.dart b/lib/features/email/domain/usecases/get_email_content_interactor.dart index cc1e770ad..60501dbdb 100644 --- a/lib/features/email/domain/usecases/get_email_content_interactor.dart +++ b/lib/features/email/domain/usecases/get_email_content_interactor.dart @@ -15,7 +15,9 @@ class GetEmailContentInteractor { try { yield Right(LoadingState()); final email = await emailRepository.getEmailContent(accountId, emailId); - yield Right(GetEmailContentSuccess(email.toEmailContent())); + yield Right(GetEmailContentSuccess( + email.emailContentList, + email.allAttachments)); } catch (e) { yield Left(GetEmailContentFailure(e)); } diff --git a/lib/features/email/presentation/email_controller.dart b/lib/features/email/presentation/email_controller.dart index eadc73be2..b0ec46a01 100644 --- a/lib/features/email/presentation/email_controller.dart +++ b/lib/features/email/presentation/email_controller.dart @@ -26,6 +26,7 @@ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_star_email_ import 'package:tmail_ui_user/features/email/domain/usecases/mark_as_email_read_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/move_to_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; +import 'package:tmail_ui_user/features/email/presentation/model/web_view_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/mailbox_dashboard_controller.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/app_routes.dart'; @@ -48,7 +49,8 @@ class EmailController extends BaseController { final emailAddressExpandMode = ExpandMode.COLLAPSE.obs; final attachmentsExpandMode = ExpandMode.COLLAPSE.obs; - final emailContent = Rxn(); + final emailContents = [].obs; + final attachments = [].obs; EmailId? _currentEmailId; EmailController( @@ -109,7 +111,7 @@ class EmailController extends BaseController { }, (success) { if (success is GetEmailContentSuccess) { - emailContent.value = success.emailContent; + _getEmailContentSuccess(success); } else if (success is MarkAsEmailReadSuccess) { _markAsEmailReadSuccess(success); } else if (success is ExportAttachmentSuccess) { @@ -126,10 +128,19 @@ class EmailController extends BaseController { void onError(error) { } + void _getEmailContentSuccess(GetEmailContentSuccess success) { + emailContents.value = success.emailContents; + attachments.value = success.attachments; + if (emailContents.isNotEmpty) { + dispatchState(Right(WebViewLoadingState())); + } + } + void _clearEmailContent() { toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE); attachmentsExpandMode.value = ExpandMode.COLLAPSE; - emailContent.value = null; + emailContents.clear(); + attachments.clear(); } void toggleDisplayEmailAddressAction({required ExpandMode expandMode}) { @@ -150,13 +161,19 @@ class EmailController extends BaseController { } mailboxDashBoardController.dispatchState(Right(success)); - if (success is MarkAsEmailReadSuccess && success.readActions == ReadActions.markAsUnread) { + if (success is MarkAsEmailReadSuccess + && success.readActions == ReadActions.markAsUnread + && Get.context != null + && !responsiveUtils.isDesktop(Get.context!)) { backToThreadView(); } } void _markAsEmailReadFailure(Failure failure) { - if (failure is MarkAsEmailReadFailure && failure.readActions == ReadActions.markAsUnread) { + if (failure is MarkAsEmailReadFailure + && failure.readActions == ReadActions.markAsUnread + && Get.context != null + && !responsiveUtils.isDesktop(Get.context!)) { backToThreadView(); } } @@ -330,17 +347,25 @@ class EmailController extends BaseController { && mailboxDashBoardController.selectedEmail.value != null; void backToThreadView() { + if (emailContents.isNotEmpty) { + dispatchState(Right(WebViewLoadCompletedState())); + } popBack(); } void pressEmailAction(EmailActionType emailActionType) { if (canComposeEmail()) { + if (emailContents.isNotEmpty) { + clearState(); + } + push( AppRoutes.COMPOSER, arguments: ComposerArguments( emailActionType: emailActionType, presentationEmail: mailboxDashBoardController.selectedEmail.value!, - emailContent: emailContent.value, + emailContents: emailContents, + attachments: attachments, mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role, session: mailboxDashBoardController.sessionCurrent!, userProfile: mailboxDashBoardController.userProfile.value!, diff --git a/lib/features/email/presentation/email_view.dart b/lib/features/email/presentation/email_view.dart index 7e0806c8a..0f418a1f5 100644 --- a/lib/features/email/presentation/email_view.dart +++ b/lib/features/email/presentation/email_view.dart @@ -8,13 +8,12 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/email/presentation/email_controller.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/text_format.dart'; import 'package:tmail_ui_user/features/email/presentation/model/web_view_state.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/extensions/email_content_extension.dart'; import 'package:tmail_ui_user/features/email/presentation/extensions/list_attachment_extension.dart'; +import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart'; import 'package:tmail_ui_user/features/email/presentation/widgets/sender_and_receiver_information_tile_builder.dart'; import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:filesize/filesize.dart'; @@ -28,6 +27,11 @@ class EmailView extends GetView { @override Widget build(BuildContext context) { + emailController.viewState.value.map((success) { + if (success is WebViewLoadCompletedState && emailController.emailContents.isNotEmpty) { + emailController.dispatchState(Right(WebViewLoadingState())); + } + }); return Scaffold( resizeToAvoidBottomInset: false, backgroundColor: AppColor.primaryLightColor, @@ -41,7 +45,7 @@ class EmailView extends GetView { crossAxisAlignment: CrossAxisAlignment.center, children: [ _buildAppBar(context), - Expanded(child: _buildEmailContent(context)), + Expanded(child: _buildEmailBody(context)), _buildBottomBar(context), ]) ) @@ -74,7 +78,7 @@ class EmailView extends GetView { .build()); } - Widget _buildEmailContent(BuildContext context) { + Widget _buildEmailBody(BuildContext context) { return Container( color: AppColor.bgMessenger, margin: EdgeInsets.zero, @@ -122,12 +126,7 @@ class EmailView extends GetView { return Obx(() => emailController.viewState.value.fold( (failure) => SizedBox.shrink(), (success) => success is LoadingState || success is WebViewLoadingState - ? Center(child: Padding( - padding: EdgeInsets.only(top: 16, bottom: 16), - child: SizedBox( - width: 30, - height: 30, - child: CircularProgressIndicator(color: AppColor.primaryColor)))) + ? EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils) : SizedBox.shrink())); } @@ -150,29 +149,27 @@ class EmailView extends GetView { emailController.emailAddressExpandMode.value) .onOpenExpandAddressReceiverActionClick(() => emailController.toggleDisplayEmailAddressAction(expandMode: ExpandMode.EXPAND)) .build()), - _buildListAttachments(context), _buildLoadingView(), - _buildListMessageContent(), + _buildAttachments(context), + _buildEmailContent(), ], ) ); } - Widget _buildListAttachments(BuildContext context) { - if (emailController.mailboxDashBoardController.selectedEmail.value?.hasAttachment == true) { - return Obx(() { - if (emailController.emailContent.value != null) { - final attachments = emailController.emailContent.value!.getListAttachment(); - return attachments.isNotEmpty - ? _buildAttachmentsBody(context, attachments) - : SizedBox.shrink(); - } else { - return SizedBox.shrink(); - } - }); - } else { - return SizedBox.shrink(); - } + Widget _buildAttachments(BuildContext context) { + return Obx(() => emailController.viewState.value.fold( + (failure) => SizedBox.shrink(), + (success) { + if (success is LoadingState || success is WebViewLoadingState) { + return SizedBox.shrink(); + } else { + final attachments = emailController.attachments.attachmentsWithDispositionAttachment; + return attachments.isNotEmpty + ? _buildAttachmentsBody(context, attachments) + : SizedBox.shrink(); + } + })); } int _getAttachmentLimitDisplayed(BuildContext context) { @@ -281,31 +278,12 @@ class EmailView extends GetView { ); } - Widget _buildListMessageContent() { - return Obx(() { - final messageContents = emailController.emailContent.value?.getListMessageContent(); - - if (messageContents != null && messageContents.isNotEmpty) { - return messageContents.first.textFormat == TextFormat.PLAIN - ? Padding( - padding: EdgeInsets.only(top: 16), - child: Text( - '${messageContents.first.content}', - style: TextStyle(fontSize: 12, - color: AppColor.mailboxTextColor))) - : HtmlContentViewer( - message: messageContents.first.content, - onWebViewLoadStart: (webController, uri) { - emailController.dispatchState(Right(WebViewLoadingState())); - }, - onWebViewLoadStop: (webController, uri) { - emailController.clearState(); - }, - mailtoDelegate: (uri) async {}, - ); - } else { - return SizedBox.shrink(); - } - }); + Widget _buildEmailContent() { + return Obx(() => emailController.emailContents.isNotEmpty + ? HtmlContentViewer( + message: emailController.emailContents.first.content, + onWebViewLoadStop: (webController, uri) => emailController.dispatchState(Right(WebViewLoadCompletedState())), + mailtoDelegate: (uri) async {}) + : SizedBox.shrink()); } } \ No newline at end of file diff --git a/lib/features/email/presentation/extensions/email_content_extension.dart b/lib/features/email/presentation/extensions/email_content_extension.dart deleted file mode 100644 index 9631f2ed6..000000000 --- a/lib/features/email/presentation/extensions/email_content_extension.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; -import 'package:tmail_ui_user/features/email/presentation/constants/email_constants.dart'; -import 'package:model/model.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/message_content.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/text_format.dart'; - -extension EmailContentExtension on EmailContent { - - List getListMessageContent() { - Map mapHtmlBody = Map(); - List listMessageContent = []; - - htmlBody?.forEach((element) { - if (element.partId != null && element.type != null) { - mapHtmlBody[element.partId!] = element.type!.mimeType == EmailConstants.HTML_TEXT - ? TextFormat.HTML - : TextFormat.PLAIN; - } - }); - - bodyValues?.forEach((key, value) { - listMessageContent.add(MessageContent(mapHtmlBody[key] ?? TextFormat.PLAIN, value.value)); - }); - - return listMessageContent; - } - - List getListAttachment() { - if (attachments != null) { - return attachments! - .where((element) => (element.disposition != null && element.disposition != 'inline')) - .map((item) => item.toAttachment()) - .toList(); - } - return []; - } - - List getListAttachmentInline() { - if (attachments != null) { - return attachments! - .where((element) => element.disposition == 'inline') - .map((item) => item.toAttachment()) - .toList(); - } - return []; - } -} \ No newline at end of file diff --git a/lib/features/email/presentation/extensions/list_attachment_extension.dart b/lib/features/email/presentation/extensions/list_attachment_extension.dart index 8bc3f1543..1abfffc31 100644 --- a/lib/features/email/presentation/extensions/list_attachment_extension.dart +++ b/lib/features/email/presentation/extensions/list_attachment_extension.dart @@ -11,4 +11,12 @@ extension ListAttachmentExtension on List { }); return totalSize; } + + List get attachmentsWithDispositionAttachment { + return where((element) => element.disposition == ContentDisposition.attachment).toList(); + } + + List get attachmentWithDispositionInlines { + return where((element) => element.disposition == ContentDisposition.inline).toList(); + } } \ No newline at end of file diff --git a/lib/features/email/presentation/model/composer_arguments.dart b/lib/features/email/presentation/model/composer_arguments.dart index e57fda001..bb58e4fe5 100644 --- a/lib/features/email/presentation/model/composer_arguments.dart +++ b/lib/features/email/presentation/model/composer_arguments.dart @@ -7,7 +7,8 @@ import 'package:model/model.dart'; class ComposerArguments with EquatableMixin { final EmailActionType emailActionType; final PresentationEmail? presentationEmail; - final EmailContent? emailContent; + final List? emailContents; + final List? attachments; final Session session; final UserProfile userProfile; final Map mapMailboxId; @@ -16,7 +17,8 @@ class ComposerArguments with EquatableMixin { ComposerArguments({ this.emailActionType = EmailActionType.compose, this.presentationEmail, - this.emailContent, + this.emailContents, + this.attachments, this.mailboxRole, required this.session, required this.userProfile, @@ -27,7 +29,8 @@ class ComposerArguments with EquatableMixin { List get props => [ emailActionType, presentationEmail, - emailContent, + emailContents, + attachments, mailboxRole, session, userProfile, diff --git a/lib/features/email/presentation/model/message_content.dart b/lib/features/email/presentation/model/message_content.dart deleted file mode 100644 index 831fe5948..000000000 --- a/lib/features/email/presentation/model/message_content.dart +++ /dev/null @@ -1,29 +0,0 @@ - -import 'package:equatable/equatable.dart'; -import 'package:jmap_dart_client/jmap/account_id.dart'; -import 'package:tmail_ui_user/features/email/presentation/model/text_format.dart'; -import 'package:model/model.dart'; - -class MessageContent with EquatableMixin { - - final TextFormat textFormat; - final String content; - - MessageContent(this.textFormat, this.content); - - bool hasImageInlineWithCid() => content.contains('cid:'); - - String getContentHasInlineAttachment(String baseDownloadUrl, AccountId accountId, List attachments) { - var contentValid = content; - attachments.forEach((attachment) { - if(attachment.cid != null) { - final urlDownloadImage = attachment.getDownloadUrl(baseDownloadUrl, accountId); - contentValid = content.replaceAll('cid:${attachment.cid}', '$urlDownloadImage'); - } - }); - return contentValid; - } - - @override - List get props => [textFormat, content]; -} \ No newline at end of file diff --git a/lib/features/email/presentation/model/text_format.dart b/lib/features/email/presentation/model/text_format.dart deleted file mode 100644 index 15e1f221c..000000000 --- a/lib/features/email/presentation/model/text_format.dart +++ /dev/null @@ -1,5 +0,0 @@ - -enum TextFormat { - HTML, - PLAIN -} \ No newline at end of file diff --git a/lib/features/email/presentation/model/web_view_state.dart b/lib/features/email/presentation/model/web_view_state.dart index fede26ce7..6a0bad60d 100644 --- a/lib/features/email/presentation/model/web_view_state.dart +++ b/lib/features/email/presentation/model/web_view_state.dart @@ -4,6 +4,13 @@ import 'package:core/core.dart'; class WebViewLoadingState extends UIState { WebViewLoadingState(); + @override + List get props => []; +} + +class WebViewLoadCompletedState extends UIState { + WebViewLoadCompletedState(); + @override List get props => []; } \ No newline at end of file diff --git a/lib/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart b/lib/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart new file mode 100644 index 000000000..a4d6e1158 --- /dev/null +++ b/lib/features/email/presentation/widgets/email_content_place_holder_loading_widget.dart @@ -0,0 +1,145 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +const List _defaultColors = [Color.fromRGBO(0, 0, 0, 0.1), Color(0x44CCCCCC), Color.fromRGBO(0, 0, 0, 0.1)]; + +Decoration _radiusBoxDecoration({ + required Animation animation, + bool hasCustomColors = false, + AlignmentGeometry beginAlign = Alignment.topLeft, + AlignmentGeometry endAlign = Alignment.bottomRight, + List colors = _defaultColors, + double radius = 10.0 +}) { + return BoxDecoration( + borderRadius: BorderRadius.circular(radius), + shape: BoxShape.rectangle, + gradient: LinearGradient( + begin: beginAlign, + end: endAlign, + colors: hasCustomColors + ? colors.map((color) { + return color; + }).toList() + : [ + Color.fromRGBO(0, 0, 0, 0.1), + Color(0x32E5E5E5), + Color.fromRGBO(0, 0, 0, 0.1), + ], + stops: [animation.value - 2, animation.value, animation.value + 1])); +} + +class EmailContentPlaceHolderLoading extends StatefulWidget { + + final ResponsiveUtils responsiveUtils; + + const EmailContentPlaceHolderLoading({ + Key? key, + required this.responsiveUtils, + }) : super(key: key); + + @override + _EmailContentPlaceHolderLoadingState createState() => _EmailContentPlaceHolderLoadingState(); +} + +class _EmailContentPlaceHolderLoadingState extends State with SingleTickerProviderStateMixin { + late AnimationController _animationController; + late Animation _animation; + + @override + void initState() { + super.initState(); + _animationController = AnimationController(duration: Duration(seconds: 1), vsync: this)..repeat(); + _animation = Tween(begin: -2, end: 2) + .animate(CurvedAnimation(curve: Curves.easeInOutSine, parent: _animationController)); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return AnimatedBuilder( + animation: _animation, + builder: (BuildContext context, Widget? child) { + return Container( + margin: EdgeInsets.only(top: 20), + padding: EdgeInsets.zero, + color: Colors.transparent, + child: Column( + children: [ + Row(children: [ + _placeHolderAttachment(context), + SizedBox(width: 16), + _placeHolderAttachment(context), + if (!widget.responsiveUtils.isMobile(context)) + SizedBox(width: 16), + if (!widget.responsiveUtils.isMobile(context)) + _placeHolderAttachment(context), + if (widget.responsiveUtils.isTablet(context)) + SizedBox(width: 16), + if (widget.responsiveUtils.isTablet(context)) + _placeHolderAttachment(context), + ]), + SizedBox(height: 16), + Container( + margin: EdgeInsets.only(right: 64), + height: 10, + decoration: _radiusBoxDecoration(animation: _animation, radius: 5)), + SizedBox(height: 16), + Container( + height: 10, + decoration: _radiusBoxDecoration(animation: _animation, radius: 5)), + SizedBox(height: 16), + Container( + margin: EdgeInsets.only(right: 128), + height: 10, + decoration: _radiusBoxDecoration(animation: _animation, radius: 5)), + ], + ), + ); + }, + ); + } + + Widget _placeHolderAttachment(BuildContext context) { + double width = MediaQuery.of(context).size.width; + final percentAttachment = widget.responsiveUtils.isMobile(context) + ? 0.4 + : widget.responsiveUtils.isTablet(context) ? 0.22 : 0.14; + return Container( + height: 55, + width: width * percentAttachment, + margin: EdgeInsets.zero, + padding: EdgeInsets.symmetric(horizontal: 8, vertical: 16), + decoration: _radiusBoxDecoration(animation: _animation), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 24, + width: 24, + decoration: _radiusBoxDecoration(animation: _animation, radius: 12)), + SizedBox(width: 8), + Expanded(child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 5, + decoration: _radiusBoxDecoration(animation: _animation)), + SizedBox(height: 10), + Container( + height: 5, + decoration: _radiusBoxDecoration(animation: _animation)) + ], + )) + ] + ) + ); + } +} \ No newline at end of file diff --git a/model/lib/email/attachment.dart b/model/lib/email/attachment.dart index d10b0acdd..8185f6abf 100644 --- a/model/lib/email/attachment.dart +++ b/model/lib/email/attachment.dart @@ -9,15 +9,13 @@ import 'package:uri/uri.dart'; class Attachment with EquatableMixin { - static final String dispositionAttachment = 'attachment'; - final PartId? partId; final Id? blobId; final UnsignedInt? size; final String? name; final MediaType? type; final String? cid; - final String? disposition; + final ContentDisposition? disposition; Attachment({ this.partId, @@ -42,4 +40,39 @@ class Attachment with EquatableMixin { @override List get props => [partId, blobId, size, name, type, cid, disposition]; +} + +enum ContentDisposition { + inline, + attachment, + other +} + +extension ContentDispositionExtension on ContentDisposition { + String get value { + switch(this) { + case ContentDisposition.inline: + return 'inline'; + case ContentDisposition.attachment: + return 'attachment'; + case ContentDisposition.other: + return this.toString(); + } + } +} + +extension DispositionStringExtension on String? { + ContentDisposition? toContentDisposition() { + if (this != null) { + switch(this) { + case 'inline': + return ContentDisposition.inline; + case 'attachment': + return ContentDisposition.attachment; + default: + return ContentDisposition.other; + } + } + return null; + } } \ No newline at end of file diff --git a/model/lib/email/email_content.dart b/model/lib/email/email_content.dart index af5c633a4..a9e54b43f 100644 --- a/model/lib/email/email_content.dart +++ b/model/lib/email/email_content.dart @@ -1,30 +1,29 @@ import 'package:equatable/equatable.dart'; -import 'package:jmap_dart_client/jmap/mail/email/email.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/account_id.dart'; +import 'package:model/email/email_content_type.dart'; +import 'package:model/model.dart'; class EmailContent with EquatableMixin { - final EmailId id; - final Set? htmlBody; - final Set? attachments; - final Map? bodyValues; + final String content; + final EmailContentType type; - EmailContent( - this.id, - { - this.htmlBody, - this.attachments, - this.bodyValues - } - ); + EmailContent(this.type, this.content); + + bool hasImageInlineWithCid() => content.contains('cid:'); + + String getContentHasInlineAttachment(String baseDownloadUrl, AccountId accountId, List attachments) { + var contentValid = content; + attachments.forEach((attachment) { + if(attachment.cid != null) { + final urlDownloadImage = attachment.getDownloadUrl(baseDownloadUrl, accountId); + contentValid = content.replaceAll('cid:${attachment.cid}', '$urlDownloadImage'); + } + }); + return contentValid; + } @override - List get props => [ - id, - htmlBody, - attachments, - bodyValues - ]; + List get props => [type, content]; } \ No newline at end of file diff --git a/model/lib/email/email_content_type.dart b/model/lib/email/email_content_type.dart new file mode 100644 index 000000000..8e94935ef --- /dev/null +++ b/model/lib/email/email_content_type.dart @@ -0,0 +1,6 @@ + +enum EmailContentType { + textHtml, + textPlain, + other +} \ No newline at end of file diff --git a/model/lib/extensions/email_body_part_extension.dart b/model/lib/extensions/email_body_part_extension.dart index 6c5b2c332..9cfc214fa 100644 --- a/model/lib/extensions/email_body_part_extension.dart +++ b/model/lib/extensions/email_body_part_extension.dart @@ -9,5 +9,5 @@ extension EmailBodyPartExtension on EmailBodyPart { name: name, type: type, cid: cid, - disposition: disposition); + disposition: disposition.toContentDisposition()); } diff --git a/model/lib/extensions/email_extension.dart b/model/lib/extensions/email_extension.dart index 86369ef24..c7db294c2 100644 --- a/model/lib/extensions/email_extension.dart +++ b/model/lib/extensions/email_extension.dart @@ -1,6 +1,8 @@ +import 'package:http_parser/http_parser.dart'; import 'package:jmap_dart_client/jmap/core/properties/properties.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email_body_part.dart'; import 'package:jmap_dart_client/jmap/mail/email/keyword_identifier.dart'; import 'package:model/email/email_property.dart'; import 'package:model/model.dart'; @@ -22,15 +24,6 @@ extension EmailExtension on Email { return listEmailAddress; } - EmailContent toEmailContent() { - return EmailContent( - id, - htmlBody: htmlBody, - attachments: attachments, - bodyValues: bodyValues - ); - } - Email updatedEmail({Map? newKeywords}) { return Email( id, @@ -86,4 +79,32 @@ extension EmailExtension on Email { mailboxIds: updatedProperties.contain(EmailProperty.mailboxIds) ? newEmail.mailboxIds : mailboxIds, ); } + + List get emailContentList { + final newHtmlBody = htmlBody + ?.where((emailBody) => emailBody.partId != null && emailBody.type != null) + .toList() ?? []; + + final mapHtmlBody = Map.fromIterable( + newHtmlBody, + key: (emailBody) => emailBody.partId!, + value: (emailBody) => emailBody.type!, + ); + + final emailContents = bodyValues?.entries + .map((entries) => EmailContent(mapHtmlBody[entries.key].toEmailContentType(), entries.value.value)) + .toList(); + + return emailContents ?? []; + } + + List get allAttachments { + if (attachments != null) { + return attachments! + .where((element) => element.disposition != null) + .map((item) => item.toAttachment()) + .toList(); + } + return []; + } } \ No newline at end of file diff --git a/model/lib/extensions/media_type_extension.dart b/model/lib/extensions/media_type_extension.dart new file mode 100644 index 000000000..6a86e4639 --- /dev/null +++ b/model/lib/extensions/media_type_extension.dart @@ -0,0 +1,20 @@ +import 'package:http_parser/http_parser.dart'; +import 'package:model/email/email_content_type.dart'; + +extension MediaTypeExtension on MediaType? { + + EmailContentType toEmailContentType() { + if (this == null) { + return EmailContentType.textHtml; + } else { + switch(this!.mimeType) { + case 'text/html': + return EmailContentType.textHtml; + case 'text/plain': + return EmailContentType.textPlain; + default: + return EmailContentType.other; + } + } + } +} \ No newline at end of file diff --git a/model/lib/model.dart b/model/lib/model.dart index a239b312f..54f82f415 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -29,6 +29,7 @@ export 'email/move_action.dart'; export 'email/mark_star_action.dart'; export 'email/email_property.dart'; export 'email/email_filter.dart'; +export 'email/email_content_type.dart'; // Extensions export 'extensions/email_address_extension.dart'; @@ -50,6 +51,7 @@ export 'extensions/properties_extension.dart'; export 'extensions/list_mailbox_extension.dart'; export 'extensions/list_extension.dart'; export 'extensions/list_email_extension.dart'; +export 'extensions/media_type_extension.dart'; // Download export 'download/download_task_id.dart';