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'; import 'dart:math' as math; 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; final GlobalKey logEntryForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); late DateTime _time; double? _glucoseTrend; 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; late DetailBottomRow detailBottomRowWhileSaving; 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, onAction: handleSaveAction, onMiddleAction: () => handleSaveAction(close: true), ); detailBottomRowWhileSaving = DetailBottomRow( onCancel: handleCancelAction, onAction: null, ); actionButton = null; appBarActions = [closeButton]; bottomNav = detailBottomRow; if (_logEntry != null) { _time = _logEntry!.time; _mgPerDlController.text = (_logEntry!.mgPerDl ?? '').toString(); _mmolPerLController.text = (_logEntry!.mmolPerL ?? '').toString(); _glucoseTrend = _logEntry!.glucoseTrend; _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() { int? mgPerDl; double? mmolPerL; if (Settings.glucoseMeasurement != GlucoseMeasurement.mmolPerL && _mgPerDlController.text != '') { mgPerDl = int.tryParse(_mgPerDlController.text); setState(() { _mmolPerLController.text = Utils.convertMgPerDlToMmolPerL(mgPerDl ?? 0).toString(); }); } if (Settings.glucoseMeasurement != GlucoseMeasurement.mgPerDl && _mmolPerLController.text != '') { mmolPerL = double.tryParse(_mmolPerLController.text); setState(() { _mgPerDlController.text = Utils.convertMmolPerLToMgPerDl(mmolPerL ?? 0).toString(); }); } } void handleSaveAction({bool close = false}) async { setState(() { bottomNav = detailBottomRowWhileSaving; }); if (logEntryForm.currentState!.validate()) { LogEntry logEntry = LogEntry( id: widget.id, time: _time, mgPerDl: int.tryParse(_mgPerDlController.text), mmolPerL: double.tryParse(_mmolPerLController.text), glucoseTrend: _glucoseTrend, notes: _notesController.text, ); LogEntry.put(logEntry); if (close) { Navigator.pop( context, ['${_isNew ? 'New' : ''} Log Entry Saved', logEntry]); } else { if (_isNew) { Navigator.push( context, MaterialPageRoute( builder: (context) => LogEntryScreen(id: logEntry.id), ), ).then((result) => Navigator.pop(context, result)); } else { reload(message: 'Log Entry Saved'); } } } setState(() { bottomNav = detailBottomRow; }); } 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((result) => reload(message: result?[0])); } void handleAddNewBolus() async { Navigator.push( context, MaterialPageRoute( builder: (context) { return LogBolusDetailScreen(logEntryId: _logEntry!.id); }, ), ).then((result) => reload(message: result?[0])); } 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 = [ Scrollbar( controller: _scrollController, child: SingleChildScrollView( controller: _scrollController, 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', ), readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mmolPerL, controller: _mgPerDlController, onChanged: (_) async { await Future.delayed( const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL(); }, keyboardType: const TextInputType .numberWithOptions(), validator: (value) { if (value!.trim().isEmpty && _mmolPerLController.text .trim() .isEmpty) { return 'How high is your blood sugar?'; } return null; }, ), ) : Container(), [ GlucoseDisplayMode.both, GlucoseDisplayMode.bothForDetail ].contains(Settings.glucoseDisplayMode) ? const SizedBox(width: 10.0) : 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', ), readOnly: Settings.glucoseMeasurement == GlucoseMeasurement.mgPerDl, controller: _mmolPerLController, onChanged: (_) async { await Future.delayed( const Duration(seconds: 1)); convertBetweenMgPerDlAndMmolPerL(); }, 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(), Transform.rotate( angle: (_glucoseTrend ?? 90) * math.pi / 180, child: IconButton( onPressed: () => setState(() { _glucoseTrend = (_glucoseTrend ?? -45) + 45; if (_glucoseTrend! > 180) { _glucoseTrend = null; } }), icon: Icon(Icons.arrow_upward, color: _glucoseTrend != null ? Theme.of(context).iconTheme.color : Theme.of(context).disabledColor), ), ), ], ), TextFormField( controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', ), keyboardType: TextInputType.multiline, minLines: 2, maxLines: 5, ), ], ), ]), ), ), ]; 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, ); }), ); } }