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: