From a59170940f8d06d61233710d5a1b9b6ae1687bfc Mon Sep 17 00:00:00 2001 From: spinel Date: Sun, 5 Dec 2021 00:44:46 +0100 Subject: [PATCH] add delayed bolus percentage functionality, improve active profile handling (display and setting), move settings to objectbox, improve cancellation check on log bolus, add warning for already running events, implement accuracy reordering --- TODO | 45 ++- lib/components/forms.dart | 9 +- lib/config.dart | 28 -- lib/main.dart | 13 - lib/models/accuracy.dart | 26 +- lib/models/basal_profile.dart | 26 ++ lib/models/bolus.dart | 1 - lib/models/log_entry.dart | 6 +- lib/models/log_event.dart | 60 +++- lib/models/settings.dart | 95 ++++++ lib/navigation.dart | 8 - lib/objectbox-model.json | 81 ++++- lib/objectbox.g.dart | 210 +++++++++++- lib/screens/accuracy_detail.dart | 38 ++- lib/screens/accuracy_list.dart | 133 ++++---- lib/screens/basal/basal_detail.dart | 4 +- lib/screens/basal/basal_list.dart | 4 +- lib/screens/basal/basal_profile_detail.dart | 4 +- lib/screens/basal/basal_profile_list.dart | 98 +++--- lib/screens/bolus/bolus_detail.dart | 32 +- lib/screens/bolus/bolus_list.dart | 7 +- lib/screens/bolus/bolus_profile_detail.dart | 4 +- lib/screens/bolus/bolus_profile_list.dart | 102 +++--- lib/screens/log/log.dart | 277 +++++++--------- .../log/log_entry/log_bolus_detail.dart | 310 +++++++++++++----- lib/screens/log/log_entry/log_bolus_list.dart | 14 +- lib/screens/log/log_entry/log_entry.dart | 25 +- .../log/log_entry/log_meal_detail.dart | 19 +- lib/screens/log/log_entry/log_meal_list.dart | 4 +- .../log/log_event/log_event_detail.dart | 259 ++++++++------- lib/screens/log/log_event/log_event_list.dart | 167 ++++++---- .../log/log_event/log_event_type_detail.dart | 4 +- lib/screens/meal/meal_category_detail.dart | 4 +- lib/screens/meal/meal_category_list.dart | 5 +- lib/screens/meal/meal_detail.dart | 19 +- lib/screens/meal/meal_list.dart | 4 +- .../meal/meal_portion_type_detail.dart | 4 +- lib/screens/meal/meal_portion_type_list.dart | 4 +- lib/screens/meal/meal_source_detail.dart | 4 +- lib/screens/meal/meal_source_list.dart | 4 +- lib/settings.dart | 294 ++++++++--------- lib/utils/date_time_utils.dart | 12 +- pubspec.lock | 81 +++-- 43 files changed, 1569 insertions(+), 979 deletions(-) delete mode 100644 lib/config.dart create mode 100644 lib/models/settings.dart diff --git a/TODO b/TODO index 98e506e..b74630c 100644 --- a/TODO +++ b/TODO @@ -1,31 +1,11 @@ MAIN TASKS: - General/Framework: - ☐ find a general way to deal with duration fields - - Accuracies: - ☐ implement reordering - - Basal/Bolus: - ☐ replace active profile picking mode with simple dropdown - ☐ indicate both the default rate and the currently active one (according to event) - - Log Entry: - ☐ check for multiple active events with temporary basal/bolus profiles (smallest covered time frame counts) - ☐ provide splitting functionality for overlapping events - ☐ provide percentage functionality for delayed bolus - ☐ get rid of useless cancellation warnings - - Events: - ☐ when adding an event later on, prompt if existing boli should be recalculated - ☐ show event start AND end times in list - Settings: - ☐ add objectbox class and use instead of shared preferences + ☐ fix settings saving FUTURE TASKS: General/Framework: - ☐ make all fields readonly if user somehow gets to a deleted record detail view - ☐ add functionality to delete dead records + ☐ show indicator and make all fields readonly if user somehow gets to a deleted record detail view + ☐ add functionality to delete dead records (meaning: set deleted flag and no relations) ☐ clean up controllers (dispose method of each stateful widget) ☐ add explanations to each section ☐ account for deleted/disabled elements in dropdowns @@ -33,20 +13,35 @@ FUTURE TASKS: ☐ add clear button to dropdown (or all text fields?) ☐ check through all detail forms and set required fields/according messages ☐ find a better way to work with multiple measurements (or disable it?) + ☐ evaluate if some fields should be readonly instead of completely hidden + ☐ implement component for durations Log Overview: ☐ add pagination ☐ apply target color settings to glucose + Log Entry: + ☐ check if there is still an active bolus when suggesting glucose bolus Event Types: + ☐ add pagination ☐ add colors as indicators for log entries (and later graphs in reports) ☐ implement reminders as push notifications Settings: ☐ add fields for preferred date and time formats ☐ add fields for glucose target (as map of cutoff glucose and colors) - ☐ add option to hide warning dialogs on cancel or event stop - ☐ add option to hide extra customization options (ie. changing pre calculated values) + ☐ add option to hide warning dialogs on cancel, delete or event stop + ☐ add option to hide extra customization options (ie. changing pre calculated values)? ☐ add setting for decimal places + ☐ add field for active insulin duration Archive: + ✔ add objectbox settings class and use instead of shared preferences @done(21-12-05 00:41) @project(MAIN TASKS.Settings) + ✔ provide percentage functionality for delayed bolus @done(21-12-04 21:39) @project(MAIN TASKS.Log Entry) + ✔ create two bolus entries accordingly @done(21-12-04 22:12) @project(MAIN TASKS.Log Entry) + ✔ replace active profile picking mode with simple dropdown @done(21-12-04 20:10) @project(MAIN TASKS.Basal/Bolus) + ✔ indicate both the default rate and the currently active one (according to event) @done(21-12-04 20:10) @project(MAIN TASKS.Basal/Bolus) + ✔ get rid of excessive cancellation warnings @done(21-12-04 19:09) @project(MAIN TASKS.Log Entry) + ✔ give a warning if event of same type is already running @done(21-12-04 18:50) @project(MAIN TASKS.Events) + ✔ implement reordering @started(21-12-03 23:12) @done(21-12-04 17:01) @lasted(17h49m38s) @project(MAIN TASKS.Accuracies) + ✔ show event start AND end times in list @done(21-12-03 22:04) @project(MAIN TASKS.Events) ✔ separate events from log entries @done(21-12-01 23:37) @project(MAIN TASKS.Events) ✔ show total bolus and carbs per entry @done(21-12-01 19:50) @project(MAIN TASKS.Log Overview) ✔ display boli correctly @done(21-11-30 04:14) @project(MAIN TASKS.Log Entry) diff --git a/lib/components/forms.dart b/lib/components/forms.dart index 3af6b2c..f8b2858 100644 --- a/lib/components/forms.dart +++ b/lib/components/forms.dart @@ -49,13 +49,15 @@ class BooleanFormField extends StatefulWidget { final String label; final void Function(bool) onChanged; final bool? enabled; + final EdgeInsets? contentPadding; const BooleanFormField( {Key? key, required this.value, required this.label, required this.onChanged, - this.enabled}) + this.enabled, + this.contentPadding}) : super(key: key); @override @@ -67,6 +69,7 @@ class _BooleanFormFieldState extends State { Widget build(BuildContext context) { return FormField(builder: (state) { return ListTile( + contentPadding: widget.contentPadding, onTap: () => widget.onChanged(!widget.value), trailing: Switch( value: widget.value, @@ -115,7 +118,8 @@ class _DateTimeFormFieldState extends State { context: context, initialDate: widget.date, firstDate: widget.minDate ?? DateTime(2000, 1, 1), - lastDate: widget.maxDate ?? DateTime.now().add(const Duration(days: 365)), + lastDate: + widget.maxDate ?? DateTime.now().add(const Duration(days: 365)), ); widget.onChanged(newTime); }, @@ -160,4 +164,3 @@ class _TimeOfDayFormFieldState extends State { ); } } - diff --git a/lib/config.dart b/lib/config.dart deleted file mode 100644 index d242e7e..0000000 --- a/lib/config.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:diameter/settings.dart'; - -const keyApplicationId = 'DFfD2aeppmqQnVmox02kUZhYOUc7vAtGfunAP7hn'; -const keyClientKey = '0ROGEVQP0Id21EMEqK05wJP3nBDuOW5DM5Cpzdt3'; -const keyParseServerUrl = 'https://parseapi.back4app.com'; - -// settings -NutritionMeasurement nutritionMeasurement = NutritionMeasurement.grams; -GlucoseMeasurement glucoseMeasurement = GlucoseMeasurement.mgPerDl; -GlucoseDisplayMode glucoseDisplayMode = GlucoseDisplayMode.bothForList; - -DateTime dummyDate = DateTime(2000); -String dateFormat = 'MM/dd/yy'; -String? longDateFormat = 'MMMM dd, yyyy'; -String timeFormat = 'HH:mm'; -String? longTimeFormat = 'HH:mm:ss'; - -bool showConfirmationDialogOnCancel = true; -bool showConfirmationDialogOnDelete = true; -bool showConfirmationDialogOnStopEvent = true; - -int lowGlucoseMgPerDl = 80; -int moderateGlucoseMgPerDl = 140; -int highGlucoseMgPerDl = 240; - -double lowGlucoseMmolPerL = 4.44; -double moderateGlucoseMmolPerL = 7.77; -double highGlucoseMmolPerDl = 13.32; diff --git a/lib/main.dart b/lib/main.dart index cfa8fc3..bbddeba 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,9 +19,7 @@ import 'package:diameter/screens/meal/meal_source_detail.dart'; import 'package:diameter/screens/meal/meal_source_list.dart'; import 'package:diameter/settings.dart'; import 'package:flutter/material.dart'; -import 'package:parse_server_sdk_flutter/parse_server_sdk.dart'; import 'package:diameter/screens/accuracy_list.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/screens/basal/basal_profile_list.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/navigation.dart'; @@ -29,17 +27,6 @@ import 'package:diameter/navigation.dart'; late ObjectBox objectBox; Future main() async { WidgetsFlutterBinding.ensureInitialized(); - - await Parse().initialize( - keyApplicationId, - keyParseServerUrl, - clientKey: keyClientKey, - debug: true, - coreStore: await CoreStoreSharedPrefsImp.getInstance(), - ); - - Settings.loadSettingsIntoConfig(); - objectBox = await ObjectBox.create(); runApp( diff --git a/lib/models/accuracy.dart b/lib/models/accuracy.dart index 3cf20aa..39093f0 100644 --- a/lib/models/accuracy.dart +++ b/lib/models/accuracy.dart @@ -31,12 +31,11 @@ class Accuracy { static void put(Accuracy accuracy) => box.put(accuracy); static List getAll() { - QueryBuilder all = box - .query(Accuracy_.deleted.equals(false)) + QueryBuilder all = box.query(Accuracy_.deleted.equals(false)) ..order(Accuracy_.confidenceRating); return all.build().find(); } - + static void remove(int id) { final item = box.get(id); if (item != null) { @@ -44,21 +43,32 @@ class Accuracy { box.put(item); } } - + static List getAllForPortionSize() { - QueryBuilder allForPortionSize = box - .query(Accuracy_.forPortionSize.equals(true) & Accuracy_.deleted.equals(false)) + QueryBuilder allForPortionSize = box.query( + Accuracy_.forPortionSize.equals(true) & Accuracy_.deleted.equals(false)) ..order(Accuracy_.confidenceRating); return allForPortionSize.build().find(); } static List getAllForCarbsRatio() { - QueryBuilder allForCarbsRatio = box - .query(Accuracy_.forCarbsRatio.equals(true) & Accuracy_.deleted.equals(false)) + QueryBuilder allForCarbsRatio = box.query( + Accuracy_.forCarbsRatio.equals(true) & Accuracy_.deleted.equals(false)) ..order(Accuracy_.confidenceRating); return allForCarbsRatio.build().find(); } + static void reorder(Accuracy accuracy, int? newPosition) { + QueryBuilder all = box.query(Accuracy_.deleted.equals(false).and(Accuracy_.id.notEquals(accuracy.id))) + ..order(Accuracy_.confidenceRating); + List accuracies = all.build().find(); + newPosition == null || newPosition >= accuracies.length ? accuracies.add(accuracy) : accuracies.insert(newPosition, accuracy); + box.putMany(accuracies.map((item) { + item.confidenceRating = accuracies.indexOf(item); + return item; + }).toList()); + } + @override String toString() { return value; diff --git a/lib/models/basal_profile.dart b/lib/models/basal_profile.dart index b97a044..7111d8a 100644 --- a/lib/models/basal_profile.dart +++ b/lib/models/basal_profile.dart @@ -1,4 +1,5 @@ import 'package:diameter/main.dart'; +import 'package:diameter/models/log_event.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show BasalProfile_; @@ -52,6 +53,31 @@ class BasalProfile { }).toList()); } + static BasalProfile? getActive(DateTime? dateTime) { + if (dateTime != null) { + List activeEvents = LogEvent.getAllActiveForTime(dateTime) + .where((event) => event.basalProfile.target != null).toList(); + if (activeEvents.length > 1) { + final now = DateTime.now(); + activeEvents = + activeEvents.where((item) => !activeEvents.any((other) => + item.time.isBefore(other.time) || (item.endTime ?? now).isAfter(other.endTime ?? now) + )).toList(); + } + if (activeEvents.length == 1) { + return activeEvents.single.basalProfile.target; + } + } + + Query query = box + .query(BasalProfile_.active + .equals(true) + .and(BasalProfile_.deleted.equals(false))) + .build(); + final result = query.find(); + return result.length != 1 ? null : result.single; + } + @override String toString() { return name; diff --git a/lib/models/bolus.dart b/lib/models/bolus.dart index c87075e..23f794e 100644 --- a/lib/models/bolus.dart +++ b/lib/models/bolus.dart @@ -1,4 +1,3 @@ -import 'package:diameter/config.dart'; import 'package:diameter/main.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/utils/date_time_utils.dart'; diff --git a/lib/models/log_entry.dart b/lib/models/log_entry.dart index 663eda6..a9e6cbc 100644 --- a/lib/models/log_entry.dart +++ b/lib/models/log_entry.dart @@ -1,6 +1,6 @@ -import 'package:diameter/config.dart'; import 'package:diameter/main.dart'; import 'package:diameter/models/log_bolus.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:objectbox/objectbox.dart'; import 'package:diameter/objectbox.g.dart' show LogEntry_; @@ -43,8 +43,8 @@ class LogEntry { static bool hasUncorrectedGlucose(int id) { final entry = box.get(id); - if (((entry?.mgPerDl ?? 0) > moderateGlucoseMgPerDl || - (entry?.mmolPerL ?? 0) > moderateGlucoseMmolPerL)) { + if (((entry?.mgPerDl ?? 0) > Settings.get().moderateGlucoseMgPerDl || + (entry?.mmolPerL ?? 0) > Settings.get().moderateGlucoseMmolPerL)) { return !LogBolus.glucoseBolusForEntryExists(id); } return false; diff --git a/lib/models/log_event.dart b/lib/models/log_event.dart index 48b5807..4b87371 100644 --- a/lib/models/log_event.dart +++ b/lib/models/log_event.dart @@ -3,7 +3,7 @@ import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/log_event_type.dart'; import 'package:objectbox/objectbox.dart'; -import 'package:diameter/objectbox.g.dart' show LogEvent_; +import 'package:diameter/objectbox.g.dart' show LogEvent_, LogEventType_; @Entity(uid: 4303325892753185970) class LogEvent { @@ -20,6 +20,11 @@ class LogEvent { int? reminderDuration; String? notes; + @Transient() + String? title; + @Transient() + bool isEndEvent = false; + // relations final eventType = ToOne(); final bolusProfile = ToOne(); @@ -60,7 +65,7 @@ class LogEvent { if (dateTime != null) { QueryBuilder builder = box.query( LogEvent_.hasEndTime.equals(true) & LogEvent_.deleted.equals(false)) - ..order(LogEvent_.time); + ..order(LogEvent_.time, flags: Order.descending); final eventsWithEndTime = builder.build().find(); return eventsWithEndTime.where((event) { return (!dateTime.isBefore(event.time)) && @@ -70,21 +75,68 @@ class LogEvent { return []; } + static bool eventTypeExistsForTime(int id, DateTime? dateTime) { + QueryBuilder builder = box.query( + LogEvent_.hasEndTime.equals(true) & LogEvent_.deleted.equals(false)) + ..order(LogEvent_.time, flags: Order.descending); + builder.link(LogEvent_.eventType, LogEventType_.id.equals(id)); + final eventsWithEndTime = builder.build().find(); + if (dateTime != null) { + return eventsWithEndTime.where((event) { + return (!dateTime.isBefore(event.time)) && + !dateTime.isAfter(event.endTime ?? DateTime.now()); + }).isNotEmpty; + } + return eventsWithEndTime.isNotEmpty; + } + static Map> getDailyEntryMap() { Map> dateMap = >{}; + Map> sortedDateMap = >{}; QueryBuilder allByDate = box .query(LogEvent_.deleted.equals(false)) ..order(LogEvent_.time, flags: Order.descending); List events = allByDate.build().find(); + DateTime? date; for (LogEvent event in events) { date = DateTime.utc(event.time.year, event.time.month, event.time.day); - dateMap.putIfAbsent(date, () => []).add(event); + LogEvent startEvent = event; + startEvent.title = + '${event.toString()} ${event.hasEndTime ? '(Start)' : ''}'; + dateMap.putIfAbsent(date, () => []).add(startEvent); } - return dateMap; + QueryBuilder allByEndDate = box + .query(LogEvent_.deleted.equals(false).and(LogEvent_.endTime.notNull())) + ..order(LogEvent_.endTime, flags: Order.descending); + List endEvents = allByEndDate.build().find(); + + for (LogEvent event in endEvents) { + date = DateTime.utc( + event.endTime!.year, event.endTime!.month, event.endTime!.day); + LogEvent endEvent = event; + endEvent.isEndEvent = true; + endEvent.title = '${event.toString()} (End)'; + dateMap.putIfAbsent(date, () => []).add(endEvent); + } + + final dates = dateMap.keys.toList(); + dates.sort(); + for (DateTime date in dates.reversed) { + dateMap[date]!.sort((LogEvent a, LogEvent b) { + final dateA = a.isEndEvent ? a.endTime : a.time; + final dateB = b.isEndEvent ? b.endTime : b.time; + return -(dateA!.compareTo(dateB!)); + }); + sortedDateMap + .putIfAbsent(date, () => []) + .addAll(dateMap[date]!); + } + + return sortedDateMap; } @override diff --git a/lib/models/settings.dart b/lib/models/settings.dart new file mode 100644 index 0000000..b7292cf --- /dev/null +++ b/lib/models/settings.dart @@ -0,0 +1,95 @@ +import 'package:diameter/main.dart'; +import 'package:objectbox/objectbox.dart'; + +enum GlucoseDisplayMode { activeOnly, bothForList, bothForDetail, both } +List glucoseDisplayModeLabels = [ + 'activeOnly', + 'bothForList', + 'bothForDetail', + 'both', +]; + +enum GlucoseMeasurement { + mgPerDl, + mmolPerL, +} +List glucoseMeasurementSuffixes = [ + 'mg/dl', + 'mmol/l', +]; +List glucoseMeasurementLabels = [ + 'mgPerDl', + 'mmolPerL', +]; + +enum NutritionMeasurement { + grams, + ounces, + lbs, +} +List nutritionMeasurementSuffixes = [ + 'g', + 'oz', + 'lbs', +]; +List nutritionMeasurementLabels = [ + 'grams', + 'ounces', + 'lbs', +]; + +@Entity(uid: 3989341091218179227) +class Settings { + static final Box box = objectBox.store.box(); + + // properties + int id; + NutritionMeasurement nutritionMeasurement; + GlucoseDisplayMode glucoseDisplayMode; + GlucoseMeasurement glucoseMeasurement; + + String dateFormat; + String? longDateFormat; + String timeFormat; + String? longTimeFormat; + + bool showConfirmationDialogOnCancel; + bool showConfirmationDialogOnDelete; + bool showConfirmationDialogOnStopEvent; + + int lowGlucoseMgPerDl; + int moderateGlucoseMgPerDl; + int highGlucoseMgPerDl; + double lowGlucoseMmolPerL; + double moderateGlucoseMmolPerL; + double highGlucoseMmolPerDl; + + // constructor + Settings({ + this.id = 0, + this.nutritionMeasurement = NutritionMeasurement.grams, + this.glucoseDisplayMode = GlucoseDisplayMode.bothForList, + this.glucoseMeasurement = GlucoseMeasurement.mgPerDl, + this.dateFormat = 'MM/dd/yy', + this.longDateFormat = 'MMMM dd, yyyy', + this.timeFormat = 'HH:mm', + this.longTimeFormat = 'HH:mm:ss', + this.showConfirmationDialogOnCancel = true, + this.showConfirmationDialogOnDelete = true, + this.showConfirmationDialogOnStopEvent = true, + this.lowGlucoseMgPerDl = 80, + this.moderateGlucoseMgPerDl = 140, + this.highGlucoseMgPerDl = 240, + this.lowGlucoseMmolPerL = 4.44, + this.moderateGlucoseMmolPerL = 7.77, + this.highGlucoseMmolPerDl = 13.32, + }); + + // methods + static Settings get() => box.getAll().single; + static void put(Settings settings) => box.put(settings); + static void reset() { + box.removeAll(); + box.put(Settings()); + } +} diff --git a/lib/navigation.dart b/lib/navigation.dart index 1f4a93b..3f2995c 100644 --- a/lib/navigation.dart +++ b/lib/navigation.dart @@ -99,14 +99,6 @@ class _NavigationState extends State { }, selected: widget.currentLocation == Routes.log, ), - ListTile( - title: const Text('Log Entry'), - leading: const Icon(Icons.description), - onTap: () { - selectDestination(Routes.logEntry); - }, - selected: Routes.logEntryRoutes.contains(widget.currentLocation), - ), ListTile( title: const Text('Log Events'), leading: const Icon(Icons.event), diff --git a/lib/objectbox-model.json b/lib/objectbox-model.json index e1f1479..1bd0e66 100644 --- a/lib/objectbox-model.json +++ b/lib/objectbox-model.json @@ -791,9 +791,88 @@ } ], "relations": [] + }, + { + "id": "16:3989341091218179227", + "lastPropertyId": "14:3282706593658092097", + "name": "Settings", + "properties": [ + { + "id": "1:7803753645747063723", + "name": "id", + "type": 6, + "flags": 1 + }, + { + "id": "2:4703380985530623101", + "name": "dateFormat", + "type": 9 + }, + { + "id": "3:2983395924801005937", + "name": "longDateFormat", + "type": 9 + }, + { + "id": "4:2579032794029389590", + "name": "timeFormat", + "type": 9 + }, + { + "id": "5:3970690908108519507", + "name": "longTimeFormat", + "type": 9 + }, + { + "id": "6:349893175332801783", + "name": "showConfirmationDialogOnCancel", + "type": 1 + }, + { + "id": "7:4049915860178079910", + "name": "showConfirmationDialogOnDelete", + "type": 1 + }, + { + "id": "8:3088241443557186512", + "name": "showConfirmationDialogOnStopEvent", + "type": 1 + }, + { + "id": "9:310032577683835406", + "name": "lowGlucoseMgPerDl", + "type": 6 + }, + { + "id": "10:596980591281311896", + "name": "moderateGlucoseMgPerDl", + "type": 6 + }, + { + "id": "11:5588897884422150510", + "name": "highGlucoseMgPerDl", + "type": 6 + }, + { + "id": "12:7638848982383620744", + "name": "lowGlucoseMmolPerL", + "type": 8 + }, + { + "id": "13:3633551763915044903", + "name": "moderateGlucoseMmolPerL", + "type": 8 + }, + { + "id": "14:3282706593658092097", + "name": "highGlucoseMmolPerDl", + "type": 8 + } + ], + "relations": [] } ], - "lastEntityId": "15:291512798403320400", + "lastEntityId": "16:3989341091218179227", "lastIndexId": "28:4563029809754152081", "lastRelationId": "0:0", "lastSequenceId": "0:0", diff --git a/lib/objectbox.g.dart b/lib/objectbox.g.dart index aa1b012..b9b2d43 100644 --- a/lib/objectbox.g.dart +++ b/lib/objectbox.g.dart @@ -23,6 +23,7 @@ import 'models/meal.dart'; import 'models/meal_category.dart'; import 'models/meal_portion_type.dart'; import 'models/meal_source.dart'; +import 'models/settings.dart'; export 'package:objectbox/objectbox.dart'; // so that callers only have to import this file @@ -789,6 +790,85 @@ final _entities = [ flags: 0) ], relations: [], + backlinks: []), + ModelEntity( + id: const IdUid(16, 3989341091218179227), + name: 'Settings', + lastPropertyId: const IdUid(14, 3282706593658092097), + flags: 0, + properties: [ + ModelProperty( + id: const IdUid(1, 7803753645747063723), + name: 'id', + type: 6, + flags: 1), + ModelProperty( + id: const IdUid(2, 4703380985530623101), + name: 'dateFormat', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(3, 2983395924801005937), + name: 'longDateFormat', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(4, 2579032794029389590), + name: 'timeFormat', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(5, 3970690908108519507), + name: 'longTimeFormat', + type: 9, + flags: 0), + ModelProperty( + id: const IdUid(6, 349893175332801783), + name: 'showConfirmationDialogOnCancel', + type: 1, + flags: 0), + ModelProperty( + id: const IdUid(7, 4049915860178079910), + name: 'showConfirmationDialogOnDelete', + type: 1, + flags: 0), + ModelProperty( + id: const IdUid(8, 3088241443557186512), + name: 'showConfirmationDialogOnStopEvent', + type: 1, + flags: 0), + ModelProperty( + id: const IdUid(9, 310032577683835406), + name: 'lowGlucoseMgPerDl', + type: 6, + flags: 0), + ModelProperty( + id: const IdUid(10, 596980591281311896), + name: 'moderateGlucoseMgPerDl', + type: 6, + flags: 0), + ModelProperty( + id: const IdUid(11, 5588897884422150510), + name: 'highGlucoseMgPerDl', + type: 6, + flags: 0), + ModelProperty( + id: const IdUid(12, 7638848982383620744), + name: 'lowGlucoseMmolPerL', + type: 8, + flags: 0), + ModelProperty( + id: const IdUid(13, 3633551763915044903), + name: 'moderateGlucoseMmolPerL', + type: 8, + flags: 0), + ModelProperty( + id: const IdUid(14, 3282706593658092097), + name: 'highGlucoseMmolPerDl', + type: 8, + flags: 0) + ], + relations: [], backlinks: []) ]; @@ -812,7 +892,7 @@ Future openStore( ModelDefinition getObjectBoxModel() { final model = ModelInfo( entities: _entities, - lastEntityId: const IdUid(15, 291512798403320400), + lastEntityId: const IdUid(16, 3989341091218179227), lastIndexId: const IdUid(28, 4563029809754152081), lastRelationId: const IdUid(0, 0), lastSequenceId: const IdUid(0, 0), @@ -1545,6 +1625,76 @@ ModelDefinition getObjectBoxModel() { notes: const fb.StringReader() .vTableGetNullable(buffer, rootOffset, 14)); + return object; + }), + Settings: EntityDefinition( + model: _entities[14], + toOneRelations: (Settings object) => [], + toManyRelations: (Settings object) => {}, + getId: (Settings object) => object.id, + setId: (Settings object, int id) { + object.id = id; + }, + objectToFB: (Settings object, fb.Builder fbb) { + final dateFormatOffset = fbb.writeString(object.dateFormat); + final longDateFormatOffset = object.longDateFormat == null + ? null + : fbb.writeString(object.longDateFormat!); + final timeFormatOffset = fbb.writeString(object.timeFormat); + final longTimeFormatOffset = object.longTimeFormat == null + ? null + : fbb.writeString(object.longTimeFormat!); + fbb.startTable(15); + fbb.addInt64(0, object.id); + fbb.addOffset(1, dateFormatOffset); + fbb.addOffset(2, longDateFormatOffset); + fbb.addOffset(3, timeFormatOffset); + fbb.addOffset(4, longTimeFormatOffset); + fbb.addBool(5, object.showConfirmationDialogOnCancel); + fbb.addBool(6, object.showConfirmationDialogOnDelete); + fbb.addBool(7, object.showConfirmationDialogOnStopEvent); + fbb.addInt64(8, object.lowGlucoseMgPerDl); + fbb.addInt64(9, object.moderateGlucoseMgPerDl); + fbb.addInt64(10, object.highGlucoseMgPerDl); + fbb.addFloat64(11, object.lowGlucoseMmolPerL); + fbb.addFloat64(12, object.moderateGlucoseMmolPerL); + fbb.addFloat64(13, object.highGlucoseMmolPerDl); + fbb.finish(fbb.endTable()); + return object.id; + }, + objectFromFB: (Store store, ByteData fbData) { + final buffer = fb.BufferContext(fbData); + final rootOffset = buffer.derefObject(0); + + final object = Settings( + id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), + dateFormat: + const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''), + longDateFormat: const fb.StringReader() + .vTableGetNullable(buffer, rootOffset, 8), + timeFormat: + const fb.StringReader().vTableGet(buffer, rootOffset, 10, ''), + longTimeFormat: const fb.StringReader() + .vTableGetNullable(buffer, rootOffset, 12), + showConfirmationDialogOnCancel: const fb.BoolReader() + .vTableGet(buffer, rootOffset, 14, false), + showConfirmationDialogOnDelete: const fb.BoolReader() + .vTableGet(buffer, rootOffset, 16, false), + showConfirmationDialogOnStopEvent: const fb.BoolReader() + .vTableGet(buffer, rootOffset, 18, false), + lowGlucoseMgPerDl: + const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0), + moderateGlucoseMgPerDl: + const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0), + highGlucoseMgPerDl: + const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0), + lowGlucoseMmolPerL: + const fb.Float64Reader().vTableGet(buffer, rootOffset, 26, 0), + moderateGlucoseMmolPerL: + const fb.Float64Reader().vTableGet(buffer, rootOffset, 28, 0), + highGlucoseMmolPerDl: + const fb.Float64Reader().vTableGet(buffer, rootOffset, 30, 0)); + return object; }) }; @@ -2046,3 +2196,61 @@ class Accuracy_ { static final deleted = QueryBooleanProperty(_entities[13].properties[6]); } + +/// [Settings] entity fields to define ObjectBox queries. +class Settings_ { + /// see [Settings.id] + static final id = QueryIntegerProperty(_entities[14].properties[0]); + + /// see [Settings.dateFormat] + static final dateFormat = + QueryStringProperty(_entities[14].properties[1]); + + /// see [Settings.longDateFormat] + static final longDateFormat = + QueryStringProperty(_entities[14].properties[2]); + + /// see [Settings.timeFormat] + static final timeFormat = + QueryStringProperty(_entities[14].properties[3]); + + /// see [Settings.longTimeFormat] + static final longTimeFormat = + QueryStringProperty(_entities[14].properties[4]); + + /// see [Settings.showConfirmationDialogOnCancel] + static final showConfirmationDialogOnCancel = + QueryBooleanProperty(_entities[14].properties[5]); + + /// see [Settings.showConfirmationDialogOnDelete] + static final showConfirmationDialogOnDelete = + QueryBooleanProperty(_entities[14].properties[6]); + + /// see [Settings.showConfirmationDialogOnStopEvent] + static final showConfirmationDialogOnStopEvent = + QueryBooleanProperty(_entities[14].properties[7]); + + /// see [Settings.lowGlucoseMgPerDl] + static final lowGlucoseMgPerDl = + QueryIntegerProperty(_entities[14].properties[8]); + + /// see [Settings.moderateGlucoseMgPerDl] + static final moderateGlucoseMgPerDl = + QueryIntegerProperty(_entities[14].properties[9]); + + /// see [Settings.highGlucoseMgPerDl] + static final highGlucoseMgPerDl = + QueryIntegerProperty(_entities[14].properties[10]); + + /// see [Settings.lowGlucoseMmolPerL] + static final lowGlucoseMmolPerL = + QueryDoubleProperty(_entities[14].properties[11]); + + /// see [Settings.moderateGlucoseMmolPerL] + static final moderateGlucoseMmolPerL = + QueryDoubleProperty(_entities[14].properties[12]); + + /// see [Settings.highGlucoseMmolPerDl] + static final highGlucoseMmolPerDl = + QueryDoubleProperty(_entities[14].properties[13]); +} diff --git a/lib/screens/accuracy_detail.dart b/lib/screens/accuracy_detail.dart index 90f754a..1b25802 100644 --- a/lib/screens/accuracy_detail.dart +++ b/lib/screens/accuracy_detail.dart @@ -1,6 +1,6 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/components/forms.dart'; @@ -57,14 +57,16 @@ class _AccuracyDetailScreenState extends State { _isSaving = true; }); if (_accuracyForm.currentState!.validate()) { - Accuracy.box.put(Accuracy( + Accuracy accuracy = Accuracy( id: widget.id, value: _valueController.text, forCarbsRatio: _forCarbsRatio, forPortionSize: _forPortionSize, - confidenceRating: int.tryParse(_confidenceRatingController.text), notes: _notesController.text, - )); + ); + Accuracy.box.put(accuracy); + Accuracy.reorder( + accuracy, int.tryParse(_confidenceRatingController.text)); Navigator.pop(context, '${_isNew ? 'New' : ''} Accuracy saved'); } setState(() { @@ -73,20 +75,20 @@ class _AccuracyDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && - (_isNew && - (_forCarbsRatio || - _forPortionSize || - _valueController.text != '' || - int.tryParse(_confidenceRatingController.text) != null || - _notesController.text != '')) || - (!_isNew && - (_forCarbsRatio != _accuracy!.forCarbsRatio || - _forPortionSize != _accuracy!.forPortionSize || - _accuracy!.value != _valueController.text || - int.tryParse(_confidenceRatingController.text) != - _accuracy!.confidenceRating || - (_accuracy!.notes ?? '') != _notesController.text))) { + if (Settings.get().showConfirmationDialogOnCancel && + (_isNew && + (_forCarbsRatio || + _forPortionSize || + _valueController.text != '' || + int.tryParse(_confidenceRatingController.text) != null || + _notesController.text != '')) || + (!_isNew && + (_forCarbsRatio != _accuracy!.forCarbsRatio || + _forPortionSize != _accuracy!.forPortionSize || + _accuracy!.value != _valueController.text || + int.tryParse(_confidenceRatingController.text) != + _accuracy!.confidenceRating || + (_accuracy!.notes ?? '') != _notesController.text))) { Dialogs.showCancelConfirmationDialog( context: context, isNew: _isNew, diff --git a/lib/screens/accuracy_list.dart b/lib/screens/accuracy_list.dart index 4ec9b3c..9815916 100644 --- a/lib/screens/accuracy_list.dart +++ b/lib/screens/accuracy_list.dart @@ -1,5 +1,5 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/accuracy_detail.dart'; import 'package:flutter/material.dart'; @@ -46,7 +46,7 @@ class _AccuracyListScreenState extends State { } void handleDeleteAction(Accuracy accuracy) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(accuracy), @@ -83,68 +83,75 @@ class _AccuracyListScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Expanded( - child: _accuracies.isNotEmpty ? ListView.builder( - padding: const EdgeInsets.only(top: 10.0), - itemCount: _accuracies.length, - itemBuilder: (context, index) { - final accuracy = _accuracies[index]; - return ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - AccuracyDetailScreen(id: accuracy.id), - ), - ).then((message) => reload(message: message)); - }, - title: Text(accuracy.value), - leading: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.reorder), - onPressed: () { - // ignore: todo - // TODO: implement reordering - }, - ), - ], + child: _accuracies.isNotEmpty + ? ReorderableListView.builder( + padding: const EdgeInsets.only(top: 10.0), + itemCount: _accuracies.length, + onReorder: (oldIndex, newIndex) { + Accuracy.reorder(_accuracies[oldIndex], newIndex); + reload(); + }, + itemBuilder: (context, index) { + final accuracy = _accuracies[index]; + return ListTile( + key: Key(index.toString()), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + AccuracyDetailScreen(id: accuracy.id), + ), + ).then((message) => reload(message: message)); + }, + title: Text(accuracy.value), + leading: Row( + mainAxisSize: MainAxisSize.min, + children: const [ + Icon(Icons.reorder), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: Icon( + Icons.square_foot, + color: accuracy.forPortionSize + ? Theme.of(context) + .toggleableActiveColor + : Theme.of(context).highlightColor, + ), + onPressed: () => + handleToggleForPortionSizeAction( + accuracy), + ), + IconButton( + icon: Icon( + Icons.pie_chart, + color: accuracy.forCarbsRatio + ? Theme.of(context) + .toggleableActiveColor + : Theme.of(context).highlightColor, + ), + onPressed: () => + handleToggleForCarbsRatioAction( + accuracy), + ), + const SizedBox(width: 24), + IconButton( + icon: const Icon(Icons.delete), + onPressed: () => + handleDeleteAction(accuracy), + ) + ], + ), + ); + }) + : const Center( + child: Text('You have not created any Accuracies yet!'), ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: Icon( - Icons.square_foot, - color: accuracy.forPortionSize - ? Theme.of(context).toggleableActiveColor - : Theme.of(context).highlightColor, - ), - onPressed: () => handleToggleForPortionSizeAction(accuracy), - ), - IconButton( - icon: Icon( - Icons.pie_chart, - color: accuracy.forCarbsRatio - ? Theme.of(context).toggleableActiveColor - : Theme.of(context).highlightColor, - ), - onPressed: () => handleToggleForCarbsRatioAction(accuracy), - ), - const SizedBox(width: 24), - IconButton( - icon: const Icon(Icons.delete), - onPressed: () => handleDeleteAction(accuracy), - ) - ], - ), - ); - } - ) : const Center( - child: Text('You have not created any Accuracies yet!'), - ), - ), + ), ], ), floatingActionButton: FloatingActionButton( diff --git a/lib/screens/basal/basal_detail.dart b/lib/screens/basal/basal_detail.dart index 1d8c04e..8ab4101 100644 --- a/lib/screens/basal/basal_detail.dart +++ b/lib/screens/basal/basal_detail.dart @@ -1,6 +1,6 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; @@ -151,7 +151,7 @@ class _BasalDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) || _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) || diff --git a/lib/screens/basal/basal_list.dart b/lib/screens/basal/basal_list.dart index c852dce..da88c6c 100644 --- a/lib/screens/basal/basal_list.dart +++ b/lib/screens/basal/basal_list.dart @@ -1,5 +1,5 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/basal.dart'; @@ -57,7 +57,7 @@ class _BasalListScreenState extends State { } void handleDeleteAction(Basal basal) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(basal), diff --git a/lib/screens/basal/basal_profile_detail.dart b/lib/screens/basal/basal_profile_detail.dart index 9cf0f8a..797bc98 100644 --- a/lib/screens/basal/basal_profile_detail.dart +++ b/lib/screens/basal/basal_profile_detail.dart @@ -1,7 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/basal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/basal/basal_detail.dart'; import 'package:flutter/material.dart'; @@ -237,7 +237,7 @@ class _BasalProfileDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && (_isNew && (_active != widget.active || _nameController.text != '' || diff --git a/lib/screens/basal/basal_profile_list.dart b/lib/screens/basal/basal_profile_list.dart index 02117e4..fc341e0 100644 --- a/lib/screens/basal/basal_profile_list.dart +++ b/lib/screens/basal/basal_profile_list.dart @@ -1,5 +1,6 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/components/dropdown.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/basal_profile.dart'; @@ -18,6 +19,8 @@ class _BasalProfileListScreenState extends State { Widget banner = Container(); bool pickActiveProfileMode = false; + final BasalProfile? _activeProfile = BasalProfile.getActive(DateTime.now()); + void refresh({String? message}) { setState(() { pickActiveProfileMode = false; @@ -77,7 +80,7 @@ class _BasalProfileListScreenState extends State { } void handleDeleteAction(BasalProfile basalProfile) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(basalProfile), @@ -88,17 +91,24 @@ class _BasalProfileListScreenState extends State { } } - void onPickActive(BasalProfile basalProfile) { - BasalProfile.setAllInactive; - basalProfile.active = true; - BasalProfile.put(basalProfile); - refresh(message: '${basalProfile.name} has been set as your active Profile'); + void onPickActive(BasalProfile? basalProfile) { + if (basalProfile != null) { + BasalProfile.setAllInactive; + basalProfile.active = true; + BasalProfile.put(basalProfile); + refresh( + message: '${basalProfile.name} has been set as your active Profile'); + } } void handlePickActiveProfileAction() { setState(() { banner = MaterialBanner( - content: const Text('Click one of the profiles to active it.'), + content: AutoCompleteDropdownButton( + items: _basalProfiles, + label: 'Default Basal Profile', + onChanged: onPickActive, + ), leading: const CircleAvatar(child: Icon(Icons.info)), forceActionsBelow: true, actions: [ @@ -116,8 +126,8 @@ class _BasalProfileListScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => BasalProfileDetailScreen( - id: basalProfile?.id ?? 0, active: active), + builder: (context) => + BasalProfileDetailScreen(id: basalProfile?.id ?? 0, active: active), ), ).then((message) => refresh(message: message)); } @@ -152,42 +162,42 @@ class _BasalProfileListScreenState extends State { children: [ banner, Expanded( - child: _basalProfiles.isNotEmpty ? ListView.builder( - itemCount: _basalProfiles.length, - itemBuilder: (context, index) { - final basalProfile = _basalProfiles[index]; - - return ListTile( - tileColor: basalProfile.active - ? Colors.green.shade100 - : null, - onTap: () { - pickActiveProfileMode - ? onPickActive(basalProfile) - : onEdit(basalProfile); - }, - title: Text( - basalProfile.name, - ), - subtitle: Text(basalProfile.notes!), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon( - Icons.delete, - color: Colors.blue, + child: _basalProfiles.isNotEmpty + ? ListView.builder( + itemCount: _basalProfiles.length, + itemBuilder: (context, index) { + final basalProfile = _basalProfiles[index]; + String activeProfileText = basalProfile.active + ? 'Default Profile' + : basalProfile.id == _activeProfile?.id + ? 'Current Active Profile' + : ''; + if (activeProfileText != '' && (basalProfile.notes ?? '') != '') { + activeProfileText += '\n'; + } + return ListTile( + selected: basalProfile.active || basalProfile.id == _activeProfile?.id, + onTap: () => onEdit(basalProfile), + title: Text(basalProfile.name), + subtitle: Text('$activeProfileText${basalProfile.notes ?? ''}'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.blue, + ), + onPressed: () => handleDeleteAction(basalProfile), + ), + ], ), - onPressed: () => - handleDeleteAction(basalProfile), - ), - ], + ); + }, + ) + : const Center( + child: Text('You have not created any Basal Profiles yet!'), ), - ); - }, - ) : const Center( - child: Text('You have not created any Basal Profiles yet!'), - ), ), ], ), diff --git a/lib/screens/bolus/bolus_detail.dart b/lib/screens/bolus/bolus_detail.dart index 7277946..69f2105 100644 --- a/lib/screens/bolus/bolus_detail.dart +++ b/lib/screens/bolus/bolus_detail.dart @@ -1,8 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -166,7 +165,7 @@ class _BolusDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) || _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) || @@ -297,12 +296,7 @@ class _BolusDetailScreenState extends State { TextFormField( decoration: InputDecoration( labelText: 'per carbs', - suffixText: nutritionMeasurement == - NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == NutritionMeasurement.ounces - ? 'oz' - : '', + suffixText: nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], ), controller: _carbsController, keyboardType: @@ -316,9 +310,9 @@ class _BolusDetailScreenState extends State { ), Row( children: [ - glucoseMeasurement == GlucoseMeasurement.mgPerDl || - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseMeasurement == GlucoseMeasurement.mgPerDl || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( @@ -343,8 +337,8 @@ class _BolusDetailScreenState extends State { ), ) : Container(), - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( @@ -352,9 +346,9 @@ class _BolusDetailScreenState extends State { icon: const Icon(Icons.calculate), ) : Container(), - glucoseMeasurement == GlucoseMeasurement.mmolPerL || - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseMeasurement == GlucoseMeasurement.mmolPerL || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( @@ -380,8 +374,8 @@ class _BolusDetailScreenState extends State { ), ) : Container(), - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( diff --git a/lib/screens/bolus/bolus_list.dart b/lib/screens/bolus/bolus_list.dart index 3f9c5c6..f8590af 100644 --- a/lib/screens/bolus/bolus_list.dart +++ b/lib/screens/bolus/bolus_list.dart @@ -1,6 +1,5 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; -import 'package:diameter/settings.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/bolus.dart'; @@ -55,7 +54,7 @@ class _BolusListScreenState extends State { } void handleDeleteAction(Bolus bolus) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(bolus), @@ -128,7 +127,7 @@ class _BolusListScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - '${bolus.units} U per ${bolus.carbs}${nutritionMeasurement == NutritionMeasurement.grams ? ' g' : ' oz'} carbs/${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL} ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 'mg/dl' : 'mmol/l'}'), + '${bolus.units} U per ${bolus.carbs}${nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index]} carbs/${Settings.get().glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL} ${glucoseMeasurementSuffixes[Settings.get().glucoseMeasurement.index]}'), error != null ? Text(error, style: const TextStyle(color: Colors.red)) diff --git a/lib/screens/bolus/bolus_profile_detail.dart b/lib/screens/bolus/bolus_profile_detail.dart index 08dc75d..e0e6d3b 100644 --- a/lib/screens/bolus/bolus_profile_detail.dart +++ b/lib/screens/bolus/bolus_profile_detail.dart @@ -1,7 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/bolus.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/bolus/bolus_detail.dart'; import 'package:flutter/material.dart'; @@ -238,7 +238,7 @@ class _BolusProfileDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && (_isNew && (_active != widget.active || _nameController.text != '' || diff --git a/lib/screens/bolus/bolus_profile_list.dart b/lib/screens/bolus/bolus_profile_list.dart index 47f7470..54ae351 100644 --- a/lib/screens/bolus/bolus_profile_list.dart +++ b/lib/screens/bolus/bolus_profile_list.dart @@ -1,5 +1,6 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/components/dropdown.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/bolus_profile.dart'; @@ -18,6 +19,8 @@ class _BolusProfileListScreenState extends State { Widget banner = Container(); bool pickActiveProfileMode = false; + final BolusProfile? _activeProfile = BolusProfile.getActive(DateTime.now()); + void reload({String? message}) { setState(() { pickActiveProfileMode = false; @@ -25,7 +28,7 @@ class _BolusProfileListScreenState extends State { }); updateBanner(); - + setState(() { if (message != null) { var snackBar = SnackBar( @@ -80,7 +83,7 @@ class _BolusProfileListScreenState extends State { } void handleDeleteAction(BolusProfile bolusProfile) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(bolusProfile), @@ -91,18 +94,23 @@ class _BolusProfileListScreenState extends State { } } - void onPickActive(BolusProfile bolusProfile) { - BolusProfile.setAllInactive; - bolusProfile.active = true; - BolusProfile.put(bolusProfile); - reload( - message: '${bolusProfile.name} has been set as your active Profile'); + void onPickActive(BolusProfile? bolusProfile) { + if (bolusProfile != null) { + BolusProfile.setAllInactive; + bolusProfile.active = true; + BolusProfile.put(bolusProfile); + reload(message: '${bolusProfile.name} has been set as your active Profile'); + } } void handlePickActiveProfileAction() { setState(() { banner = MaterialBanner( - content: const Text('Click one of the profiles to active it.'), + content: AutoCompleteDropdownButton( + items: _bolusProfiles, + label: 'Default Basal Profile', + onChanged: onPickActive, + ), leading: const CircleAvatar(child: Icon(Icons.info)), forceActionsBelow: true, actions: [ @@ -120,8 +128,8 @@ class _BolusProfileListScreenState extends State { Navigator.push( context, MaterialPageRoute( - builder: (context) => BolusProfileDetailScreen( - id: bolusProfile?.id ?? 0, active: active), + builder: (context) => + BolusProfileDetailScreen(id: bolusProfile?.id ?? 0, active: active), ), ).then((message) => reload(message: message)); } @@ -156,41 +164,45 @@ class _BolusProfileListScreenState extends State { children: [ banner, Expanded( - child: _bolusProfiles.isNotEmpty ? ListView.builder( - itemCount: _bolusProfiles.length, - itemBuilder: (context, index) { - final bolusProfile = _bolusProfiles[index]; - return ListTile( - tileColor: bolusProfile.active - ? Colors.green.shade100 - : null, - onTap: () { - pickActiveProfileMode - ? onPickActive(bolusProfile) - : onEdit(bolusProfile); - }, - title: Text( - bolusProfile.name, - ), - subtitle: Text(bolusProfile.notes!), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon( - Icons.delete, - color: Colors.blue, + child: _bolusProfiles.isNotEmpty + ? ListView.builder( + itemCount: _bolusProfiles.length, + itemBuilder: (context, index) { + final bolusProfile = _bolusProfiles[index]; + String activeProfileText = bolusProfile.active + ? 'Default Profile' + : bolusProfile.id == _activeProfile?.id + ? 'Current Active Profile' + : ''; + if (activeProfileText != '' && (bolusProfile.notes ?? '') != '') { + activeProfileText += '\n'; + } + return ListTile( + selected: bolusProfile.active || bolusProfile.id == _activeProfile?.id, + onTap: () => onEdit(bolusProfile), + title: Text( + bolusProfile.name, ), - onPressed: () => - handleDeleteAction(bolusProfile), - ), - ], + isThreeLine: activeProfileText != '' && (bolusProfile.notes ?? '') != '', + subtitle: Text('$activeProfileText${bolusProfile.notes ?? ''}'), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.blue, + ), + onPressed: () => handleDeleteAction(bolusProfile), + ), + ], + ), + ); + }, + ) + : const Center( + child: Text('You have not created any Bolus Profiles yet!'), ), - ); - }, - ) : const Center( - child: Text('You have not created any Bolus Profiles yet!'), - ), ), ], ), diff --git a/lib/screens/log/log.dart b/lib/screens/log/log.dart index 3011813..7cfe2cc 100644 --- a/lib/screens/log/log.dart +++ b/lib/screens/log/log.dart @@ -1,11 +1,10 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; @@ -49,7 +48,7 @@ class _LogScreenState extends State { } void handleDeleteAction(LogEntry logEntry) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(logEntry), @@ -71,165 +70,125 @@ class _LogScreenState extends State { ), drawer: const Navigation(currentLocation: LogScreen.routeName), body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: [ Expanded( child: _logEntryDailyMap.isNotEmpty - ? SingleChildScrollView( - child: ListView.builder( - shrinkWrap: true, - padding: const EdgeInsets.all(10.0), - itemCount: _logEntryDailyMap.length, - itemBuilder: (context, dateIndex) { - List dateList = - _logEntryDailyMap.keys.toList(); - final date = dateList[dateIndex]; - final entryList = _logEntryDailyMap[date]; - return ListBody( - children: [ - Text(DateTimeUtils.displayDate(date)), - entryList != null && entryList.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - itemCount: entryList.length, - itemBuilder: (context, index) { - final logEntry = entryList[index]; - double bolus = - LogBolus.getTotalBolusForEntry( - logEntry.id); - double carbs = - LogMeal.getTotalCarbsForEntry( - logEntry.id); - return ListTile( - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => - LogEntryScreen( - id: logEntry.id), - ), - ).then((message) => - reload(message: message)); - }, - title: Row( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - Expanded( - child: Text( - DateTimeUtils.displayTime( - logEntry.time), - ), - ), - Expanded( - child: Column( - children: logEntry - .mgPerDl != - null && - (glucoseMeasurement == GlucoseMeasurement.mgPerDl || - glucoseDisplayMode == - GlucoseDisplayMode - .both || - glucoseDisplayMode == - GlucoseDisplayMode - .bothForList) - ? [ - Text(logEntry - .mgPerDl - .toString()), - const Text( - 'mg/dl', - textScaleFactor: - 0.75, - ), - ] - : [], - ), - ), - Expanded( - child: Column( - children: logEntry - .mmolPerL != - null && - (glucoseMeasurement == GlucoseMeasurement.mmolPerL || - glucoseDisplayMode == - GlucoseDisplayMode - .both || - glucoseDisplayMode == - GlucoseDisplayMode - .bothForList) - ? [ - Text(logEntry - .mmolPerL - .toString()), - const Text( - 'mmol/l', - textScaleFactor: - 0.75, - ), - ] - : [], - ), - ), - Expanded( - child: Column( - children: (bolus > 0) - ? [ - Text(bolus - .toStringAsPrecision( - 3)), - const Text('U', - textScaleFactor: - 0.75), - ] - : [], - ), - ), - Expanded( - child: Column( - children: (carbs > 0) - ? [ - Text(carbs - .toStringAsPrecision( - 3)), - Text( - nutritionMeasurement == - NutritionMeasurement - .grams - ? 'g carbs' - : nutritionMeasurement == - NutritionMeasurement - .ounces - ? 'oz carbs' - : '', - textScaleFactor: - 0.75), - ] - : [], - ), - ), - ], - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - onPressed: () => - handleDeleteAction(logEntry), - icon: const Icon(Icons.delete, - color: Colors.blue), - ) - ], - ), - ); - }) - : Container(), - ], + ? ListView.builder( + padding: const EdgeInsets.all(10.0), + shrinkWrap: true, + itemCount: _logEntryDailyMap.length, + itemBuilder: (context, dateIndex) { + List dateList = _logEntryDailyMap.keys.toList(); + final date = dateList[dateIndex]; + final entryList = _logEntryDailyMap[date]; + final tiles = []; + for (LogEntry logEntry in entryList!) { + double bolus = + LogBolus.getTotalBolusForEntry(logEntry.id); + double carbs = + LogMeal.getTotalCarbsForEntry(logEntry.id); + tiles.add(ListTile( + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + LogEntryScreen(id: logEntry.id), + ), + ).then((message) => reload(message: message)); + }, + title: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: Text( + DateTimeUtils.displayTime(logEntry.time), + ), + ), + Expanded( + child: Column( + children: logEntry.mgPerDl != null && + (Settings.get().glucoseMeasurement == + GlucoseMeasurement.mgPerDl || + Settings.get().glucoseDisplayMode == + GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == + GlucoseDisplayMode + .bothForList) + ? [ + Text(logEntry.mgPerDl.toString()), + const Text( + 'mg/dl', + textScaleFactor: 0.75, + ), + ] + : [], + ), + ), + Expanded( + child: Column( + children: logEntry.mmolPerL != null && + (Settings.get().glucoseMeasurement == + GlucoseMeasurement.mmolPerL || + Settings.get().glucoseDisplayMode == + GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == + GlucoseDisplayMode + .bothForList) + ? [ + Text(logEntry.mmolPerL.toString()), + const Text( + 'mmol/l', + textScaleFactor: 0.75, + ), + ] + : [], + ), + ), + Expanded( + child: Column( + children: (bolus > 0) + ? [ + Text(bolus.toStringAsPrecision(3)), + const Text('U', + textScaleFactor: 0.75), + ] + : [], + ), + ), + Expanded( + child: Column( + children: (carbs > 0) + ? [ + Text(carbs.toStringAsPrecision(3)), + Text( + nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], + textScaleFactor: 0.75), + ] + : [], + ), + ), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + onPressed: () => handleDeleteAction(logEntry), + icon: const Icon(Icons.delete, + color: Colors.blue), + ) + ], + ), + )); + + } + return ListBody( + children: [Text(DateTimeUtils.displayDate(date))] + tiles, ); - }, - ), + }, + ) : const Center( child: Text('You have not created any Log Entries yet!'), diff --git a/lib/screens/log/log_entry/log_bolus_detail.dart b/lib/screens/log/log_entry/log_bolus_detail.dart index c417b5e..77566e6 100644 --- a/lib/screens/log/log_entry/log_bolus_detail.dart +++ b/lib/screens/log/log_entry/log_bolus_detail.dart @@ -4,13 +4,12 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/bolus.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -34,8 +33,7 @@ class LogBolusDetailScreen extends StatefulWidget { final int logEntryId; final int id; - const LogBolusDetailScreen( - {Key? key, this.logEntryId = 0, this.id = 0}) + const LogBolusDetailScreen({Key? key, this.logEntryId = 0, this.id = 0}) : super(key: key); @override @@ -62,10 +60,14 @@ class _LogBolusDetailScreenState extends State { final _delayController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); + final _delayedUnitsController = TextEditingController(text: ''); + final _immediateUnitsController = TextEditingController(text: ''); + bool _setManually = false; BolusType _bolusType = BolusType.meal; LogMeal? _meal; Bolus? _rate; + double _delayPercentage = 0; List _logMeals = []; @@ -76,7 +78,7 @@ class _LogBolusDetailScreenState extends State { _logEntry = LogEntry.get(widget.logEntryId); _logMeals = LogMeal.getAllForEntry(widget.logEntryId); - + if (widget.id != 0) { _carbsController.text = (_logBolus!.carbs ?? '').toString(); _delayController.text = (_logBolus!.delay ?? '').toString(); @@ -87,27 +89,46 @@ class _LogBolusDetailScreenState extends State { } _rate ??= Bolus.getRateForTime(_logEntry?.time); - - _mgPerDlCurrentController.text = - (_logBolus?.mgPerDlCurrent ?? (LogEntry.hasUncorrectedGlucose(widget.logEntryId) ? _logEntry?.mgPerDl ?? 0 : 0)).toString(); + + _mgPerDlCurrentController.text = (_logBolus?.mgPerDlCurrent ?? + (LogEntry.hasUncorrectedGlucose(widget.logEntryId) + ? _logEntry?.mgPerDl ?? 0 + : 0)) + .toString(); _mgPerDlTargetController.text = - (_logBolus?.mgPerDlTarget ?? moderateGlucoseMgPerDl).toString(); - _mgPerDlCorrectionController.text = - (_logBolus?.mgPerDlCorrection ?? max((int.tryParse(_mgPerDlCurrentController.text) ?? 0) - (int.tryParse(_mgPerDlTargetController.text) ?? 0), 0)).toString(); - _mmolPerLCurrentController.text = - (_logBolus?.mmolPerLCurrent ?? (LogEntry.hasUncorrectedGlucose(widget.logEntryId) ? _logEntry?.mmolPerL ?? 0 : 0)).toString(); + (_logBolus?.mgPerDlTarget ?? Settings.get().moderateGlucoseMgPerDl).toString(); + _mgPerDlCorrectionController.text = (_logBolus?.mgPerDlCorrection ?? + max( + (int.tryParse(_mgPerDlCurrentController.text) ?? 0) - + (int.tryParse(_mgPerDlTargetController.text) ?? 0), + 0)) + .toString(); + _mmolPerLCurrentController.text = (_logBolus?.mmolPerLCurrent ?? + (LogEntry.hasUncorrectedGlucose(widget.logEntryId) + ? _logEntry?.mmolPerL ?? 0 + : 0)) + .toString(); _mmolPerLTargetController.text = - (_logBolus?.mmolPerLTarget ?? moderateGlucoseMmolPerL).toString(); - _mmolPerLCorrectionController.text = - (_logBolus?.mmolPerLCorrection ?? max((double.tryParse(_mmolPerLCurrentController.text) ?? 0) - (double.tryParse(_mmolPerLTargetController.text) ?? 0), 0)).toString(); + (_logBolus?.mmolPerLTarget ?? Settings.get().moderateGlucoseMmolPerL).toString(); + _mmolPerLCorrectionController.text = (_logBolus?.mmolPerLCorrection ?? + max( + (double.tryParse(_mmolPerLCurrentController.text) ?? 0) - + (double.tryParse(_mmolPerLTargetController.text) ?? 0), + 0)) + .toString(); _unitsController.text = (_logBolus?.units ?? - (_rate != null && !_setManually ? ((int.tryParse(_mgPerDlCorrectionController.text) ?? 0) / ((_rate!.mgPerDl ?? 0) / _rate!.units)) : 0) - ).toString(); + (_rate != null && !_setManually + ? ((int.tryParse(_mgPerDlCorrectionController.text) ?? 0) / + ((_rate!.mgPerDl ?? 0) / _rate!.units)) + : 0)) + .toString(); if (widget.id == 0 && LogEntry.hasUncorrectedGlucose(widget.logEntryId)) { _bolusType = BolusType.glucose; } + + updateDelayedRatio(); } void reload() { @@ -119,6 +140,23 @@ class _LogBolusDetailScreenState extends State { _isNew = _logBolus == null; } + void updateDelayedRatio() { + if (_unitsController.text != '') { + setState(() { + _delayedUnitsController.text = + ((double.tryParse(_unitsController.text) ?? 0) * + _delayPercentage / + 100) + .toString(); + _immediateUnitsController.text = + ((double.tryParse(_unitsController.text) ?? 0) * + (100 - _delayPercentage) / + 100) + .toString(); + }); + } + } + void onSelectMeal(LogMeal meal) { setState(() { _meal = meal; @@ -135,6 +173,18 @@ class _LogBolusDetailScreenState extends State { _unitsController.text = ((double.tryParse(_carbsController.text) ?? 0) / (_rate!.carbs / _rate!.units)) .toString(); + if (_unitsController.text != '') { + _delayedUnitsController.text = + ((double.tryParse(_unitsController.text) ?? 0) * + _delayPercentage / + 100) + .toString(); + _immediateUnitsController.text = + ((double.tryParse(_unitsController.text) ?? 0) * + (100 - _delayPercentage) / + 100) + .toString(); + } } }); } @@ -206,15 +256,41 @@ class _LogBolusDetailScreenState extends State { _isSaving = true; }); if (_logBolusForm.currentState!.validate()) { - LogBolus logBolus = LogBolus( - id: widget.id, - units: double.tryParse(_unitsController.text) ?? 0, - delay: int.tryParse(_delayController.text), - setManually: _setManually, - notes: _notesController.text, - ); + LogBolus logBolus; + LogBolus? delayedBolus; + + if ((int.tryParse(_delayController.text) ?? 0) != 0 && + _delayPercentage != 0 && + _delayPercentage != 100) { + logBolus = LogBolus( + id: widget.id, + units: double.tryParse(_immediateUnitsController.text) ?? 0, + setManually: _setManually, + notes: _notesController.text, + ); + delayedBolus = LogBolus( + delay: int.tryParse(_delayController.text), + units: double.tryParse(_delayedUnitsController.text) ?? 0, + setManually: _setManually, + notes: _notesController.text, + ); + } else { + logBolus = LogBolus( + id: widget.id, + units: double.tryParse(_unitsController.text) ?? 0, + delay: _delayPercentage == 100 + ? int.tryParse(_delayController.text) + : null, + setManually: _setManually, + notes: _notesController.text, + ); + } + if (_bolusType == BolusType.meal) { logBolus.carbs = double.tryParse(_carbsController.text); + if (delayedBolus != null) { + delayedBolus.carbs = double.tryParse(_carbsController.text); + } logBolus.mgPerDlCurrent = null; logBolus.mmolPerLCurrent = null; } else { @@ -225,14 +301,37 @@ class _LogBolusDetailScreenState extends State { logBolus.mgPerDlTarget = int.tryParse(_mgPerDlTargetController.text); logBolus.mmolPerLTarget = double.tryParse(_mmolPerLTargetController.text); - logBolus.mgPerDlCorrection = int.tryParse(_mgPerDlCorrectionController.text); + logBolus.mgPerDlCorrection = + int.tryParse(_mgPerDlCorrectionController.text); logBolus.mmolPerLCorrection = double.tryParse(_mmolPerLCorrectionController.text); + if (delayedBolus != null) { + delayedBolus.mgPerDlCurrent = + int.tryParse(_mgPerDlCurrentController.text); + delayedBolus.mmolPerLCurrent = + double.tryParse(_mmolPerLCurrentController.text); + delayedBolus.mgPerDlTarget = + int.tryParse(_mgPerDlTargetController.text); + delayedBolus.mmolPerLTarget = + double.tryParse(_mmolPerLTargetController.text); + delayedBolus.mgPerDlCorrection = + int.tryParse(_mgPerDlCorrectionController.text); + delayedBolus.mmolPerLCorrection = + double.tryParse(_mmolPerLCorrectionController.text); + } } logBolus.logEntry.target = _logEntry; logBolus.meal.target = _meal; logBolus.rate.target = _rate; LogBolus.put(logBolus); + + if (delayedBolus != null) { + delayedBolus.logEntry.target = _logEntry; + delayedBolus.meal.target = _meal; + delayedBolus.rate.target = _rate; + LogBolus.put(delayedBolus); + } + Navigator.pop(context, '${_isNew ? 'New' : ''} Bolus Saved'); } setState(() { @@ -241,16 +340,18 @@ class _LogBolusDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && - (_unitsController.text != '' || - _carbsController.text != '' || - _mgPerDlCurrentController.text != '' || - _mgPerDlTargetController.text != '' || - _mgPerDlCorrectionController.text != '' || - _mmolPerLCurrentController.text != '' || - _mmolPerLTargetController.text != '' || - _mmolPerLCorrectionController.text != '' || + (_carbsController.text != '' || + (_bolusType == BolusType.glucose && + (_mgPerDlCurrentController.text != + (_logEntry?.mgPerDl.toString() ?? '') || + _mmolPerLCurrentController.text != + (_logEntry?.mmolPerL.toString() ?? ''))) || + _mgPerDlTargetController.text != + Settings.get().moderateGlucoseMgPerDl.toString() || + _mmolPerLTargetController.text != + Settings.get().moderateGlucoseMmolPerL.toString() || _delayController.text != '' || _setManually || _notesController.text != '')) || @@ -297,28 +398,39 @@ class _LogBolusDetailScreenState extends State { FormWrapper( formState: _logBolusForm, fields: [ - TextFormField( - decoration: const InputDecoration( - labelText: 'Bolus Units', - suffixText: ' U', - ), - controller: _unitsController, - onChanged: (_) { - setState(() { - _setManually = true; - }); - }, - keyboardType: - const TextInputType.numberWithOptions(decimal: true), - ), - BooleanFormField( - value: _setManually, - label: 'set manually', - onChanged: (value) { - setState(() { - _setManually = value; - }); - }, + Row( + children: [ + Expanded( + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Bolus Units', + suffixText: ' U', + ), + controller: _unitsController, + onChanged: (_) { + setState(() { + _setManually = true; + }); + updateDelayedRatio(); + }, + keyboardType: const TextInputType.numberWithOptions( + decimal: true), + ), + ), + Expanded( + child: BooleanFormField( + contentPadding: const EdgeInsets.only( + left: 10.0, right: 10.0, top: 10.0), + value: _setManually, + label: 'set manually', + onChanged: (value) { + setState(() { + _setManually = value; + }); + }, + ), + ), + ], ), Row( children: [ @@ -350,11 +462,11 @@ class _LogBolusDetailScreenState extends State { children: _bolusType == BolusType.glucose ? [ Row( - children: glucoseMeasurement == + children: Settings.get().glucoseMeasurement == GlucoseMeasurement.mgPerDl || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? [ Expanded( @@ -408,9 +520,9 @@ class _LogBolusDetailScreenState extends State { ), ), ), - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => onChangeGlucose( @@ -424,11 +536,11 @@ class _LogBolusDetailScreenState extends State { : [], ), Row( - children: glucoseMeasurement == + children: Settings.get().glucoseMeasurement == GlucoseMeasurement.mmolPerL || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? [ Expanded( @@ -483,9 +595,9 @@ class _LogBolusDetailScreenState extends State { ), ), ), - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => onChangeGlucose( @@ -512,13 +624,7 @@ class _LogBolusDetailScreenState extends State { TextFormField( decoration: InputDecoration( labelText: 'Carbs', - suffixText: nutritionMeasurement == - NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == - NutritionMeasurement.ounces - ? 'oz' - : '', + suffixText: nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], ), controller: _carbsController, onChanged: (_) => onChangeCarbs(), @@ -533,8 +639,64 @@ class _LogBolusDetailScreenState extends State { suffixText: ' min', ), controller: _delayController, + onChanged: (value) => setState(() {}), keyboardType: const TextInputType.numberWithOptions(), ), + (int.tryParse(_delayController.text) ?? 0) != 0 + ? Slider( + label: '${_delayPercentage.floor().toString()}%', + divisions: 100, + value: _delayPercentage, + min: 0, + max: 100, + onChanged: _delayController.text != '' + ? (value) { + setState(() { + _delayPercentage = value; + }); + updateDelayedRatio(); + } + : null, + ) + : Container(), + Row( + children: (int.tryParse(_delayController.text) ?? 0) != 0 + ? [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 5.0), + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Immediate Bolus', + suffixText: ' U', + ), + controller: _immediateUnitsController, + readOnly: true, + enabled: (int.tryParse(_delayController.text) ?? + 0) != + 0, + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5.0), + child: TextFormField( + decoration: const InputDecoration( + labelText: 'Delayed Bolus', + suffixText: ' U', + ), + controller: _delayedUnitsController, + readOnly: true, + enabled: (int.tryParse(_delayController.text) ?? + 0) != + 0, + ), + ), + ), + ] + : [], + ), TextFormField( controller: _notesController, decoration: const InputDecoration( diff --git a/lib/screens/log/log_entry/log_bolus_list.dart b/lib/screens/log/log_entry/log_bolus_list.dart index 0490e9a..b77c746 100644 --- a/lib/screens/log/log_entry/log_bolus_list.dart +++ b/lib/screens/log/log_entry/log_bolus_list.dart @@ -1,10 +1,9 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; -import 'package:diameter/settings.dart'; import 'package:flutter/material.dart'; class LogBolusListScreen extends StatefulWidget { @@ -58,7 +57,7 @@ class _LogBolusListScreenState extends State { } void handleDeleteAction(LogBolus logBolus) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(logBolus), @@ -92,12 +91,15 @@ class _LogBolusListScreenState extends State { itemCount: widget.logBoli.length, itemBuilder: (context, index) { final bolus = widget.logBoli[index]; + String titleText = '${bolus.units} U ${(bolus.delay ?? 0) != 0 + ? ' (delayed by ${bolus.delay} min)' + : ''}'; return ListTile( onTap: () => handleEditAction(bolus), - title: Text('${bolus.units} U'), + title: Text(titleText), subtitle: Text(bolus.carbs != null ? - 'for ${bolus.meal.target.toString()} (${bolus.carbs}${nutritionMeasurement == NutritionMeasurement.grams ? ' g' : ' oz'} carbs)' - : 'to correct ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDlCorrection : bolus.mmolPerLCorrection} ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 'mg/dl' : 'mmol/l'}'), + 'for ${bolus.meal.target.toString()} (${bolus.carbs}${nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index]} carbs)' + : 'to correct ${Settings.get().glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDlCorrection : bolus.mmolPerLCorrection} ${glucoseMeasurementSuffixes[Settings.get().glucoseMeasurement.index]}'), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/screens/log/log_entry/log_entry.dart b/lib/screens/log/log_entry/log_entry.dart index 02abefd..3cb55e3 100644 --- a/lib/screens/log/log_entry/log_entry.dart +++ b/lib/screens/log/log_entry/log_entry.dart @@ -1,16 +1,15 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_list.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_list.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -175,7 +174,7 @@ class _LogEntryScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (int.tryParse(_mgPerDlController.text) != null || double.tryParse(_mmolPerLController.text) != null || @@ -311,10 +310,10 @@ class _LogEntryScreenState extends State { ), Row( children: [ - glucoseMeasurement == GlucoseMeasurement.mgPerDl || - glucoseDisplayMode == + Settings.get().glucoseMeasurement == GlucoseMeasurement.mgPerDl || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( @@ -341,8 +340,8 @@ class _LogEntryScreenState extends State { ), ) : Container(), - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => @@ -352,10 +351,10 @@ class _LogEntryScreenState extends State { icon: const Icon(Icons.calculate), ) : Container(), - glucoseMeasurement == GlucoseMeasurement.mmolPerL || - glucoseDisplayMode == + Settings.get().glucoseMeasurement == GlucoseMeasurement.mmolPerL || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( @@ -383,8 +382,8 @@ class _LogEntryScreenState extends State { ), ) : Container(), - glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.both || + Settings.get().glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => diff --git a/lib/screens/log/log_entry/log_meal_detail.dart b/lib/screens/log/log_entry/log_meal_detail.dart index 5fd120d..fbc1441 100644 --- a/lib/screens/log/log_entry/log_meal_detail.dart +++ b/lib/screens/log/log_entry/log_meal_detail.dart @@ -2,15 +2,14 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_portion_type.dart'; import 'package:diameter/models/meal_source.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -145,7 +144,7 @@ class _LogMealDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_valueController.text != '' || _meal != null || @@ -325,12 +324,7 @@ class _LogMealDetailScreenState extends State { decoration: InputDecoration( labelText: 'Portion size', suffixText: - nutritionMeasurement == NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == - NutritionMeasurement.ounces - ? 'oz' - : '', + nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], alignLabelWithHint: true, ), controller: _portionSizeController, @@ -366,12 +360,7 @@ class _LogMealDetailScreenState extends State { decoration: InputDecoration( labelText: 'Carbs per portion', suffixText: - nutritionMeasurement == NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == - NutritionMeasurement.ounces - ? 'oz' - : '', + nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], ), controller: _carbsPerPortionController, keyboardType: const TextInputType.numberWithOptions( diff --git a/lib/screens/log/log_entry/log_meal_list.dart b/lib/screens/log/log_entry/log_meal_list.dart index 55ecbc5..3e7701b 100644 --- a/lib/screens/log/log_entry/log_meal_list.dart +++ b/lib/screens/log/log_entry/log_meal_list.dart @@ -1,7 +1,7 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:flutter/material.dart'; @@ -53,7 +53,7 @@ class _LogMealListScreenState extends State { } void handleDeleteAction(LogMeal meal) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(meal), diff --git a/lib/screens/log/log_event/log_event_detail.dart b/lib/screens/log/log_event/log_event_detail.dart index 2d89a1f..629bc40 100644 --- a/lib/screens/log/log_event/log_event_detail.dart +++ b/lib/screens/log/log_event/log_event_detail.dart @@ -2,11 +2,11 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/log_event.dart'; import 'package:diameter/models/log_event_type.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; @@ -132,24 +132,63 @@ class _LogEventDetailScreenState extends State { }); } + Future checkIfActiveEventOfTypeExistsBeforeSaving() async { + if (_eventType != null && LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) { + await showDialog( + context: context, + builder: (BuildContext context) { + return AlertDialog( + content: const Text( + 'An Event of this type is already active within the set time frame. What would you like to do?'), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, 'DISCARD'), + child: const Text('DISCARD'), + ), + TextButton( + onPressed: () => Navigator.pop(context, 'EDIT'), + child: const Text('KEEP EDITING'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, 'SAVE'), + child: const Text('SAVE'), + ) + ], + ); + }).then((value) async { + if (value == 'DISCARD') { + Navigator.pop(context); + } else if (value == 'SAVE') { + onSave(); + } + }); + } else { + onSave(); + } + } + + void onSave() { + LogEvent event = LogEvent( + id: widget.id, + time: _time, + endTime: _endTime, + hasEndTime: _hasEndTime, + reminderDuration: int.tryParse(_reminderDurationController.text), + notes: _notesController.text, + ); + event.eventType.target = _eventType; + event.basalProfile.target = _basalProfile; + event.bolusProfile.target = _bolusProfile; + LogEvent.put(event); + Navigator.pop(context, '${_isNew ? 'New' : ''} Event Saved'); + } + void handleSaveAction() async { setState(() { _isSaving = true; }); if (_logEventForm.currentState!.validate()) { - LogEvent event = LogEvent( - id: widget.id, - time: _time, - endTime: _endTime, - hasEndTime: _hasEndTime, - reminderDuration: int.tryParse(_reminderDurationController.text), - notes: _notesController.text, - ); - event.eventType.target = _eventType; - event.basalProfile.target = _basalProfile; - event.bolusProfile.target = _bolusProfile; - LogEvent.put(event); - Navigator.pop(context, '${_isNew ? 'New' : ''} Event Saved'); + await checkIfActiveEventOfTypeExistsBeforeSaving(); } setState(() { _isSaving = false; @@ -157,7 +196,7 @@ class _LogEventDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_notesController.text != '' || _eventType != null || @@ -202,55 +241,47 @@ class _LogEventDetailScreenState extends State { }, ), Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(right: 5), - child: DateTimeFormField( - date: _time, - label: _hasEndTime ? 'Start Date' : 'Date', - controller: _dateController, - onChanged: (newTime) { - if (newTime != null) { - setState(() { - _time = DateTime( - newTime.year, - newTime.month, - newTime.day, - _time.hour, - _time.minute); - }); - updateTime(); - } - }, - ), - ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 5), - child: TimeOfDayFormField( - time: TimeOfDay.fromDateTime(_time), - label: _hasEndTime ? 'Start Time' : 'Time', - controller: _timeController, - onChanged: (newTime) { - if (newTime != null) { - setState(() { - _time = DateTime( - _time.year, - _time.month, - _time.day, - newTime.hour, - newTime.minute); - }); - updateTime(); - } - }, - ), - ), - ), - ], + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 5), + child: DateTimeFormField( + date: _time, + label: _hasEndTime ? 'Start Date' : 'Date', + controller: _dateController, + onChanged: (newTime) { + if (newTime != null) { + setState(() { + _time = DateTime(newTime.year, newTime.month, + newTime.day, _time.hour, _time.minute); + }); + updateTime(); + } + }, + ), ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5), + child: TimeOfDayFormField( + time: TimeOfDay.fromDateTime(_time), + label: _hasEndTime ? 'Start Time' : 'Time', + controller: _timeController, + onChanged: (newTime) { + if (newTime != null) { + setState(() { + _time = DateTime(_time.year, _time.month, + _time.day, newTime.hour, newTime.minute); + }); + updateTime(); + } + }, + ), + ), + ), + ], + ), BooleanFormField( value: _hasEndTime, onChanged: (value) { @@ -261,57 +292,59 @@ class _LogEventDetailScreenState extends State { label: 'has end time', ), Column( - children: _hasEndTime? [ - Row( - children: [ - Expanded( - child: Padding( - padding: const EdgeInsets.only(right: 5), - child: DateTimeFormField( - date: _endTime ?? now, - label: 'End Date', - controller: _endDateController, - onChanged: (newTime) { - if (newTime != null) { - setState(() { - _endTime = DateTime( - newTime.year, - newTime.month, - newTime.day, - _endTime?.hour ?? 0, - _endTime?.minute ?? 0); - }); - updateEndTime(); - } - }, - ), + children: _hasEndTime + ? [ + Row( + children: [ + Expanded( + child: Padding( + padding: const EdgeInsets.only(right: 5), + child: DateTimeFormField( + date: _endTime ?? now, + label: 'End Date', + controller: _endDateController, + onChanged: (newTime) { + if (newTime != null) { + setState(() { + _endTime = DateTime( + newTime.year, + newTime.month, + newTime.day, + _endTime?.hour ?? 0, + _endTime?.minute ?? 0); + }); + updateEndTime(); + } + }, + ), + ), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.only(left: 5), + child: TimeOfDayFormField( + time: TimeOfDay.fromDateTime( + _endTime ?? now), + label: 'End Time', + controller: _endTimeController, + onChanged: (newTime) { + if (newTime != null) { + setState(() { + _endTime = DateTime( + _endTime?.year ?? now.year, + _endTime?.month ?? now.month, + _endTime?.day ?? now.day, + newTime.hour, + newTime.minute); + }); + updateEndTime(); + } + }, + ), + ), + ), + ], ), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.only(left: 5), - child: TimeOfDayFormField( - time: TimeOfDay.fromDateTime(_endTime ?? now), - label: 'End Time', - controller: _endTimeController, - onChanged: (newTime) { - if (newTime != null) { - setState(() { - _endTime = DateTime( - _endTime?.year ?? now.year, - _endTime?.month ?? now.month, - _endTime?.day ?? now.day, - newTime.hour, - newTime.minute); - }); - updateEndTime(); - } - }, - ), - ), - ), - ], - ), TextFormField( controller: _reminderDurationController, keyboardType: diff --git a/lib/screens/log/log_event/log_event_list.dart b/lib/screens/log/log_event/log_event_list.dart index 09e1200..48ed4e3 100644 --- a/lib/screens/log/log_event/log_event_list.dart +++ b/lib/screens/log/log_event/log_event_list.dart @@ -1,6 +1,6 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/log_event.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/screens/log/log_event/log_event_detail.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; @@ -71,7 +71,7 @@ class _LogEventListScreenState extends State { } void handleDeleteAction(LogEvent logEvent) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(logEvent), @@ -89,7 +89,7 @@ class _LogEventListScreenState extends State { } void handleStopAction(LogEvent event) async { - if (showConfirmationDialogOnStopEvent) { + if (Settings.get().showConfirmationDialogOnStopEvent) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onStop(event), @@ -101,7 +101,6 @@ class _LogEventListScreenState extends State { } } - @override Widget build(BuildContext context) { return Scaffold( @@ -113,73 +112,101 @@ class _LogEventListScreenState extends State { ), drawer: const Navigation(currentLocation: LogEventListScreen.routeName), body: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Expanded( - child: _logEventDailyMap.isNotEmpty - ? SingleChildScrollView( - child: ListView.builder( - shrinkWrap: true, - padding: const EdgeInsets.all(10.0), - itemCount: _logEventDailyMap.length, - itemBuilder: (context, dateIndex) { - List dateList = [null] + + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Expanded( + child: _logEventDailyMap.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + padding: const EdgeInsets.all(10.0), + itemCount: _logEventDailyMap.length, + itemBuilder: (context, dateIndex) { + List dateList = (_activeEvents.isNotEmpty + ? [null] + : []) + _logEventDailyMap.keys.toList(); - final date = dateList[dateIndex]; - final eventList = date != null ? _logEventDailyMap[date] : _activeEvents; - return ListBody( - children: [ - Text(DateTimeUtils.displayDate(date, fallback: 'Active Events')), - eventList != null && eventList.isNotEmpty - ? ListView.builder( - shrinkWrap: true, - itemCount: eventList.length, - itemBuilder: (context, index) { - final event = eventList[index]; - return ListTile( - onTap: () { - handleEditAction(event); - }, - title: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Expanded( - child: Text(event.eventType.target?.value ?? '')), - ], - ), - subtitle: Text( - '${DateTimeUtils.displayDateTime(event.time)}${event.hasEndTime ? ' - ${DateTimeUtils.displayDateTime(event.endTime, fallback: '(ongoing)')}' : ''}'), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - event.hasEndTime && event.endTime == null ? IconButton( - icon: const Icon( - Icons.stop, - color: Colors.blue, - ), - onPressed: () => handleStopAction(event), - ) : Container(), - const SizedBox(width: 24), - IconButton( - icon: const Icon( - Icons.delete, - color: Colors.blue, - ), - onPressed: () => handleDeleteAction(event), - ), - ], - ), - ); - }) : Container(), - ], - ); - }, - ), - ) : const Center( - child: Text('There are no Events!'), - ), - ), - ], + final date = dateList[dateIndex]; + final eventList = date != null + ? _logEventDailyMap[date] + : _activeEvents; + final tiles = []; + if (eventList != null) { + for (LogEvent event in eventList) { + tiles.add(ListTile( + onTap: () { + handleEditAction(event); + }, + title: Row( + crossAxisAlignment: + CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + children: [ + Expanded( + child: Text(date == null + ? DateTimeUtils + .displayDateTime( + event.time) + : DateTimeUtils.displayTime( + event.isEndEvent + ? event.endTime + : event.time))), + const SizedBox(width: 24), + Expanded( + child: Text(event.title ?? + event.eventType.target?.value ?? + ''), + ), + ], + ), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + event.hasEndTime && + event.endTime == null + ? IconButton( + icon: const Icon( + Icons.stop, + color: Colors.blue, + ), + onPressed: () => + handleStopAction(event), + ) + : const SizedBox(width: 50), + IconButton( + icon: const Icon( + Icons.edit, + color: Colors.blue, + ), + onPressed: () => + handleEditAction(event), + ), + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.blue, + ), + onPressed: () => + handleDeleteAction(event), + ), + ], + ), + )); + } + } + return eventList != null && eventList.isNotEmpty ? ListBody( + children: [ + Text(DateTimeUtils.displayDate(date, + fallback: 'Active Events')) + ] + tiles, + + ) : Container(); + }, + ) + : const Center( + child: Text('There are no Events!'), + ), + ), + ], ), floatingActionButton: FloatingActionButton( onPressed: handleAddNewEvent, diff --git a/lib/screens/log/log_event/log_event_type_detail.dart b/lib/screens/log/log_event/log_event_type_detail.dart index 8844b16..4b6c7f9 100644 --- a/lib/screens/log/log_event/log_event_type_detail.dart +++ b/lib/screens/log/log_event/log_event_type_detail.dart @@ -2,10 +2,10 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/log_event_type.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; @@ -90,7 +90,7 @@ class _EventTypeDetailScreenState extends State { void handleCancelAction() { bool isNew = _logEventType == null; - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((isNew && (_valueController.text != '' || int.tryParse(_defaultReminderDurationController.text) != diff --git a/lib/screens/meal/meal_category_detail.dart b/lib/screens/meal/meal_category_detail.dart index 960e1fa..86c84a4 100644 --- a/lib/screens/meal/meal_category_detail.dart +++ b/lib/screens/meal/meal_category_detail.dart @@ -1,7 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_category.dart'; @@ -57,7 +57,7 @@ class _MealCategoryDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && (_isNew && (_valueController.text != '' || _notesController.text != '')) || (!_isNew && diff --git a/lib/screens/meal/meal_category_list.dart b/lib/screens/meal/meal_category_list.dart index 1a3fb2e..40a960e 100644 --- a/lib/screens/meal/meal_category_list.dart +++ b/lib/screens/meal/meal_category_list.dart @@ -1,6 +1,5 @@ import 'package:diameter/components/dialogs.dart'; -// import 'package:diameter/components/progress_indicator.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:flutter/material.dart'; @@ -47,7 +46,7 @@ class _MealCategoryListScreenState extends State { } void handleDeleteAction(MealCategory mealCategory) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealCategory), diff --git a/lib/screens/meal/meal_detail.dart b/lib/screens/meal/meal_detail.dart index a2aec06..bcc96e3 100644 --- a/lib/screens/meal/meal_detail.dart +++ b/lib/screens/meal/meal_detail.dart @@ -2,14 +2,13 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_portion_type.dart'; import 'package:diameter/models/meal_source.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/settings.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; @@ -122,7 +121,7 @@ class _MealDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_valueController.text != '' || _mealSource != null || @@ -313,12 +312,7 @@ class _MealDetailScreenState extends State { decoration: InputDecoration( labelText: 'Portion size', suffixText: - nutritionMeasurement == NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == - NutritionMeasurement.ounces - ? 'oz' - : '', + nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], alignLabelWithHint: true, ), controller: _portionSizeController, @@ -354,12 +348,7 @@ class _MealDetailScreenState extends State { decoration: InputDecoration( labelText: 'Carbs per portion', suffixText: - nutritionMeasurement == NutritionMeasurement.grams - ? 'g' - : nutritionMeasurement == - NutritionMeasurement.ounces - ? 'oz' - : '', + nutritionMeasurementSuffixes[Settings.get().nutritionMeasurement.index], ), controller: _carbsPerPortionController, keyboardType: const TextInputType.numberWithOptions( diff --git a/lib/screens/meal/meal_list.dart b/lib/screens/meal/meal_list.dart index 5717cc2..4bafb7e 100644 --- a/lib/screens/meal/meal_list.dart +++ b/lib/screens/meal/meal_list.dart @@ -1,6 +1,6 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/meal.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:flutter/material.dart'; @@ -46,7 +46,7 @@ class _MealListScreenState extends State { } void handleDeleteAction(Meal meal) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(meal), diff --git a/lib/screens/meal/meal_portion_type_detail.dart b/lib/screens/meal/meal_portion_type_detail.dart index 845008b..14c0768 100644 --- a/lib/screens/meal/meal_portion_type_detail.dart +++ b/lib/screens/meal/meal_portion_type_detail.dart @@ -1,7 +1,7 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; import 'package:diameter/models/meal_portion_type.dart'; @@ -60,7 +60,7 @@ class _MealPortionTypeDetailScreenState } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_valueController.text != '' || _notesController.text != '')) || (!_isNew && diff --git a/lib/screens/meal/meal_portion_type_list.dart b/lib/screens/meal/meal_portion_type_list.dart index eb6d667..81db16f 100644 --- a/lib/screens/meal/meal_portion_type_list.dart +++ b/lib/screens/meal/meal_portion_type_list.dart @@ -1,5 +1,5 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; import 'package:flutter/material.dart'; @@ -47,7 +47,7 @@ class _MealPortionTypeListScreenState extends State { } void handleDeleteAction(MealPortionType mealPortionType) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealPortionType), diff --git a/lib/screens/meal/meal_source_detail.dart b/lib/screens/meal/meal_source_detail.dart index a5702b5..cfe0f15 100644 --- a/lib/screens/meal/meal_source_detail.dart +++ b/lib/screens/meal/meal_source_detail.dart @@ -2,11 +2,11 @@ import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/accuracy.dart'; import 'package:diameter/models/meal_category.dart'; import 'package:diameter/models/meal_portion_type.dart'; import 'package:diameter/models/meal_source.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; @@ -90,7 +90,7 @@ class _MealSourceDetailScreenState extends State { } void handleCancelAction() { - if (showConfirmationDialogOnCancel && + if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (_valueController.text != '' || _defaultCarbsRatioAccuracy != null || diff --git a/lib/screens/meal/meal_source_list.dart b/lib/screens/meal/meal_source_list.dart index 1ddf59e..bf4ece8 100644 --- a/lib/screens/meal/meal_source_list.dart +++ b/lib/screens/meal/meal_source_list.dart @@ -1,6 +1,6 @@ import 'package:diameter/components/dialogs.dart'; -import 'package:diameter/config.dart'; import 'package:diameter/models/meal_source.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_source_detail.dart'; import 'package:flutter/material.dart'; @@ -46,7 +46,7 @@ class _MealSourceListScreenState extends State { } void handleDeleteAction(MealSource mealSource) async { - if (showConfirmationDialogOnDelete) { + if (Settings.get().showConfirmationDialogOnDelete) { Dialogs.showConfirmationDialog( context: context, onConfirm: () => onDelete(mealSource), diff --git a/lib/settings.dart b/lib/settings.dart index a30793f..3544fb1 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,88 +1,9 @@ import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dropdown.dart'; import 'package:diameter/components/forms.dart'; -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; -import 'package:shared_preferences/shared_preferences.dart'; - -enum GlucoseDisplayMode { activeOnly, bothForList, bothForDetail, both } - -enum GlucoseMeasurement { - mgPerDl, - mmolPerL, -} - -enum NutritionMeasurement { - grams, - ounces, - cups, -} - -class Settings { - static void loadSettingsIntoConfig() async { - nutritionMeasurement = await getNutritionMeasurement(); - glucoseMeasurement = await getGlucoseMeasurement(); - glucoseDisplayMode = await getGlucoseDisplayMode(); - } - - static Future getGlucoseDisplayMode() async { - final settings = await SharedPreferences.getInstance(); - int? index = settings.getInt('glucoseDisplayMode'); - return index != null && index < GlucoseDisplayMode.values.length - ? GlucoseDisplayMode.values[index] - : GlucoseDisplayMode.bothForList; - } - - static Future getGlucoseMeasurement() async { - final settings = await SharedPreferences.getInstance(); - int? index = settings.getInt('glucoseMeasurement'); - return index != null && index < GlucoseMeasurement.values.length - ? GlucoseMeasurement.values[index] - : GlucoseMeasurement.mgPerDl; - } - - static Future getNutritionMeasurement() async { - final settings = await SharedPreferences.getInstance(); - int? index = settings.getInt('nutritionMeasurement'); - return index != null && index < NutritionMeasurement.values.length - ? NutritionMeasurement.values[index] - : NutritionMeasurement.grams; - } - - static void setGlucoseDisplayMode( - GlucoseDisplayMode? glucoseDisplayMode) async { - final settings = await SharedPreferences.getInstance(); - if (glucoseDisplayMode != null) { - settings.setInt('glucoseDisplayMode', glucoseDisplayMode.index); - } - } - - static void setGlucoseMeasurement( - GlucoseMeasurement? glucoseMeasurement) async { - final settings = await SharedPreferences.getInstance(); - if (glucoseMeasurement != null) { - settings.setInt('preferredGlucoseMeasurement', glucoseMeasurement.index); - } - } - - static void setNutritionMeasurement( - NutritionMeasurement? nutritionMeasurement) async { - final settings = await SharedPreferences.getInstance(); - if (nutritionMeasurement != null) { - settings.setInt( - 'preferredNutritionMeasurement', nutritionMeasurement.index); - } - } - - static void resetAll() async { - final settings = await SharedPreferences.getInstance(); - - settings.remove('glucoseDisplayMode'); - settings.remove('preferredGlucoseMeasurement'); - settings.remove('preferredNutritionMeasurement'); - } -} class SettingsScreen extends StatefulWidget { static const String routeName = '/settings'; @@ -94,12 +15,73 @@ class SettingsScreen extends StatefulWidget { } class _SettingsScreenState extends State { - final GlobalKey _settingsForm = GlobalKey(); + late Settings _settings; + + late bool _onlyDisplayActiveGlucoseMeasurement; + late bool _displayBothGlucoseMeasurementsInDetailView; + late bool _displayBothGlucoseMeasurementsInListView; + + @override + void initState() { + super.initState(); + reload(); + } void onReset() { - Settings.resetAll(); + Settings.reset(); + reload(message: 'Settings have been reset to default'); + } + + void saveSettings() { + Settings.put(Settings( + id: _settings.id, + nutritionMeasurement: _settings.nutritionMeasurement, + glucoseDisplayMode: _settings.glucoseDisplayMode, + glucoseMeasurement: _settings.glucoseMeasurement, + dateFormat: _settings.dateFormat, + longDateFormat: _settings.longDateFormat, + timeFormat: _settings.timeFormat, + longTimeFormat: _settings.longTimeFormat, + showConfirmationDialogOnCancel: _settings.showConfirmationDialogOnCancel, + showConfirmationDialogOnDelete: _settings.showConfirmationDialogOnDelete, + showConfirmationDialogOnStopEvent: + _settings.showConfirmationDialogOnStopEvent, + lowGlucoseMgPerDl: _settings.lowGlucoseMgPerDl, + moderateGlucoseMgPerDl: _settings.moderateGlucoseMgPerDl, + highGlucoseMgPerDl: _settings.highGlucoseMgPerDl, + lowGlucoseMmolPerL: _settings.lowGlucoseMmolPerL, + moderateGlucoseMmolPerL: _settings.moderateGlucoseMmolPerL, + highGlucoseMmolPerDl: _settings.highGlucoseMmolPerDl, + )); + reload(message: 'Settings updated'); + } + + void reload({String? message}) { setState(() { - Settings.loadSettingsIntoConfig(); + _settings = Settings.get(); + }); + + setState(() { + _onlyDisplayActiveGlucoseMeasurement = + _settings.glucoseDisplayMode == GlucoseDisplayMode.activeOnly; + _displayBothGlucoseMeasurementsInDetailView = + _settings.glucoseDisplayMode == GlucoseDisplayMode.both || + _settings.glucoseDisplayMode == GlucoseDisplayMode.bothForDetail; + _displayBothGlucoseMeasurementsInListView = + _settings.glucoseDisplayMode == GlucoseDisplayMode.both || + _settings.glucoseDisplayMode == GlucoseDisplayMode.bothForList; + }); + + setState(() { + if (message != null) { + var snackBar = SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + ); + ScaffoldMessenger.of(context) + ..removeCurrentSnackBar() + ..showSnackBar(snackBar); + } }); } @@ -111,12 +93,6 @@ class _SettingsScreenState extends State { ); } - @override - initState() { - super.initState(); - Settings.loadSettingsIntoConfig(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -126,88 +102,78 @@ class _SettingsScreenState extends State { drawer: const Navigation(currentLocation: SettingsScreen.routeName), body: SingleChildScrollView( child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - FormWrapper( - formState: _settingsForm, - fields: [ - AutoCompleteDropdownButton( - selectedItem: nutritionMeasurement, - label: 'Preferred Nutrition Measurement', - items: NutritionMeasurement.values, - onChanged: (value) { - if (value != null) { - Settings.setNutritionMeasurement(value); - setState(() { - nutritionMeasurement = value; - }); - } - }, - ), - AutoCompleteDropdownButton( - selectedItem: glucoseMeasurement, - label: 'Preferred Glucose Measurement', - items: GlucoseMeasurement.values, - onChanged: (value) { - if (value != null) { - Settings.setGlucoseMeasurement(value); - setState(() { - glucoseMeasurement = value; - }); - } - }, - ), - BooleanFormField( - value: glucoseDisplayMode == GlucoseDisplayMode.activeOnly, - label: 'only display active glucose measurement', - onChanged: (_) { - GlucoseDisplayMode mode = - glucoseDisplayMode == GlucoseDisplayMode.activeOnly - ? GlucoseDisplayMode.both - : GlucoseDisplayMode.activeOnly; - Settings.setGlucoseDisplayMode(mode); - setState(() { - glucoseDisplayMode = mode; - }); - }, - ), - BooleanFormField( - value: glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == GlucoseDisplayMode.bothForDetail, - enabled: glucoseDisplayMode != GlucoseDisplayMode.activeOnly, - label: 'display both glucose measurements in detail view', - onChanged: (_) { - GlucoseDisplayMode mode = glucoseDisplayMode == - GlucoseDisplayMode.both + AutoCompleteDropdownButton( + selectedItem: nutritionMeasurementLabels[ + _settings.nutritionMeasurement.index], + label: 'Preferred Nutrition Measurement', + items: nutritionMeasurementLabels, + onChanged: (value) { + if (value != null) { + _settings.nutritionMeasurement = NutritionMeasurement.values + .elementAt(nutritionMeasurementLabels.indexOf(value)); + saveSettings(); + } + }, + ), + AutoCompleteDropdownButton( + selectedItem: + glucoseMeasurementLabels[_settings.glucoseMeasurement.index], + label: 'Preferred Glucose Measurement', + items: glucoseMeasurementLabels, + onChanged: (value) { + if (value != null) { + _settings.glucoseMeasurement = GlucoseMeasurement.values + .elementAt(glucoseMeasurementLabels.indexOf(value)); + saveSettings(); + } + }, + ), + BooleanFormField( + value: _onlyDisplayActiveGlucoseMeasurement, + label: 'only display active glucose measurement', + onChanged: (_) { + GlucoseDisplayMode mode = _settings.glucoseDisplayMode == + GlucoseDisplayMode.activeOnly + ? GlucoseDisplayMode.both + : GlucoseDisplayMode.activeOnly; + _settings.glucoseDisplayMode = mode; + saveSettings(); + }, + ), + BooleanFormField( + value: _displayBothGlucoseMeasurementsInDetailView, + enabled: + !_onlyDisplayActiveGlucoseMeasurement, + label: 'display both glucose measurements in detail view', + onChanged: (_) { + GlucoseDisplayMode mode = + _settings.glucoseDisplayMode == GlucoseDisplayMode.both ? GlucoseDisplayMode.bothForList - : glucoseDisplayMode == GlucoseDisplayMode.bothForList + : _settings.glucoseDisplayMode == + GlucoseDisplayMode.bothForList ? GlucoseDisplayMode.both : GlucoseDisplayMode.activeOnly; - Settings.setGlucoseDisplayMode(mode); - setState(() { - glucoseDisplayMode = mode; - }); - }, - ), - BooleanFormField( - value: glucoseDisplayMode == GlucoseDisplayMode.both || - glucoseDisplayMode == GlucoseDisplayMode.bothForList, - enabled: glucoseDisplayMode != GlucoseDisplayMode.activeOnly, - label: 'display both glucose measurements in list view', - onChanged: (_) { - GlucoseDisplayMode mode = glucoseDisplayMode == - GlucoseDisplayMode.both + _settings.glucoseDisplayMode = mode; + saveSettings(); + }, + ), + BooleanFormField( + value: _displayBothGlucoseMeasurementsInListView, + enabled: + !_onlyDisplayActiveGlucoseMeasurement, + label: 'display both glucose measurements in list view', + onChanged: (_) { + GlucoseDisplayMode mode = + _settings.glucoseDisplayMode == GlucoseDisplayMode.both ? GlucoseDisplayMode.bothForDetail - : glucoseDisplayMode == GlucoseDisplayMode.bothForDetail + : _settings.glucoseDisplayMode == + GlucoseDisplayMode.bothForDetail ? GlucoseDisplayMode.both : GlucoseDisplayMode.activeOnly; - Settings.setGlucoseDisplayMode(mode); - setState(() { - glucoseDisplayMode = mode; - }); - }, - ), - ], + _settings.glucoseDisplayMode = mode; + saveSettings(); + }, ), ], ), diff --git a/lib/utils/date_time_utils.dart b/lib/utils/date_time_utils.dart index 9023e30..f997fd8 100644 --- a/lib/utils/date_time_utils.dart +++ b/lib/utils/date_time_utils.dart @@ -1,14 +1,16 @@ -import 'package:diameter/config.dart'; +import 'package:diameter/models/settings.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; +final DateTime dummyDate = DateTime(2000); + class DateTimeUtils { static String displayDateTime(DateTime? date, {String fallback = ''}) { if (date == null) { return fallback; } DateTime localDate = date.toLocal(); - final DateFormat formatter = DateFormat('$dateFormat $timeFormat'); + final DateFormat formatter = DateFormat('${Settings.get().dateFormat} ${Settings.get().timeFormat}'); return formatter.format(localDate); } @@ -17,7 +19,7 @@ class DateTimeUtils { return fallback; } DateTime localDate = date.toLocal(); - final DateFormat formatter = DateFormat(longDateFormat ?? dateFormat); + final DateFormat formatter = DateFormat(Settings.get().longDateFormat ?? Settings.get().dateFormat); return formatter.format(localDate); } @@ -28,7 +30,7 @@ class DateTimeUtils { } DateTime localDate = date.toLocal(); final DateFormat formatter = DateFormat( - longFormat == true ? longTimeFormat ?? timeFormat : timeFormat); + longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat); return formatter.format(localDate); } @@ -38,7 +40,7 @@ class DateTimeUtils { return fallback; } final DateFormat formatter = DateFormat( - longFormat == true ? longTimeFormat ?? timeFormat : timeFormat); + longFormat == true ? Settings.get().longTimeFormat ?? Settings.get().timeFormat : Settings.get().timeFormat); return formatter.format(convertTimeOfDayToDateTime(time)); } diff --git a/pubspec.lock b/pubspec.lock index 3e177e8..c41da0e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "30.0.0" + version: "31.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "2.7.0" + version: "2.8.0" args: dependency: transitive description: @@ -63,14 +63,14 @@ packages: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "2.0.4" + version: "2.0.5" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.5" build_runner_core: dependency: transitive description: @@ -154,7 +154,7 @@ packages: name: connectivity_plus_linux url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" connectivity_plus_macos: dependency: transitive description: @@ -168,7 +168,7 @@ packages: name: connectivity_plus_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.1.1" connectivity_plus_web: dependency: transitive description: @@ -203,7 +203,7 @@ packages: name: cupertino_icons url: "https://pub.dartlang.org" source: hosted - version: "1.0.3" + version: "1.0.4" dart_style: dependency: transitive description: @@ -217,14 +217,14 @@ packages: name: dbus url: "https://pub.dartlang.org" source: hosted - version: "0.5.6" + version: "0.6.6" dio: dependency: transitive description: name: dio url: "https://pub.dartlang.org" source: hosted - version: "4.0.1" + version: "4.0.4" fake_async: dependency: transitive description: @@ -358,7 +358,7 @@ packages: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "4.3.0" + version: "4.4.0" lints: dependency: transitive description: @@ -414,28 +414,28 @@ packages: name: nm url: "https://pub.dartlang.org" source: hosted - version: "0.3.0" + version: "0.4.1" objectbox: dependency: "direct main" description: name: objectbox url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" objectbox_flutter_libs: dependency: "direct main" description: name: objectbox_flutter_libs url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" objectbox_generator: dependency: "direct dev" description: name: objectbox_generator url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" package_config: dependency: transitive description: @@ -512,21 +512,35 @@ packages: name: path_provider url: "https://pub.dartlang.org" source: hosted - version: "2.0.6" + version: "2.0.7" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + path_provider_ios: + dependency: transitive + description: + name: path_provider_ios + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.7" path_provider_linux: dependency: transitive description: name: path_provider_linux url: "https://pub.dartlang.org" source: hosted - version: "2.1.0" + version: "2.1.2" path_provider_macos: dependency: transitive description: name: path_provider_macos url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.4" path_provider_platform_interface: dependency: transitive description: @@ -540,14 +554,7 @@ packages: name: path_provider_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.3" - pedantic: - dependency: transitive - description: - name: pedantic - url: "https://pub.dartlang.org" - source: hosted - version: "1.11.1" + version: "2.0.4" petitparser: dependency: transitive description: @@ -561,7 +568,7 @@ packages: name: platform url: "https://pub.dartlang.org" source: hosted - version: "3.0.2" + version: "3.1.0" plugin_platform_interface: dependency: transitive description: @@ -624,6 +631,20 @@ packages: name: shared_preferences url: "https://pub.dartlang.org" source: hosted + version: "2.0.9" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + shared_preferences_ios: + dependency: transitive + description: + name: shared_preferences_ios + url: "https://pub.dartlang.org" + source: hosted version: "2.0.8" shared_preferences_linux: dependency: transitive @@ -631,7 +652,7 @@ packages: name: shared_preferences_linux url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" shared_preferences_macos: dependency: transitive description: @@ -659,7 +680,7 @@ packages: name: shared_preferences_windows url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.0.3" shelf: dependency: transitive description: @@ -685,7 +706,7 @@ packages: name: source_gen url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "1.2.0" source_span: dependency: transitive description: @@ -804,7 +825,7 @@ packages: name: win32 url: "https://pub.dartlang.org" source: hosted - version: "2.2.10" + version: "2.3.1" xdg_directories: dependency: transitive description: