TF-105 [BUG] Fix loading is blinking when reading email

This commit is contained in:
dab246
2021-10-11 23:59:40 +07:00
committed by Dat H. Pham
parent ca28b55d24
commit e654fe2866
20 changed files with 359 additions and 213 deletions
@@ -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<HtmlContentViewer> {
@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);
@@ -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<MessageContent>, List<Attachment>, 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 = '</br></br></br>$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}</br>';
@@ -281,7 +267,7 @@ class ComposerController extends BaseController {
Set<EmailBodyPart> _generateAttachments() {
return attachments.map((attachment) =>
attachment.toEmailBodyPart(Attachment.dispositionAttachment)).toSet();
attachment.toEmailBodyPart(ContentDisposition.attachment.value)).toSet();
}
void sendEmailAction(BuildContext context) async {
@@ -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<EmailContent> emailContents;
final List<Attachment> attachments;
GetEmailContentSuccess(this.emailContent);
GetEmailContentSuccess(this.emailContents, this.attachments);
@override
List<Object?> get props => [emailContent];
List<Object?> get props => [emailContents, attachments];
}
class GetEmailContentFailure extends FeatureFailure {
@@ -15,7 +15,9 @@ class GetEmailContentInteractor {
try {
yield Right<Failure, Success>(LoadingState());
final email = await emailRepository.getEmailContent(accountId, emailId);
yield Right<Failure, Success>(GetEmailContentSuccess(email.toEmailContent()));
yield Right<Failure, Success>(GetEmailContentSuccess(
email.emailContentList,
email.allAttachments));
} catch (e) {
yield Left(GetEmailContentFailure(e));
}
@@ -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<EmailContent>();
final emailContents = <EmailContent>[].obs;
final attachments = <Attachment>[].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!,
+31 -53
View File
@@ -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());
}
}
@@ -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<MessageContent> getListMessageContent() {
Map<PartId, TextFormat> mapHtmlBody = Map();
List<MessageContent> 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<Attachment> getListAttachment() {
if (attachments != null) {
return attachments!
.where((element) => (element.disposition != null && element.disposition != 'inline'))
.map((item) => item.toAttachment())
.toList();
}
return [];
}
List<Attachment> getListAttachmentInline() {
if (attachments != null) {
return attachments!
.where((element) => element.disposition == 'inline')
.map((item) => item.toAttachment())
.toList();
}
return [];
}
}
@@ -11,4 +11,12 @@ extension ListAttachmentExtension on List<Attachment> {
});
return totalSize;
}
List<Attachment> get attachmentsWithDispositionAttachment {
return where((element) => element.disposition == ContentDisposition.attachment).toList();
}
List<Attachment> get attachmentWithDispositionInlines {
return where((element) => element.disposition == ContentDisposition.inline).toList();
}
}
@@ -7,7 +7,8 @@ import 'package:model/model.dart';
class ComposerArguments with EquatableMixin {
final EmailActionType emailActionType;
final PresentationEmail? presentationEmail;
final EmailContent? emailContent;
final List<EmailContent>? emailContents;
final List<Attachment>? attachments;
final Session session;
final UserProfile userProfile;
final Map<Role, MailboxId> 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<Object?> get props => [
emailActionType,
presentationEmail,
emailContent,
emailContents,
attachments,
mailboxRole,
session,
userProfile,
@@ -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<Attachment> 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<Object?> get props => [textFormat, content];
}
@@ -1,5 +0,0 @@
enum TextFormat {
HTML,
PLAIN
}
@@ -4,6 +4,13 @@ import 'package:core/core.dart';
class WebViewLoadingState extends UIState {
WebViewLoadingState();
@override
List<Object?> get props => [];
}
class WebViewLoadCompletedState extends UIState {
WebViewLoadCompletedState();
@override
List<Object?> get props => [];
}
@@ -0,0 +1,145 @@
import 'package:core/core.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
const List<Color> _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<Color> 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<EmailContentPlaceHolderLoading> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _animation;
@override
void initState() {
super.initState();
_animationController = AnimationController(duration: Duration(seconds: 1), vsync: this)..repeat();
_animation = Tween<double>(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))
],
))
]
)
);
}
}
+36 -3
View File
@@ -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<Object?> 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;
}
}
+20 -21
View File
@@ -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<EmailBodyPart>? htmlBody;
final Set<EmailBodyPart>? attachments;
final Map<PartId, EmailBodyValue>? 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<Attachment> 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<Object?> get props => [
id,
htmlBody,
attachments,
bodyValues
];
List<Object?> get props => [type, content];
}
+6
View File
@@ -0,0 +1,6 @@
enum EmailContentType {
textHtml,
textPlain,
other
}
@@ -9,5 +9,5 @@ extension EmailBodyPartExtension on EmailBodyPart {
name: name,
type: type,
cid: cid,
disposition: disposition);
disposition: disposition.toContentDisposition());
}
+30 -9
View File
@@ -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<KeyWordIdentifier, bool>? newKeywords}) {
return Email(
id,
@@ -86,4 +79,32 @@ extension EmailExtension on Email {
mailboxIds: updatedProperties.contain(EmailProperty.mailboxIds) ? newEmail.mailboxIds : mailboxIds,
);
}
List<EmailContent> get emailContentList {
final newHtmlBody = htmlBody
?.where((emailBody) => emailBody.partId != null && emailBody.type != null)
.toList() ?? <EmailBodyPart>[];
final mapHtmlBody = Map<PartId, MediaType>.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<Attachment> get allAttachments {
if (attachments != null) {
return attachments!
.where((element) => element.disposition != null)
.map((item) => item.toAttachment())
.toList();
}
return [];
}
}
@@ -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;
}
}
}
}
+2
View File
@@ -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';