import 'package:diameter/components/detail.dart'; import 'package:diameter/components/forms/boolean_form_field.dart'; import 'package:diameter/utils/dialog_utils.dart'; import 'package:diameter/models/basal.dart'; import 'package:diameter/models/settings.dart'; import 'package:diameter/navigation.dart'; import 'package:diameter/screens/basal/basal_detail.dart'; import 'package:flutter/material.dart'; import 'package:diameter/components/forms/form_wrapper.dart'; import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/screens/basal/basal_list.dart'; class BasalProfileDetailScreen extends StatefulWidget { static const String routeName = '/basal-profile'; final int id; final bool active; const BasalProfileDetailScreen({Key? key, this.active = false, this.id = 0}) : super(key: key); @override _BasalProfileDetailScreenState createState() => _BasalProfileDetailScreenState(); } class _BasalProfileDetailScreenState extends State { BasalProfile? _basalProfile; List _basalRates = []; bool _isNew = true; final GlobalKey _basalProfileForm = GlobalKey(); final ScrollController _scrollController = ScrollController(); late FloatingActionButton addBasalButton; late IconButton refreshButton; late IconButton closeButton; late DetailBottomRow detailBottomRow; late DetailBottomRow detailBottomRowWhileSaving; FloatingActionButton? actionButton; List appBarActions = []; DetailBottomRow? bottomNav; final _nameController = TextEditingController(text: ''); final _notesController = TextEditingController(text: ''); bool _active = false; @override void initState() { super.initState(); reload(); if (_basalProfile != null) { _nameController.text = _basalProfile!.name; _active = _basalProfile!.active; _notesController.text = _basalProfile!.notes ?? ''; } if (widget.active) { _active = true; } addBasalButton = FloatingActionButton( onPressed: handleAddNewBasal, 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; } @override void dispose() { _scrollController.dispose(); _nameController.dispose(); _notesController.dispose(); super.dispose(); } void reload({String? message}) { if (widget.id != 0) { setState(() { _basalProfile = BasalProfile.get(widget.id); _basalRates = Basal.getAllForProfile(widget.id); }); _isNew = _basalProfile == null; } setState(() { if (message != null) { var snackBar = SnackBar( content: Text(message), duration: const Duration(seconds: 2), ); ScaffoldMessenger.of(context) ..removeCurrentSnackBar() ..showSnackBar(snackBar); } }); } Future checkActiveProfiles() async { int _activeCount = BasalProfile.activeCount(); if (_active && (_activeCount > 1 || _activeCount == 1 && (_isNew || !_basalProfile!.active))) { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: const Text( 'There are already one or more active profiles. What would you like to do?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), child: const Text('IGNORE'), ), TextButton( onPressed: () => Navigator.pop(context, 1), child: Text('DEACTIVATE ${_nameController.text.toUpperCase()}'), ), ElevatedButton( onPressed: () => Navigator.pop(context, 2), child: const Text('DEACTIVATE ALL OTHERS'), ) ], ); }).then((value) async { if (value == 1) { setState(() { _active = false; }); } else if (value == 2) { BasalProfile.setAllInactive(); } }); } else if (!_active && ((_isNew && _activeCount == 0) || (!_isNew && _activeCount == 1 && _basalProfile!.active))) { await showDialog( context: context, builder: (BuildContext context) { return AlertDialog( content: const Text( 'There is currently no active profile. Would you like to set this one as active?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, 0), child: const Text('IGNORE'), ), TextButton( onPressed: () => Navigator.pop(context, 1), child: const Text('ACTIVATE THIS PROFILE'), ), ], ); }).then((value) { if (value == 1) { setState(() { _active = true; }); } }); } } void handleAddNewBasal() async { TimeOfDay? suggestedStartTime; TimeOfDay? suggestedEndTime; if (_basalRates.isEmpty) { suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); } else { _basalRates.asMap().forEach((index, basal) { if (suggestedStartTime == null && suggestedEndTime == null) { if (index == 0 && (basal.startTime.hour != 0 || basal.startTime.minute != 0)) { suggestedStartTime = const TimeOfDay(hour: 0, minute: 0); suggestedEndTime = TimeOfDay.fromDateTime(basal.startTime); } else if ((index == _basalRates.length - 1) && (basal.endTime.hour != 0 || basal.endTime.minute != 0)) { suggestedStartTime = TimeOfDay.fromDateTime(basal.endTime); suggestedEndTime = const TimeOfDay(hour: 0, minute: 0); } else if (index != 0) { var lastEndTime = _basalRates[index - 1].endTime; if (basal.startTime.isAfter(lastEndTime)) { suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime); suggestedEndTime = TimeOfDay.fromDateTime(basal.startTime); } } } }); } Navigator.push( context, MaterialPageRoute( builder: (context) { return BasalDetailScreen( basalProfileId: widget.id, suggestedStartTime: suggestedStartTime, suggestedEndTime: suggestedEndTime, ); }, ), ).then((result) => reload(message: result?[0])); } void handleSaveAction({bool close = false}) async { setState(() { bottomNav = detailBottomRowWhileSaving; }); if (_basalProfileForm.currentState!.validate()) { await checkActiveProfiles(); BasalProfile basalProfile = BasalProfile( id: widget.id, name: _nameController.text, active: _active, notes: _notesController.text, ); BasalProfile.put(basalProfile); if (close) { Navigator.pop(context, ['${_isNew ? 'New' : ''} Basal Profile saved', basalProfile]); } else { if (_isNew) { Navigator.push( context, MaterialPageRoute( builder: (context) => BasalProfileDetailScreen(id: basalProfile.id), ), ).then((result) => Navigator.pop(context, result)); } else { reload(message: 'Basal Profile saved'); } } } setState(() { bottomNav = detailBottomRow; }); } void handleCancelAction() { if (Settings.get().showConfirmationDialogOnCancel && (_isNew && (_active != widget.active || _nameController.text != '' || _notesController.text != '')) || (!_isNew && (_basalProfile!.active != _active || _basalProfile!.name != _nameController.text || (_basalProfile!.notes ?? '') != _notesController.text))) { DialogUtils.showCancelConfirmationDialog( context: context, isNew: _isNew, onSave: handleSaveAction, ); } else { Navigator.pop(context); } } void renderTabButtons(index) { if (_basalProfile != null) { setState(() { switch (index) { case 1: actionButton = addBasalButton; appBarActions = [refreshButton, closeButton]; bottomNav = null; break; default: actionButton = null; appBarActions = [closeButton]; bottomNav = detailBottomRow; } }); } } @override Widget build(BuildContext context) { return DefaultTabController( length: _isNew ? 1 : 2, 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: _basalProfileForm, fields: [ TextFormField( controller: _nameController, decoration: const InputDecoration( labelText: 'Name', ), validator: (value) { if (value!.trim().isEmpty) { return 'Empty title'; } return null; }, ), TextFormField( keyboardType: TextInputType.multiline, controller: _notesController, decoration: const InputDecoration( labelText: 'Notes', ), minLines: 2, maxLines: 5, ), BooleanFormField( value: _active, onChanged: (value) { setState(() { _active = value; }); }, label: 'active', ), ], ), ], ), ), ), ]; if (!_isNew) { tabs.add(BasalListScreen( basalProfile: _basalProfile!, basalRates: _basalRates, reload: reload)); } return Scaffold( appBar: AppBar( title: Text(_isNew ? 'New Basal Profile' : _basalProfile!.name), bottom: _isNew ? PreferredSize(child: Container(), preferredSize: Size.zero) : const TabBar( tabs: [ Tab(text: 'PROFILE'), Tab(text: 'RATES'), ], ), actions: appBarActions, ), drawer: const Navigation( currentLocation: BasalProfileDetailScreen.routeName), body: TabBarView(children: tabs), bottomNavigationBar: bottomNav, floatingActionButton: actionButton, floatingActionButtonLocation: FloatingActionButtonLocation.endFloat, ); }), ); } }