Commit 560a6f0

derdilla <derdilla06@gmail.com>
2023-07-30 16:37:29
implement basic pdf data export
1 parent 0de517e
Changed files (4)
lib/l10n/app_en.arb
@@ -352,10 +352,10 @@
   "@pdfDocumentTitle": {
     "placeholders": {
       "start": {
-        "type": "string"
+        "type": "String"
       },
       "end": {
-        "type": "string"
+        "type": "String"
       }
     }
   }
lib/model/export_import.dart
@@ -20,11 +20,18 @@ import 'package:sqflite/sqflite.dart';
 
 import 'blood_pressure.dart';
 
+extension PdfCompatability on Color {
+  PdfColor toPdfColor() {
+    return PdfColor(red / 256, green / 256, blue / 256, opacity);
+  }
+}
+
 class ExportFileCreator {
   final Settings settings;
   final AppLocalizations localizations;
+  final ThemeData theme;
 
-  ExportFileCreator(this.settings, this.localizations);
+  ExportFileCreator(this.settings, this.localizations, this.theme);
 
   Future<Uint8List> createFile(List<BloodPressureRecord> records) async {
     switch (settings.exportFormat) {
@@ -167,47 +174,95 @@ class ExportFileCreator {
   }
 
   Future<Uint8List> createPdfFile(List<BloodPressureRecord> data) async {
-    final analyzer = BloodPressureAnalyser(data);
+    final analyzer = BloodPressureAnalyser(data.toList());
     final dateFormatter = DateFormat(settings.dateFormatString);
 
     pw.Document pdf = pw.Document();
 
-    pdf.addPage(pw.Page(
+    pdf.addPage(pw.MultiPage(
         pageFormat: PdfPageFormat.a4,
         build: (pw.Context context) {
-          return pw.Column(
-            children: [
-              pw.Text(localizations.pdfDocumentTitle(dateFormatter.format(analyzer.firstDay!),
-                  dateFormatter.format(analyzer.lastDay!))),
-              pw.Table(
-                children: [
-                  pw.TableRow(
-                      children: [
-                        pw.Text('timestamp'),
-                        pw.Text('systolic'),
-                        pw.Text('diastolic'),
-                        pw.Text('pulse'),
-                        pw.Text('note')
-                      ]
-                  ),
-                  for (var entry in data)
-                    pw.TableRow(
-                        children: [
-                          pw.Text(dateFormatter.format(entry.creationTime)),
-                          pw.Text((entry.systolic ?? '-').toString()),
-                          pw.Text((entry.diastolic ?? '-').toString()),
-                          pw.Text((entry.pulse ?? '-').toString()),
-                          pw.Text(entry.notes ?? '-')
-                        ]
-                    )
-                ]
-              )
-            ],
-          );
+          return [
+            _buildDocumentTitle(dateFormatter, analyzer),
+            _buildPdfTable(data, dateFormatter),
+          ];
         }));
     return await pdf.save();
   }
 
+  pw.Widget _buildDocumentTitle(DateFormat dateFormatter, BloodPressureAnalyser analyzer) {
+    return pw.Container(
+      child: pw.Text(
+        localizations.pdfDocumentTitle(dateFormatter.format(analyzer.firstDay!), dateFormatter.format(analyzer.lastDay!)),
+        style: const pw.TextStyle(
+          fontSize: 16,
+        )
+      )
+    );
+  }
+
+  pw.SpanningWidget _buildPdfTable(List<BloodPressureRecord> data, DateFormat dateFormatter) {
+    final tableHeaders = [
+      localizations.time,
+      localizations.sysLong,
+      localizations.diaLong,
+      localizations.pulLong,
+      localizations.notes
+    ];
+
+    return pw.TableHelper.fromTextArray(
+        border: null,
+        cellAlignment: pw.Alignment.centerLeft,
+        headerDecoration: const pw.BoxDecoration(
+          border: pw.Border(bottom: pw.BorderSide(color: PdfColors.black))
+        ),
+        headerHeight: 20,
+        cellHeight: 15,
+        cellAlignments: {
+          0: pw.Alignment.centerLeft,
+          1: pw.Alignment.centerLeft,
+          2: pw.Alignment.centerLeft,
+          3: pw.Alignment.centerLeft,
+          4: pw.Alignment.centerRight,
+        },
+        headerStyle: pw.TextStyle(
+          color: PdfColors.black,
+          fontSize: 10,
+          fontWeight: pw.FontWeight.bold,
+        ),
+        cellStyle: const pw.TextStyle(
+          fontSize: 8,
+        ),
+        headerCellDecoration: pw.BoxDecoration(
+          border: pw.Border(
+            bottom: pw.BorderSide(
+              color: theme.colorScheme.primaryContainer.toPdfColor(),
+              width: 5,
+            ),
+          ),
+        ),
+        rowDecoration: const pw.BoxDecoration(
+          border: pw.Border(
+            bottom: pw.BorderSide(
+              color: PdfColors.blueGrey,
+              width: .5,
+            ),
+          ),
+        ),
+        headers: tableHeaders,
+        data: List<List<String>>.generate(
+          data.length,
+              (row) => [
+                dateFormatter.format(data[row].creationTime),
+                (data[row].systolic ?? '-').toString(),
+                (data[row].diastolic ?? '-').toString(),
+                (data[row].pulse ?? '-').toString(),
+                data[row].notes ?? '-'
+              ],
+        )
+    );
+  }
+
   Future<Uint8List> copyDBFile() async {
     var dbPath = await getDatabasesPath();
 
@@ -228,11 +283,13 @@ class Exporter {
   final BloodPressureModel model;
   final ScaffoldMessengerState messenger;
   final AppLocalizations localizations;
-  Exporter(this.settings, this.model, this.messenger, this.localizations);
+  final ThemeData theme;
+
+  Exporter(this.settings, this.model, this.messenger, this.localizations, this.theme);
 
   Future<void> export() async {
     final entries = await model.getInTimeRange(settings.displayDataStart, settings.displayDataEnd);
-    var fileContents = await ExportFileCreator(settings, localizations).createFile(entries);
+    var fileContents = await ExportFileCreator(settings, localizations, theme).createFile(entries);
     String filename = 'blood_press_${DateTime.now().toIso8601String()}';
     String ext;
     switch(settings.exportFormat) {
@@ -293,7 +350,7 @@ class Exporter {
     var path = result.files.single.path;
     assert(path != null); // null state directly linked to binary content
 
-    var fileContents = await ExportFileCreator(settings, localizations).parseFile(path! ,binaryContent);
+    var fileContents = await ExportFileCreator(settings, localizations, theme).parseFile(path! ,binaryContent);
     if (fileContents == null) {
       messenger.showSnackBar(SnackBar(content: Text(localizations.errNotImportable)));
       return;
lib/screens/subsettings/export_import_screen.dart
@@ -290,6 +290,7 @@ class ExportImportButtons extends StatelessWidget {
     final model = Provider.of<BloodPressureModel>(context, listen: false);
     final messenger = ScaffoldMessenger.of(context);
     final localizations = AppLocalizations.of(context)!;
+    final theme = Theme.of(context);
 
     return Container(
       height: 60,
@@ -302,7 +303,7 @@ class ExportImportButtons extends StatelessWidget {
                 child: MaterialButton(
                   height: 60,
                   child:  Text(AppLocalizations.of(context)!.export),
-                  onPressed: () => Exporter(settings, model, messenger, localizations).export(),
+                  onPressed: () => Exporter(settings, model, messenger, localizations, theme).export(),
                 )
             ),
             const VerticalDivider(),
@@ -311,7 +312,7 @@ class ExportImportButtons extends StatelessWidget {
                 child: MaterialButton(
                   height: 60,
                   child: Text(AppLocalizations.of(context)!.import),
-                  onPressed: () => Exporter(settings, model, messenger, localizations).import(),
+                  onPressed: () => Exporter(settings, model, messenger, localizations, theme).import(),
                 )
             ),
           ],
lib/screens/add_measurement.dart
@@ -188,7 +188,8 @@ class _AddMeasurementPageState extends State<AddMeasurementPage> {
 
                                 await model.add(BloodPressureRecord(_time, _systolic, _diastolic, _pulse, _note));
                                 if (settings.exportAfterEveryEntry && context.mounted) {
-                                  final exporter = Exporter(settings, model, ScaffoldMessenger.of(context), AppLocalizations.of(context)!);
+                                  final exporter = Exporter(settings, model, ScaffoldMessenger.of(context),
+                                      AppLocalizations.of(context)!, Theme.of(context));
                                   exporter.export();
                                 }
                                 navigator.pop();