diff --git a/TODO b/TODO index dc50418..c6a26d8 100644 --- a/TODO +++ b/TODO @@ -1,37 +1,48 @@ -General/Framework: - ☐ add active/deleted flag to all data models - ☐ account for deleted/disabled elements in dropdowns - ☐ place dropdown items right below their input - ✔ use local database instead of back4app @done(21-11-07 18:53) - ☐ find a general way to deal with duration fields - ☐ add explanations to each section - ☐ use ids instead of passing entities around where possible +MAIN TASKS: + General/Framework: + ☐ add active/deleted flag to all data models + ☐ find a general way to deal with duration fields + + Accuracies: + ☐ implement reordering -Accuracies: - ☐ implement reordering + Basal/Bolus: + ☐ create distinct visual mode for picking the active profile -Basal/Bolus: - ☐ create distinct visual mode for picking the active profile + Log Overview: + ☐ add active events view (as main menu item?) + ☐ adjust/debug active events view + ☐ display more information + ☐ apply target color settings to glucose + ☐ total bolus and carbs per entry -Meal: - none + Log Entry: + ☐ replace meal and glucose boli with logbolus entities + ☐ fix logmeals/logboli/logevents (probably use queries for associated items instead of onetomany) -Log Overview: - ☐ add active events view - ☐ adjust/debug active events view - ☐ display more information - ☐ apply target color settings to glucose - ☐ total bolus and carbs per entry + Event Types: + ☐ add option to change bolus/basal profile for event duration -Log Entry: - ☐ add tab for bolus overview - ☐ calculate bolus suggestions according to active profile - ☐ add time picker for entry date/time + Settings: + ☐ add objectbox class and use instead of shared preferences + ☐ add fields for date and time formats + ☐ add fields for glucose target -Event Types: - ☐ add option to change bolus/basal profile for event duration +FUTURE TASKS: + General/Framework: + ☐ add explanations to each section + ☐ account for deleted/disabled elements in dropdowns + ☐ hide dropdown overlay on tapping anywhere else (especially menu) + ☐ add clear button to dropdown (or all text fields?) + Log Overview: + ☐ add pagination + -Settings: - ☐ add objectbox class and use instead of shared preferences - ☐ add fields for date and time formats - ☐ add fields for glucose target \ No newline at end of file +ARCHIVE: + ✔ add tab for bolus overview @done(21-11-24 22:05) @project(Log Entry) + ✔ calculate bolus suggestions according to active profile @done(21-11-24 22:05) @project(Log Entry) + ✔ place dropdown items right below their input @done(21-11-23 20:33) @project(General/Framework) + ✔ add autocomplete function to dropdowns @done(21-11-23 20:33) @project(General/Framework) + ✔ use local database instead of back4app @done(21-11-07 18:53) @project(General/Framework) + ✔ use ids instead of passing entities around where possible @done(21-11-10 00:06) @project(General/Framework) + ✔ add time picker for entry date/time @done(21-11-10 00:06) @project(Log Entry) diff --git a/lib/components/app_theme.dart b/lib/components/app_theme.dart index 658b771..5fb6e15 100644 --- a/lib/components/app_theme.dart +++ b/lib/components/app_theme.dart @@ -5,6 +5,7 @@ class AppTheme { AppTheme._(); static ThemeData lightTheme = FlexColorScheme.light( + surfaceStyle: FlexSurface.medium, scheme: FlexScheme.mandyRed, fontFamily: 'RobotoCondensed', ).toTheme; @@ -16,8 +17,7 @@ class AppTheme { static ThemeData makeTheme(ThemeData baseThemeData) { return baseThemeData.copyWith( - visualDensity: VisualDensity.compact, - bottomNavigationBarTheme: BottomNavigationBarThemeData( - backgroundColor: baseThemeData.primaryColor)); + bottomNavigationBarTheme: BottomNavigationBarThemeData( + backgroundColor: baseThemeData.primaryColor)); } } diff --git a/lib/components/dropdown.dart b/lib/components/dropdown.dart new file mode 100644 index 0000000..2424168 --- /dev/null +++ b/lib/components/dropdown.dart @@ -0,0 +1,199 @@ +import 'dart:math'; +import 'package:flutter/material.dart'; + +class AutoCompleteDropdownButton extends StatefulWidget { + final String label; + final T? selectedItem; + final List items; + final String Function(T item) renderItem; + final void Function(T? value) onChanged; + final List Function(String? value)? applyQuery; + + const AutoCompleteDropdownButton( + {Key? key, + this.selectedItem, + required this.label, + required this.items, + required this.renderItem, + required this.onChanged, + this.applyQuery}) + : super(key: key); + + @override + _AutoCompleteDropdownButtonState createState() => + _AutoCompleteDropdownButtonState(); +} + +class _AutoCompleteDropdownButtonState + extends State> { + TextEditingController controller = TextEditingController(text: ''); + late List options; + late List suggestions; + + final LayerLink layerLink = LayerLink(); + OverlayEntry? entry; + bool isOpen = false; + + @override + void initState() { + super.initState(); + setState(() { + controller.text = widget.selectedItem == null + ? '' + : widget.renderItem(widget.selectedItem!); + options = widget.items; + suggestions = []; + }); + } + + void toggleOverlay() { + isOpen ? hideOverlay() : showOverlay(); + } + + void showOverlay() { + hideOverlay(); + + List items = []; + Divider? divider; + + final overlay = Overlay.of(context)!; + final renderBox = context.findRenderObject() as RenderBox; + + if (widget.selectedItem != null) { + items.add(buildListTile(widget.selectedItem!)); + } + + if (suggestions.isNotEmpty) { + items.addAll(suggestions + .where((item) => item != widget.selectedItem) + .map((item) => buildListTile(item))); + } + + if ((widget.selectedItem != null || suggestions.isNotEmpty) && + (options.length - + suggestions.length - + (widget.selectedItem == null ? 0 : 1) > + 0)) { + divider = const Divider(height: 10); + items.add(divider); + } + + items.addAll(options + .where((item) => + !(widget.selectedItem != null && + widget.renderItem(item) == + widget.renderItem(widget.selectedItem!)) && + !suggestions.contains(item)) + .map((item) => buildListTile(item)) + .toList()); + + final screenHeight = MediaQuery.of(context).size.height; + final neededHeight = + renderBox.size.height * (items.length - 1) + (divider?.height ?? + renderBox.size.height); + final availableHeight = screenHeight - + (renderBox.localToGlobal(Offset.zero).dy + renderBox.size.height); + bool displayAbove = neededHeight > availableHeight && + screenHeight - availableHeight > availableHeight; + final height = min(neededHeight, + max(availableHeight, screenHeight - availableHeight - 55) - 55); + + entry = OverlayEntry( + builder: (context) => Positioned( + width: renderBox.size.width, + height: height, + child: CompositedTransformFollower( + link: layerLink, + targetAnchor: displayAbove ? Alignment.bottomLeft : Alignment.topLeft, + followerAnchor: + displayAbove ? Alignment.bottomLeft : Alignment.topLeft, + offset: Offset(0, renderBox.size.height * (displayAbove ? -1 : 1)), + showWhenUnlinked: false, + child: Material( + elevation: 8, + child: SingleChildScrollView( + child: Column( + children: items, + ), + ), + ), + ), + ), + ); + + overlay.insert(entry!); + isOpen = true; + } + + ListTile buildListTile(T item) { + return ListTile( + onTap: () => handleChanged(item), + selected: item == widget.selectedItem, + title: Row( + children: [ + Expanded( + child: Text(widget.renderItem(item)), + ), + ], + ), + ); + } + + void hideOverlay() { + entry?.remove(); + entry = null; + isOpen = false; + } + + void handleChanged(item) { + widget.onChanged(item); + controller.text = widget.renderItem(item); + hideOverlay(); + } + + void onChangeQuery(String value) { + if (value.trim() == '' || + (widget.selectedItem != null && + value == widget.renderItem(widget.selectedItem!))) { + setState(() { + suggestions = []; + }); + } else { + if (widget.applyQuery == null) { + setState(() { + suggestions = widget.items.where((item) { + String itemString = widget.renderItem(item).toLowerCase(); + String valueLowercase = value.toLowerCase(); + return itemString.contains(valueLowercase); + }).toList(); + }); + } else { + setState(() { + suggestions = widget.applyQuery!(value) + .where((item) => item != widget.selectedItem) + .toList(); + }); + } + showOverlay(); + } + } + + @override + Widget build(BuildContext context) { + return CompositedTransformTarget( + link: layerLink, + child: TextFormField( + onChanged: onChangeQuery, + onTap: toggleOverlay, + controller: controller, + decoration: InputDecoration( + labelText: widget.label, + suffixIcon: IconButton( + onPressed: toggleOverlay, + icon: const Icon(Icons.arrow_drop_down), + ), + ), + ), + ); + } +} diff --git a/lib/components/forms.dart b/lib/components/forms.dart index cb9a507..a9bc89f 100644 --- a/lib/components/forms.dart +++ b/lib/components/forms.dart @@ -65,7 +65,7 @@ class BooleanFormField extends StatefulWidget { class _BooleanFormFieldState extends State { @override Widget build(BuildContext context) { - return FormField(builder: (context) { + return FormField(builder: (state) { return ListTile( onTap: () => widget.onChanged(!widget.value), trailing: Switch( @@ -98,8 +98,7 @@ class DateTimeFormField extends StatefulWidget { : super(key: key); @override - _DateTimeFormFieldState createState() => - _DateTimeFormFieldState(); + _DateTimeFormFieldState createState() => _DateTimeFormFieldState(); } class _DateTimeFormFieldState extends State { @@ -115,7 +114,7 @@ class _DateTimeFormFieldState extends State { final newTime = await showDatePicker( context: context, initialDate: widget.date, - firstDate: widget.minDate ?? DateTime(2000,1,1), + firstDate: widget.minDate ?? DateTime(2000, 1, 1), lastDate: widget.maxDate ?? DateTime.now(), ); widget.onChanged(newTime); @@ -139,8 +138,7 @@ class TimeOfDayFormField extends StatefulWidget { : super(key: key); @override - _TimeOfDayFormFieldState createState() => - _TimeOfDayFormFieldState(); + _TimeOfDayFormFieldState createState() => _TimeOfDayFormFieldState(); } class _TimeOfDayFormFieldState extends State { @@ -163,42 +161,3 @@ class _TimeOfDayFormFieldState extends State { } } -class LabeledDropdownButton extends StatefulWidget { - final String label; - final T? selectedItem; - final List items; - final Widget Function(T item) renderItem; - final void Function(T? value) onChanged; - - const LabeledDropdownButton( - {Key? key, - this.selectedItem, - required this.label, - required this.items, - required this.renderItem, - required this.onChanged}) - : super(key: key); - - @override - _LabeledDropdownButtonState createState() => _LabeledDropdownButtonState(); -} - -class _LabeledDropdownButtonState extends State> { - @override - Widget build(BuildContext context) { - return DropdownButtonFormField( - decoration: InputDecoration( - labelText: widget.label, - ), - value: widget.selectedItem, - onChanged: widget.onChanged, - items: widget.items - .map((item) => DropdownMenuItem( - value: item, - child: widget.renderItem(item), - )) - .toList(), - ); - } -} - diff --git a/lib/main.dart b/lib/main.dart index 1f7e8e6..14128b3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,8 +4,8 @@ import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/screens/log/log.dart'; -import 'package:diameter/screens/log/log_entry.dart'; -import 'package:diameter/screens/log/log_event_detail.dart'; +import 'package:diameter/screens/log/log_entry/log_entry.dart'; +import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/screens/log/log_event_type_detail.dart'; import 'package:diameter/screens/log/log_event_type_list.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart'; diff --git a/lib/models/bolus.dart b/lib/models/bolus.dart index 3147023..afee10d 100644 --- a/lib/models/bolus.dart +++ b/lib/models/bolus.dart @@ -1,6 +1,8 @@ import 'package:diameter/main.dart'; import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/objectbox.g.dart'; +import 'package:diameter/utils/date_time_utils.dart'; +import 'package:flutter/material.dart'; @Entity(uid: 3417770529060202389) class Bolus { @@ -37,4 +39,22 @@ class Bolus { builder.link(Bolus_.bolusProfile, BolusProfile_.id.equals(id)); return builder.build().find(); } + + static Bolus? getRateForTime(DateTime? dateTime) { + if (dateTime != null) { + // ignore: todo + // TODO: check if an event is active that would change the active profile + final bolusProfile = BolusProfile.getActive(); + final time = DateTimeUtils.convertTimeOfDayToDateTime(TimeOfDay.fromDateTime(dateTime)); + if (bolusProfile != null) { + final rates = Bolus.getAllForProfile(bolusProfile.id); + final result = rates.where((rate) => + (time.isAfter(rate.startTime) || + time.isAtSameMomentAs(rate.startTime)) && + time.isBefore(rate.endTime)); + return result.length != 1 ? null : result.single; + } + return null; + } + } } diff --git a/lib/models/bolus_profile.dart b/lib/models/bolus_profile.dart index 4863826..a4e9936 100644 --- a/lib/models/bolus_profile.dart +++ b/lib/models/bolus_profile.dart @@ -34,4 +34,11 @@ class BolusProfile { return element; }).toList()); } + + static BolusProfile? getActive() { + Query query = + box.query(BolusProfile_.active.equals(true)).build(); + final result = query.find(); + return result.length != 1 ? null : result.single; + } } diff --git a/lib/models/log_bolus.dart b/lib/models/log_bolus.dart new file mode 100644 index 0000000..70b1d59 --- /dev/null +++ b/lib/models/log_bolus.dart @@ -0,0 +1,38 @@ +import 'package:diameter/main.dart'; +import 'package:diameter/models/bolus.dart'; +import 'package:diameter/models/log_entry.dart'; +import 'package:diameter/models/log_meal.dart'; +import 'package:diameter/objectbox.g.dart'; + +@Entity(uid: 0) +class LogBolus { + static final Box box = objectBox.store.box(); + + int id; + double units; + double? carbs; + int? delay; + int? mgPerDl; + double? mmolPerL; + bool manuallyAdjusted; + String? notes; + + final logEntry = ToOne(); + final rate = ToOne(); + final meal = ToOne(); + + LogBolus({ + this.id = 0, + this.units = 0, + this.carbs, + this.delay, + this.mgPerDl, + this.mmolPerL, + this.manuallyAdjusted = false, + this.notes, + }); + + static LogBolus? get(int id) => box.get(id); + static void put(LogBolus logBolus) => box.put(logBolus); + static void remove(int id) => box.remove(id); +} diff --git a/lib/models/log_entry.dart b/lib/models/log_entry.dart index b6f58eb..e6a3577 100644 --- a/lib/models/log_entry.dart +++ b/lib/models/log_entry.dart @@ -1,4 +1,5 @@ import 'package:diameter/main.dart'; +import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_event.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/objectbox.g.dart'; @@ -28,6 +29,9 @@ class LogEntry { @Backlink('logEntry') final meals = ToMany(); + @Backlink('logEntry') + final boli = ToMany(); + LogEntry({ this.id = 0, required this.time, @@ -47,7 +51,8 @@ class LogEntry { static Map> getDailyEntryMap() { Map> dateMap = >{}; - QueryBuilder allByDate = box.query()..order(LogEntry_.time, flags: Order.descending); + QueryBuilder allByDate = box.query() + ..order(LogEntry_.time, flags: Order.descending); List entries = allByDate.build().find(); DateTime? date; diff --git a/lib/navigation.dart b/lib/navigation.dart index ff1f876..f682937 100644 --- a/lib/navigation.dart +++ b/lib/navigation.dart @@ -7,11 +7,11 @@ import 'package:diameter/screens/bolus/bolus_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/screens/log/log.dart'; -import 'package:diameter/screens/log/log_entry.dart'; -import 'package:diameter/screens/log/log_event_detail.dart'; +import 'package:diameter/screens/log/log_entry/log_entry.dart'; +import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/screens/log/log_event_type_detail.dart'; import 'package:diameter/screens/log/log_event_type_list.dart'; -import 'package:diameter/screens/log/log_meal_detail.dart'; +import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/meal/meal_category_list.dart'; import 'package:diameter/screens/meal/meal_detail.dart'; diff --git a/lib/screens/basal/basal_profile_list.dart b/lib/screens/basal/basal_profile_list.dart index c2be7a7..02117e4 100644 --- a/lib/screens/basal/basal_profile_list.dart +++ b/lib/screens/basal/basal_profile_list.dart @@ -2,7 +2,6 @@ import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/navigation.dart'; import 'package:flutter/material.dart'; -// import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart'; @@ -24,7 +23,6 @@ class _BasalProfileListScreenState extends State { pickActiveProfileMode = false; _basalProfiles = BasalProfile.getAll(); }); - // _basalProfiles.then((list) => updateBanner(); setState(() { if (message != null) { @@ -94,7 +92,6 @@ class _BasalProfileListScreenState extends State { BasalProfile.setAllInactive; basalProfile.active = true; BasalProfile.put(basalProfile); - // (exception: basalProfile.objectId!).then((_) => refresh(message: '${basalProfile.name} has been set as your active Profile'); } diff --git a/lib/screens/log/active_log_event_list.dart b/lib/screens/log/active_log_event_list.dart index df08e7a..b5da166 100644 --- a/lib/screens/log/active_log_event_list.dart +++ b/lib/screens/log/active_log_event_list.dart @@ -2,20 +2,17 @@ import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_event.dart'; -// import 'package:diameter/models/log_event_type.dart'; -import 'package:diameter/screens/log/log_event_detail.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; -// import 'package:diameter/components/progress_indicator.dart'; class ActiveLogEventListScreen extends StatefulWidget { static const String routeName = '/active-log-events'; - final LogEntry? endLogEntry; + final int endLogEntryId; final Function()? onSetEndTime; const ActiveLogEventListScreen( - {Key? key, this.endLogEntry, this.onSetEndTime}) + {Key? key, this.endLogEntryId = 0, this.onSetEndTime}) : super(key: key); @override @@ -26,7 +23,13 @@ class ActiveLogEventListScreen extends StatefulWidget { class _ActiveLogEventListScreenState extends State { List _activeLogEvents = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _activeLogEvents = LogEvent.getAllOngoing(); }); @@ -47,9 +50,9 @@ class _ActiveLogEventListScreenState extends State { void onStop(LogEvent event) async { event.endTime = DateTime.now(); event.endLogEntry.target = - widget.endLogEntry ?? LogEntry(time: DateTime.now()); + LogEntry.get(widget.endLogEntryId) ?? LogEntry(time: DateTime.now()); LogEvent.put(event); - refresh(); + reload(); if (widget.onSetEndTime != null) { widget.onSetEndTime!(); } @@ -69,7 +72,7 @@ class _ActiveLogEventListScreenState extends State { void onDelete(LogEvent event) { LogEvent.remove(event.id); - refresh(message: 'Event deleted'); + reload(message: 'Event deleted'); } void handleDeleteAction(LogEvent event) async { @@ -84,11 +87,6 @@ class _ActiveLogEventListScreenState extends State { } } - @override - void initState() { - super.initState(); - refresh(); - } @override Widget build(BuildContext context) { @@ -101,20 +99,7 @@ class _ActiveLogEventListScreenState extends State { primary: false, automaticallyImplyLeading: false, actions: [ - IconButton( - icon: const Icon(Icons.add), - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => LogEventDetailScreen( - logEntry: widget.endLogEntry!, - ), - ), - ).then((message) => refresh(message: message)); - }, - ), - IconButton(icon: const Icon(Icons.refresh), onPressed: refresh), + IconButton(icon: const Icon(Icons.refresh), onPressed: reload), ], ), _activeLogEvents.isNotEmpty ? diff --git a/lib/screens/log/log.dart b/lib/screens/log/log.dart index e587ddb..25176c2 100644 --- a/lib/screens/log/log.dart +++ b/lib/screens/log/log.dart @@ -2,7 +2,7 @@ import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/screens/log/log_entry.dart'; +import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; @@ -17,7 +17,13 @@ class LogScreen extends StatefulWidget { class _LogScreenState extends State { late Map> _logEntryDailyMap; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _logEntryDailyMap = LogEntry.getDailyEntryMap(); }); @@ -36,7 +42,7 @@ class _LogScreenState extends State { void onDelete(LogEntry logEntry) { LogEntry.remove(logEntry.id); - refresh(message: 'Log Entry deleted'); + reload(message: 'Log Entry deleted'); } void handleDeleteAction(LogEntry logEntry) async { @@ -51,12 +57,6 @@ class _LogScreenState extends State { } } - @override - void initState() { - super.initState(); - refresh(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -64,7 +64,7 @@ class _LogScreenState extends State { title: const Text('Log Entries'), actions: [ IconButton( - onPressed: refresh, + onPressed: reload, icon: const Icon(Icons.refresh) ), ], @@ -101,7 +101,7 @@ class _LogScreenState extends State { LogEntryScreen( id: logEntry.id), ), - ).then((message) => refresh( + ).then((message) => reload( message: message)); }, title: Text( @@ -150,7 +150,7 @@ class _LogScreenState extends State { MaterialPageRoute( builder: (context) => const LogEntryScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/screens/log/log_entry/log_bolus_detail.dart b/lib/screens/log/log_entry/log_bolus_detail.dart new file mode 100644 index 0000000..cacdd9c --- /dev/null +++ b/lib/screens/log/log_entry/log_bolus_detail.dart @@ -0,0 +1,226 @@ +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/navigation.dart'; +import 'package:diameter/settings.dart'; +import 'package:flutter/material.dart'; + +class LogBolusDetailScreen extends StatefulWidget { + static const String routeName = '/log-bolus'; + + final int logEntryId; + final int id; + + const LogBolusDetailScreen({Key? key, this.logEntryId = 0, this.id = 0}) + : super(key: key); + + @override + _LogBolusDetailScreenState createState() => _LogBolusDetailScreenState(); +} + +class _LogBolusDetailScreenState extends State { + LogEntry? _logEntry; + LogBolus? _logBolus; + + bool _isNew = true; + bool _isSaving = false; + + final GlobalKey _logBolusForm = GlobalKey(); + + final _unitsController = TextEditingController(text: ''); + final _carbsController = TextEditingController(text: ''); + final _mgPerDlController = TextEditingController(text: ''); + final _mmolPerLController = TextEditingController(text: ''); + final _delayController = TextEditingController(text: ''); + final _notesController = TextEditingController(text: ''); + + bool _manuallyAdjusted = false; + LogMeal? _meal; + Bolus? _rate; + + List _logMeals = []; + + @override + void initState() { + super.initState(); + reload(); + + _logEntry = LogEntry.get(widget.logEntryId); + _logMeals = _logEntry?.meals ?? []; + + if (widget.id != 0) { + _unitsController.text = _logBolus!.units.toString(); + _carbsController.text = (_logBolus!.carbs ?? '').toString(); + _mgPerDlController.text = (_logBolus!.mgPerDl ?? '').toString(); + _mmolPerLController.text = (_logBolus!.mmolPerL ?? '').toString(); + _delayController.text = (_logBolus!.delay ?? '').toString(); + _notesController.text = _logBolus!.notes ?? ''; + _manuallyAdjusted = _logBolus!.manuallyAdjusted; + _meal = _logBolus!.meal.target; + _rate = _logBolus!.rate.target ?? Bolus.getRateForTime(_logEntry?.time); + } + } + + void reload() { + if (widget.id != 0) { + setState(() { + _logBolus = LogBolus.get(widget.id); + }); + } + _isNew = _logBolus == null; + } + + Future onSelectMeal(LogMeal meal) async { + setState(() { + _meal = meal; + if (meal.carbsPerPortion != null) { + _carbsController.text = meal.carbsPerPortion.toString(); + + if (_rate != null) { + _unitsController.text = + (meal.carbsPerPortion ?? 0 / (_rate!.carbs / _rate!.units)) + .toString(); + } + } + }); + } + + void handleSaveAction() async { + setState(() { + _isSaving = true; + }); + if (_logBolusForm.currentState!.validate()) { + LogBolus logBolus = LogBolus( + id: widget.id, + units: double.tryParse(_unitsController.text) ?? 0, + carbs: double.tryParse(_carbsController.text), + mgPerDl: int.tryParse(_mgPerDlController.text), + mmolPerL: double.tryParse(_mmolPerLController.text), + delay: int.tryParse(_delayController.text), + manuallyAdjusted: _manuallyAdjusted, + notes: _notesController.text, + ); + logBolus.logEntry.target = _logEntry; + logBolus.meal.target = _meal; + logBolus.rate.target = _rate; + LogBolus.put(logBolus); + Navigator.pop(context, '${_isNew ? 'New' : ''} Bolus Saved'); + } + setState(() { + _isSaving = false; + }); + } + + void handleCancelAction() { + if (showConfirmationDialogOnCancel && + ((_isNew && + (_unitsController.text != '' || + _carbsController.text != '' || + _mgPerDlController.text != '' || + _mmolPerLController.text != '' || + _delayController.text != '' || + _manuallyAdjusted || + _notesController.text != '')) || + (!_isNew && + (double.tryParse(_unitsController.text) != _logBolus!.units || + double.tryParse(_carbsController.text) != _logBolus!.carbs || + int.tryParse(_mgPerDlController.text) != _logBolus!.mgPerDl || + double.tryParse(_mmolPerLController.text) != _logBolus!.mmolPerL || + int.tryParse(_delayController.text) != _logBolus!.delay || + _manuallyAdjusted != _logBolus!.manuallyAdjusted || + _notesController.text != (_logBolus!.notes ?? ''))))) { + Dialogs.showCancelConfirmationDialog( + context: context, + isNew: _isNew, + onSave: handleSaveAction, + ); + } else { + Navigator.pop(context); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(_isNew ? 'New Bolus' : 'Edit Bolus'), + ), + drawer: const Navigation(currentLocation: LogBolusDetailScreen.routeName), + body: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FormWrapper( + formState: _logBolusForm, + fields: [ + TextFormField( + decoration: const InputDecoration( + labelText: 'Bolus Units', + suffixText: ' U', + ), + controller: _unitsController, + keyboardType: + const TextInputType.numberWithOptions(decimal: true), + ), + AutoCompleteDropdownButton( + selectedItem: _meal, + label: 'Meal', + items: _logMeals, + renderItem: (item) => item.value, + onChanged: (value) { + if (value != null) { + onSelectMeal(value); + } + }, + ), + Expanded( + child: TextFormField( + decoration: InputDecoration( + labelText: 'Carbs', + suffixText: + nutritionMeasurement == NutritionMeasurement.grams + ? 'g' + : nutritionMeasurement == + NutritionMeasurement.ounces + ? 'oz' + : '', + ), + controller: _carbsController, + keyboardType: const TextInputType.numberWithOptions( + decimal: true), + ), + ), + TextFormField( + decoration: const InputDecoration( + labelText: 'Delayed Bolus Duration', + suffixText: ' min', + ), + controller: _delayController, + keyboardType: const TextInputType.numberWithOptions(), + ), + TextFormField( + controller: _notesController, + decoration: const InputDecoration( + labelText: 'Notes', + alignLabelWithHint: true, + ), + keyboardType: TextInputType.multiline, + ), + ], + ), + ], + ), + ), + bottomNavigationBar: DetailBottomRow( + onCancel: handleCancelAction, + onSave: _isSaving ? null : handleSaveAction, + ), + ); + } +} diff --git a/lib/screens/log/log_entry/log_bolus_list.dart b/lib/screens/log/log_entry/log_bolus_list.dart new file mode 100644 index 0000000..20a3322 --- /dev/null +++ b/lib/screens/log/log_entry/log_bolus_list.dart @@ -0,0 +1,127 @@ +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/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 { + final LogEntry logEntry; + final List logBoli; + final Function() reload; + + const LogBolusListScreen( + {Key? key, + required this.logEntry, + this.logBoli = const [], + required this.reload}) + : super(key: key); + + @override + _LogBolusListScreenState createState() => _LogBolusListScreenState(); +} + +class _LogBolusListScreenState extends State { + void reload({String? message}) { + widget.reload(); + + setState(() { + if (message != null) { + var snackBar = SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + ); + ScaffoldMessenger.of(context) + ..removeCurrentSnackBar() + ..showSnackBar(snackBar); + } + }); + } + + void handleEditAction(LogBolus logBolus) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LogBolusDetailScreen( + logEntryId: widget.logEntry.id, + id: logBolus.id, + ), + ), + ).then((message) => reload(message: message)); + } + + void onDelete(LogBolus logBolus) { + LogBolus.remove(logBolus.id); + reload(message: 'Meal deleted'); + } + + void handleDeleteAction(LogBolus logBolus) async { + if (showConfirmationDialogOnDelete) { + Dialogs.showConfirmationDialog( + context: context, + onConfirm: () => onDelete(logBolus), + message: 'Are you sure you want to delete this Bolus?', + ); + } else { + onDelete(logBolus); + } + } + + void handleEditMealAction(int mealId) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LogMealDetailScreen( + logEntryId: widget.logEntry.id, + id: mealId, + ), + ), + ).then((message) => reload(message: message)); + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: widget.logEntry.boli.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + itemCount: widget.logEntry.boli.length, + itemBuilder: (context, index) { + final bolus = widget.logEntry.boli[index]; + return ListTile( + onTap: () => handleEditAction(bolus), + title: + 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'}'), + trailing: Row( + children: [ + bolus.meal.target != null ? IconButton( + icon: const Icon(Icons.restaurant), + onPressed: () => handleEditMealAction(bolus.meal.targetId), + ) : Container(), + const SizedBox(width: 24), + IconButton( + icon: const Icon( + Icons.delete, + color: Colors.blue, + ), + onPressed: () => handleDeleteAction(bolus), + ), + ], + ), + ); + }, + ) + : const Center( + child: Text( + 'You have not added any Boli to this Log Entry yet!'), + ), + ), + ], + ); + } +} diff --git a/lib/screens/log/log_entry.dart b/lib/screens/log/log_entry/log_entry.dart similarity index 90% rename from lib/screens/log/log_entry.dart rename to lib/screens/log/log_entry/log_entry.dart index ff29b4b..d29c624 100644 --- a/lib/screens/log/log_entry.dart +++ b/lib/screens/log/log_entry/log_entry.dart @@ -2,12 +2,17 @@ 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_event.dart'; +import 'package:diameter/models/log_meal.dart'; import 'package:diameter/navigation.dart'; -import 'package:diameter/screens/log/log_event_detail.dart'; -import 'package:diameter/screens/log/log_event_list.dart'; -import 'package:diameter/screens/log/log_meal_detail.dart'; -import 'package:diameter/screens/log/log_meal_list.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_event_list.dart'; +import 'package:diameter/screens/log/log_entry/log_event_detail.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'; @@ -25,6 +30,9 @@ class LogEntryScreen extends StatefulWidget { class _LogEntryScreenState extends State { LogEntry? _logEntry; + List _logMeals = []; + List _logEvents = []; + List _logBoli = []; bool _isNew = true; bool _isSaving = false; @@ -42,6 +50,7 @@ class _LogEntryScreenState extends State { final _notesController = TextEditingController(text: ''); late FloatingActionButton addMealButton; + late FloatingActionButton addBolusButton; late FloatingActionButton addEventButton; late IconButton refreshButton; late IconButton closeButton; @@ -62,6 +71,11 @@ class _LogEntryScreenState extends State { child: const Icon(Icons.add), ); + addBolusButton = FloatingActionButton( + onPressed: handleAddNewBolus, + child: const Icon(Icons.add), + ); + addEventButton = FloatingActionButton( onPressed: handleAddNewEvent, child: const Icon(Icons.add), @@ -105,6 +119,9 @@ class _LogEntryScreenState extends State { if (widget.id != 0) { setState(() { _logEntry = LogEntry.get(widget.id); + _logMeals = _logEntry?.meals ?? []; + _logEvents = _logEntry?.events ?? []; + _logBoli = _logEntry?.boli ?? []; }); _isNew = _logEntry == null; } @@ -216,7 +233,18 @@ class _LogEntryScreenState extends State { context, MaterialPageRoute( builder: (context) { - return LogMealDetailScreen(logEntry: _logEntry!); + return LogMealDetailScreen(logEntryId: _logEntry!.id); + }, + ), + ).then((message) => reload(message: message)); + } + + void handleAddNewBolus() async { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) { + return LogBolusDetailScreen(logEntryId: _logEntry!.id); }, ), ).then((message) => reload(message: message)); @@ -227,7 +255,7 @@ class _LogEntryScreenState extends State { context, MaterialPageRoute( builder: (context) { - return LogEventDetailScreen(logEntry: _logEntry!); + return LogEventDetailScreen(logEntryId: _logEntry!.id); }, ), ).then((message) => reload(message: message)); @@ -243,6 +271,11 @@ class _LogEntryScreenState extends State { bottomNav = null; break; case 2: + actionButton = addBolusButton; + appBarActions = [refreshButton, closeButton]; + bottomNav = null; + break; + case 3: actionButton = addEventButton; appBarActions = [refreshButton, closeButton]; bottomNav = null; @@ -259,7 +292,7 @@ class _LogEntryScreenState extends State { @override Widget build(BuildContext context) { return DefaultTabController( - length: _isNew ? 1 : 3, + length: _isNew ? 1 : 4, child: Builder(builder: (BuildContext context) { final TabController tabController = DefaultTabController.of(context)!; tabController.addListener(() { @@ -452,8 +485,12 @@ class _LogEntryScreenState extends State { ]; if (!_isNew) { - tabs.add(LogMealListScreen(logEntry: _logEntry!, reload: reload)); - tabs.add(LogEventListScreen(logEntry: _logEntry!, reload: reload)); + tabs.add(LogMealListScreen( + logEntry: _logEntry!, logMeals: _logMeals, reload: reload)); + tabs.add(LogBolusListScreen( + logEntry: _logEntry!, logBoli: _logBoli, reload: reload)); + tabs.add(LogEventListScreen( + logEntry: _logEntry!, logEvents: _logEvents, reload: reload)); } return Scaffold( @@ -465,6 +502,7 @@ class _LogEntryScreenState extends State { tabs: [ Tab(text: 'GENERAL'), Tab(text: 'MEALS'), + Tab(text: 'BOLI'), Tab(text: 'EVENTS'), ], ), diff --git a/lib/screens/log/log_event_detail.dart b/lib/screens/log/log_entry/log_event_detail.dart similarity index 66% rename from lib/screens/log/log_event_detail.dart rename to lib/screens/log/log_entry/log_event_detail.dart index aea8da1..d151eb4 100644 --- a/lib/screens/log/log_event_detail.dart +++ b/lib/screens/log/log_entry/log_event_detail.dart @@ -1,5 +1,6 @@ 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/log_entry.dart'; @@ -10,11 +11,13 @@ import 'package:flutter/material.dart'; class LogEventDetailScreen extends StatefulWidget { static const String routeName = '/log-event'; - final LogEntry? logEntry; - final LogEntry? endLogEntry; - final LogEvent? logEvent; + + final int logEntryId; + final int endLogEntryId; + final int id; + const LogEventDetailScreen( - {Key? key, this.logEntry, this.endLogEntry, this.logEvent}) + {Key? key, this.logEntryId = 0, this.endLogEntryId = 0, this.id = 0}) : super(key: key); @override @@ -22,59 +25,55 @@ class LogEventDetailScreen extends StatefulWidget { } class _LogEventDetailScreenState extends State { + LogEvent? _logEvent; + bool _isNew = true; + bool _isSaving = false; + final GlobalKey _logEventForm = GlobalKey(); - final _notesController = TextEditingController(text: ''); LogEventType? _eventType; bool _hasEndTime = false; + final _notesController = TextEditingController(text: ''); List _logEventTypes = []; - bool _isSaving = false; - @override void initState() { super.initState(); + reload(); - if (widget.logEvent != null) { - _notesController.text = widget.logEvent!.notes ?? ''; - _eventType = widget.logEvent!.eventType.target; - _hasEndTime = widget.logEvent!.hasEndTime; + if (_logEvent != null) { + _notesController.text = _logEvent!.notes ?? ''; + _eventType = _logEvent!.eventType.target; + _hasEndTime = _logEvent!.hasEndTime; } _logEventTypes = LogEventType.getAll(); } + void reload() { + if (widget.id != 0) { + setState(() { + _logEvent = LogEvent.get(widget.id); + }); + } + _isNew = _logEvent == null; + } + void handleSaveAction() async { setState(() { _isSaving = true; }); if (_logEventForm.currentState!.validate()) { - bool isNew = widget.logEvent == null; - // isNew - // ? await LogEvent.save( - // logEntry: widget.logEntry!.objectId!, - // eventType: _eventType!, - // time: widget.logEntry!.time, - // hasEndTime: _hasEndTime, - // notes: _notesController.text, - // ) - // : await LogEvent.update( - // widget.logEvent!.objectId!, - // eventType: _eventType!, - // time: widget.logEntry!.time, - // hasEndTime: _hasEndTime, - // notes: _notesController.text, - // ); LogEvent event = LogEvent( - id: widget.logEvent?.id ?? 0, - time: widget.logEntry!.time, + id: widget.id, + time: LogEntry.get(widget.logEntryId)!.time, hasEndTime: _hasEndTime, notes: _notesController.text, ); event.eventType.target = _eventType; LogEvent.put(event); - Navigator.pop(context, '${isNew ? 'New' : ''} Event Saved'); + Navigator.pop(context, '${_isNew ? 'New' : ''} Event Saved'); } setState(() { _isSaving = false; @@ -82,19 +81,18 @@ class _LogEventDetailScreenState extends State { } void handleCancelAction() { - bool isNew = widget.logEvent == null; if (showConfirmationDialogOnCancel && - ((isNew && + ((_isNew && (_notesController.text != '' || _eventType != null || _hasEndTime)) || - (!isNew && - (_notesController.text != (widget.logEvent!.notes ?? '') || - _eventType != widget.logEvent!.eventType.target || - _hasEndTime != widget.logEvent!.hasEndTime)))) { + (!_isNew && + (_notesController.text != (_logEvent!.notes ?? '') || + _eventType != _logEvent!.eventType.target || + _hasEndTime != _logEvent!.hasEndTime)))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -104,10 +102,9 @@ class _LogEventDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.logEvent == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Event' : 'Edit Event'), + title: Text(_isNew ? 'New Event' : 'Edit Event'), ), drawer: const Navigation(currentLocation: LogEventDetailScreen.routeName), body: SingleChildScrollView( @@ -117,11 +114,11 @@ class _LogEventDetailScreenState extends State { FormWrapper( formState: _logEventForm, fields: [ - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _eventType, label: 'Event Type', items: _logEventTypes, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _eventType = value; @@ -147,7 +144,6 @@ class _LogEventDetailScreenState extends State { ), ], ), - // ActiveLogEventListScreen(onSetEndTime: onSetEndTime) ], ), ), diff --git a/lib/screens/log/log_event_list.dart b/lib/screens/log/log_entry/log_event_list.dart similarity index 77% rename from lib/screens/log/log_event_list.dart rename to lib/screens/log/log_entry/log_event_list.dart index 46036ef..bc70bd2 100644 --- a/lib/screens/log/log_event_list.dart +++ b/lib/screens/log/log_entry/log_event_list.dart @@ -2,15 +2,17 @@ import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_event.dart'; -import 'package:diameter/screens/log/log_event_detail.dart'; +import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; class LogEventListScreen extends StatefulWidget { final LogEntry logEntry; + final List logEvents; final Function() reload; - const LogEventListScreen({Key? key, required this.logEntry, required this.reload}) + const LogEventListScreen( + {Key? key, required this.logEntry, this.logEvents = const [], required this.reload}) : super(key: key); @override @@ -39,8 +41,8 @@ class _LogEventListScreenState extends State { context, MaterialPageRoute( builder: (context) => LogEventDetailScreen( - endLogEntry: widget.logEntry, - logEvent: event, + logEntryId: widget.logEntry.id, + id: event.id, ), ), ).then((message) => reload(message: message)); @@ -66,15 +68,18 @@ class _LogEventListScreenState extends State { @override Widget build(BuildContext context) { return Column( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Expanded( - child: (widget.logEntry.events.isNotEmpty || widget.logEntry.endedEvents.isNotEmpty) + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: (widget.logEntry.events.isNotEmpty || + widget.logEntry.endedEvents.isNotEmpty) ? ListView.builder( shrinkWrap: true, - itemCount: widget.logEntry.events.length + widget.logEntry.endedEvents.length, + itemCount: widget.logEntry.events.length + + widget.logEntry.endedEvents.length, itemBuilder: (context, index) { - final event = (widget.logEntry.events + widget.logEntry.endedEvents)[index]; + final event = (widget.logEntry.events + + widget.logEntry.endedEvents)[index]; return ListTile( onTap: () { handleEditAction(event); @@ -103,10 +108,11 @@ class _LogEventListScreenState extends State { ); }) : const Center( - child: Text('You have not added any Events to this Log Entry yet!'), - ), - ), - ], + child: Text( + 'You have not added any Events to this Log Entry yet!'), + ), + ), + ], ); } } diff --git a/lib/screens/log/log_meal_detail.dart b/lib/screens/log/log_entry/log_meal_detail.dart similarity index 69% rename from lib/screens/log/log_meal_detail.dart rename to lib/screens/log/log_entry/log_meal_detail.dart index b188ad8..ca62274 100644 --- a/lib/screens/log/log_meal_detail.dart +++ b/lib/screens/log/log_entry/log_meal_detail.dart @@ -1,9 +1,9 @@ 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_entry.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/models/meal_category.dart'; @@ -16,9 +16,11 @@ import 'package:flutter/material.dart'; class LogMealDetailScreen extends StatefulWidget { static const String routeName = '/log-meal'; - final LogEntry logEntry; - final LogMeal? logMeal; - const LogMealDetailScreen({Key? key, required this.logEntry, this.logMeal}) + + final int logEntryId; + final int id; + + const LogMealDetailScreen({Key? key, this.logEntryId = 0, this.id = 0}) : super(key: key); @override @@ -26,6 +28,10 @@ class LogMealDetailScreen extends StatefulWidget { } class _LogMealDetailScreenState extends State { + LogMeal? _logMeal; + bool _isNew = true; + bool _isSaving = false; + final GlobalKey _logMealForm = GlobalKey(); final _valueController = TextEditingController(text: ''); @@ -36,6 +42,7 @@ class _LogMealDetailScreenState extends State { final _delayedBolusRateController = TextEditingController(text: ''); final _delayedBolusDurationController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); + Meal? _meal; MealSource? _mealSource; MealCategory? _mealCategory; @@ -50,11 +57,10 @@ class _LogMealDetailScreenState extends State { List _portionSizeAccuracies = []; List _carbsRatioAccuracies = []; - bool _isSaving = false; - @override void initState() { super.initState(); + reload(); _portionSizeAccuracies = Accuracy.getAllForPortionSize(); _carbsRatioAccuracies = Accuracy.getAllForCarbsRatio(); @@ -63,35 +69,30 @@ class _LogMealDetailScreenState extends State { _mealPortionTypes = MealPortionType.getAll(); _mealSources = MealSource.getAll(); - if (widget.logMeal != null) { - _valueController.text = widget.logMeal!.value; - _carbsRatioController.text = - (widget.logMeal!.carbsRatio ?? '').toString(); - _portionSizeController.text = - (widget.logMeal!.portionSize ?? '').toString(); + if (widget.id != 0) { + _valueController.text = _logMeal!.value; + _carbsRatioController.text = (_logMeal!.carbsRatio ?? '').toString(); + _portionSizeController.text = (_logMeal!.portionSize ?? '').toString(); _carbsPerPortionController.text = - (widget.logMeal!.carbsPerPortion ?? '').toString(); - _bolusController.text = (widget.logMeal!.bolus ?? '').toString(); + (_logMeal!.carbsPerPortion ?? '').toString(); + _bolusController.text = (_logMeal!.bolus ?? '').toString(); _delayedBolusRateController.text = - (widget.logMeal!.delayedBolusRate ?? '').toString(); + (_logMeal!.delayedBolusRate ?? '').toString(); _delayedBolusDurationController.text = - (widget.logMeal!.delayedBolusDuration ?? '').toString(); - _notesController.text = widget.logMeal!.notes ?? ''; - - // _meal = widget.logMeal!.meal; - // _source = widget.logMeal!.source; - // _category = widget.logMeal!.category; - // _portionType = widget.logMeal!.portionType; - // _portionSizeAccuracy = _portionSizeAccuracies.firstWhere((element) => - // element.id == - // int.tryParse(widget.logMeal!.portionSizeAccuracy ?? '')); - // _carbsRatioAccuracy = _carbsRatioAccuracies.firstWhere((element) => - // element.id == int.tryParse(widget.logMeal!.carbsRatioAccuracy ?? '')); - // _portionSizeAccuracy = widget.meal!.portionSizeAccuracy; - // _carbsRatioAccuracy = widget.meal!.carbsRatioAccuracy; + (_logMeal!.delayedBolusDuration ?? '').toString(); + _notesController.text = _logMeal!.notes ?? ''; } } + void reload() { + if (widget.id != 0) { + setState(() { + _logMeal = LogMeal.get(widget.id); + }); + } + _isNew = _logMeal == null; + } + Future onSelectMeal(Meal meal) async { setState(() { _meal = meal; @@ -135,52 +136,8 @@ class _LogMealDetailScreenState extends State { _isSaving = true; }); if (_logMealForm.currentState!.validate()) { - bool isNew = widget.logMeal == null; - // isNew - // ? await LogMeal.save( - // logEntry: widget.logEntry.objectId!, - // meal: _meal, - // value: _valueController.text, - // source: _mealSource, - // category: _category, - // portionType: _portionType, - // carbsRatio: double.tryParse(_carbsRatioController.text), - // portionSize: double.tryParse(_portionSizeController.text), - // carbsPerPortion: double.tryParse(_carbsPerPortionController.text), - // // portionSizeAccuracy: _portionSizeAccuracy, - // // carbsRatioAccuracy: _carbsRatioAccuracy, - // portionSizeAccuracy: _portionSizeAccuracy?.id.toString(), - // carbsRatioAccuracy: _carbsRatioAccuracy?.id.toString(), - // bolus: double.tryParse(_bolusController.text), - // delayedBolusDuration: - // int.tryParse(_delayedBolusDurationController.text), - // delayedBolusRate: - // double.tryParse(_delayedBolusRateController.text), - // notes: _notesController.text, - // ) - // : await LogMeal.update( - // widget.logMeal!.objectId!, - // meal: _meal, - // value: _valueController.text, - // source: _mealSource, - // category: _category, - // portionType: _portionType, - // carbsRatio: double.tryParse(_carbsRatioController.text), - // portionSize: double.tryParse(_portionSizeController.text), - // carbsPerPortion: double.tryParse(_carbsPerPortionController.text), - // // portionSizeAccuracy: _portionSizeAccuracy, - // // carbsRatioAccuracy: _carbsRatioAccuracy, - // portionSizeAccuracy: _portionSizeAccuracy?.id.toString(), - // carbsRatioAccuracy: _carbsRatioAccuracy?.id.toString(), - // bolus: double.tryParse(_bolusController.text), - // delayedBolusDuration: - // int.tryParse(_delayedBolusDurationController.text), - // delayedBolusRate: - // double.tryParse(_delayedBolusRateController.text), - // notes: _notesController.text, - // ); LogMeal logMeal = LogMeal( - id: widget.logMeal?.id ?? 0, + id: widget.id, value: _valueController.text, carbsRatio: double.tryParse(_carbsRatioController.text), portionSize: double.tryParse(_portionSizeController.text), @@ -197,9 +154,9 @@ class _LogMealDetailScreenState extends State { logMeal.mealPortionType.target = _mealPortionType; logMeal.portionSizeAccuracy.target = _portionSizeAccuracy; logMeal.carbsRatioAccuracy.target = _carbsRatioAccuracy; - + LogMeal.put(logMeal); - Navigator.pop(context, '${isNew ? 'New' : ''} Meal Saved'); + Navigator.pop(context, '${_isNew ? 'New' : ''} Meal Saved'); } setState(() { _isSaving = false; @@ -207,9 +164,8 @@ class _LogMealDetailScreenState extends State { } void handleCancelAction() { - bool isNew = widget.logMeal == null; if (showConfirmationDialogOnCancel && - ((isNew && + ((_isNew && (_valueController.text != '' || _meal != null || _mealSource != null || @@ -225,35 +181,32 @@ class _LogMealDetailScreenState extends State { null || double.tryParse(_delayedBolusRateController.text) != null || _notesController.text != '')) || - (!isNew && - (_valueController.text != widget.logMeal!.value || - _meal != widget.logMeal!.meal.target || - _mealSource != widget.logMeal!.mealSource.target || - _mealCategory != widget.logMeal!.mealCategory.target || - _mealPortionType != widget.logMeal!.mealPortionType.target || + (!_isNew && + (_valueController.text != _logMeal!.value || + _meal != _logMeal!.meal.target || + _mealSource != _logMeal!.mealSource.target || + _mealCategory != _logMeal!.mealCategory.target || + _mealPortionType != _logMeal!.mealPortionType.target || double.tryParse(_carbsRatioController.text) != - widget.logMeal!.carbsRatio || + _logMeal!.carbsRatio || double.tryParse(_portionSizeController.text) != - widget.logMeal!.portionSize || + _logMeal!.portionSize || double.tryParse(_carbsPerPortionController.text) != - widget.logMeal!.carbsPerPortion || - // _carbsRatioAccuracy != widget.logMeal!.carbsRatioAccuracy || - // _portionSizeAccuracy != - // widget.logMeal!.portionSizeAccuracy || + _logMeal!.carbsPerPortion || _carbsRatioAccuracy != - widget.logMeal!.carbsRatioAccuracy.target || + _logMeal!.carbsRatioAccuracy.target || _portionSizeAccuracy != - widget.logMeal!.portionSizeAccuracy.target || + _logMeal!.portionSizeAccuracy.target || double.tryParse(_bolusController.text) != - widget.logMeal!.bolus || + _logMeal!.bolus || int.tryParse(_delayedBolusDurationController.text) != - widget.logMeal!.delayedBolusDuration || + _logMeal!.delayedBolusDuration || double.tryParse(_delayedBolusRateController.text) != - widget.logMeal!.delayedBolusRate || - _notesController.text != (widget.logMeal!.notes ?? ''))))) { + _logMeal!.delayedBolusRate || + _notesController.text != (_logMeal!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -305,10 +258,9 @@ class _LogMealDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.logMeal == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Meal' : widget.logMeal!.value), + title: Text(_isNew ? 'New Meal' : _logMeal!.value), ), drawer: const Navigation(currentLocation: LogMealDetailScreen.routeName), body: SingleChildScrollView( @@ -330,48 +282,44 @@ class _LogMealDetailScreenState extends State { return null; }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _meal, label: 'Meal', items: _meals, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { if (value != null) { onSelectMeal(value); } }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealSource, label: 'Meal Source', items: _mealSources, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _mealSource = value; }); }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealCategory, label: 'Meal Category', items: _mealCategories, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _mealCategory = value; }); }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealPortionType, label: 'Meal Portion Type', items: _mealPortionTypes, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _mealPortionType = value; @@ -434,30 +382,18 @@ class _LogMealDetailScreenState extends State { ), ], ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _portionSizeAccuracy, label: 'Portion Size Accuracy', items: _portionSizeAccuracies, // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _portionSizeAccuracy = value; }); }, ), - // StyledFutureDropdownButton( - // selectedItem: _portionSizeAccuracy, - // label: 'Portion Size Accuracy', - // items: _portionSizeAccuracies, - // getItemValue: (item) => item.objectId, - // renderItem: (item) => Text(item.value), - // onChanged: (value) { - // setState(() { - // _portionSizeAccuracy = value; - // }); - // }, - // ), Row( children: [ Expanded( @@ -488,30 +424,17 @@ class _LogMealDetailScreenState extends State { ), ], ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _carbsRatioAccuracy = value; }); }, ), - // StyledFutureDropdownButton( - // selectedItem: _carbsRatioAccuracy, - // label: 'Carbs Ratio Accuracy', - // items: _carbsRatioAccuracies, - // getItemValue: (item) => item.objectId, - // renderItem: (item) => Text(item.value), - // onChanged: (value) { - // setState(() { - // _carbsRatioAccuracy = value; - // }); - // }, - // ), TextFormField( decoration: const InputDecoration( labelText: 'Bolus Units', diff --git a/lib/screens/log/log_entry/log_meal_list.dart b/lib/screens/log/log_entry/log_meal_list.dart new file mode 100644 index 0000000..4ba0651 --- /dev/null +++ b/lib/screens/log/log_entry/log_meal_list.dart @@ -0,0 +1,110 @@ +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/screens/log/log_entry/log_meal_detail.dart'; +import 'package:flutter/material.dart'; + +class LogMealListScreen extends StatefulWidget { + final LogEntry logEntry; + final List logMeals; + final Function() reload; + + const LogMealListScreen( + {Key? key, required this.logEntry, this.logMeals = const [], required this.reload}) + : super(key: key); + + @override + _LogMealListScreenState createState() => _LogMealListScreenState(); +} + +class _LogMealListScreenState extends State { + void reload({String? message}) { + widget.reload(); + + setState(() { + if (message != null) { + var snackBar = SnackBar( + content: Text(message), + duration: const Duration(seconds: 2), + ); + ScaffoldMessenger.of(context) + ..removeCurrentSnackBar() + ..showSnackBar(snackBar); + } + }); + } + + void handleEditAction(LogMeal meal) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => LogMealDetailScreen( + logEntryId: widget.logEntry.id, + id: meal.id, + ), + ), + ).then((message) => reload(message: message)); + } + + void onDelete(LogMeal logMeal) { + LogMeal.remove(logMeal.id); + reload(message: 'Meal deleted'); + } + + void handleDeleteAction(LogMeal meal) async { + if (showConfirmationDialogOnDelete) { + Dialogs.showConfirmationDialog( + context: context, + onConfirm: () => onDelete(meal), + message: 'Are you sure you want to delete this Meal?', + ); + } else { + onDelete(meal); + } + } + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Expanded( + child: widget.logEntry.meals.isNotEmpty + ? ListView.builder( + shrinkWrap: true, + itemCount: widget.logEntry.meals.length, + itemBuilder: (context, index) { + final meal = widget.logEntry.meals[index]; + return ListTile( + onTap: () => handleEditAction(meal), + title: Row( + children: [ + Expanded(child: Text(meal.value)), + Expanded( + child: Text(meal.carbsPerPortion != null + ? '${meal.carbsPerPortion} g carbs' + : '')), + Expanded( + child: Text( + meal.bolus != null ? '${meal.bolus} U' : '')) + ], + ), + trailing: IconButton( + icon: const Icon( + Icons.delete, + color: Colors.blue, + ), + onPressed: () => handleDeleteAction(meal), + ), + ); + }, + ) + : const Center( + child: Text( + 'You have not added any Meals to this Log Entry yet!'), + ), + ), + ], + ); + } +} diff --git a/lib/screens/log/log_event_type_detail.dart b/lib/screens/log/log_event_type_detail.dart index bd80755..d4de5e6 100644 --- a/lib/screens/log/log_event_type_detail.dart +++ b/lib/screens/log/log_event_type_detail.dart @@ -8,9 +8,8 @@ import 'package:flutter/material.dart'; class LogEventTypeDetailScreen extends StatefulWidget { static const String routeName = '/log-event-type'; - final LogEventType? logEventType; - const LogEventTypeDetailScreen({Key? key, this.logEventType}) - : super(key: key); + final int id; + const LogEventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _LogEventTypeDetailScreenState createState() => @@ -18,35 +17,49 @@ class LogEventTypeDetailScreen extends StatefulWidget { } class _LogEventTypeDetailScreenState extends State { + LogEventType? _logEventType; + bool _isNew = true; + bool _isSaving = false; + final GlobalKey _logEventTypeForm = GlobalKey(); + final _valueController = TextEditingController(text: ''); final _defaultReminderDurationController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); bool _hasEndTime = false; - bool _isSaving = false; - @override void initState() { super.initState(); - if (widget.logEventType != null) { - _valueController.text = widget.logEventType!.value; + reload(); + + if (_logEventType != null) { + _valueController.text = _logEventType!.value; _defaultReminderDurationController.text = - (widget.logEventType!.defaultReminderDuration ?? '').toString(); - _notesController.text = widget.logEventType!.notes ?? ''; - _hasEndTime = widget.logEventType!.hasEndTime; + (_logEventType!.defaultReminderDuration ?? '').toString(); + _notesController.text = _logEventType!.notes ?? ''; + _hasEndTime = _logEventType!.hasEndTime; } } + void reload() { + if (widget.id != 0) { + setState(() { + _logEventType = LogEventType.get(widget.id); + }); + } + _isNew = _logEventType == null; + } + void handleSaveAction() async { setState(() { _isSaving = true; }); if (_logEventTypeForm.currentState!.validate()) { - bool isNew = widget.logEventType == null; + bool isNew = _logEventType == null; LogEventType.put(LogEventType( - id: widget.logEventType?.id ?? 0, + id: widget.id, value: _valueController.text, notes: _notesController.text, defaultReminderDuration: @@ -61,7 +74,7 @@ class _LogEventTypeDetailScreenState extends State { } void handleCancelAction() { - bool isNew = widget.logEventType == null; + bool isNew = _logEventType == null; if (showConfirmationDialogOnCancel && ((isNew && (_valueController.text != '' || @@ -70,12 +83,11 @@ class _LogEventTypeDetailScreenState extends State { _notesController.text != '' || _hasEndTime)) || (!isNew && - (_valueController.text != widget.logEventType!.value || + (_valueController.text != _logEventType!.value || int.tryParse(_defaultReminderDurationController.text) != - widget.logEventType!.defaultReminderDuration || - _notesController.text != - (widget.logEventType!.notes ?? '') || - _hasEndTime != widget.logEventType!.hasEndTime)))) { + _logEventType!.defaultReminderDuration || + _notesController.text != (_logEventType!.notes ?? '') || + _hasEndTime != _logEventType!.hasEndTime)))) { Dialogs.showCancelConfirmationDialog( context: context, isNew: isNew, @@ -88,10 +100,9 @@ class _LogEventTypeDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.logEventType == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Log Event Type' : widget.logEventType!.value), + title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value), ), drawer: const Navigation(currentLocation: LogEventTypeDetailScreen.routeName), diff --git a/lib/screens/log/log_event_type_list.dart b/lib/screens/log/log_event_type_list.dart index e6c7cfb..394915d 100644 --- a/lib/screens/log/log_event_type_list.dart +++ b/lib/screens/log/log_event_type_list.dart @@ -1,4 +1,3 @@ -// import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/log/log_event_type_detail.dart'; @@ -15,7 +14,13 @@ class LogEventTypeListScreen extends StatefulWidget { class _LogEventTypeListScreenState extends State { List _logEventTypes = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _logEventTypes = LogEventType.getAll(); }); @@ -32,17 +37,11 @@ class _LogEventTypeListScreenState extends State { }); } - @override - void initState() { - super.initState(); - refresh(); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Log Event Types'), actions: [ - IconButton(onPressed: refresh, icon: const Icon(Icons.refresh)) + IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ]), drawer: const Navigation(currentLocation: LogEventTypeListScreen.routeName), @@ -54,7 +53,6 @@ class _LogEventTypeListScreenState extends State { padding: const EdgeInsets.all(10.0), itemCount: _logEventTypes.length, itemBuilder: (context, index) { - // final logEventType = snapshot.data![index]; final logEventType = _logEventTypes[index]; return ListTile( onTap: () { @@ -63,9 +61,9 @@ class _LogEventTypeListScreenState extends State { MaterialPageRoute( builder: (context) => LogEventTypeDetailScreen( - logEventType: logEventType), + id: logEventType.id), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, title: Text(logEventType.value), subtitle: Text(logEventType.notes ?? ''), @@ -76,7 +74,7 @@ class _LogEventTypeListScreenState extends State { onPressed: () async { LogEventType.remove(logEventType.id); // await logEventType.delete().then((_) { - refresh( + reload( message: 'Log Event Type deleted'); // }); }, @@ -100,7 +98,7 @@ class _LogEventTypeListScreenState extends State { MaterialPageRoute( builder: (context) => const LogEventTypeDetailScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/screens/log/log_meal_list.dart b/lib/screens/log/log_meal_list.dart deleted file mode 100644 index cb0c0ea..0000000 --- a/lib/screens/log/log_meal_list.dart +++ /dev/null @@ -1,107 +0,0 @@ -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/screens/log/log_meal_detail.dart'; -import 'package:flutter/material.dart'; - -class LogMealListScreen extends StatefulWidget { - final LogEntry logEntry; - final Function() reload; - - const LogMealListScreen({Key? key, required this.logEntry, required this.reload}) - : super(key: key); - - @override - _LogMealListScreenState createState() => _LogMealListScreenState(); -} - -class _LogMealListScreenState extends State { - void reload({String? message}) { - widget.reload(); - - setState(() { - if (message != null) { - var snackBar = SnackBar( - content: Text(message), - duration: const Duration(seconds: 2), - ); - ScaffoldMessenger.of(context) - ..removeCurrentSnackBar() - ..showSnackBar(snackBar); - } - }); - } - - void handleEditAction(LogMeal meal) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => LogMealDetailScreen( - logEntry: widget.logEntry, - logMeal: meal, - ), - ), - ).then((message) => reload(message: message)); - } - - void onDelete(LogMeal logMeal) { - LogMeal.remove(logMeal.id); - reload(message: 'Meal deleted'); - } - - void handleDeleteAction(LogMeal meal) async { - if (showConfirmationDialogOnDelete) { - Dialogs.showConfirmationDialog( - context: context, - onConfirm: () => onDelete(meal), - message: 'Are you sure you want to delete this Meal?', - ); - } else { - onDelete(meal); - } - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Expanded( - child: widget.logEntry.meals.isNotEmpty ? ListView.builder( - shrinkWrap: true, - itemCount: widget.logEntry.meals.length, - itemBuilder: (context, index) { - final meal = widget.logEntry.meals[index]; - return ListTile( - onTap: () => handleEditAction(meal), - title: Row( - children: [ - Expanded(child: Text(meal.value)), - Expanded( - child: Text(meal.carbsPerPortion != null - ? '${meal.carbsPerPortion} g carbs' - : '')), - Expanded( - child: Text(meal.bolus != null - ? '${meal.bolus} U' - : '')) - ], - ), - trailing: IconButton( - icon: const Icon( - Icons.delete, - color: Colors.blue, - ), - onPressed: () => handleDeleteAction(meal), - ), - ); - }, - ) : const Center( - child: Text( - 'You have not added any Meals to this Log Entry yet!'), - ), - ), - ], - ); - } -} diff --git a/lib/screens/meal/meal_category_detail.dart b/lib/screens/meal/meal_category_detail.dart index 9967b78..960e1fa 100644 --- a/lib/screens/meal/meal_category_detail.dart +++ b/lib/screens/meal/meal_category_detail.dart @@ -8,10 +8,9 @@ import 'package:diameter/models/meal_category.dart'; class MealCategoryDetailScreen extends StatefulWidget { static const String routeName = '/meal-category'; - final MealCategory? mealCategory; + final int id; - const MealCategoryDetailScreen({Key? key, this.mealCategory}) - : super(key: key); + const MealCategoryDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _MealCategoryDetailScreenState createState() => @@ -19,45 +18,54 @@ class MealCategoryDetailScreen extends StatefulWidget { } class _MealCategoryDetailScreenState extends State { + MealCategory? _mealCategory; + bool _isNew = true; + final GlobalKey _mealCategoryForm = GlobalKey(); + final _valueController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); @override void initState() { super.initState(); - if (widget.mealCategory != null) { - _valueController.text = widget.mealCategory!.value; - _notesController.text = widget.mealCategory!.notes ?? ''; + reload(); + + if (_mealCategory != null) { + _valueController.text = _mealCategory!.value; + _notesController.text = _mealCategory!.notes ?? ''; } } + void reload() { + if (widget.id != 0) { + setState(() { + _mealCategory = MealCategory.get(widget.id); + }); + } + _isNew = _mealCategory == null; + } + void handleSaveAction() async { if (_mealCategoryForm.currentState!.validate()) { - bool isNew = widget.mealCategory == null; - // isNew - // ? await MealCategory.save( - // value: _valueController.text, notes: _notesController.text) - // : await MealCategory.update(widget.mealCategory!.objectId!, - // value: _valueController.text, notes: _notesController.text); - MealCategory.put(MealCategory(id: widget.mealCategory?.id ?? 0, - value: _valueController.text, notes: _notesController.text)); - Navigator.pop(context, '${isNew ? 'New' : ''} Meal Category saved'); + MealCategory.put(MealCategory( + id: widget.id, + value: _valueController.text, + notes: _notesController.text)); + Navigator.pop(context, '${_isNew ? 'New' : ''} Meal Category saved'); } } void handleCancelAction() { - bool isNew = widget.mealCategory == null; - if (showConfirmationDialogOnCancel && - (isNew && + (_isNew && (_valueController.text != '' || _notesController.text != '')) || - (!isNew && - (widget.mealCategory!.value != _valueController.text || - (widget.mealCategory!.notes ?? '') != _notesController.text))) { + (!_isNew && + (_mealCategory!.value != _valueController.text || + (_mealCategory!.notes ?? '') != _notesController.text))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -67,10 +75,9 @@ class _MealCategoryDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.mealCategory == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Meal Category' : widget.mealCategory!.value), + title: Text(_isNew ? 'New Meal Category' : _mealCategory!.value), ), drawer: const Navigation(currentLocation: MealCategoryDetailScreen.routeName), diff --git a/lib/screens/meal/meal_category_list.dart b/lib/screens/meal/meal_category_list.dart index ead7fde..1a3fb2e 100644 --- a/lib/screens/meal/meal_category_list.dart +++ b/lib/screens/meal/meal_category_list.dart @@ -18,7 +18,13 @@ class MealCategoryListScreen extends StatefulWidget { class _MealCategoryListScreenState extends State { List _mealCategories = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _mealCategories = MealCategory.getAll(); }); @@ -37,7 +43,7 @@ class _MealCategoryListScreenState extends State { void onDelete(MealCategory mealCategory) { MealCategory.remove(mealCategory.id); - refresh(message: 'Meal Category deleted'); + reload(message: 'Meal Category deleted'); } void handleDeleteAction(MealCategory mealCategory) async { @@ -52,12 +58,6 @@ class _MealCategoryListScreenState extends State { } } - @override - void initState() { - super.initState(); - refresh(); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -65,7 +65,7 @@ class _MealCategoryListScreenState extends State { title: const Text('Meal Categories'), actions: [ IconButton( - onPressed: refresh, + onPressed: reload, icon: const Icon(Icons.refresh), ), ], @@ -88,11 +88,11 @@ class _MealCategoryListScreenState extends State { context, MaterialPageRoute( builder: (context) => - MealCategoryDetailScreen( - mealCategory: mealCategory, + MealCategoryDetailScreen( + id: mealCategory.id, ), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, title: Text(mealCategory.value), subtitle: Text(mealCategory.notes ?? ''), @@ -124,7 +124,7 @@ class _MealCategoryListScreenState extends State { MaterialPageRoute( builder: (context) => const MealCategoryDetailScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/screens/meal/meal_detail.dart b/lib/screens/meal/meal_detail.dart index ac09991..f2966ec 100644 --- a/lib/screens/meal/meal_detail.dart +++ b/lib/screens/meal/meal_detail.dart @@ -1,5 +1,6 @@ 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'; @@ -8,22 +9,25 @@ 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/navigation.dart'; -// import 'package:diameter/objectbox.g.dart'; import 'package:diameter/settings.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; class MealDetailScreen extends StatefulWidget { static const String routeName = '/meal'; + final int id; - final Meal? meal; - const MealDetailScreen({Key? key, this.meal}) : super(key: key); + const MealDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _MealDetailScreenState createState() => _MealDetailScreenState(); } class _MealDetailScreenState extends State { + Meal? _meal; + bool _isNew = true; + bool _isSaving = false; + final GlobalKey _mealForm = GlobalKey(); final _valueController = TextEditingController(text: ''); @@ -46,84 +50,54 @@ class _MealDetailScreenState extends State { List _portionSizeAccuracies = []; List _carbsRatioAccuracies = []; - bool isSaving = false; - @override void initState() { super.initState(); + reload(); + _portionSizeAccuracies = Accuracy.getAllForPortionSize(); _carbsRatioAccuracies = Accuracy.getAllForCarbsRatio(); _mealCategories = MealCategory.getAll(); _mealPortionTypes = MealPortionType.getAll(); _mealSources = MealSource.getAll(); - if (widget.meal != null) { - _valueController.text = widget.meal!.value; - _carbsRatioController.text = (widget.meal!.carbsRatio ?? '').toString(); - _portionSizeController.text = (widget.meal!.portionSize ?? '').toString(); + if (_meal != null) { + _valueController.text = _meal!.value; + _carbsRatioController.text = (_meal!.carbsRatio ?? '').toString(); + _portionSizeController.text = (_meal!.portionSize ?? '').toString(); _carbsPerPortionController.text = - (widget.meal!.carbsPerPortion ?? '').toString(); + (_meal!.carbsPerPortion ?? '').toString(); _delayedBolusRateController.text = - (widget.meal!.delayedBolusRate ?? '').toString(); + (_meal!.delayedBolusRate ?? '').toString(); _delayedBolusDurationController.text = - (widget.meal!.delayedBolusDuration ?? '').toString(); - _notesController.text = widget.meal!.notes ?? ''; + (_meal!.delayedBolusDuration ?? '').toString(); + _notesController.text = _meal!.notes ?? ''; - _mealSource = widget.meal!.mealSource.target; - _mealCategory = widget.meal!.mealCategory.target; - _mealPortionType = widget.meal!.mealPortionType.target; - _portionSizeAccuracy = widget.meal!.portionSizeAccuracy.target; - _carbsRatioAccuracy = widget.meal!.carbsRatioAccuracy.target; + _mealSource = _meal!.mealSource.target; + _mealCategory = _meal!.mealCategory.target; + _mealPortionType = _meal!.mealPortionType.target; + _portionSizeAccuracy = _meal!.portionSizeAccuracy.target; + _carbsRatioAccuracy = _meal!.carbsRatioAccuracy.target; } } + void reload() { + if (widget.id != 0) { + setState(() { + _meal = Meal.get(widget.id); + }); + } + _isNew = _meal == null; + } + void handleSaveAction() async { setState(() { - isSaving = true; + _isSaving = true; }); if (_mealForm.currentState!.validate()) { - bool isNew = widget.meal == null; - // isNew - // ? await Meal.save( - // value: _valueController.text, - // source: _mealSource, - // category: _mealCategory, - // portionType: _mealPortionType, - // carbsRatio: double.tryParse(_carbsRatioController.text), - // portionSize: double.tryParse(_portionSizeController.text), - // carbsPerPortion: double.tryParse(_carbsPerPortionController.text), - // // portionSizeAccuracy: _portionSizeAccuracy, - // // carbsRatioAccuracy: _carbsRatioAccuracy, - // portionSizeAccuracy: _portionSizeAccuracy?.id.toString(), - // carbsRatioAccuracy: _carbsRatioAccuracy?.id.toString(), - // delayedBolusDuration: - // int.tryParse(_delayedBolusDurationController.text), - // delayedBolusRate: - // double.tryParse(_delayedBolusRateController.text), - // notes: _notesController.text, - // ) - // : await Meal.update( - // widget.meal!.objectId!, - // value: _valueController.text, - // source: _mealSource, - // category: _mealCategory, - // portionType: _mealPortionType, - // carbsRatio: double.tryParse(_carbsRatioController.text), - // portionSize: double.tryParse(_portionSizeController.text), - // carbsPerPortion: double.tryParse(_carbsPerPortionController.text), - // // portionSizeAccuracy: _portionSizeAccuracy, - // // carbsRatioAccuracy: _carbsRatioAccuracy, - // portionSizeAccuracy: _portionSizeAccuracy?.id.toString(), - // carbsRatioAccuracy: _carbsRatioAccuracy?.id.toString(), - // delayedBolusDuration: - // int.tryParse(_delayedBolusDurationController.text), - // delayedBolusRate: - // double.tryParse(_delayedBolusRateController.text), - // notes: _notesController.text, - // ); Meal meal = Meal( - id: widget.meal?.id ?? 0, + id: widget.id, value: _valueController.text, carbsRatio: double.tryParse(_carbsRatioController.text), portionSize: double.tryParse(_portionSizeController.text), @@ -140,17 +114,16 @@ class _MealDetailScreenState extends State { meal.carbsRatioAccuracy.target = _carbsRatioAccuracy; Meal.put(meal); - Navigator.pop(context, '${isNew ? 'New' : ''} Meal Saved'); + Navigator.pop(context, '${_isNew ? 'New' : ''} Meal Saved'); } setState(() { - isSaving = false; + _isSaving = false; }); } void handleCancelAction() { - bool isNew = widget.meal == null; if (showConfirmationDialogOnCancel && - ((isNew && + ((_isNew && (_valueController.text != '' || _mealSource != null || _mealCategory != null || @@ -164,27 +137,27 @@ class _MealDetailScreenState extends State { null || double.tryParse(_delayedBolusRateController.text) != null || _notesController.text != '')) || - (!isNew && - (_valueController.text != widget.meal!.value || - _mealSource != widget.meal!.mealSource.target || - _mealCategory != widget.meal!.mealCategory.target || - _mealPortionType != widget.meal!.mealPortionType.target || + (!_isNew && + (_valueController.text != _meal!.value || + _mealSource != _meal!.mealSource.target || + _mealCategory != _meal!.mealCategory.target || + _mealPortionType != _meal!.mealPortionType.target || double.tryParse(_carbsRatioController.text) != - widget.meal!.carbsRatio || + _meal!.carbsRatio || double.tryParse(_portionSizeController.text) != - widget.meal!.portionSize || + _meal!.portionSize || double.tryParse(_carbsPerPortionController.text) != - widget.meal!.carbsPerPortion || - _carbsRatioAccuracy != widget.meal!.carbsRatioAccuracy.target || - _portionSizeAccuracy != widget.meal!.portionSizeAccuracy.target || + _meal!.carbsPerPortion || + _carbsRatioAccuracy != _meal!.carbsRatioAccuracy.target || + _portionSizeAccuracy != _meal!.portionSizeAccuracy.target || int.tryParse(_delayedBolusDurationController.text) != - widget.meal!.delayedBolusDuration || + _meal!.delayedBolusDuration || double.tryParse(_delayedBolusRateController.text) != - widget.meal!.delayedBolusRate || - _notesController.text != (widget.meal!.notes ?? ''))))) { + _meal!.delayedBolusRate || + _notesController.text != (_meal!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -196,12 +169,10 @@ class _MealDetailScreenState extends State { setState(() { _mealSource = mealSource; if (mealSource.defaultCarbsRatioAccuracy.hasValue) { - _carbsRatioAccuracy = - mealSource.defaultCarbsRatioAccuracy.target; + _carbsRatioAccuracy = mealSource.defaultCarbsRatioAccuracy.target; } if (mealSource.defaultPortionSizeAccuracy.hasValue) { - _portionSizeAccuracy = - mealSource.defaultPortionSizeAccuracy.target; + _portionSizeAccuracy = mealSource.defaultPortionSizeAccuracy.target; } if (mealSource.defaultMealCategory.hasValue) { _mealCategory = mealSource.defaultMealCategory.target; @@ -256,10 +227,9 @@ class _MealDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.meal == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Meal' : widget.meal!.value), + title: Text(_isNew ? 'New Meal' : _meal!.value), ), drawer: const Navigation(currentLocation: MealDetailScreen.routeName), body: SingleChildScrollView( @@ -281,36 +251,33 @@ class _MealDetailScreenState extends State { return null; }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealSource, label: 'Meal Source', items: _mealSources, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { if (value != null) { onSelectMealSource(value); } }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealCategory, label: 'Meal Category', items: _mealCategories, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _mealCategory = value; }); }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _mealPortionType, label: 'Meal Portion Type', items: _mealPortionTypes, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _mealPortionType = value; @@ -373,12 +340,11 @@ class _MealDetailScreenState extends State { ), ], ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _portionSizeAccuracy, label: 'Portion Size Accuracy', items: _portionSizeAccuracies, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _portionSizeAccuracy = value; @@ -415,12 +381,11 @@ class _MealDetailScreenState extends State { ), ], ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _carbsRatioAccuracy, label: 'Carbs Ratio Accuracy', items: _carbsRatioAccuracies, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _carbsRatioAccuracy = value; @@ -461,7 +426,7 @@ class _MealDetailScreenState extends State { ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, - onSave: isSaving ? null : handleSaveAction, + onSave: _isSaving ? null : handleSaveAction, ), ); } diff --git a/lib/screens/meal/meal_list.dart b/lib/screens/meal/meal_list.dart index d0f37b6..5717cc2 100644 --- a/lib/screens/meal/meal_list.dart +++ b/lib/screens/meal/meal_list.dart @@ -1,5 +1,4 @@ import 'package:diameter/components/dialogs.dart'; -// import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/meal.dart'; import 'package:diameter/navigation.dart'; @@ -18,7 +17,13 @@ class MealListScreen extends StatefulWidget { class _MealListScreenState extends State { List _meals = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _meals = Meal.getAll(); }); @@ -37,7 +42,7 @@ class _MealListScreenState extends State { void onDelete(Meal meal) { Meal.remove(meal.id); - refresh(message: 'Meal deleted'); + reload(message: 'Meal deleted'); } void handleDeleteAction(Meal meal) async { @@ -52,17 +57,11 @@ class _MealListScreenState extends State { } } - @override - void initState() { - super.initState(); - refresh(); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('Meals'), actions: [ - IconButton(onPressed: refresh, icon: const Icon(Icons.refresh)) + IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ]), drawer: const Navigation(currentLocation: MealListScreen.routeName), body: Column( @@ -81,9 +80,9 @@ class _MealListScreenState extends State { context, MaterialPageRoute( builder: (context) => - MealDetailScreen(meal: meal), + MealDetailScreen(id: meal.id), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, title: Text(meal.value), subtitle: Text(meal.notes ?? ''), @@ -112,7 +111,7 @@ class _MealListScreenState extends State { MaterialPageRoute( builder: (context) => const MealDetailScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/screens/meal/meal_portion_type_detail.dart b/lib/screens/meal/meal_portion_type_detail.dart index fb53e07..845008b 100644 --- a/lib/screens/meal/meal_portion_type_detail.dart +++ b/lib/screens/meal/meal_portion_type_detail.dart @@ -9,10 +9,9 @@ import 'package:diameter/models/meal_portion_type.dart'; class MealPortionTypeDetailScreen extends StatefulWidget { static const String routeName = '/meal-portion-type'; - final MealPortionType? mealPortionType; + final int id; - const MealPortionTypeDetailScreen({Key? key, this.mealPortionType}) - : super(key: key); + const MealPortionTypeDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _MealPortionTypeDetailScreenState createState() => @@ -21,53 +20,56 @@ class MealPortionTypeDetailScreen extends StatefulWidget { class _MealPortionTypeDetailScreenState extends State { + MealPortionType? _mealPortionType; + bool _isNew = true; + final GlobalKey _mealPortionTypeForm = GlobalKey(); + final _valueController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); @override void initState() { super.initState(); - if (widget.mealPortionType != null) { - _valueController.text = widget.mealPortionType!.value; - _notesController.text = widget.mealPortionType!.notes ?? ''; + reload(); + + if (_mealPortionType != null) { + _valueController.text = _mealPortionType!.value; + _notesController.text = _mealPortionType!.notes ?? ''; } } + void reload() { + if (widget.id != 0) { + setState(() { + _mealPortionType = MealPortionType.get(widget.id); + }); + } + _isNew = _mealPortionType == null; + } + void handleSaveAction() async { if (_mealPortionTypeForm.currentState!.validate()) { - bool isNew = widget.mealPortionType == null; - // isNew - // ? MealPortionType.save( - // value: _valueController.text, - // notes: _notesController.text, - // ) - // : MealPortionType.update( - // widget.mealPortionType!.objectId!, - // value: _valueController.text, - // notes: _notesController.text, - // ); MealPortionType.put(MealPortionType( - id: widget.mealPortionType?.id ?? 0, + id: _mealPortionType?.id ?? 0, value: _valueController.text, notes: _notesController.text, )); - Navigator.pop(context, '${isNew ? 'New' : ''} Meal Portion Type saved'); + Navigator.pop(context, '${_isNew ? 'New' : ''} Meal Portion Type saved'); } } void handleCancelAction() { - bool isNew = widget.mealPortionType == null; if (showConfirmationDialogOnCancel && - ((isNew && + ((_isNew && (_valueController.text != '' || _notesController.text != '')) || - (!isNew && - (_valueController.text != widget.mealPortionType!.value || + (!_isNew && + (_valueController.text != _mealPortionType!.value || _notesController.text != - (widget.mealPortionType!.notes ?? ''))))) { + (_mealPortionType!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -77,11 +79,11 @@ class _MealPortionTypeDetailScreenState @override Widget build(BuildContext context) { - bool isNew = widget.mealPortionType == null; + bool isNew = _mealPortionType == null; return Scaffold( appBar: AppBar( title: Text( - isNew ? 'New Meal Portion Type' : widget.mealPortionType!.value), + isNew ? 'New Meal Portion Type' : _mealPortionType!.value), ), drawer: const Navigation( currentLocation: MealPortionTypeDetailScreen.routeName), diff --git a/lib/screens/meal/meal_portion_type_list.dart b/lib/screens/meal/meal_portion_type_list.dart index c59a96e..eb6d667 100644 --- a/lib/screens/meal/meal_portion_type_list.dart +++ b/lib/screens/meal/meal_portion_type_list.dart @@ -1,5 +1,4 @@ import 'package:diameter/components/dialogs.dart'; -// import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/config.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/meal/meal_portion_type_detail.dart'; @@ -19,7 +18,13 @@ class MealPortionTypeListScreen extends StatefulWidget { class _MealPortionTypeListScreenState extends State { List _mealPortionTypes = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _mealPortionTypes = MealPortionType.getAll(); }); @@ -38,7 +43,7 @@ class _MealPortionTypeListScreenState extends State { void onDelete(MealPortionType mealPortionType) { MealPortionType.remove(mealPortionType.id); - refresh(message: 'Meal Portion Type deleted'); + reload(message: 'Meal Portion Type deleted'); } void handleDeleteAction(MealPortionType mealPortionType) async { @@ -52,20 +57,14 @@ class _MealPortionTypeListScreenState extends State { onDelete(mealPortionType); } } - - @override - void initState() { - super.initState(); - refresh(); - } - + @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Meal Portion Types'), actions: [ - IconButton(onPressed: refresh, icon: const Icon(Icons.refresh)) + IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], ), drawer: const Navigation( @@ -86,12 +85,12 @@ class _MealPortionTypeListScreenState extends State { context, MaterialPageRoute( builder: (context) => - MealPortionTypeDetailScreen( - mealPortionType: mealPortionType, + MealPortionTypeDetailScreen( + id: mealPortionType.id, ), ), ).then( - (message) => refresh(message: message)); + (message) => reload(message: message)); }, title: Text(mealPortionType.value), subtitle: Text(mealPortionType.notes ?? ''), @@ -123,7 +122,7 @@ class _MealPortionTypeListScreenState extends State { MaterialPageRoute( builder: (context) => const MealPortionTypeDetailScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/screens/meal/meal_source_detail.dart b/lib/screens/meal/meal_source_detail.dart index 5a512f7..5d732b6 100644 --- a/lib/screens/meal/meal_source_detail.dart +++ b/lib/screens/meal/meal_source_detail.dart @@ -1,28 +1,30 @@ 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/main.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/navigation.dart'; -// import 'package:diameter/objectbox.g.dart'; import 'package:flutter/material.dart'; class MealSourceDetailScreen extends StatefulWidget { static const String routeName = '/meal-source'; - final MealSource? mealSource; + final int id; - const MealSourceDetailScreen({Key? key, this.mealSource}) : super(key: key); + const MealSourceDetailScreen({Key? key, this.id = 0}) : super(key: key); @override _MealSourceDetailScreenState createState() => _MealSourceDetailScreenState(); } class _MealSourceDetailScreenState extends State { + MealSource? _mealSource; + bool _isNew = true; + List _portionSizeAccuracies = []; List _carbsRatioAccuracies = []; List _mealCategories = []; @@ -41,52 +43,40 @@ class _MealSourceDetailScreenState extends State { void initState() { super.initState(); + reload(); + _portionSizeAccuracies = Accuracy.getAllForPortionSize(); _carbsRatioAccuracies = Accuracy.getAllForCarbsRatio(); _mealCategories = MealCategory.getAll(); _mealPortionTypes = MealPortionType.getAll(); - if (widget.mealSource != null) { - _valueController.text = widget.mealSource!.value; - _notesController.text = widget.mealSource!.notes ?? ''; + if (_mealSource != null) { + _valueController.text = _mealSource!.value; + _notesController.text = _mealSource!.notes ?? ''; _defaultPortionSizeAccuracy = - widget.mealSource!.defaultPortionSizeAccuracy.target; - _defaultCarbsRatioAccuracy = widget.mealSource!.defaultCarbsRatioAccuracy.target; + _mealSource!.defaultPortionSizeAccuracy.target; + _defaultCarbsRatioAccuracy = + _mealSource!.defaultCarbsRatioAccuracy.target; - _defaultMealCategory = widget.mealSource!.defaultMealCategory.target; + _defaultMealCategory = _mealSource!.defaultMealCategory.target; _defaultMealPortionType = - widget.mealSource!.defaultMealPortionType.target; + _mealSource!.defaultMealPortionType.target; } } + void reload() { + if (widget.id != 0) { + setState(() { + _mealSource = MealSource.get(widget.id); + }); + } + _isNew = _mealSource == null; + } + void handleSaveAction() async { - bool isNew = widget.mealSource == null; - if (_mealSourceForm.currentState!.validate()) { - // isNew - // ? await MealSource.save( - // value: _valueController.text, - // defaultCarbsRatioAccuracy: _defaultCarbsRatioAccuracy?.id.toString(), - // defaultPortionSizeAccuracy: _defaultPortionSizeAccuracy?.id.toString(), - // // defaultCarbsRatioAccuracy: _defaultCarbsRatioAccuracy, - // // defaultPortionSizeAccuracy: _defaultPortionSizeAccuracy, - // defaultMealCategory: _defaultMealCategory, - // defaultMealPortionType: _defaultMealPortionType, - // notes: _notesController.text, - // ) - // : await MealSource.update( - // widget.mealSource!.objectId!, - // value: _valueController.text, - // defaultCarbsRatioAccuracy: _defaultCarbsRatioAccuracy?.id.toString(), - // defaultPortionSizeAccuracy: _defaultPortionSizeAccuracy?.id.toString(), - // // defaultCarbsRatioAccuracy: _defaultCarbsRatioAccuracy, - // // defaultPortionSizeAccuracy: _defaultPortionSizeAccuracy, - // defaultMealCategory: _defaultMealCategory, - // defaultMealPortionType: _defaultMealPortionType, - // notes: _notesController.text, - // ); - MealSource mealSource = MealSource( - id: widget.mealSource?.id ?? 0, + MealSource mealSource = MealSource( + id: widget.id, value: _valueController.text, notes: _notesController.text, ); @@ -96,35 +86,33 @@ class _MealSourceDetailScreenState extends State { mealSource.defaultMealCategory.target = _defaultMealCategory; mealSource.defaultMealPortionType.target = _defaultMealPortionType; MealSource.put(mealSource); - Navigator.pop(context, '${isNew ? 'New' : ''} Meal Source saved'); - } + Navigator.pop(context, '${_isNew ? 'New' : ''} Meal Source saved'); } void handleCancelAction() { - bool isNew = widget.mealSource == null; if (showConfirmationDialogOnCancel && - ((isNew && + ((_isNew && (_valueController.text != '' || _defaultCarbsRatioAccuracy != null || _defaultPortionSizeAccuracy != null || _defaultMealCategory != null || _defaultMealPortionType != null || _notesController.text != '')) || - (!isNew && - (_valueController.text != widget.mealSource!.value || + (!_isNew && + (_valueController.text != _mealSource!.value || _defaultCarbsRatioAccuracy != - widget.mealSource!.defaultCarbsRatioAccuracy.target || + _mealSource!.defaultCarbsRatioAccuracy.target || _defaultPortionSizeAccuracy != - widget.mealSource!.defaultPortionSizeAccuracy.target || + _mealSource!.defaultPortionSizeAccuracy.target || _defaultMealCategory != - widget.mealSource!.defaultMealCategory.target || + _mealSource!.defaultMealCategory.target || _defaultMealPortionType != - widget.mealSource!.defaultMealPortionType.target || + _mealSource!.defaultMealPortionType.target || _notesController.text != - (widget.mealSource!.notes ?? ''))))) { + (_mealSource!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, - isNew: isNew, + isNew: _isNew, onSave: handleSaveAction, ); } else { @@ -134,10 +122,9 @@ class _MealSourceDetailScreenState extends State { @override Widget build(BuildContext context) { - bool isNew = widget.mealSource == null; return Scaffold( appBar: AppBar( - title: Text(isNew ? 'New Meal Source' : widget.mealSource!.value), + title: Text(_isNew ? 'New Meal Source' : _mealSource!.value), ), drawer: const Navigation(currentLocation: MealSourceDetailScreen.routeName), @@ -160,70 +147,44 @@ class _MealSourceDetailScreenState extends State { return null; }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _defaultCarbsRatioAccuracy, label: 'Default Carbs Ratio Accuracy', items: _carbsRatioAccuracies, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _defaultCarbsRatioAccuracy = value; }); }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _defaultPortionSizeAccuracy, label: 'Default Portion Size Accuracy', items: _portionSizeAccuracies, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _defaultPortionSizeAccuracy = value; }); }, ), - // StyledFutureDropdownButton( - // selectedItem: _defaultCarbsRatioAccuracy, - // label: 'Default Carbs Ratio Accuracy', - // items: _carbsRatioAccuracies, - // getItemValue: (item) => item.objectId, - // renderItem: (item) => Text(item.value), - // onChanged: (value) { - // setState(() { - // _defaultCarbsRatioAccuracy = value; - // }); - // }, - // ), - // StyledFutureDropdownButton( - // selectedItem: _defaultPortionSizeAccuracy, - // label: 'Default Portion Size Accuracy', - // items: _portionSizeAccuracies, - // getItemValue: (item) => item.objectId, - // renderItem: (item) => Text(item.value), - // onChanged: (value) { - // setState(() { - // _defaultPortionSizeAccuracy = value; - // }); - // }, - // ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _defaultMealCategory, label: 'Default Meal Category', items: _mealCategories, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _defaultMealCategory = value; }); }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: _defaultMealPortionType, label: 'Default Meal Portion Type', items: _mealPortionTypes, - // getItemValue: (item) => item.objectId, - renderItem: (item) => Text(item.value), + renderItem: (item) => item.value, onChanged: (value) { setState(() { _defaultMealPortionType = value; diff --git a/lib/screens/meal/meal_source_list.dart b/lib/screens/meal/meal_source_list.dart index c72ed19..1ddf59e 100644 --- a/lib/screens/meal/meal_source_list.dart +++ b/lib/screens/meal/meal_source_list.dart @@ -1,4 +1,3 @@ -// import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/models/meal_source.dart'; @@ -18,7 +17,13 @@ class MealSourceListScreen extends StatefulWidget { class _MealSourceListScreenState extends State { List _mealSources = []; - void refresh({String? message}) { + @override + void initState() { + super.initState(); + reload(); + } + + void reload({String? message}) { setState(() { _mealSources = MealSource.getAll(); }); @@ -37,7 +42,7 @@ class _MealSourceListScreenState extends State { void onDelete(MealSource mealSource) { MealSource.remove(mealSource.id); - refresh(message: 'Meal Source deleted'); + reload(message: 'Meal Source deleted'); } void handleDeleteAction(MealSource mealSource) async { @@ -52,19 +57,13 @@ class _MealSourceListScreenState extends State { } } - @override - void initState() { - super.initState(); - refresh(); - } - @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Meal Sources'), actions: [ - IconButton(onPressed: refresh, icon: const Icon(Icons.refresh)) + IconButton(onPressed: reload, icon: const Icon(Icons.refresh)) ], ), drawer: const Navigation(currentLocation: MealSourceListScreen.routeName), @@ -84,10 +83,10 @@ class _MealSourceListScreenState extends State { context, MaterialPageRoute( builder: (context) => MealSourceDetailScreen( - mealSource: mealSource, + id: mealSource.id, ), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, title: Text(mealSource.value), subtitle: Text(mealSource.notes ?? ''), @@ -120,7 +119,7 @@ class _MealSourceListScreenState extends State { MaterialPageRoute( builder: (context) => const MealSourceDetailScreen(), ), - ).then((message) => refresh(message: message)); + ).then((message) => reload(message: message)); }, child: const Icon(Icons.add), ), diff --git a/lib/settings.dart b/lib/settings.dart index 33dce90..afcf694 100644 --- a/lib/settings.dart +++ b/lib/settings.dart @@ -1,4 +1,5 @@ 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/navigation.dart'; @@ -130,11 +131,11 @@ class _SettingsScreenState extends State { FormWrapper( formState: _settingsForm, fields: [ - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: nutritionMeasurement, label: 'Preferred Nutrition Measurement', items: NutritionMeasurement.values, - renderItem: (item) => Text(item.toString().split('.')[1]), + renderItem: (item) => item.toString().split('.')[1], onChanged: (value) { if (value != null) { Settings.setNutritionMeasurement(value); @@ -144,11 +145,11 @@ class _SettingsScreenState extends State { } }, ), - LabeledDropdownButton( + AutoCompleteDropdownButton( selectedItem: glucoseMeasurement, label: 'Preferred Glucose Measurement', items: GlucoseMeasurement.values, - renderItem: (item) => Text(item.toString().split('.')[1]), + renderItem: (item) => item.toString().split('.')[1], onChanged: (value) { if (value != null) { Settings.setGlucoseMeasurement(value);