Commit 1e8593d

derdilla <derdilla06@gmail.com>
2023-06-19 16:23:01
make export range conditional
1 parent f5d1b32
lib/l10n/app_de.arb
@@ -78,6 +78,7 @@
   "data": "Daten",
 
   "exportImport": "Exportieren / Importieren",
+  "exportLimitDataRange": "Datenbereich einschränken",
   "exportInterval": "Datenbereich",
   "exportFormat": "Exportformat",
   "exportMimeType": "Export MIME typ",
lib/l10n/app_en.arb
@@ -78,6 +78,7 @@
   "data": "data",
 
   "exportImport": "export / import",
+  "exportLimitDataRange": "limit data range",
   "exportInterval": "data range",
   "exportFormat": "export format",
   "exportMimeType": "export MIME type",
lib/model/export_import.dart
@@ -1,12 +1,56 @@
 
-import 'package:intl/intl.dart';
+import 'dart:convert';
+import 'dart:typed_data';
 
-class CSVExportSettings {
-  final DateFormat dateFormatter;
-  final String? fieldDelimiter;
-  final String? textDelimiter;
+import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:csv/csv.dart';
 
-  CSVExportSettings(this.dateFormatter, this.fieldDelimiter, this.textDelimiter);
+import 'blood_pressure.dart';
+
+class DataExporter {
+  Settings settings;
+
+  DataExporter(this.settings);
+
+  Uint8List createFile(List<BloodPressureRecord> records) {
+    if (settings.exportFormat == ExportFormat.csv) {
+      var csvHead = '';
+      for (var attribute in settings.exportItems) {
+        csvHead += attribute;
+        csvHead += settings.csvFieldDelimiter;
+      }
+      csvHead += '\n';
+
+      List<List<dynamic>> items = [];
+      for (var record in records) {
+        List<dynamic> row = [];
+        for (var attribute in settings.exportItems) {
+          switch (attribute) {
+            case 'timestampUnixMs':
+              row.add(record.creationTime.millisecondsSinceEpoch);
+              break;
+            case 'systolic':
+              row.add(record.systolic);
+              break;
+            case 'diastolic':
+              row.add(record.diastolic);
+              break;
+            case 'pulse':
+              row.add(record.pulse);
+              break;
+            case 'notes':
+              row.add(record.notes);
+              break;
+          }
+        }
+        items.add(row);
+      }
+      var converter = ListToCsvConverter(fieldDelimiter: settings.csvFieldDelimiter, textDelimiter: settings.csvTextDelimiter);
+      var csvData = converter.convert(items);
+      return Uint8List.fromList(utf8.encode(csvHead + csvData));
+    }
+    return Uint8List(0);
+  }
 }
 
 class ExportFormat {
lib/model/settings_store.dart
@@ -23,6 +23,10 @@ class Settings extends ChangeNotifier {
     return component;
   }
 
+  void forceNotifyListeners() {
+    notifyListeners();
+  }
+
   int get graphStepSize {
     return _prefs.getInt('graphStepSize') ?? TimeStep.day;
   }
@@ -338,6 +342,15 @@ class Settings extends ChangeNotifier {
     notifyListeners();
   }
 
+  bool get exportLimitDataRange {
+    return _prefs.getBool('exportLimitDataRange') ?? false;
+  }
+
+  set exportLimitDataRange(bool value) {
+    _prefs.setBool('exportLimitDataRange', value);
+    notifyListeners();
+  }
+
   DateTimeRange? get exportDataRange {
     return _exportDataRange;
   }
lib/screens/subsettings/export_import_screen.dart
@@ -1,12 +1,16 @@
 
+import 'dart:io';
+
 import 'package:blood_pressure_app/components/settings_widgets.dart';
 import 'package:blood_pressure_app/model/blood_pressure.dart';
 import 'package:blood_pressure_app/model/export_import.dart';
 import 'package:blood_pressure_app/model/settings_store.dart';
+import 'package:file_saver/file_saver.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_gen/gen_l10n/app_localizations.dart';
 import 'package:intl/intl.dart';
 import 'package:provider/provider.dart';
+import 'package:share_plus/share_plus.dart';
 
 class ExportImportScreen extends StatelessWidget {
   const ExportImportScreen({super.key});
@@ -54,21 +58,28 @@ class ExportImportScreen extends StatelessWidget {
         }
 
         List<Widget> options = [
-          SettingsTile(
-            title: Text(AppLocalizations.of(context)!.exportInterval),
-            description: (exportRangeText != null) ? Text(exportRangeText) : null,
-            onPressed: (context) async {
-              var model = Provider.of<BloodPressureModel>(context, listen: false);
-              var newRange = await showDateRangePicker(context: context, firstDate: await model.firstDay, lastDate: await model.lastDay);
-              if (newRange == null && context.mounted) {
-                ScaffoldMessenger.of(context)
-                    .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.errNoRangeForExport)));
-                return;
+          SwitchSettingsTile(
+              title: Text(AppLocalizations.of(context)!.exportLimitDataRange),
+              initialValue: settings.exportLimitDataRange,
+              onToggle: (value) {
+                settings.exportLimitDataRange = value;
               }
-              settings.exportDataRange = newRange;
-
-            }
           ),
+          (settings.exportLimitDataRange) ? SettingsTile(
+              title: Text(AppLocalizations.of(context)!.exportInterval),
+              description: (exportRangeText != null) ? Text(exportRangeText) : null,
+              onPressed: (context) async {
+                var model = Provider.of<BloodPressureModel>(context, listen: false);
+                var newRange = await showDateRangePicker(context: context, firstDate: await model.firstDay, lastDate: await model.lastDay);
+                if (newRange == null && context.mounted) {
+                  ScaffoldMessenger.of(context)
+                      .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.errNoRangeForExport)));
+                  return;
+                }
+                settings.exportDataRange = newRange;
+
+              }
+          ) : const SizedBox.shrink(),
           DropDownSettingsTile<ExportFormat>(
             key: const Key('exportFormat'),
             title: Text(AppLocalizations.of(context)!.exportFormat),
@@ -119,7 +130,7 @@ class ExportImportScreen extends StatelessWidget {
                     height: 60,
                     child:  Text(AppLocalizations.of(context)!.export),
                     onPressed: () async {
-                      var settings = Provider.of<Settings>(context);
+                      var settings = Provider.of<Settings>(context, listen: false);
                       var range = settings.exportDataRange;
                       if (range == null) {
                         ScaffoldMessenger.of(context)
@@ -130,17 +141,23 @@ class ExportImportScreen extends StatelessWidget {
                       var entries = await Provider.of<BloodPressureModel>(context, listen: false).getInTimeRange(settings.exportDataRange!.start, settings.exportDataRange!.end);
                       var fileContents = DataExporter(settings).createFile(entries);
 
-                      /*
-                      .save((success, msg) {
-                        if (success && msg != null) {
-                          ScaffoldMessenger.of(context)
-                              .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.success(msg))));
-                        } else if (!success && msg != null) {
-                          ScaffoldMessenger.of(context)
-                              .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.error(msg))));
-                        }
-                      }, exportAsText: false);
-                       */
+                      String filename = 'blood_press_${DateTime.now().toIso8601String()}';
+                      String path = await FileSaver.instance.saveFile(name: filename, bytes: fileContents);
+
+                      if ((Platform.isLinux || Platform.isWindows || Platform.isMacOS) && context.mounted) {
+                        ScaffoldMessenger.of(context)
+                            .showSnackBar(SnackBar(content: Text(AppLocalizations.of(context)!.success(path))));
+                      } else if (Platform.isAndroid || Platform.isIOS) {
+                        Share.shareXFiles([
+                          XFile(
+                            path,
+                            mimeType: MimeType.csv.type
+                          )
+                        ]);
+                      } else {
+                        ScaffoldMessenger.of(context)
+                            .showSnackBar(const SnackBar(content: Text('UNSUPPORTED PLATFORM')));
+                      }
                     },
                   )
               ),
@@ -202,38 +219,40 @@ class CsvItemsOrderCreator extends StatelessWidget {
             }
             final String item = settings.exportItems.removeAt(oldIndex);
             settings.exportItems.insert(newIndex, item);
+            settings.forceNotifyListeners();
           },
           footer: (settings.exportAddableItems.isNotEmpty) ? InkWell(
-            onTap: () {
-              showDialog(context: context,
-                  builder: (context) {
-                    return Dialog(
-                      shape: const RoundedRectangleBorder(
-                          borderRadius: BorderRadius.all(Radius.circular(50))
-                      ),
-                      child: Container(
-                        height: 330,
-                        padding: const EdgeInsets.all(30),
-                        child: ListView(
-                          children: [
-                            for (int i = 0; i < settings.exportAddableItems.length; i += 1)
-                              ListTile(
-                                title: Text(settings.exportAddableItems[i]),
-                                onTap: () {
-                                  var addedItem = settings.exportAddableItems.removeAt(i);
-                                  settings.exportItems.add(addedItem);
-                                  Navigator.of(context).pop();
+            onTap: () async {
+              await showDialog(context: context,
+                builder: (context) {
+                  return Dialog(
+                    shape: const RoundedRectangleBorder(
+                        borderRadius: BorderRadius.all(Radius.circular(50))
+                    ),
+                    child: Container(
+                      height: 330,
+                      padding: const EdgeInsets.all(30),
+                      child: ListView(
+                        children: [
+                          for (int i = 0; i < settings.exportAddableItems.length; i += 1)
+                            ListTile(
+                              title: Text(settings.exportAddableItems[i]),
+                              onTap: () {
+                                var addedItem = settings.exportAddableItems.removeAt(i);
+                                settings.exportItems.add(addedItem);
+                                Navigator.of(context).pop();
 
-                                },
-                              )
-                          ],
-                        ),
+                              },
+                            )
+                        ],
                       ),
-                    );
-                  }
+                    ),
+                  );
+                }
               );
+              settings.forceNotifyListeners();
             },
-            child:  const Center(
+            child: const Center(
               child: Row(
                 mainAxisSize: MainAxisSize.min,
                 children: [
@@ -254,6 +273,7 @@ class CsvItemsOrderCreator extends StatelessWidget {
                   onDismissed: (direction) {
                     var removedItem = settings.exportItems.removeAt(i);
                     settings.exportAddableItems.add(removedItem);
+                    settings.forceNotifyListeners();
                   },
                   child: ListTile(
                     title: Text(settings.exportItems[i]),