Commit fe5e146
Changed files (5)
app
lib
model
blood_pressure
test
app/lib/model/blood_pressure/model.dart
@@ -1,15 +1,13 @@
import 'dart:async';
import 'dart:convert';
+import 'dart:io';
import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
import 'package:blood_pressure_app/model/blood_pressure/record.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/subsettings/export_import/export_button_bar.dart';
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:path/path.dart';
-import 'package:provider/provider.dart';
import 'package:sqflite/sqflite.dart';
/// Model to access values in the measurement database.
@@ -20,34 +18,6 @@ class BloodPressureModel extends ChangeNotifier {
late final Database _database;
- Future<void> _asyncInit(String? dbPath, bool isFullPath) async {
- dbPath ??= await getDatabasesPath();
-
- if (dbPath != inMemoryDatabasePath && !isFullPath) {
- dbPath = join(dbPath, 'blood_pressure.db');
- }
-
- // In case safer data loading is needed: finish this.
- /*
- String? backupPath;
- if (dbPath != inMemoryDatabasePath) {
- assert(_database.isUndefinedOrNull);
- backupPath = join(Directory.systemTemp.path, 'blood_pressure_bu_${DateTime.now().millisecondsSinceEpoch}.db');
- final copiedFile = File(dbPath).copy(backupPath);
- copiedFile.onError((error, stackTrace) => null)
- }
- var preserveBackup = false;
- */
-
- _database = await openDatabase(
- dbPath,
- onCreate: _onDBCreate,
- onUpgrade: _onDBUpgrade,
- // When increasing the version an update procedure from every other possible version is needed
- version: 2,
- );
- }
-
FutureOr<void> _onDBCreate(Database db, int version) => db.execute('CREATE TABLE bloodPressureModel('
'timestamp INTEGER(14) PRIMARY KEY,'
'systolic INTEGER, diastolic INTEGER,'
@@ -70,85 +40,20 @@ class BloodPressureModel extends ChangeNotifier {
///
/// [dbPath] is the path to the folder the database is in. When [dbPath] is left empty the default database file is
/// used. The [isFullPath] option tells the constructor not to add the default filename at the end of [dbPath].
- static Future<BloodPressureModel> create({String? dbPath, bool isFullPath = false}) async {
+ static Future<BloodPressureModel?> create({String? dbPath, bool isFullPath = false}) async {
final component = BloodPressureModel._create();
- await component._asyncInit(dbPath, isFullPath);
- return component;
- }
-
- /// Adds a new measurement at the correct chronological position in the List.
- ///
- /// This is not suitable for user inputs, as in this case export is needed as
- /// well. Consider using [BloodPressureModel.addAndExport] instead.
- Future<void> add(BloodPressureRecord measurement) async {
- if (!_database.isOpen) return;
- final existing = await _database.query('bloodPressureModel',
- where: 'timestamp = ?', whereArgs: [measurement.creationTime.millisecondsSinceEpoch],);
- if (existing.isNotEmpty) {
- await _database.update(
- 'bloodPressureModel',
- {
- 'systolic': measurement.systolic,
- 'diastolic': measurement.diastolic,
- 'pulse': measurement.pulse,
- 'notes': measurement.notes,
- 'needlePin': jsonEncode(measurement.needlePin?.toMap()),
- },
- where: 'timestamp = ?',
- whereArgs: [measurement.creationTime.millisecondsSinceEpoch],);
- } else {
- await _database.insert('bloodPressureModel', {
- 'timestamp': measurement.creationTime.millisecondsSinceEpoch,
- 'systolic': measurement.systolic,
- 'diastolic': measurement.diastolic,
- 'pulse': measurement.pulse,
- 'notes': measurement.notes,
- 'needlePin': jsonEncode(measurement.needlePin?.toMap()),
- });
- }
- notifyListeners();
- }
-
- /// Convenience wrapper for [add] that follows best practices.
- ///
- /// This ensures no timeout occurs by waiting for operations to finish and
- /// exports in case the [context] is provided and the option in export
- /// settings is active.
- Future<void> addAll(
- List<BloodPressureRecord> measurements,
- BuildContext? context,
- ) async {
- for (final measurement in measurements) {
- await add(measurement);
- }
-
- if (context == null || !context.mounted) return;
- final exportSettings = Provider.of<ExportSettings>(context, listen: false);
- if (exportSettings.exportAfterEveryEntry) {
- performExport(context);
- }
- }
-
- /// Adds a measurement to the model and tries to export all measurements, if [ExportSettings.exportAfterEveryEntry] is
- /// true.
- Future<void> addAndExport(BuildContext context, BloodPressureRecord record) async {
- await add(record);
+ dbPath ??= await getDatabasesPath();
- if (!context.mounted) return;
- final exportSettings = Provider.of<ExportSettings>(context, listen: false);
- if (exportSettings.exportAfterEveryEntry) {
- performExport(context);
+ if (dbPath != inMemoryDatabasePath && !isFullPath) {
+ dbPath = join(dbPath, 'blood_pressure.db');
}
- }
-
- /// Try to remove the measurement at a specific timestamp from the database.
- ///
- /// When no measurement at that time exists, the operation won't fail and
- /// listeners will get notified anyways.
- Future<void> delete(DateTime timestamp) async {
- if (!_database.isOpen) return;
- _database.delete('bloodPressureModel', where: 'timestamp = ?', whereArgs: [timestamp.millisecondsSinceEpoch]);
- notifyListeners();
+ if (!File(dbPath).existsSync()) return null;
+ component._database = await openDatabase(
+ dbPath,
+ onUpgrade: component._onDBUpgrade,
+ version: 2,
+ );
+ return component;
}
/// Returns all recordings in saved in a range in ascending order
app/test/model/bood_pressure_test.dart
@@ -4,7 +4,6 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:path/path.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
-import '../ram_only_implementations.dart';
void main() {
group('BloodPressureRecord', () {
@@ -30,123 +29,11 @@ void main() {
databaseFactory = databaseFactoryFfi;
});
- test('should initialize', () async {
- expect(() async {
- await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldInit.db'));
- }, returnsNormally,);
- });
- test('should start empty', () async {
- final m = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldStartEmpty.db'));
-
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 0);
- });
-
- test('should notify when adding entries', () async {
- final m = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldNotifyWhenAdding.db'));
-
- int listenerCalls = 0;
- m.addListener(() {
- listenerCalls++;
- });
-
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 0, 0, 0, ''));
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 0, 0, 0, ''));
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 0, 0, 0, ''));
-
- expect(listenerCalls, 3);
- });
-
- test('should return entries as added', () async {
- final m = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldReturnAddedEntries.db'));
-
- final r = BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(31415926), -172, 10000, 0,
- '((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈๏ แผ่นดินฮั่นเABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвг, \n \t д∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა',);
- m.addListener(() async {
- final res = (await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).first;
- expect(res, isNotNull);
- expect(res.creationTime, r.creationTime);
- expect(res.systolic, r.systolic);
- expect(res.diastolic, r.diastolic);
- expect(res.pulse, r.pulse);
- expect(res.notes, r.notes);
- return;
- });
-
- m.add(r);
- });
-
- test('should save and load between objects/sessions', () async {
- final m = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldPersist.db'));
- final r = BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(31415926), -172, 10000, 0,
- '((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈๏ แผ่นดินฮั่นเABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвг, \n \t д∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა',);
- await m.add(r);
-
- final m2 = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldPersist.db'));
- final res = (await m2.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).first;
-
- expect(res.creationTime, r.creationTime);
- expect(res.systolic, r.systolic);
- expect(res.diastolic, r.diastolic);
- expect(res.pulse, r.pulse);
- expect(res.notes, r.notes);
- });
-
- test('should delete', () async {
- final m = await BloodPressureModel.create(dbPath: join(inMemoryDatabasePath, 'BPMShouldDelete.db'));
-
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(758934), 123, 87, 65, ';)'));
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 1);
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 1);
-
- await m.delete(DateTime.fromMillisecondsSinceEpoch(758934));
-
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 0);
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 0);
- });
- });
-
- group('RamBloodPressureModel should behave like BloodPressureModel', () {
- test('should initialize', () async {
- expect(() async => RamBloodPressureModel(), returnsNormally);
- });
-
- test('should start empty', () async {
- final m = RamBloodPressureModel();
- expect((await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).length, 0);
- });
-
- test('should notify when adding entries', () async {
- final m = RamBloodPressureModel();
-
- int listenerCalls = 0;
- m.addListener(() {
- listenerCalls++;
- });
-
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 0, 0, 0, ''));
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(1), 0, 0, 0, ''));
- await m.add(BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(2), 0, 0, 0, ''));
-
- expect(listenerCalls, 3);
- });
-
- test('should return entries as added', () async {
- final m = RamBloodPressureModel();
-
- final r = BloodPressureRecord(DateTime.fromMillisecondsSinceEpoch(31415926), -172, 10000, 0,
- '((V⍳V)=⍳⍴V)/V←,V ⌷←⍳→⍴∆∇⊃‾⍎⍕⌈๏ แผ่นดินฮั่นเABCDEFGHIJKLMNOPQRSTUVWXYZ /0123456789abcdefghijklmnopqrstuvwxyz £©µÀÆÖÞßéöÿ–—‘“”„†•…‰™œŠŸž€ ΑΒΓΔΩαβγδω АБВГДабвг, \n \t д∀∂∈ℝ∧∪≡∞ ↑↗↨↻⇣ ┐┼╔╘░►☺♀ fi�⑀₂ἠḂӥẄɐː⍎אԱა',);
- m.addListener(() async {
- final res = (await m.getInTimeRange(DateTime.fromMillisecondsSinceEpoch(1), DateTime.now())).first;
- expect(res, isNotNull);
- expect(res.creationTime, r.creationTime);
- expect(res.systolic, r.systolic);
- expect(res.diastolic, r.diastolic);
- expect(res.pulse, r.pulse);
- expect(res.notes, r.notes);
- return;
- });
-
- m.add(r);
+ test("Doesn't create new models", () async {
+ final model = await BloodPressureModel
+ .create(dbPath: join(inMemoryDatabasePath, 'BPMShouldInit.db'));
+ expect(model, isNull);
});
+ // TODO: test loading with db files from older versions
});
}
app/test/ui/components/util.dart
@@ -1,5 +1,3 @@
-import 'package:blood_pressure_app/model/blood_pressure/medicine/intake_history.dart';
-import 'package:blood_pressure_app/model/blood_pressure/model.dart';
import 'package:blood_pressure_app/model/storage/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@@ -9,8 +7,6 @@ import 'package:health_data_store/health_data_store.dart';
import 'package:provider/provider.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
-import '../../ram_only_implementations.dart';
-
/// Create a root material widget with localizations.
Widget materialApp(Widget child) => MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
@@ -18,59 +14,8 @@ Widget materialApp(Widget child) => MaterialApp(
home: Scaffold(body:child),
);
-/// Create a root material widget with localizations and all providers but
-/// without a app root.
-@Deprecated('replace with newAppBase')
-Future<Widget> appBase(Widget child, {
- Settings? settings,
- ExportSettings? exportSettings,
- CsvExportSettings? csvExportSettings,
- PdfExportSettings? pdfExportSettings,
- IntervallStoreManager? intervallStoreManager,
- IntakeHistory? intakeHistory,
- BloodPressureModel? model,
-}) async {
- // TODO: migrate arguments
- final db = await _getHealthDateStore();
-
- final meds = settings?.medications.map((e) => Medicine(
- designation: e.designation,
- color: e.color.value,
- dosis: e.defaultDosis != null ? Weight.mg(e.defaultDosis!) : null),
- );
- final medRepo = db.medRepo;
- meds?.forEach(medRepo.add);
-
- final intakeRepo = db.intakeRepo;
- for (final e in intakeHistory?.getIntakes(DateTimeRange(
- start: DateTime.fromMillisecondsSinceEpoch(0),
- end: DateTime.fromMillisecondsSinceEpoch(999999999999))) ?? []) {
- expect(meds, isNotNull);
- expect(meds, isNotEmpty);
- final med = meds!.firstWhere((e2) => e2.designation == e.medicine.designation);
- intakeRepo.add(MedicineIntake(
- time: e.timestamp,
- dosis: Weight.mg(e.dosis),
- medicine: med,
- ));
- }
-
- return Provider<BloodPressureModel>(
- create: (_) => model ?? RamBloodPressureModel(),
- child: await newAppBase(child,
- settings: settings,
- exportSettings: exportSettings,
- csvExportSettings: csvExportSettings,
- pdfExportSettings: pdfExportSettings,
- intervallStoreManager: intervallStoreManager,
- medRepo: medRepo,
- intakeRepo: intakeRepo,
- ),);
-
- // TODO: bpRepo
-}
/// Creates a the same App as the main method.
-Future<Widget> newAppBase(Widget child, {
+Future<Widget> appBase(Widget child, {
Settings? settings,
ExportSettings? exportSettings,
CsvExportSettings? csvExportSettings,
@@ -124,7 +69,7 @@ Future<Widget> appBaseWithData(Widget child, {
final medRepo = db.medRepo;
final intakeRepo = db.intakeRepo;
- return newAppBase(
+ return appBase(
child,
settings: settings,
exportSettings: exportSettings,
app/test/ram_only_implementations.dart
@@ -1,57 +0,0 @@
-import 'dart:collection';
-
-import 'package:blood_pressure_app/model/blood_pressure/model.dart';
-import 'package:blood_pressure_app/model/blood_pressure/record.dart';
-import 'package:flutter/material.dart';
-
-class RamBloodPressureModel extends ChangeNotifier implements BloodPressureModel {
- final List<BloodPressureRecord> _records = [];
-
- static RamBloodPressureModel fromEntries(List<BloodPressureRecord> records) {
- final m = RamBloodPressureModel();
- for (final e in records) {
- m.add(e);
- }
- return m;
- }
-
- @override
- Future<void> add(BloodPressureRecord measurement) async {
- _records.add(measurement);
- notifyListeners();
- }
-
- @override
- Future<void> delete(DateTime timestamp) async {
- _records.removeWhere((element) => element.creationTime.isAtSameMomentAs(timestamp));
- }
-
- @override
- Future<UnmodifiableListView<BloodPressureRecord>> getInTimeRange(DateTime from, DateTime to) async {
- final List<BloodPressureRecord> recordsInTime = [];
- for (final e in _records) {
- if (e.creationTime.isAfter(from) && e.creationTime.isBefore(to)) {
- recordsInTime.add(e);
- }
- }
- return UnmodifiableListView(recordsInTime);
- }
-
- @override
- Future<UnmodifiableListView<BloodPressureRecord>> get all async => UnmodifiableListView(_records);
-
- @override
- Future<void> close() async {}
-
- @override
- Future<void> addAndExport(BuildContext context, BloodPressureRecord record) async {
- add(record);
- }
-
- @override
- Future<void> addAll(List<BloodPressureRecord> measurements, BuildContext? context) async {
- for (final m in measurements) {
- add(m);
- }
- }
-}