Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export import rework #59

Merged
merged 35 commits into from
Jun 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
715cc13
add dropdown setting
derdilla Jun 15, 2023
a80780b
use dropdown for theme
derdilla Jun 15, 2023
0e4d797
add structure for export page
derdilla Jun 16, 2023
4daaec1
add export page with advanced settings
derdilla Jun 16, 2023
2db2fa9
move export buttons to second screen
derdilla Jun 17, 2023
52936a4
add export range
derdilla Jun 18, 2023
8ef819e
add entry order option
derdilla Jun 18, 2023
f5d1b32
save export entry order in different scope
derdilla Jun 18, 2023
1e8593d
make export range conditional
derdilla Jun 19, 2023
a038eb8
make export settings persistent
derdilla Jun 19, 2023
aed54eb
fix export screen display on small devices
derdilla Jun 19, 2023
5b68973
add all entries to default export range
derdilla Jun 20, 2023
5b9f69e
fix export entry order with persistent storage
derdilla Jun 20, 2023
35983a1
make custom export entries optional
derdilla Jun 20, 2023
02709cc
make headline optional
derdilla Jun 20, 2023
e8d3a52
rewrite function to parse csv files
derdilla Jun 20, 2023
ea253ea
rewrite import
derdilla Jun 20, 2023
a18d942
remove old model method
derdilla Jun 20, 2023
642b803
fix tests andwarnings
derdilla Jun 21, 2023
299e9fa
add more option to export in iso format
derdilla Jun 21, 2023
406df24
fix bottom button overlapping on snack bar
derdilla Jun 21, 2023
76a6894
Add warn banner for non-importable configuration
derdilla Jun 22, 2023
f972010
migrate ExportFormat to enum
derdilla Jun 22, 2023
d57554e
add minimal pdf implementation
derdilla Jun 22, 2023
0c20a72
add file extensions
derdilla Jun 22, 2023
9b5d3b0
export as db file
derdilla Jun 22, 2023
ba5672e
move pdf creation to export file
derdilla Jun 22, 2023
57c41b4
more expandable structure for DataExporter class
derdilla Jun 22, 2023
70864b4
refactor ExportImportScreen to be more manageable
derdilla Jun 22, 2023
787d13d
allow sqlite import
derdilla Jun 22, 2023
c59b045
add DB parse
derdilla Jun 23, 2023
d87d693
fix invalid option showing
derdilla Jun 23, 2023
846413e
fix localization
derdilla Jun 23, 2023
9dd90cb
hide pdf option and fix file extension to finalize
derdilla Jun 23, 2023
1e8c385
add missing localizations
derdilla Jun 23, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ app.*.map.json
/android/app/profile
/android/app/release
/main.dart.incremental.dill
/l10n_errors.txt
3 changes: 2 additions & 1 deletion l10n.yaml
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
output-localization-file: app_localizations.dart
untranslated-messages-file: l10n_errors.txt
37 changes: 37 additions & 0 deletions lib/components/settings_widgets.dart
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,43 @@ class _InputSettingsTileState extends State<InputSettingsTile> {
}
}

class DropDownSettingsTile<T> extends StatelessWidget {
final Widget title;
final Widget? leading;
final Widget? description;
final bool disabled;

final T value;
final List<DropdownMenuItem<T>> items;
final void Function(T? value) onChanged;


const DropDownSettingsTile({required this.title, required this.value,
required this.onChanged, required this.items, this.disabled = false, this.leading, this.description, super.key});

@override
Widget build(BuildContext context) {
return SettingsTile(
title: title,
description: description,
leading: leading,
disabled: disabled,
onPressed: (BuildContext context) { },
trailing: Row(
children: [
DropdownButton<T>(
value: value,
items: items,
onChanged: onChanged,
),
const SizedBox(width: 15,)
],
),
);
}

}

class SettingsSection extends StatelessWidget {
final Widget title;
final List<Widget> children;
Expand Down
47 changes: 41 additions & 6 deletions lib/l10n/app_de.arb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@
"errNoValue": "Bitte Wert eingeben",
"errNotEnoughDataToGraph": "Zuwenig Daten für Graphen",
"errNoData": "Keine Daten",
"errNoRangeForExport": "Sie müssen angeben, welche daten sie exportieren wollen.",
"errPleaseSelect": "Bitte auswählen",
"errWrongImportFormat": "Es können nur Dateien im csv und SQLite db Format importiert werden.",
"errNeedHeadline": "Es können nur Dateien mit einer Überschrift importiert werden.",
"errCantReadFile": "Der Inhalt der Datei kann nicht gelesen werden",
"errNotImportable": "Diese Datei kann nicht importiert werden.",


"btnCancel": "ABBRUCH",
"btnSave": "OK",
Expand All @@ -52,8 +59,10 @@
"layout": "Layout",
"allowManualTimeInput": "Editierbare Zeitangaben",
"enterTimeFormatScreen": "Datums-/Zeitformat",
"followSystemDarkMode": "Thema wie System",
"darkMode": "Dunkles Thema",
"theme": "Thema",
"system": "System",
"dark": "Dunkel",
"light": "Hell",
"iconSize": "Größe der Knöpfe",
"graphLineThickness": "Linienstärke d. Graphen",
"animationSpeed": "Animationsdauer",
Expand All @@ -73,9 +82,25 @@
"sysWarn": "Warnwert Sys",
"diaWarn": "Warnwert Dia",
"data": "Daten",
"useExportCompatability": "Kompatibler Export",
"useExportCompatabilityDesc": "Signalisiert Export als Text",
"export": "Exportieren",

"exportImport": "Exportieren / Importieren",
"exportLimitDataRange": "Datenbereich einschränken",
"exportInterval": "Datenbereich",
"exportFormat": "Exportformat",
"exportCustomEntries": "Eigene Felder",
"addEntry": "Feld hinzufügen",
"exportMimeType": "Export MIME typ",
"exportMimeTypeDesc": "gibt anderen dateityp weiter",
"exportCsvHeadline": "Überschrift",
"exportCsvHeadlineDesc": "Feldbezeichnungen zum Differenzieren",
"csv": "CSV",
"pdf": "PDF",
"db": "SQLITE DB",
"text": "Text",
"other": "Anderes",
"fieldDelimiter": "Feldseparator",
"textDelimiter": "Textbegrenzung",
"export": "EXPORT",
"exportSuccess": "Exportiert in: {path}",
"@exportSuccess": {
"placeholders": {
Expand All @@ -84,10 +109,20 @@
}
}
},
"exportWarnConfigNotImportable": "Hey! Nur eine freundliche Info: Die aktuelle Exportkonfiguration ist nicht importierbar. Um das zu beheben, stelle sicher, dass der Exporttyp als CSV festgelegt ist, die Überschrift aktiviert ist und die Felder 'diastolic', 'systolic', 'pulse', 'notes' sowie eines der verfügbaren Zeitformate enthalten sind.",

"shared": "Geteilt",
"import": "Import",
"import": "IMPORT",
"sourceCode": "Quellcode",
"licenses": "Lizenzen dritter",
"importSuccess": "Es wurden {count} Einträge erfolgreich importiert",
"@importSuccess": {
"placeholders": {
"count": {
"type": "int"
}
}
},

"statistics": "Statistik",
"measurementCount": "Anzahl Messungen",
Expand Down
45 changes: 39 additions & 6 deletions lib/l10n/app_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
"errNoValue": "Please enter a value",
"errNotEnoughDataToGraph": "not enough data to draw graph",
"errNoData": "no data",
"errNoRangeForExport": "You need to specify a range in which data is exported.",
"errPleaseSelect": "please select",
"errWrongImportFormat": "You can only import files in csv and SQLite db format.",
"errNeedHeadline": "You can only import files with a headline.",
"errCantReadFile": "The file contents can not be read",
"errNotImportable": "This file can't be imported",

"btnCancel": "CANCEL",
"btnSave": "SAVE",
Expand All @@ -52,8 +58,10 @@
"layout": "layout",
"allowManualTimeInput": "allow manual time input",
"enterTimeFormatScreen": "time format",
"followSystemDarkMode": "follow system dark mode",
"darkMode": "enable dark mode",
"theme": "theme",
"system": "System",
"dark": "dark",
"light": "light",
"iconSize": "icon size",
"graphLineThickness": "line thickness",
"animationSpeed": "animation duration",
Expand All @@ -73,9 +81,25 @@
"sysWarn": "systolic warn",
"diaWarn": "diastolic warn",
"data": "data",
"useExportCompatability": "compatability export",
"useExportCompatabilityDesc": "sets export mime type to text",
"export": "export",

"exportImport": "export / import",
"exportLimitDataRange": "limit data range",
"exportInterval": "data range",
"exportFormat": "export format",
"exportCustomEntries": "customize fields",
"addEntry": "Add field",
"exportMimeType": "export MIME type",
"exportMimeTypeDesc": "signalizes type to other apps",
"exportCsvHeadline": "headline",
"exportCsvHeadlineDesc": "Helps to discriminate types",
"csv": "CSV",
"pdf": "PDF",
"db": "SQLITE DB",
"text": "text",
"other": "other",
"fieldDelimiter": "field delimiter",
"textDelimiter": "text delimiter",
"export": "EXPORT",
"exportSuccess": "Exported to: {path}",
"@exportSuccess": {
"placeholders": {
Expand All @@ -85,9 +109,18 @@
}
},
"shared": "shared",
"import": "import",
"import": "IMPORT",
"sourceCode": "source code",
"licenses": "3rd party licenses",
"importSuccess": "Successfully imported {count} entries",
"@importSuccess": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"exportWarnConfigNotImportable": "Hey! Just a friendly heads up: the current export configuration won't be importable. To fix it, make sure you set the export type as CSV, enable the headline, and include the fields 'diastolic', 'systolic', 'pulse', 'notes', along with one of the time formats available.",

"statistics": "Statistics",
"measurementCount": "Measurement count",
Expand Down
70 changes: 4 additions & 66 deletions lib/model/blood_pressure.dart
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
import 'dart:convert' show utf8;
import 'dart:io';

import 'package:collection/collection.dart';
import 'package:csv/csv.dart';
import 'package:file_picker/file_picker.dart';
import 'package:file_saver/file_saver.dart';
import 'package:flutter/foundation.dart';
import 'package:path/path.dart';
import 'package:share_plus/share_plus.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';

class BloodPressureModel extends ChangeNotifier {
static const maxEntries = 2E64; // https://www.sqlite.org/limits.html Nr.13
late final Database _database;

BloodPressureModel._create();
Future<void> _asyncInit(String? dbPath) async {
Future<void> _asyncInit(String? dbPath, bool isFullPath) async {
dbPath ??= await getDatabasesPath();

if (dbPath != inMemoryDatabasePath) {
if (dbPath != inMemoryDatabasePath && !isFullPath) {
dbPath = join(dbPath, 'blood_pressure.db');
}

Expand All @@ -34,7 +29,7 @@ class BloodPressureModel extends ChangeNotifier {
}

// factory method, to allow for async constructor
static Future<BloodPressureModel> create({String? dbPath}) async {
static Future<BloodPressureModel> create({String? dbPath, bool isFullPath = false}) async {
if (Platform.isWindows || Platform.isLinux) {
// Initialize FFI
sqfliteFfiInit();
Expand All @@ -43,7 +38,7 @@ class BloodPressureModel extends ChangeNotifier {
}

final component = BloodPressureModel._create();
await component._asyncInit(dbPath);
await component._asyncInit(dbPath, isFullPath);
return component;
}

Expand Down Expand Up @@ -156,63 +151,6 @@ class BloodPressureModel extends ChangeNotifier {
return (res as int?) ?? -1;
}

Future<void> save(void Function(bool success, String? msg) callback, {bool exportAsText = false}) async {
// create csv
String csvData = 'timestampUnixMs, systolic, diastolic, pulse, notes\n';
List<Map<String, Object?>> allEntries = await _database.query('bloodPressureModel', orderBy: 'timestamp DESC');
List<List<dynamic>> data = [];
for (var e in allEntries) {
data.add([e['timestamp'],e['systolic'], e['diastolic'], e['pulse'], e['notes']]);
}
csvData += const ListToCsvConverter().convert(data, delimitAllFields: true);

// save data
String filename = 'blood_press_${DateTime.now().toIso8601String()}';
String path = await FileSaver.instance
.saveFile(name: filename, bytes: Uint8List.fromList(utf8.encode(csvData)), ext: 'csv', mimeType: MimeType.csv);


// notify user about location
if (Platform.isLinux || Platform.isWindows || Platform.isMacOS) {
callback(true, path);
} else if (Platform.isAndroid || Platform.isIOS) {
var mimeType = MimeType.csv;
if (exportAsText) {
mimeType = MimeType.text;
}
Share.shareXFiles([
XFile(
path,
mimeType: mimeType.type,
)
]);
callback(true, null);
} else {}
}

Future<void> import(void Function(bool) callback) async {
var result = await FilePicker.platform.pickFiles(
allowMultiple: false,
withData: true,
);

if (result != null) {
var binaryContent = result.files.single.bytes;
if (binaryContent != null) {
final csvContents = const CsvToListConverter()
.convert(utf8.decode(binaryContent), fieldDelimiter: ',', textDelimiter: '"', eol: '\n');
for (var i = 1; i < csvContents.length; i++) {
var line = csvContents[i];
BloodPressureRecord record = BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(line[0] as int),
(line[1] as int), (line[2] as int), (line[3] as int), line[4].toString());
add(record);
}
return callback(true);
}
}
return callback(false);
}

void close() {
_database.close();
}
Expand Down
Loading