import 'package:diameter/components/detail.dart'; import 'package:diameter/components/dialogs.dart'; import 'package:diameter/config.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/utils/date_time_utils.dart'; import 'package:flutter/material.dart'; import 'package:diameter/components/forms.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/basal_profile.dart'; class BasalDetailScreen extends StatefulWidget { static const String routeName = '/basal'; final int basalProfileId; final int id; final TimeOfDay? suggestedStartTime; final TimeOfDay? suggestedEndTime; const BasalDetailScreen( {Key? key, this.basalProfileId = 0, this.id = 0, this.suggestedStartTime, this.suggestedEndTime}) : super(key: key); @override _BasalDetailScreenState createState() => _BasalDetailScreenState(); } class _BasalDetailScreenState extends State { Basal? _basal; bool _isNew = true; bool _isSaving = false; final GlobalKey _basalForm = GlobalKey(); TimeOfDay _startTime = const TimeOfDay(hour: 0, minute: 0); TimeOfDay _endTime = const TimeOfDay(hour: 0, minute: 0); final _startTimeController = TextEditingController(text: ''); final _endTimeController = TextEditingController(text: ''); final _unitsController = TextEditingController(text: ''); @override void initState() { super.initState(); reload(); if (widget.suggestedStartTime != null) { _startTime = widget.suggestedStartTime!; } if (widget.suggestedEndTime != null) { _endTime = widget.suggestedEndTime!; } if (_basal != null) { _startTime = TimeOfDay.fromDateTime(_basal!.startTime); _endTime = TimeOfDay.fromDateTime(_basal!.endTime); _unitsController.text = _basal!.units.toString(); } updateStartTime(); updateEndTime(); } void reload() { if (widget.id != 0) { setState(() { _basal = Basal.get(widget.id); }); } _isNew = _basal == null; } void updateStartTime() { _startTimeController.text = DateTimeUtils.displayTimeOfDay(_startTime); } void updateEndTime() { _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); } Future validateTimePeriod() async { String? error; List basalRates = Basal.getAllForProfile(widget.basalProfileId); // TODO use a query for the following checks instead? // check for duplicates if (basalRates .where((other) => (widget.id != other.id) && _startTime.hour == other.startTime.hour && _startTime.minute == other.startTime.minute) .isNotEmpty) { error = 'There\'s already a rate with this start time.'; } if (basalRates .where((other) => (widget.id != other.id) && DateTimeUtils.convertTimeOfDayToDateTime(_startTime) .isBefore(other.startTime) && DateTimeUtils.convertTimeOfDayToDateTime(_endTime) .isAfter(other.startTime)) .isNotEmpty) { error = 'This rate\'s time period overlaps with another one.'; } return error == null ? null : showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: Text(error!), actions: [ TextButton( onPressed: () => Navigator.pop(context, 'CANCEL'), child: const Text('GO BACK TO EDITING'), ), ElevatedButton( onPressed: () => Navigator.pop(context, 'CONFIRM'), child: const Text('SAVE AS IS'), ), ], ); }); } void handleSaveAction() async { setState(() { _isSaving = true; }); if (_basalForm.currentState!.validate()) { await validateTimePeriod().then((value) async { if (value != 'CANCEL') { Basal basal = Basal( id: widget.id, startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime), endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime), units: double.parse(_unitsController.text), ); basal.basalProfile.targetId = widget.basalProfileId; Basal.put(basal); Navigator.pop(context, '${_isNew ? 'New' : ''} Basal Rate saved'); } }); } setState(() { _isSaving = false; }); } void handleCancelAction() { if (showConfirmationDialogOnCancel && ((_isNew && (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) || _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) || _startTime.minute != (widget.suggestedStartTime?.minute ?? 0) || _endTime.minute != (widget.suggestedEndTime?.minute ?? 0) || double.tryParse(_unitsController.text) != null)) || (!_isNew && (TimeOfDay.fromDateTime(_basal!.startTime) != _startTime || TimeOfDay.fromDateTime(_basal!.endTime) != _endTime || (double.tryParse(_unitsController.text) ?? 0) != _basal!.units)))) { 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' : 'Edit'} Basal Rate for ${BasalProfile.get(widget.basalProfileId)?.name}'), ), drawer: const Navigation(currentLocation: BasalDetailScreen.routeName), body: Column( children: [ StyledForm( formState: _basalForm, fields: [ Row( children: [ Expanded( child: Padding( padding: const EdgeInsets.only(right: 5), child: StyledTimeOfDayFormField( label: 'Start Time', controller: _startTimeController, time: _startTime, onChanged: (newStartTime) { if (newStartTime != null) { setState(() { _startTime = newStartTime; }); updateStartTime(); } }, ), ), ), Expanded( child: Padding( padding: const EdgeInsets.only(left: 5), child: StyledTimeOfDayFormField( label: 'End Time', controller: _endTimeController, time: _endTime, onChanged: (newEndTime) { if (newEndTime != null) { setState(() { _endTime = newEndTime; }); updateEndTime(); } }, ), ), ), ], ), TextFormField( controller: _unitsController, keyboardType: const TextInputType.numberWithOptions(decimal: true), decoration: const InputDecoration( labelText: 'Units', suffixText: 'U', ), validator: (value) { if (value!.trim().isEmpty) { return 'Empty amount of units'; } return null; }, ), ], ), ], ), bottomNavigationBar: DetailBottomRow( onCancel: handleCancelAction, onSave: _isSaving ? null : handleSaveAction, ), ); } }