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_entry.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/settings.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:diameter/utils/utils.dart'; import 'package:flutter/material.dart'; class LogEntryScreen extends StatefulWidget { static const String routeName = '/log-entry'; final int id; const LogEntryScreen({Key? key, this.id = 0}) : super(key: key); @override _LogEntryScreenState createState() => _LogEntryScreenState(); } class _LogEntryScreenState extends State { LogEntry? _logEntry; bool _isNew = true; bool _isSaving = false; final GlobalKey logEntryForm = GlobalKey(); DateTime _time = DateTime.now(); final _timeController = TextEditingController(text: ''); final _dateController = TextEditingController(text: ''); final _mgPerDlController = TextEditingController(text: ''); final _mmolPerLController = TextEditingController(text: ''); final _bolusGlucoseController = TextEditingController(text: ''); final _delayedBolusRateController = TextEditingController(text: ''); final _delayedBolusDurationController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); late FloatingActionButton addMealButton; late FloatingActionButton addEventButton; late IconButton refreshButton; late IconButton closeButton; late DetailBottomRow detailBottomRow; FloatingActionButton? actionButton; List appBarActions = []; DetailBottomRow? bottomNav; @override void initState() { super.initState(); reload(); addMealButton = FloatingActionButton( onPressed: handleAddNewMeal, child: const Icon(Icons.add), ); addEventButton = FloatingActionButton( onPressed: handleAddNewEvent, child: const Icon(Icons.add), ); refreshButton = IconButton( icon: const Icon(Icons.refresh), onPressed: reload, ); closeButton = IconButton( onPressed: handleCancelAction, icon: const Icon(Icons.close), ); detailBottomRow = DetailBottomRow( onCancel: handleCancelAction, onSave: _isSaving ? null : handleSaveAction, ); actionButton = null; appBarActions = [closeButton]; bottomNav = detailBottomRow; if (_logEntry != null) { _time = _logEntry!.time; _mgPerDlController.text = (_logEntry!.mgPerDl ?? '').toString(); _mmolPerLController.text = (_logEntry!.mmolPerL ?? '').toString(); _bolusGlucoseController.text = (_logEntry!.bolusGlucose ?? '').toString(); _delayedBolusRateController.text = (_logEntry!.delayedBolusRate ?? '').toString(); _delayedBolusDurationController.text = (_logEntry!.delayedBolusDuration ?? '').toString(); _notesController.text = _logEntry!.notes ?? ''; } updateTime(); } void reload({String? message}) { if (widget.id != 0) { setState(() { _logEntry = LogEntry.get(widget.id); }); _isNew = _logEntry == null; } setState(() { if (message != null) { var snackBar = SnackBar( content: Text(message), duration: const Duration(seconds: 2), ); ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(snackBar); } }); } void updateTime() { _timeController.text = DateTimeUtils.displayTime(_time); _dateController.text = DateTimeUtils.displayDate(_time); } void convertBetweenMgPerDlAndMmolPerL({GlucoseMeasurement? calculateFrom}) { int? mgPerDl; double? mmolPerL; if (calculateFrom != GlucoseMeasurement.mmolPerL && _mgPerDlController.text != '') { mgPerDl = int.tryParse(_mgPerDlController.text); } if (calculateFrom != GlucoseMeasurement.mgPerDl && _mmolPerLController.text != '') { mmolPerL = double.tryParse(_mmolPerLController.text); } if (mgPerDl != null && mmolPerL == null) { setState(() { _mmolPerLController.text = Utils.convertMgPerDlToMmolPerL(mgPerDl!).toString(); }); } if (mmolPerL != null && mgPerDl == null) { setState(() { _mgPerDlController.text = Utils.convertMmolPerLToMgPerDl(mmolPerL!).toString(); }); } } void handleSaveAction() async { setState(() { _isSaving = true; }); if (logEntryForm.currentState!.validate()) { LogEntry.put(LogEntry( id: widget.id, time: _time, mgPerDl: int.tryParse(_mgPerDlController.text), mmolPerL: double.tryParse(_mmolPerLController.text), bolusGlucose: double.tryParse(_bolusGlucoseController.text), delayedBolusDuration: int.tryParse(_delayedBolusDurationController.text), delayedBolusRate: double.tryParse(_delayedBolusRateController.text), notes: _notesController.text, )); Navigator.pushReplacementNamed(context, '/log', arguments: '${_isNew ? 'New' : ''} Log Entry Saved'); } setState(() { _isSaving = false; }); } void handleCancelAction() { if (showConfirmationDialogOnCancel && ((_isNew && (int.tryParse(_mgPerDlController.text) != null || double.tryParse(_mmolPerLController.text) != null || double.tryParse(_bolusGlucoseController.text) != null || int.tryParse(_delayedBolusDurationController.text) != null || double.tryParse(_delayedBolusRateController.text) != null || _notesController.text != '')) || (!_isNew && (int.tryParse(_mgPerDlController.text) != _logEntry!.mgPerDl || double.tryParse(_mmolPerLController.text) != _logEntry!.mmolPerL || double.tryParse(_bolusGlucoseController.text) != _logEntry!.bolusGlucose || int.tryParse(_delayedBolusDurationController.text) != _logEntry!.delayedBolusDuration || double.tryParse(_delayedBolusRateController.text) != _logEntry!.delayedBolusRate || _notesController.text != (_logEntry!.notes ?? ''))))) { Dialogs.showCancelConfirmationDialog( context: context, isNew: _isNew, onSave: handleSaveAction, onDiscard: (context) => Navigator.pushReplacementNamed(context, '/log'), ); } else { Navigator.pushReplacementNamed(context, '/log', arguments: '${_isNew ? 'New' : ''} Log Entry Saved'); } } void handleAddNewMeal() async { Navigator.push( context, MaterialPageRoute( builder: (context) { return LogMealDetailScreen(logEntry: _logEntry!); }, ), ).then((message) => reload(message: message)); } void handleAddNewEvent() async { Navigator.push( context, MaterialPageRoute( builder: (context) { return LogEventDetailScreen(logEntry: _logEntry!); }, ), ).then((message) => reload(message: message)); } void renderTabButtons(index) { if (_logEntry != null) { setState(() { switch (index) { case 1: actionButton = addMealButton; appBarActions = [refreshButton, closeButton]; bottomNav = null; break; case 2: actionButton = addEventButton; appBarActions = [refreshButton, closeButton]; bottomNav = null; break; default: actionButton = null; appBarActions = [closeButton]; bottomNav = detailBottomRow; } }); } } @override Widget build(BuildContext context) { return DefaultTabController( length: _isNew ? 1 : 3, child: Builder(builder: (BuildContext context) { final TabController tabController = DefaultTabController.of(context)!; tabController.addListener(() { renderTabButtons(tabController.index); }); List tabs = [ SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ FormWrapper( formState: logEntryForm, fields: [ Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.only(right: 5), child: DateTimeFormField( date: _time, label: 'Date', controller: _dateController, onChanged: (newTime) { if (newTime != null) { setState(() { _time = DateTime( newTime.year, newTime.month, newTime.day, _time.hour, _time.minute); }); updateTime(); } }, ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 5), child: TimeOfDayFormField( time: TimeOfDay.fromDateTime(_time), label: 'Time', controller: _timeController, onChanged: (newTime) { if (newTime != null) { setState(() { _time = DateTime( _time.year, _time.month, _time.day, newTime.hour, newTime.minute); }); updateTime(); } }, ), ), ), ], ), Row( children: [ glucoseMeasurement == GlucoseMeasurement.mgPerDl || glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'mg/dl', suffixText: 'mg/dl', ), controller: _mgPerDlController, onChanged: (_) => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mgPerDl), keyboardType: const TextInputType.numberWithOptions(), validator: (value) { if (value!.trim().isEmpty && _mmolPerLController.text .trim() .isEmpty) { return 'How many mg/dl or mmol/l does the rate make up for?'; } return null; }, ), ) : Container(), glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mmolPerL), icon: const Icon(Icons.calculate), ) : Container(), glucoseMeasurement == GlucoseMeasurement.mmolPerL || glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'mmol/l', suffixText: 'mmol/l', ), controller: _mmolPerLController, onChanged: (_) => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mmolPerL), keyboardType: const TextInputType.numberWithOptions( decimal: true), validator: (value) { if (value!.trim().isEmpty && _mgPerDlController.text .trim() .isEmpty) { return 'How many mg/dl or mmol/l does rhe rate make up for?'; } return null; }, ), ) : Container(), glucoseDisplayMode == GlucoseDisplayMode.both || glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mgPerDl), icon: const Icon(Icons.calculate), ) : Container(), ], ), TextFormField( decoration: const InputDecoration( labelText: 'Bolus Units', suffixText: 'U', ), controller: _bolusGlucoseController, keyboardType: const TextInputType.numberWithOptions( decimal: true), ), // ignore: todo // TODO: change field functionality according to time format TextFormField( decoration: const InputDecoration( labelText: 'Delayed Bolus Duration', suffixText: ' min', ), controller: _delayedBolusDurationController, keyboardType: TextInputType.number, ), TextFormField( decoration: const InputDecoration( labelText: 'Delayed Bolus Units', ), controller: _delayedBolusRateController, keyboardType: const TextInputType.numberWithOptions( decimal: true), ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', alignLabelWithHint: true, ), keyboardType: TextInputType.multiline, ), ], ), ]), ), ]; if (!_isNew) { tabs.add(LogMealListScreen(logEntry: _logEntry!, reload: reload)); tabs.add(LogEventListScreen(logEntry: _logEntry!, reload: reload)); } return Scaffold( appBar: AppBar( title: Text(_isNew ? 'New Log Entry' : 'Edit Log Entry'), bottom: _isNew ? PreferredSize(child: Container(), preferredSize: Size.zero) : const TabBar( tabs: [ Tab(text: 'GENERAL'), Tab(text: 'MEALS'), Tab(text: 'EVENTS'), ], ), actions: appBarActions, ), drawer: const Navigation(currentLocation: LogEntryScreen.routeName), body: TabBarView( children: tabs, ), bottomNavigationBar: bottomNav, floatingActionButton: actionButton, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); }), ); } }