Commit eb95d0b
Changed files (14)
app
lib
features
l10n
model
app/lib/features/export_import/active_field_customization.dart
@@ -7,6 +7,7 @@ import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
import 'package:flutter/material.dart';
import 'package:blood_pressure_app/l10n/app_localizations.dart';
import 'package:provider/provider.dart';
@@ -23,6 +24,7 @@ class ActiveExportFieldCustomization extends StatelessWidget {
Widget build(BuildContext context) => switch (format) {
ExportFormat.csv => Consumer<CsvExportSettings>(builder: _builder),
ExportFormat.pdf => Consumer<PdfExportSettings>(builder: _builder),
+ ExportFormat.xsl => Consumer<ExcelExportSettings>(builder: _builder),
ExportFormat.db => const SizedBox.shrink()
};
app/lib/features/settings/export_import_screen.dart
@@ -76,11 +76,13 @@ class ExportImportScreen extends StatelessWidget {
value: settings.exportFormat,
items: [
DropdownMenuItem(
- value: ExportFormat.csv, child: Text(localizations.csv),),
+ value: ExportFormat.csv, child: Text(localizations.csv)),
DropdownMenuItem(
- value: ExportFormat.pdf, child: Text(localizations.pdf),),
+ value: ExportFormat.pdf, child: Text(localizations.pdf)),
DropdownMenuItem(
- value: ExportFormat.db, child: Text(localizations.db),),
+ value: ExportFormat.db, child: Text(localizations.db)),
+ DropdownMenuItem(
+ value: ExportFormat.xsl, child: Text(localizations.xsl)),
],
onChanged: (ExportFormat? value) {
if (value != null) {
app/lib/l10n/app_en.arb
@@ -570,5 +570,7 @@
"btnShare": "SHARE",
"@btnShare": {},
"exportSuccess": "Export successful",
- "@exportSuccess": {}
+ "@exportSuccess": {},
+ "xsl": "Excel (xsl)",
+ "@xsl": {}
}
app/lib/model/export_import/excel_converter.dart
@@ -0,0 +1,55 @@
+import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
+import 'package:health_data_store/health_data_store.dart';
+
+/// Utility class to convert [FullEntry]s to xsl files.
+class ExcelConverter {
+ /// Initialize object to convert [FullEntry]s to xsl files.
+ ExcelConverter(this.settings, this.availableColumns, this.availableMedicines);
+
+ /// Settings that apply for exports.
+ final ExcelExportSettings settings;
+
+ /// Columns manager used for export.
+ final ExportColumnsManager availableColumns;
+
+ /// Medicines to choose from during import.
+ final List<Medicine> availableMedicines;
+
+ /// Create the contents of a xls file from passed records.
+ String create(List<(DateTime, BloodPressureRecord, Note, List<MedicineIntake>, Weight?)> entries) {
+ final columns = settings.exportFieldsConfiguration.getActiveColumns(availableColumns);
+ final table = entries.map(
+ (entry) => columns.map(
+ (column) => column.encode(entry.$2, entry.$3, entry.$4, entry.$5),
+ ).toList(),
+ ).toList();
+
+ table.insert(0, columns.map((c) => c.csvTitle).toList());
+
+ return _createXls(table);
+ }
+}
+
+/// _Very_ simple string concatenation based xls file writer.
+String _createXls(List<List<String>> data) {
+ final buff = StringBuffer(_preData);
+ for (final row in data) {
+ buff.write('<Row ss:Height="12.816">');
+ for (final cell in row) {
+ final num = int.tryParse(cell) != null || double.tryParse(cell) != null;
+ buff.write('<Cell>'
+ '<Data ss:Type="${num ? 'Number' : 'String'}">$cell</Data>'
+ '</Cell>');
+ }
+ buff.write('</Row>');
+ }
+ buff.write(_postData);
+ return buff.toString();
+}
+
+const _preData = '''
+<?xml version="1.0" encoding="UTF-8"?>
+<?mso-application progid="Excel.Sheet"?><Workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet" xmlns:c="urn:schemas-microsoft-com:office:component:spreadsheet" xmlns:html="http://www.w3.org/TR/REC-html40" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet" xmlns:x2="http://schemas.microsoft.com/office/excel/2003/xml" xmlns:x="urn:schemas-microsoft-com:office:excel" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><OfficeDocumentSettings xmlns="urn:schemas-microsoft-com:office:office"><Colors><Color><Index>3</Index><RGB>#000000</RGB></Color><Color><Index>4</Index><RGB>#0000ee</RGB></Color><Color><Index>5</Index><RGB>#006600</RGB></Color><Color><Index>6</Index><RGB>#333333</RGB></Color><Color><Index>7</Index><RGB>#808080</RGB></Color><Color><Index>8</Index><RGB>#996600</RGB></Color><Color><Index>9</Index><RGB>#c0c0c0</RGB></Color><Color><Index>10</Index><RGB>#cc0000</RGB></Color><Color><Index>11</Index><RGB>#ccffcc</RGB></Color><Color><Index>12</Index><RGB>#dddddd</RGB></Color><Color><Index>13</Index><RGB>#ffcccc</RGB></Color><Color><Index>14</Index><RGB>#ffffcc</RGB></Color><Color><Index>15</Index><RGB>#ffffff</RGB></Color></Colors></OfficeDocumentSettings><ExcelWorkbook xmlns="urn:schemas-microsoft-com:office:excel"><WindowHeight>9000</WindowHeight><WindowWidth>13860</WindowWidth><WindowTopX>240</WindowTopX><WindowTopY>75</WindowTopY><ProtectStructure>False</ProtectStructure><ProtectWindows>False</ProtectWindows></ExcelWorkbook><Styles><Style ss:ID="Default" ss:Name="Default"/><Style ss:ID="Note" ss:Name="Note"><Font ss:FontName="Liberation Sans" ss:Size="10"/></Style><Style ss:ID="Default" ss:Name="Default"/><Style ss:ID="Heading" ss:Name="Heading"><Alignment/><Font ss:Bold="1" ss:Size="24"/></Style><Style ss:ID="Heading_20_1" ss:Name="Heading 1"><Alignment/><Font ss:Bold="1" ss:Size="18"/></Style><Style ss:ID="Heading_20_2" ss:Name="Heading 2"><Alignment/><Font ss:Bold="1" ss:Size="12"/></Style><Style ss:ID="Text" ss:Name="Text"><Alignment/></Style><Style ss:ID="Note" ss:Name="Note"><Alignment/><Borders><Border ss:Position="Bottom" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#808080"/><Border ss:Position="Left" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#808080"/><Border ss:Position="Right" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#808080"/><Border ss:Position="Top" ss:LineStyle="Continuous" ss:Weight="1" ss:Color="#808080"/></Borders><Interior ss:Color="#ffffcc" ss:Pattern="Solid"/></Style><Style ss:ID="Footnote" ss:Name="Footnote"><Alignment/></Style><Style ss:ID="Hyperlink" ss:Name="Hyperlink"><Alignment/></Style><Style ss:ID="Status" ss:Name="Status"><Alignment/></Style><Style ss:ID="Good" ss:Name="Good"><Alignment/><Interior ss:Color="#ccffcc" ss:Pattern="Solid"/></Style><Style ss:ID="Neutral" ss:Name="Neutral"><Alignment/><Interior ss:Color="#ffffcc" ss:Pattern="Solid"/></Style><Style ss:ID="Bad" ss:Name="Bad"><Alignment/><Interior ss:Color="#ffcccc" ss:Pattern="Solid"/></Style><Style ss:ID="Warning" ss:Name="Warning"><Alignment/></Style><Style ss:ID="Error" ss:Name="Error"><Alignment/><Interior ss:Color="#cc0000" ss:Pattern="Solid"/></Style><Style ss:ID="Accent" ss:Name="Accent"><Alignment/></Style><Style ss:ID="Accent_20_1" ss:Name="Accent 1"><Alignment/><Font ss:Bold="1" ss:Color="#ffffff"/><Interior ss:Color="#000000" ss:Pattern="Solid"/></Style><Style ss:ID="Accent_20_2" ss:Name="Accent 2"><Alignment/><Font ss:Bold="1" ss:Color="#ffffff"/><Interior ss:Color="#808080" ss:Pattern="Solid"/></Style><Style ss:ID="Accent_20_3" ss:Name="Accent 3"><Alignment/><Interior ss:Color="#dddddd" ss:Pattern="Solid"/></Style><Style ss:ID="Result" ss:Name="Result"><Alignment/><Font ss:Bold="1" ss:Italic="1" ss:Underline="Single"/></Style><Style ss:ID="co1"/><Style ss:ID="ta1"/></Styles><ss:Worksheet ss:Name="Sheet1"><Table ss:StyleID="ta1"><Column ss:Span="2" ss:Width="64.008"/>''';
+
+const _postData = '</Table><x:WorksheetOptions/></ss:Worksheet></Workbook>';
app/lib/model/storage/db/config_dao.dart
@@ -4,6 +4,7 @@ import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
import 'package:blood_pressure_app/model/storage/interval_store.dart';
import 'package:blood_pressure_app/model/storage/settings_store.dart';
@@ -185,4 +186,8 @@ class ConfigDao implements SettingsLoader {
_exportColumnsManagerInstance = columnsManager;
return columnsManager;
}
+
+ @override
+ Future<ExcelExportSettings> loadXslExportSettings() async =>
+ ExcelExportSettings(); // This was added after file settings
}
app/lib/model/storage/db/file_settings_loader.dart
@@ -7,6 +7,7 @@ import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
import 'package:blood_pressure_app/model/storage/interval_store.dart';
import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:flutter/widgets.dart';
@@ -102,6 +103,14 @@ class FileSettingsLoader implements SettingsLoader {
(e) => e.toJson(),
);
+ @override
+ Future<ExcelExportSettings> loadXslExportSettings() async => _loadFile(
+ 'xsl-export',
+ ExcelExportSettings.fromJson,
+ ExcelExportSettings.new,
+ (e) => e.toJson(),
+ );
+
@override
Future<Settings> loadSettings() async => _loadFile(
'general',
app/lib/model/storage/db/settings_loader.dart
@@ -2,6 +2,7 @@ import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
import 'package:blood_pressure_app/model/storage/interval_store.dart';
import 'package:blood_pressure_app/model/storage/settings_store.dart';
@@ -54,4 +55,12 @@ abstract class SettingsLoader {
///
/// Changes to the disk data will not propagate to the object.
Future<ExportColumnsManager> loadExportColumnsManager();
+
+ /// Loads the profiles [ExcelExportSettings] object from disk.
+ ///
+ /// If any errors occur or the object is not present, a default one will be
+ /// created. Changes in the object will save to the automatically.
+ ///
+ /// Changes to the disk data will not propagate to the object.
+ Future<ExcelExportSettings> loadXslExportSettings();
}
app/lib/model/storage/export_settings_store.dart
@@ -75,19 +75,16 @@ class ExportSettings extends ChangeNotifier {
/// File formats to which measurements can be exported.
enum ExportFormat {
csv,
+ xsl,
pdf,
db;
- int serialize() {
- switch(this) {
- case ExportFormat.csv:
- return 0;
- case ExportFormat.pdf:
- return 1;
- case ExportFormat.db:
- return 2;
- }
- }
+ int serialize() => switch(this) {
+ ExportFormat.csv => 0,
+ ExportFormat.pdf => 1,
+ ExportFormat.db => 2,
+ ExportFormat.xsl => 3,
+ };
factory ExportFormat.deserialize(value) {
final int? intValue = ConvertUtil.parseInt(value);
@@ -100,6 +97,8 @@ enum ExportFormat {
return ExportFormat.pdf;
case 2:
return ExportFormat.db;
+ case 3:
+ return ExportFormat.xsl;
default:
assert(false);
return ExportFormat.csv;
app/lib/model/storage/export_xsl_settings_store.dart
@@ -0,0 +1,54 @@
+import 'dart:convert';
+
+import 'package:blood_pressure_app/model/export_import/export_configuration.dart';
+import 'package:blood_pressure_app/model/storage/common_settings_interfaces.dart';
+import 'package:flutter/material.dart';
+
+/// Settings that are only important for exporting entries to csv files.
+class ExcelExportSettings extends ChangeNotifier implements CustomFieldsSettings {
+ ExcelExportSettings({
+ ActiveExportColumnConfiguration? exportFieldsConfiguration,
+ }) {
+ if (exportFieldsConfiguration != null) _exportFieldsConfiguration = exportFieldsConfiguration;
+
+ _exportFieldsConfiguration.addListener(notifyListeners);
+ }
+
+ /// Create a instance from a map created by [toMap].
+ factory ExcelExportSettings.fromMap(Map<String, dynamic> map) => ExcelExportSettings(
+ exportFieldsConfiguration: ActiveExportColumnConfiguration.fromJson(map['exportFieldsConfiguration']),
+ );
+
+ /// Create a instance from a map created by [toJson].
+ factory ExcelExportSettings.fromJson(String json) {
+ try {
+ return ExcelExportSettings.fromMap(jsonDecode(json));
+ } catch (e) {
+ assert(e is FormatException || e is TypeError);
+ return ExcelExportSettings();
+ }
+ }
+
+ /// Serialize the object to a restoreable map.
+ Map<String, dynamic> toMap() => <String, dynamic>{
+ 'exportFieldsConfiguration': exportFieldsConfiguration.toJson(),
+ };
+
+ /// Serializes the object to json string.
+ String toJson() => jsonEncode(toMap());
+
+ /// Copy all values from another instance.
+ void copyFrom(ExcelExportSettings other) {
+ _exportFieldsConfiguration = other._exportFieldsConfiguration;
+ notifyListeners();
+ }
+
+ /// Reset all fields to their default values.
+ void reset() => copyFrom(ExcelExportSettings());
+
+ ActiveExportColumnConfiguration _exportFieldsConfiguration = ActiveExportColumnConfiguration();
+ @override
+ ActiveExportColumnConfiguration get exportFieldsConfiguration => _exportFieldsConfiguration;
+
+ // Procedure for adding more entries described in the settings_store.dart doc comment
+}
app/lib/app.dart
@@ -5,6 +5,7 @@ import 'package:blood_pressure_app/l10n/app_localizations.dart';
import 'package:blood_pressure_app/model/storage/db/file_settings_loader.dart';
import 'package:blood_pressure_app/model/storage/db/settings_loader.dart';
import 'package:blood_pressure_app/model/storage/export_columns_store.dart';
+import 'package:blood_pressure_app/model/storage/export_xsl_settings_store.dart';
import 'package:blood_pressure_app/model/storage/storage.dart';
import 'package:blood_pressure_app/screens/error_reporting_screen.dart';
import 'package:blood_pressure_app/screens/home_screen.dart';
@@ -41,6 +42,7 @@ class _AppState extends State<App> {
ExportSettings? _exportSettings;
CsvExportSettings? _csvExportSettings;
PdfExportSettings? _pdfExportSettings;
+ ExcelExportSettings? _xslExportSettings;
IntervalStoreManager? _intervalStorageManager;
ExportColumnsManager? _exportColumnsManager;
@@ -52,6 +54,7 @@ class _AppState extends State<App> {
_exportSettings?.dispose();
_csvExportSettings?.dispose();
_pdfExportSettings?.dispose();
+ _xslExportSettings?.dispose();
_intervalStorageManager?.dispose();
_exportColumnsManager?.dispose();
super.dispose();
@@ -90,6 +93,7 @@ class _AppState extends State<App> {
_exportSettings ??= await settingsLoader.loadExportSettings();
_csvExportSettings ??= await settingsLoader.loadCsvExportSettings();
_pdfExportSettings ??= await settingsLoader.loadPdfExportSettings();
+ _xslExportSettings ??= await settingsLoader.loadXslExportSettings();
_intervalStorageManager ??= await settingsLoader.loadIntervalStorageManager();
_exportColumnsManager ??= await settingsLoader.loadExportColumnsManager();
} catch (e, stack) {
@@ -167,6 +171,7 @@ class _AppState extends State<App> {
ChangeNotifierProvider.value(value: _exportSettings!),
ChangeNotifierProvider.value(value: _csvExportSettings!),
ChangeNotifierProvider.value(value: _pdfExportSettings!),
+ ChangeNotifierProvider.value(value: _xslExportSettings!),
ChangeNotifierProvider.value(value: _intervalStorageManager!),
ChangeNotifierProvider.value(value: _exportColumnsManager!),
],
app/pubspec.lock
@@ -1377,7 +1377,7 @@ packages:
source: hosted
version: "1.1.0"
xml:
- dependency: transitive
+ dependency: "direct main"
description:
name: xml
sha256: "971043b3a0d3da28727e40ed3e0b5d18b742fa5a68665cca88e74b7876d5e025"
app/pubspec.yaml
@@ -43,6 +43,7 @@ dependencies:
# desktop only
sqflite_common_ffi: ^2.3.6
inline_tab_view: ^1.0.1
+ xml: ^6.6.1
dev_dependencies:
integration_test: