TF-185 [BUG] Fix can not display all parts of email which have multiple html part

This commit is contained in:
dab246
2021-10-15 16:14:41 +07:00
committed by Dat H. Pham
parent a6f2a23cec
commit ececc65a26
16 changed files with 282 additions and 154 deletions
@@ -1,3 +1,4 @@
import 'package:core/core.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
@@ -8,6 +9,8 @@ class HtmlContentViewer extends StatefulWidget {
final String contentHtml;
final int minHeight;
final double widthContent;
final Widget? loadingWidget;
/// Register this callback if you want a reference to the [InAppWebViewController].
final void Function(InAppWebViewController controller)? onCreated;
@@ -22,7 +25,9 @@ class HtmlContentViewer extends StatefulWidget {
const HtmlContentViewer({
Key? key,
required this.contentHtml,
required this.widthContent,
this.minHeight = 100,
this.loadingWidget,
this.onCreated,
this.onLoadStart,
this.onLoadStop,
@@ -39,6 +44,7 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
double? _documentWidth = 1.0;
String? _initialPageContent;
late InAppWebViewController _webViewController;
bool loading = true;
@override
void initState() {
@@ -101,15 +107,33 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
@override
Widget build(BuildContext context) {
final size = MediaQuery.of(context).size;
_documentWidth = size.width - 30;
return SizedBox(
height: _documentHeight,
width: _documentWidth,
child: _buildWebView(),
_documentWidth = widget.widthContent;
return Stack(
alignment: AlignmentDirectional.center,
children: [
SizedBox(
height: _documentHeight,
width: _documentWidth,
child: _buildWebView(),
),
if (loading) _buildLoadingView()
],
);
}
Widget _buildLoadingView() {
if (widget.loadingWidget != null) {
return widget.loadingWidget!;
} else {
return Padding(
padding: EdgeInsets.all(16),
child: SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(color: AppColor.primaryColor)));
}
}
Widget _buildWebView() {
if (_initialPageContent == null || _initialPageContent?.isEmpty == true) {
return Container();
@@ -148,6 +172,9 @@ class _HtmlContentViewState extends State<HtmlContentViewer> {
_documentHeight = (scrollHeight + 30.0);
});
}
setState(() {
loading = false;
});
if (widget.onLoadStop != null) {
widget.onLoadStop!(controller, uri);
}
@@ -214,7 +214,10 @@ class ComposerController extends BaseController {
.addBlockTag('p', attribute: 'style=\"font-size:14px;font-style:italic;color:#182952;\"')
: '';
final trustAsHtml = composerArguments.value?.emailContent?.content ?? '';
final trustAsHtml = composerArguments.value?.emailContents
?.map((emailContent) => emailContent.content)
.toList()
.join('</br>') ?? '';
final emailQuotedHtml = '</br></br></br>$headerEmailQuotedAsHtml${trustAsHtml.addBlockQuoteTag()}</br>';
@@ -2,5 +2,5 @@
import 'package:model/model.dart';
abstract class HtmlDataSource {
Future<String> transformToHtml(EmailContent emailContent);
Future<EmailContent> transformEmailContent(EmailContent emailContent);
}
@@ -9,9 +9,9 @@ class HtmlDataSourceImpl extends HtmlDataSource {
HtmlDataSourceImpl(this._htmlAnalyzer);
@override
Future<String> transformToHtml(EmailContent emailContent) {
Future<EmailContent> transformEmailContent(EmailContent emailContent) {
return Future.sync(() async {
return await _htmlAnalyzer.transformToHtml(emailContent);
return await _htmlAnalyzer.transformEmailContent(emailContent);
}).catchError((error) {
throw error;
});
@@ -5,13 +5,14 @@ import 'package:model/model.dart';
class HtmlAnalyzer {
Future<String> transformToHtml(EmailContent emailContent) async {
Future<EmailContent> transformEmailContent(EmailContent emailContent) async {
switch(emailContent.type) {
case EmailContentType.textHtml:
final htmlTransform = HtmlTransform(emailContent.content);
return htmlTransform.transformToHtml();
final htmlContent = htmlTransform.transformToHtml();
return EmailContent(emailContent.type, htmlContent);
default:
return emailContent.content;
return emailContent;
}
}
}
@@ -72,7 +72,11 @@ class EmailRepositoryImpl extends EmailRepository {
}
@override
Future<String> transformEmailContentToHtml(EmailContent emailContent) {
return _htmlDataSource.transformToHtml(emailContent);
Future<List<EmailContent>> transformEmailContent(List<EmailContent> emailContents) async {
return await Future.wait(emailContents
.map((emailContent) async {
return await _htmlDataSource.transformEmailContent(emailContent);
})
.toList());
}
}
@@ -35,5 +35,5 @@ abstract class EmailRepository {
MarkStarAction markStarAction
);
Future<String> transformEmailContentToHtml(EmailContent emailContent);
Future<List<EmailContent>> transformEmailContent(List<EmailContent> emailContents);
}
@@ -2,13 +2,13 @@ import 'package:core/core.dart';
import 'package:model/model.dart';
class GetEmailContentSuccess extends UIState {
final EmailContent emailContent;
final List<EmailContent> emailContents;
final List<Attachment> attachments;
GetEmailContentSuccess(this.emailContent, this.attachments);
GetEmailContentSuccess(this.emailContents, this.attachments);
@override
List<Object?> get props => [emailContent, attachments];
List<Object?> get props => [emailContents, attachments];
}
class GetEmailContentFailure extends FeatureFailure {
@@ -17,15 +17,8 @@ class GetEmailContentInteractor {
final email = await emailRepository.getEmailContent(accountId, emailId);
if (email.emailContentList.isNotEmpty) {
final emailContent = email.emailContentList.first;
if (emailContent.type == EmailContentType.textHtml) {
final contentHtml = await emailRepository.transformEmailContentToHtml(emailContent);
yield Right<Failure, Success>(GetEmailContentSuccess(
EmailContent(emailContent.type, contentHtml),
email.allAttachments));
} else {
yield Right<Failure, Success>(GetEmailContentSuccess(emailContent, email.allAttachments));
}
final newEmailContents = await emailRepository.transformEmailContent(email.emailContentList);
yield Right<Failure, Success>(GetEmailContentSuccess(newEmailContents, email.allAttachments));
} else {
yield Left(GetEmailContentFailure(null));
}
@@ -26,7 +26,6 @@ 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';
@@ -49,7 +48,7 @@ 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;
@@ -129,17 +128,14 @@ class EmailController extends BaseController {
}
void _getEmailContentSuccess(GetEmailContentSuccess success) {
emailContent.value = success.emailContent;
emailContents.value = success.emailContents;
attachments.value = success.attachments;
if (emailContent.value != null && emailContent.value!.type == EmailContentType.textHtml) {
dispatchState(Right(WebViewLoadingState()));
}
}
void _clearEmailContent() {
toggleDisplayEmailAddressAction(expandMode: ExpandMode.COLLAPSE);
attachmentsExpandMode.value = ExpandMode.COLLAPSE;
emailContent.value = null;
emailContents.clear();
attachments.clear();
}
@@ -357,7 +353,7 @@ class EmailController extends BaseController {
arguments: ComposerArguments(
emailActionType: emailActionType,
presentationEmail: mailboxDashBoardController.selectedEmail.value!,
emailContent: emailContent.value,
emailContents: emailContents,
attachments: attachments,
mailboxRole: mailboxDashBoardController.selectedMailbox.value?.role,
session: mailboxDashBoardController.sessionCurrent!,
+35 -46
View File
@@ -1,18 +1,18 @@
import 'dart:io';
import 'package:core/core.dart';
import 'package:dartz/dartz.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
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/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/attachments_place_holder_loading_widget.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/list_attachment_extension.dart';
import 'package:tmail_ui_user/features/email/presentation/widgets/email_content_item_builder.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';
@@ -27,21 +27,17 @@ class EmailView extends GetView {
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
backgroundColor: AppColor.primaryLightColor,
body: SafeArea(
right: false,
left: false,
child: Container(
alignment: Alignment.center,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildAppBar(context),
Expanded(child: _buildEmailBody(context)),
_buildBottomBar(context),
])
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildAppBar(context),
Expanded(child: _buildEmailBody(context)),
_buildBottomBar(context),
]
)
));
}
@@ -116,14 +112,6 @@ class EmailView extends GetView {
));
}
Widget _buildLoadingView() {
return Obx(() => emailController.viewState.value.fold(
(failure) => SizedBox.shrink(),
(success) => success is LoadingState || success is WebViewLoadingState
? EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils)
: SizedBox.shrink()));
}
Widget _buildEmailMessage(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 12, right: 12, top: 16, bottom: 16),
@@ -143,9 +131,8 @@ class EmailView extends GetView {
emailController.emailAddressExpandMode.value)
.onOpenExpandAddressReceiverActionClick(() => emailController.toggleDisplayEmailAddressAction(expandMode: ExpandMode.EXPAND))
.build()),
_buildLoadingView(),
_buildAttachments(context),
_buildEmailContent(),
_buildListEmailContent(),
],
)
);
@@ -155,8 +142,8 @@ class EmailView extends GetView {
return Obx(() => emailController.viewState.value.fold(
(failure) => SizedBox.shrink(),
(success) {
if (success is LoadingState || success is WebViewLoadingState) {
return SizedBox.shrink();
if (success is LoadingState) {
return AttachmentsPlaceHolderLoading(responsiveUtils: responsiveUtils);
} else {
final attachments = emailController.attachments.attachmentsWithDispositionAttachment;
return attachments.isNotEmpty
@@ -272,28 +259,30 @@ class EmailView extends GetView {
);
}
Widget _buildEmailContent() {
return Obx(() {
if (emailController.emailContent.value != null) {
switch(emailController.emailContent.value!.type) {
case EmailContentType.textHtml:
return HtmlContentViewer(
contentHtml: emailController.emailContent.value!.content,
onLoadStart: (webController, uri) => emailController.dispatchState(Right(WebViewLoadingState())),
onLoadStop: (webController, uri) => emailController.dispatchState(Right(WebViewLoadCompletedState())),
mailtoDelegate: (uri) async {});
case EmailContentType.textPlain:
return Padding(
padding: EdgeInsets.only(top: 16),
child: Text(
emailController.emailContent.value!.content,
style: TextStyle(fontSize: 14, color: AppColor.nameUserColor)));
case EmailContentType.other:
Widget _buildListEmailContent() {
return Obx(() => emailController.viewState.value.fold(
(failure) => SizedBox.shrink(),
(success) {
if (success is LoadingState) {
return EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils);
} else {
if (emailController.emailContents.isNotEmpty) {
return ListView.builder(
primary: false,
shrinkWrap: true,
padding: EdgeInsets.only(top: 10),
key: Key('list_email_content'),
itemCount: emailController.emailContents.length,
itemBuilder: (context, index) =>
EmailContentItemBuilder(
context,
emailController.emailContents[index],
loadingWidget: EmailContentPlaceHolderLoading(responsiveUtils: responsiveUtils)
).build());
} else {
return SizedBox.shrink();
}
}
} else {
return SizedBox.shrink();
}
});
}));
}
}
@@ -7,7 +7,7 @@ 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;
@@ -17,7 +17,7 @@ class ComposerArguments with EquatableMixin {
ComposerArguments({
this.emailActionType = EmailActionType.compose,
this.presentationEmail,
this.emailContent,
this.emailContents,
this.attachments,
this.mailboxRole,
required this.session,
@@ -28,8 +28,7 @@ class ComposerArguments with EquatableMixin {
@override
List<Object?> get props => [
emailActionType,
presentationEmail,
emailContent,
emailContents,
attachments,
mailboxRole,
session,
@@ -1,16 +0,0 @@
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,131 @@
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 AttachmentsPlaceHolderLoading extends StatefulWidget {
final ResponsiveUtils responsiveUtils;
const AttachmentsPlaceHolderLoading({
Key? key,
required this.responsiveUtils,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _AttachmentsPlaceHolderLoadingState();
}
class _AttachmentsPlaceHolderLoadingState extends State<AttachmentsPlaceHolderLoading> 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),
]),
],
),
);
},
);
}
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))
],
))
]
)
);
}
}
@@ -0,0 +1,51 @@
import 'package:core/core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:model/model.dart';
class EmailContentItemBuilder {
final BuildContext _context;
final EmailContent _emailContent;
final Widget? loadingWidget;
EmailContentItemBuilder(
this._context,
this._emailContent,
{
this.loadingWidget
}
);
Widget build() {
return Theme(
data: ThemeData(
splashColor: Colors.transparent,
highlightColor: Colors.transparent),
child: MediaQuery(
data: MediaQueryData(padding: EdgeInsets.zero),
child: _buildItem()
)
);
}
Widget _buildItem() {
switch(_emailContent.type) {
case EmailContentType.textHtml:
return HtmlContentViewer(
widthContent: MediaQuery.of(_context).size.width,
contentHtml: _emailContent.content,
loadingWidget: loadingWidget,
mailtoDelegate: (uri) async {});
case EmailContentType.textPlain:
return Padding(
padding: EdgeInsets.zero,
child: Text(
_emailContent.content,
style: TextStyle(fontSize: 14, color: AppColor.nameUserColor)));
case EmailContentType.other:
return SizedBox.shrink();
}
}
}
@@ -41,7 +41,7 @@ class EmailContentPlaceHolderLoading extends StatefulWidget {
}) : super(key: key);
@override
_EmailContentPlaceHolderLoadingState createState() => _EmailContentPlaceHolderLoadingState();
State<StatefulWidget> createState() => _EmailContentPlaceHolderLoadingState();
}
class _EmailContentPlaceHolderLoadingState extends State<EmailContentPlaceHolderLoading> with SingleTickerProviderStateMixin {
@@ -68,25 +68,11 @@ class _EmailContentPlaceHolderLoadingState extends State<EmailContentPlaceHolder
animation: _animation,
builder: (BuildContext context, Widget? child) {
return Container(
margin: EdgeInsets.only(top: 20),
margin: EdgeInsets.only(top: 16),
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,
@@ -106,40 +92,4 @@ class _EmailContentPlaceHolderLoadingState extends State<EmailContentPlaceHolder
},
);
}
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))
],
))
]
)
);
}
}