TF-105 [BUG] Fix loading is blinking when reading email
This commit is contained in:
@@ -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!,
|
||||
|
||||
@@ -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 => [];
|
||||
}
|
||||
+145
@@ -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))
|
||||
],
|
||||
))
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user