import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/forms.dart'; import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_list.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_list.dart'; import 'package:diameter/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; List _logMeals = []; List _logBoli = []; bool _isNew = true; bool _isSaving = false; final GlobalKey logEntryForm = GlobalKey(); late DateTime _time; final _timeController = TextEditingController(text: ''); final _dateController = TextEditingController(text: ''); final _mgPerDlController = TextEditingController(text: ''); final _mmolPerLController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); late FloatingActionButton addMealButton; late FloatingActionButton addBolusButton; 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), ); addBolusButton = FloatingActionButton( onPressed: handleAddNewBolus, 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(); _notesController.text = _logEntry!.notes ?? ''; } else { _time = DateTime.now(); } updateTime(); } void reload({String? message}) { if (widget.id != 0) { setState(() { _logEntry = LogEntry.get(widget.id); _logMeals = LogMeal.getAllForEntry(widget.id); _logBoli = LogBolus.getAllForEntry(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), notes: _notesController.text, )); Navigator.pushReplacementNamed(context, '/log', arguments: '${_isNew ? 'New' : ''} Log Entry Saved'); } setState(() { _isSaving = false; }); } void handleCancelAction() { if (Settings.get().showConfirmationDialogOnCancel && ((_isNew && (int.tryParse(_mgPerDlController.text) != null || double.tryParse(_mmolPerLController.text) != null || _notesController.text != '')) || (!_isNew && (int.tryParse(_mgPerDlController.text) != _logEntry!.mgPerDl || double.tryParse(_mmolPerLController.text) != _logEntry!.mmolPerL || _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(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)); } void renderTabButtons(index) { if (_logEntry != null) { setState(() { switch (index) { case 1: actionButton = addMealButton; appBarActions = [refreshButton, closeButton]; bottomNav = null; break; case 2: actionButton = addBolusButton; 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: [ Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl || [ GlucoseDisplayMode.both, GlucoseDisplayMode.bothForDetail ].contains(Settings.glucoseDisplayMode) ? Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'mg/dl', suffixText: 'mg/dl', ), controller: _mgPerDlController, onChanged: (_) async { await Future.delayed( const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mgPerDl); }, keyboardType: const TextInputType.numberWithOptions(), validator: (value) { if (value!.trim().isEmpty && _mmolPerLController.text .trim() .isEmpty) { return 'How high is your blood sugar?'; } return null; }, ), ) : Container(), Settings.glucoseDisplayMode == GlucoseDisplayMode.both || Settings.glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mmolPerL), icon: const Icon(Icons.calculate), ) : Container(), Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL || Settings.glucoseDisplayMode == GlucoseDisplayMode.both || Settings.glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? Expanded( child: TextFormField( decoration: const InputDecoration( labelText: 'mmol/l', suffixText: 'mmol/l', ), controller: _mmolPerLController, onChanged: (_) async { await Future.delayed( const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mmolPerL); }, keyboardType: const TextInputType.numberWithOptions( decimal: true), validator: (value) { if (value!.trim().isEmpty && _mgPerDlController.text .trim() .isEmpty) { return 'How high is your blood sugar?'; } return null; }, ), ) : Container(), Settings.glucoseDisplayMode == GlucoseDisplayMode.both || Settings.glucoseDisplayMode == GlucoseDisplayMode.bothForDetail ? IconButton( onPressed: () => convertBetweenMgPerDlAndMmolPerL( calculateFrom: GlucoseMeasurement.mgPerDl), icon: const Icon(Icons.calculate), ) : Container(), ], ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', alignLabelWithHint: true, ), keyboardType: TextInputType.multiline, ), ], ), ]), ), ]; if (!_isNew) { tabs.add(LogMealListScreen( logEntry: _logEntry!, logMeals: _logMeals, reload: reload)); tabs.add(LogBolusListScreen( logEntry: _logEntry!, logBoli: _logBoli, 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: 'BOLI'), ], ), actions: appBarActions, ), drawer: const Navigation(currentLocation: LogEntryScreen.routeName), body: TabBarView( children: tabs, ), bottomNavigationBar: bottomNav, floatingActionButton: actionButton, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); }), ); } }