Commit 40463fb
Changed files (9)
app
lib
components
dialoges
measurement_list
screens
test
model
medicine
app/lib/components/dialoges/add_measurement_dialoge.dart
@@ -8,7 +8,6 @@ import 'package:blood_pressure_app/model/blood_pressure/record.dart';
import 'package:blood_pressure_app/model/storage/storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
-import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:health_data_store/health_data_store.dart' hide BloodPressureRecord;
import 'package:intl/intl.dart';
@@ -20,6 +19,7 @@ class AddEntryDialoge extends StatefulWidget {
/// This is usually created through the [showAddEntryDialoge] function.
const AddEntryDialoge({super.key,
required this.settings,
+ required this.medRepo,
this.initialRecord,
});
@@ -35,6 +35,9 @@ class AddEntryDialoge extends StatefulWidget {
/// saved separately.
final BloodPressureRecord? initialRecord;
+ /// Repository that contains all selectable medicines.
+ final MedicineRepository medRepo;
+
@override
State<AddEntryDialoge> createState() => _AddEntryDialogeState();
}
@@ -106,7 +109,7 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
_measurementFormActive = true;
}
- RepositoryProvider.of<MedicineRepository>(context).getAll()
+ widget.medRepo.getAll()
.then((value) => setState(() => availableMeds.addAll(value)));
sysFocusNode.requestFocus();
@@ -431,6 +434,7 @@ class _AddEntryDialogeState extends State<AddEntryDialoge> {
Future<(BloodPressureRecord?, MedicineIntake?)?> showAddEntryDialoge(
BuildContext context,
Settings settings,
+ MedicineRepository medRepo,
[BloodPressureRecord? initialRecord,]) =>
showDialog<(BloodPressureRecord?, MedicineIntake?)>(
context: context, builder: (context) =>
@@ -438,6 +442,7 @@ Future<(BloodPressureRecord?, MedicineIntake?)?> showAddEntryDialoge(
child: AddEntryDialoge(
settings: settings,
initialRecord: initialRecord,
+ medRepo: medRepo,
),
),
);
app/lib/components/measurement_list/measurement_list_entry.dart
@@ -3,7 +3,9 @@ import 'package:blood_pressure_app/model/blood_pressure/model.dart';
import 'package:blood_pressure_app/model/blood_pressure/record.dart';
import 'package:blood_pressure_app/model/storage/storage.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:health_data_store/health_data_store.dart' hide BloodPressureRecord;
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
@@ -40,6 +42,7 @@ class MeasurementListRow extends StatelessWidget {
final model = Provider.of<BloodPressureModel>(context, listen: false);
final entry = await showAddEntryDialoge(context,
Provider.of<Settings>(context, listen: false),
+ RepositoryProvider.of<MedicineRepository>(context),
record,
);
if (entry?.$1 != null) {
app/lib/screens/elements/legacy_measurement_list.dart
@@ -7,7 +7,9 @@ import 'package:blood_pressure_app/model/storage/intervall_store.dart';
import 'package:blood_pressure_app/model/storage/settings_store.dart';
import 'package:blood_pressure_app/screens/elements/blood_pressure_builder.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
+import 'package:health_data_store/health_data_store.dart' hide BloodPressureRecord;
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
@@ -87,6 +89,7 @@ class LegacyMeasurementsList extends StatelessWidget {
final model = Provider.of<BloodPressureModel>(context, listen: false);
final entry = await showAddEntryDialoge(context,
Provider.of<Settings>(context, listen: false),
+ RepositoryProvider.of<MedicineRepository>(context),
data[index],
);
if (entry?.$1 != null) {
app/lib/screens/home_screen.dart
@@ -35,7 +35,10 @@ class AppHome extends StatelessWidget {
SchedulerBinding.instance.addPostFrameCallback((_) async {
final model = Provider.of<BloodPressureModel>(context, listen: false);
final intakes = RepositoryProvider.of<MedicineIntakeRepository>(context);
- final measurement = await showAddEntryDialoge(context, Provider.of<Settings>(context, listen: false));
+ final measurement = await showAddEntryDialoge(context,
+ Provider.of<Settings>(context, listen: false),
+ RepositoryProvider.of<MedicineRepository>(context),
+ );
if (measurement == null) return;
if (measurement.$1 != null) {
if (context.mounted) {
@@ -113,7 +116,10 @@ class AppHome extends StatelessWidget {
onPressed: () async {
final model = Provider.of<BloodPressureModel>(context, listen: false);
final intakes = RepositoryProvider.of<MedicineIntakeRepository>(context);
- final measurement = await showAddEntryDialoge(context, Provider.of<Settings>(context, listen: false));
+ final measurement = await showAddEntryDialoge(context,
+ Provider.of<Settings>(context, listen: false),
+ RepositoryProvider.of<MedicineRepository>(context),
+ );
if (measurement == null) return;
if (measurement.$1 != null) {
if (context.mounted) {
app/test/model/medicine/medicine_test.dart
@@ -49,7 +49,7 @@ void main() {
final List<Medicine> _meds = [];
-/// Creates mock intake.
+/// Creates mock medicine.
///
/// Medicines with the same properties will keep the correct id.
Medicine mockMedicine({
app/test/ui/components/add_measurement_dialoge_test.dart
@@ -1,6 +1,5 @@
import 'package:blood_pressure_app/components/dialoges/add_measurement_dialoge.dart';
import 'package:blood_pressure_app/components/settings/color_picker_list_tile.dart';
-import 'package:blood_pressure_app/model/blood_pressure/medicine/medicine.dart';
import 'package:blood_pressure_app/model/blood_pressure/medicine/medicine_intake.dart';
import 'package:blood_pressure_app/model/blood_pressure/needle_pin.dart';
import 'package:blood_pressure_app/model/blood_pressure/record.dart';
@@ -9,9 +8,9 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:health_data_store/health_data_store.dart' hide BloodPressureRecord;
import '../../model/export_import/record_formatter_test.dart';
-import '../../model/medicine/medicine_test.dart';
import 'settings/color_picker_list_tile_test.dart';
import 'util.dart';
@@ -20,6 +19,7 @@ void main() {
testWidgets('should show everything on initial page', (tester) async {
await tester.pumpWidget(materialApp(
AddEntryDialoge(
+ medRepo: await medRepo(),
settings: Settings(),
),
),);
@@ -41,6 +41,7 @@ void main() {
DateTime.now(), 123, 56, 43, 'Test note',
needlePin: const MeasurementNeedlePin(Colors.teal),
),
+ medRepo: await medRepo(),
),
),);
await tester.pumpAndSettle();
@@ -58,9 +59,8 @@ void main() {
testWidgets('should show medication picker when medications available', (tester) async {
await tester.pumpWidget(materialApp(
AddEntryDialoge(
- settings: Settings(
- medications: [mockMedicine(designation: 'testmed')],
- ),
+ settings: Settings(),
+ medRepo: await medRepo([mockMedicine(designation: 'testmed')]),
),
),);
await tester.pumpAndSettle();
@@ -78,9 +78,8 @@ void main() {
testWidgets('should reveal dosis on medication selection', (tester) async {
await tester.pumpWidget(materialApp(
AddEntryDialoge(
- settings: Settings(
- medications: [mockMedicine(designation: 'testmed')],
- ),
+ settings: Settings(),
+ medRepo: await medRepo([mockMedicine(designation: 'testmed')]),
),
),);
await tester.pumpAndSettle();
@@ -109,9 +108,8 @@ void main() {
testWidgets('should enter default dosis if available', (tester) async {
await tester.pumpWidget(materialApp(
AddEntryDialoge(
- settings: Settings(
- medications: [mockMedicine(designation: 'testmed', defaultDosis: 3.1415)],
- ),
+ settings: Settings(),
+ medRepo: await medRepo([mockMedicine(designation: 'testmed', defaultDosis: 3.1415)]),
),
),);
await tester.pumpAndSettle();
@@ -127,9 +125,8 @@ void main() {
testWidgets('should not quit when the measurement field is incorrectly filled, but a measurement is added', (tester) async {
await tester.pumpWidget(materialApp(
AddEntryDialoge(
- settings: Settings(
- medications: [mockMedicine(designation: 'testmed', defaultDosis: 3.1415)],
- ),
+ settings: Settings(),
+ medRepo: await medRepo([mockMedicine(designation: 'testmed', defaultDosis: 3.1415)]),
),
),);
await tester.pumpAndSettle();
@@ -161,6 +158,7 @@ void main() {
dynamic result = 'result before save';
await loadDialoge(tester, (context) async
=> result = await showAddEntryDialoge(context, Settings(),
+ await medRepo(),
mockRecord(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal),),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
@@ -175,7 +173,7 @@ void main() {
dynamic result = 'result before save';
final record = mockRecord(sys: 123, dia: 56, pul: 43, note: 'Test note', pin: Colors.teal);
await loadDialoge(tester, (context) async
- => result = await showAddEntryDialoge(context, Settings(), record),);
+ => result = await showAddEntryDialoge(context, Settings(), await medRepo(), record),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
expect(find.byType(AddEntryDialoge), findsOneWidget);
@@ -192,7 +190,7 @@ void main() {
testWidgets('should be able to input records', (WidgetTester tester) async {
dynamic result = 'result before save';
await loadDialoge(tester, (context) async
- => result = await showAddEntryDialoge(context, Settings()),);
+ => result = await showAddEntryDialoge(context, Settings(), await medRepo(),),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
await tester.enterText(find.ancestor(of: find.text('Systolic').first, matching: find.byType(TextFormField)), '123');
@@ -221,7 +219,7 @@ void main() {
testWidgets('should allow value only', (WidgetTester tester) async {
dynamic result = 'result before save';
await loadDialoge(tester, (context) async
- => result = await showAddEntryDialoge(context, Settings()),);
+ => result = await showAddEntryDialoge(context, Settings(), await medRepo(),),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
final localizations = await AppLocalizations.delegate.load(const Locale('en'));
@@ -251,7 +249,7 @@ void main() {
=> result = await showAddEntryDialoge(context, Settings(
allowMissingValues: true,
validateInputs: false,
- ),),);
+ ), await medRepo(),),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
final localizations = await AppLocalizations.delegate.load(const Locale('en'));
@@ -276,12 +274,10 @@ void main() {
dynamic result = 'result before save';
await loadDialoge(tester, (context) async
- => result = await showAddEntryDialoge(context, Settings(
- medications: [
- mockMedicine(designation: 'medication1'),
- med2,
- ],
- ),),);
+ => result = await showAddEntryDialoge(context, Settings(), await medRepo([
+ mockMedicine(designation: 'medication1'),
+ med2,
+ ],),),);
final localizations = await AppLocalizations.delegate.load(const Locale('en'));
await tester.tap(find.byType(DropdownButton<Medicine?>));
@@ -313,7 +309,8 @@ void main() {
);
});
testWidgets('should not allow invalid values', (tester) async {
- await loadDialoge(tester, (context) => showAddEntryDialoge(context, Settings()));
+ final mRep = await medRepo();
+ await loadDialoge(tester, (context) => showAddEntryDialoge(context, Settings(), mRep));
final localizations = await AppLocalizations.delegate.load(const Locale('en'));
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
@@ -376,8 +373,9 @@ void main() {
expect(find.text(localizations.errDiaGtSys), findsNothing);
});
testWidgets('should allow invalid values when setting is set', (tester) async {
+ final mRep = await medRepo();
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(validateInputs: false, allowMissingValues: true)),);
+ showAddEntryDialoge(context, Settings(validateInputs: false, allowMissingValues: true), mRep),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
await tester.enterText(find.ancestor(of: find.text('Systolic').first, matching: find.byType(TextFormField)), '2');
@@ -387,15 +385,17 @@ void main() {
expect(find.byType(AddEntryDialoge), findsNothing);
});
testWidgets('should respect settings.allowManualTimeInput', (tester) async {
+ final mRep = await medRepo();
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(allowManualTimeInput: false)),);
+ showAddEntryDialoge(context, Settings(allowManualTimeInput: false), mRep),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
expect(find.byIcon(Icons.edit), findsNothing);
});
testWidgets('should start with sys input focused', (tester) async {
+ final mRep = await medRepo();
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(), mockRecord(sys: 12)),);
+ showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12)),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
final primaryFocus = FocusManager.instance.primaryFocus;
@@ -409,8 +409,9 @@ void main() {
.having((p0) => p0.initialValue, 'systolic content', '12'),);
});
testWidgets('should focus next on input finished', (tester) async {
+ final mRep = await medRepo();
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(), mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+ showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
await tester.enterText(find.ancestor(of: find.text('Systolic').first, matching: find.byType(TextFormField)), '123');
@@ -451,8 +452,9 @@ void main() {
});
testWidgets('should focus last input field on backspace pressed in empty input field', (tester) async {
+ final mRep = await medRepo();
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(), mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+ showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
await tester.enterText(find.ancestor(of: find.text('note').first, matching: find.byType(TextFormField)), '');
@@ -511,11 +513,10 @@ void main() {
expect(find.descendant(of: fourthFocusedTextFormField, matching: find.text('Systolic')), findsWidgets);
});
testWidgets('should allow entering custom dosis', (tester) async {
+ final mRep = await medRepo([mockMedicine(designation: 'testmed')]);
dynamic result;
await loadDialoge(tester, (context) async =>
- result = await showAddEntryDialoge(context, Settings(
- medications: [mockMedicine(designation: 'testmed')],
- ),),
+ result = await showAddEntryDialoge(context, Settings(), mRep),
);
await tester.tap(find.byType(DropdownButton<Medicine?>));
@@ -547,11 +548,10 @@ void main() {
);
});
testWidgets('should allow modifying entered dosis', (tester) async {
+ final mRep = await medRepo([mockMedicine(designation: 'testmed')]);
dynamic result;
await loadDialoge(tester, (context) async =>
- result = await showAddEntryDialoge(context, Settings(
- medications: [mockMedicine(designation: 'testmed')],
- ),),
+ result = await showAddEntryDialoge(context, Settings(), mRep),
);
final localizations = await AppLocalizations.delegate.load(const Locale('en'));
@@ -588,8 +588,9 @@ void main() {
);
});
testWidgets('should not go back to last field when the current field is still filled', (tester) async {
+ final mRep = await medRepo([mockMedicine(designation: 'testmed')]);
await loadDialoge(tester, (context) =>
- showAddEntryDialoge(context, Settings(), mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
+ showAddEntryDialoge(context, Settings(), mRep, mockRecord(sys: 12, dia: 3, pul: 4, note: 'note')),);
expect(find.byType(DropdownButton<Medicine?>), findsNothing, reason: 'No medication in settings.');
await tester.enterText(find.ancestor(
app/test/ui/components/measurement_list_entry_test.dart
@@ -62,7 +62,7 @@ void main() {
expect(find.text('null'), findsNothing);
});
testWidgets('should open edit dialoge', (tester) async {
- await tester.pumpWidget(appBase(MeasurementListRow(
+ await tester.pumpWidget(await appBase(MeasurementListRow(
settings: Settings(), record: mockRecord(time: DateTime(2023),
sys:1, dia: 2, pul: 3, note: 'testTxt',),),),);
expect(find.byIcon(Icons.expand_more), findsOneWidget);
app/test/ui/components/util.dart
@@ -2,11 +2,12 @@ import 'package:blood_pressure_app/model/blood_pressure/medicine/intake_history.
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';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_test/flutter_test.dart';
+import 'package:health_data_store/health_data_store.dart';
import 'package:provider/provider.dart';
-
-import '../../ram_only_implementations.dart';
+import 'package:sqflite/sqflite.dart';
/// Create a root material widget with localizations.
Widget materialApp(Widget child) => MaterialApp(
@@ -17,7 +18,8 @@ Widget materialApp(Widget child) => MaterialApp(
/// Create a root material widget with localizations and all providers but
/// without a app root.
-Widget appBase(Widget child, {
+@Deprecated('replace with newAppBase')
+Future<Widget> appBase(Widget child, {
Settings? settings,
ExportSettings? exportSettings,
CsvExportSettings? csvExportSettings,
@@ -25,28 +27,83 @@ Widget appBase(Widget child, {
IntervallStoreManager? intervallStoreManager,
IntakeHistory? intakeHistory,
BloodPressureModel? model,
-}) {
- model ??= RamBloodPressureModel();
+}) async {
+ // TODO: migrate arguments
+ final db = await HealthDataStore.load(await openDatabase(inMemoryDatabasePath));
+
+ 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 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, {
+ Settings? settings,
+ ExportSettings? exportSettings,
+ CsvExportSettings? csvExportSettings,
+ PdfExportSettings? pdfExportSettings,
+ IntervallStoreManager? intervallStoreManager,
+ BloodPressureRepository? bpRepo,
+ MedicineRepository? medRepo,
+ MedicineIntakeRepository? intakeRepo,
+}) async {
settings ??= Settings();
exportSettings ??= ExportSettings();
csvExportSettings ??= CsvExportSettings();
pdfExportSettings ??= PdfExportSettings();
- intakeHistory ??= IntakeHistory([]);
intervallStoreManager ??= IntervallStoreManager(IntervallStorage(), IntervallStorage(), IntervallStorage());
- return MultiProvider(
+
+ HealthDataStore? db;
+ if (bpRepo != null || medRepo != null || intakeRepo != null) {
+ db = await HealthDataStore.load(await openDatabase(inMemoryDatabasePath));
+ }
+
+ return MultiProvider(providers: [
+ ChangeNotifierProvider(create: (_) => settings),
+ ChangeNotifierProvider(create: (_) => exportSettings),
+ ChangeNotifierProvider(create: (_) => csvExportSettings),
+ ChangeNotifierProvider(create: (_) => pdfExportSettings),
+ ChangeNotifierProvider(create: (_) => intervallStoreManager),
+ ], child: MultiRepositoryProvider(
providers: [
- ChangeNotifierProvider(create: (_) => settings),
- ChangeNotifierProvider(create: (_) => exportSettings),
- ChangeNotifierProvider(create: (_) => csvExportSettings),
- ChangeNotifierProvider(create: (_) => pdfExportSettings),
- ChangeNotifierProvider(create: (_) => intakeHistory),
- ChangeNotifierProvider(create: (_) => intervallStoreManager),
- ChangeNotifierProvider<BloodPressureModel>(create: (_) => model!),
+ RepositoryProvider(create: (context) => bpRepo ?? db!.bpRepo),
+ RepositoryProvider(create: (context) => medRepo ?? db!.medRepo),
+ RepositoryProvider(create: (context) => intakeRepo ?? db!.intakeRepo),
],
- child: materialApp(child),
- );
+ child: child,
+ ),);
}
+
/// Open a dialoge through a button press.
///
/// Example usage:
@@ -63,3 +120,39 @@ Future<void> loadDialoge(WidgetTester tester, void Function(BuildContext context
await tester.tap(find.text(dialogeStarterText));
await tester.pumpAndSettle();
}
+
+/// Get empty mock med repo.
+Future<MedicineRepository> medRepo([List<Medicine>? meds]) async {
+ final db = await HealthDataStore.load(await openDatabase(inMemoryDatabasePath));
+ final repo = db.medRepo;
+ if (meds != null) {
+ for (final med in meds) {
+ await repo.add(med);
+ }
+ }
+ return repo;
+}
+
+final List<Medicine> _meds = [];
+
+/// Creates mock Medicine.
+///
+/// Medicines with the same properties will keep the correct id.
+Medicine mockMedicine({
+ Color color = Colors.black,
+ String designation = '',
+ double? defaultDosis,
+}) {
+ final matchingMeds = _meds.where((med) => med.dosis?.mg == defaultDosis
+ && med.color == color.value
+ && med.designation == designation,
+ );
+ if (matchingMeds.isNotEmpty) return matchingMeds.first;
+ final med = Medicine(
+ designation: designation,
+ color: color.value,
+ dosis: defaultDosis == null ? null : Weight.mg(defaultDosis),
+ );
+ _meds.add(med);
+ return med;
+}