TF-2520 Handle print email to pdf

Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
dab246
2024-02-28 17:13:10 +07:00
committed by Dat H. Pham
parent 2892d7586d
commit 4f206739aa
5 changed files with 522 additions and 0 deletions
+2
View File
@@ -43,6 +43,8 @@ export 'utils/config/errors.dart';
export 'data/utils/compress_file_utils.dart';
export 'utils/platform_info.dart';
export 'utils/option_param_mixin.dart';
export 'utils/file_utils.dart';
export 'utils/print_utils.dart';
// Views
export 'presentation/views/text/slogan_builder.dart';
+21
View File
@@ -0,0 +1,21 @@
import 'package:equatable/equatable.dart';
class PrintAttachment with EquatableMixin {
final String iconSvg;
final String name;
final String size;
PrintAttachment({
required this.iconSvg,
required this.name,
required this.size
});
@override
List<Object?> get props => [
iconSvg,
name,
size
];
}
+399
View File
@@ -0,0 +1,399 @@
import 'dart:async';
import 'package:core/data/model/print_attachment.dart';
import 'package:core/presentation/extensions/color_extension.dart';
import 'package:core/presentation/resources/image_paths.dart';
import 'package:core/utils/app_logger.dart';
import 'package:flutter/services.dart';
import 'package:htmltopdfwidgets/htmltopdfwidgets.dart' as pw;
import 'package:intl/intl.dart';
import 'package:printing/printing.dart';
class PrintUtils {
final pw.HTMLToPdf _htmlToPdf;
final ImagePaths _imagePaths;
PrintUtils(this._htmlToPdf, this._imagePaths);
Future<pw.Widget> _createEmailContentWidget(String htmlDocument) async {
try {
final List<pw.Widget> bodyWidgets = await _htmlToPdf.convert(htmlDocument);
return pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
...bodyWidgets
]
);
} catch (e) {
logError('PrintUtils::_createEmailContentWidget: Exception: $e');
return pw.Text(htmlDocument);
}
}
List<pw.Widget> _createBodyWidgets({
required pw.Context context,
required String logoAppSvg,
required String appName,
required String userName,
required String title,
required String htmlDocument,
required String senderName,
required String senderEmailAddress,
required String dateTime,
required String toPrefix,
required String ccPrefix,
required String bccPrefix,
required String replyToPrefix,
required String titleAttachment,
required pw.Widget contentWidget,
String? toAddress,
String? ccAddress,
String? bccAddress,
String? replyToAddress,
List<PrintAttachment>? listAttachment,
}) {
return [
pw.Container(
padding: const pw.EdgeInsets.symmetric(horizontal: 32),
child: pw.Column(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
_createUserInformationRow(
context: context,
logoAppSvg: logoAppSvg,
appName: appName,
userName: userName
),
_createPageDivider(),
if (title.isNotEmpty)
...[
_createTitleRow(
context: context,
title: title
),
_createPageDivider(),
],
pw.SizedBox(height: 12),
pw.Row(
children: [
pw.Expanded(
child: _createSenderWidget(
context: context,
name: senderName,
emailAddress: senderEmailAddress
)
),
_createDateTimeWidget(
context: context,
dateTime: dateTime
)
]
),
if (replyToAddress?.isNotEmpty == true)
_createRecipientRow(
prefix: replyToPrefix,
emailAddress: replyToAddress!
),
if (toAddress?.isNotEmpty == true)
_createRecipientRow(
prefix: toPrefix,
emailAddress: toAddress!
),
if (ccAddress?.isNotEmpty == true)
_createRecipientRow(
prefix: ccPrefix,
emailAddress: ccAddress!
),
if (bccAddress?.isNotEmpty == true)
_createRecipientRow(
prefix: bccPrefix,
emailAddress: bccAddress!
),
pw.Padding(
padding: const pw.EdgeInsets.symmetric(
horizontal: 12,
vertical: 24),
child: contentWidget
),
if (listAttachment?.isNotEmpty == true)
...[
_createPageDivider(),
pw.SizedBox(height: 12),
_createAttachmentTitle(
context: context,
title: titleAttachment,
totalAttachments: listAttachment!.length
),
pw.SizedBox(height: 4),
...listAttachment.map((attachment) => _createAttachmentRow(
context: context,
attachment: attachment
))
]
]
)
)
];
}
pw.Widget _createPageDivider() {
return pw.Padding(
padding: const pw.EdgeInsets.only(top: 12),
child: pw.Divider()
);
}
pw.Widget _createPageHeader({
required pw.Context context,
required String appName,
required String title,
required String locale,
}) {
final currentTime = DateFormat('MM/dd/yyyy, h:mm a', locale).format(DateTime.now());
return pw.Padding(
padding: const pw.EdgeInsets.all(20),
child: pw.Row(
children: [
pw.Text(
currentTime,
style: pw.Theme.of(context).defaultTextStyle.copyWith(
fontSize: 8,
color: pw.PdfColor.fromInt(AppColor.colorTextBody.value)
)
),
pw.SizedBox(width: 16),
pw.Expanded(
child: pw.Text(
'$appName - $title',
textAlign: pw.TextAlign.center,
maxLines: 1,
style: pw.Theme.of(context).defaultTextStyle.copyWith(
fontSize: 8,
color: pw.PdfColor.fromInt(AppColor.colorTextBody.value)
)
)
),
]
)
);
}
pw.Widget _createUserInformationRow({
required pw.Context context,
required String logoAppSvg,
required String appName,
required String userName,
}) {
return pw.Row(
children: [
pw.SvgImage(
svg: logoAppSvg,
width: 32,
height: 32
),
pw.SizedBox(width: 12),
pw.Text(
appName,
style: pw.Theme.of(context).header0
),
pw.Spacer(),
pw.SizedBox(width: 12),
pw.Text(
userName,
style: pw.Theme.of(context).header4
)
]
);
}
pw.Widget _createTitleRow({
required pw.Context context,
required String title,
}) {
return pw.Padding(
padding: const pw.EdgeInsets.only(top: 12),
child: pw.Text(
title,
style: pw.Theme.of(context).header3
)
);
}
pw.Widget _createSenderWidget({
required pw.Context context,
required String name,
required String emailAddress,
}) {
return pw.Row(
children: [
if (name.isNotEmpty)
pw.Padding(
padding: const pw.EdgeInsetsDirectional.only(end: 4),
child: pw.Text(
name,
style: pw.Theme.of(context).header4
)
),
pw.Text('<$emailAddress>')
]
);
}
pw.Widget _createDateTimeWidget({
required pw.Context context,
required String dateTime,
}) {
return pw.Text(
dateTime,
style: pw.Theme.of(context).defaultTextStyle.copyWith(
fontSize: 10,
color: pw.PdfColor.fromInt(AppColor.colorTextBody.value)
)
);
}
pw.Widget _createRecipientRow({
required String prefix,
required String emailAddress,
}) {
return pw.Row(
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text('$prefix:'),
pw.SizedBox(width: 4),
pw.Expanded(child: pw.Text(emailAddress))
]
);
}
pw.Widget _createAttachmentTitle({
required pw.Context context,
required String title,
required int totalAttachments,
}) {
return pw.Text(
'$totalAttachments $title',
style: pw.Theme.of(context).header4
);
}
pw.Widget _createAttachmentRow({
required pw.Context context,
required PrintAttachment attachment,
}) {
return pw.Padding(
padding: const pw.EdgeInsets.only(top: 8),
child: pw.Row(
children: [
pw.SvgImage(
svg: attachment.iconSvg,
width: 24,
height: 24
),
pw.SizedBox(width: 8),
pw.Expanded(
child: pw.Column(
mainAxisSize: pw.MainAxisSize.min,
crossAxisAlignment: pw.CrossAxisAlignment.start,
children: [
pw.Text(
attachment.name,
style: pw.Theme.of(context).header4
),
pw.Text(attachment.size)
]
)
),
]
)
);
}
Future<bool> printEmailToPDF({
required String appName,
required String userName,
required String title,
required String emailContent,
required String senderName,
required String senderEmailAddress,
required String locale,
required String dateTime,
required String toPrefix,
required String ccPrefix,
required String bccPrefix,
required String replyToPrefix,
required String titleAttachment,
String? toAddress,
String? ccAddress,
String? bccAddress,
String? replyToAddress,
List<PrintAttachment>? listAttachment,
}) async {
return await Printing.layoutPdf(onLayout: (pw.PdfPageFormat format) async {
final googleInterRegularFont = await PdfGoogleFonts.interRegular();
final googleInterBoldFont = await PdfGoogleFonts.interBold();
final logoAppSvg = await rootBundle.loadString(_imagePaths.icTMailLogo);
final contentWidget = await _createEmailContentWidget(emailContent);
final pdfDocument = pw.Document();
pdfDocument.addPage(
pw.MultiPage(
pageFormat: pw.PdfPageFormat.standard,
orientation: pw.PageOrientation.portrait,
theme: pw.ThemeData(
defaultTextStyle: pw.TextStyle(
font: googleInterRegularFont,
fontSize: 12,
color: pw.PdfColors.black
),
header3: pw.TextStyle(
font: googleInterBoldFont,
fontSize: 14,
fontWeight: pw.FontWeight.bold,
color: pw.PdfColors.black
),
header4: pw.TextStyle(
font: googleInterBoldFont,
fontSize: 12,
fontWeight: pw.FontWeight.bold,
color: pw.PdfColors.black
),
),
margin: pw.EdgeInsets.zero,
build: (context) => _createBodyWidgets(
context: context,
contentWidget: contentWidget,
logoAppSvg: logoAppSvg,
appName: appName,
userName: userName,
title: title,
htmlDocument: emailContent,
senderName: senderName,
senderEmailAddress: senderEmailAddress,
dateTime: dateTime,
toPrefix: toPrefix,
ccPrefix: ccPrefix,
bccPrefix: toPrefix,
replyToPrefix: replyToPrefix,
toAddress: toAddress,
ccAddress: ccAddress,
bccAddress: bccAddress,
replyToAddress: replyToAddress,
titleAttachment: titleAttachment,
listAttachment: listAttachment
),
header: (context) => _createPageHeader(
context: context,
appName: appName,
title: title,
locale: locale,
),
)
);
return await pdfDocument.save();
});
}
}
+96
View File
@@ -1,6 +1,14 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
version: "3.4.10"
args:
dependency: transitive
description:
@@ -17,6 +25,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.11.0"
barcode:
dependency: transitive
description:
name: barcode
sha256: "91b143666f7bb13636f716b6d4e412e372ab15ff7969799af8c9e30a382e9385"
url: "https://pub.dev"
source: hosted
version: "2.2.6"
bidi:
dependency: transitive
description:
name: bidi
sha256: "1a7d0c696324b2089f72e7671fd1f1f64fef44c980f3cebc84e803967c597b63"
url: "https://pub.dev"
source: hosted
version: "2.0.10"
boolean_selector:
dependency: transitive
description:
@@ -65,6 +89,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
@@ -73,6 +105,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.3.3+7"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
csslib:
dependency: transitive
description:
@@ -328,6 +368,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.15.3"
htmltopdfwidgets:
dependency: "direct main"
description:
name: htmltopdfwidgets
sha256: "990e3002fbbfaf307555f1991bed61b6228d7053b7e1945815c7a68f56bbda81"
url: "https://pub.dev"
source: hosted
version: "1.0.3"
http:
dependency: "direct main"
description:
@@ -344,6 +392,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
url: "https://pub.dev"
source: hosted
version: "4.1.7"
intl:
dependency: "direct main"
description:
@@ -464,6 +520,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.2.1"
pdf:
dependency: transitive
description:
name: pdf
sha256: "243f05342fc0bdf140eba5b069398985cdbdd3dbb1d776cf43d5ea29cc570ba6"
url: "https://pub.dev"
source: hosted
version: "3.10.8"
pdf_widget_wrapper:
dependency: transitive
description:
name: pdf_widget_wrapper
sha256: e9d31fd7782ce28ae346b127ea7d1cd748d799bddee379f31191693610e23749
url: "https://pub.dev"
source: hosted
version: "1.0.1"
petitparser:
dependency: transitive
description:
@@ -496,6 +568,30 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.9.1"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "43ac87de6e10afabc85c445745a7b799e04de84cebaa4fd7bf55a5e1e9604d29"
url: "https://pub.dev"
source: hosted
version: "3.7.4"
printing:
dependency: "direct main"
description:
name: printing
sha256: "1c99cab90ebcc1fff65831d264627d5b529359d563e53f33ab9b8117f2d280bc"
url: "https://pub.dev"
source: hosted
version: "5.12.0"
qr:
dependency: transitive
description:
name: qr
sha256: "64957a3930367bf97cc211a5af99551d630f2f4625e38af10edd6b19131b64b3"
url: "https://pub.dev"
source: hosted
version: "3.0.1"
sky_engine:
dependency: transitive
description: flutter
+4
View File
@@ -75,6 +75,10 @@ dependencies:
linkify: 5.0.0
htmltopdfwidgets: 1.0.3
printing: 5.12.0
dev_dependencies:
flutter_test:
sdk: flutter