diameter/lib/screens/basal/basal_profile_detail.dart

399 lines
12 KiB
Dart
Raw Normal View History

2021-10-22 23:08:09 +00:00
import 'package:diameter/components/detail.dart';
import 'package:diameter/components/forms/boolean_form_field.dart';
import 'package:diameter/utils/dialog_utils.dart';
2021-10-22 23:08:09 +00:00
import 'package:diameter/models/basal.dart';
import 'package:diameter/models/settings.dart';
2021-10-22 23:08:09 +00:00
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';
2021-10-22 23:08:09 +00:00
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;
2021-10-22 23:08:09 +00:00
final bool active;
const BasalProfileDetailScreen({Key? key, this.active = false, this.id = 0})
2021-10-22 23:08:09 +00:00
: super(key: key);
@override
_BasalProfileDetailScreenState createState() =>
_BasalProfileDetailScreenState();
}
class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
BasalProfile? _basalProfile;
List<Basal> _basalRates = [];
bool _isNew = true;
2021-10-22 23:08:09 +00:00
final GlobalKey<FormState> _basalProfileForm = GlobalKey<FormState>();
2021-12-09 05:14:55 +00:00
final ScrollController _scrollController = ScrollController();
2021-10-22 23:08:09 +00:00
late FloatingActionButton addBasalButton;
late IconButton refreshButton;
late IconButton closeButton;
late DetailBottomRow detailBottomRow;
late DetailBottomRow detailBottomRowWhileSaving;
FloatingActionButton? actionButton;
List<Widget> appBarActions = [];
DetailBottomRow? bottomNav;
2021-10-22 23:08:09 +00:00
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 ?? '';
2021-10-22 23:08:09 +00:00
}
2021-10-22 23:08:09 +00:00
if (widget.active) {
_active = true;
}
addBasalButton = FloatingActionButton(
onPressed: handleAddNewBasal,
2021-10-22 23:08:09 +00:00
child: const Icon(Icons.add),
);
refreshButton = IconButton(
icon: const Icon(Icons.refresh),
onPressed: reload,
2021-10-22 23:08:09 +00:00
);
closeButton = IconButton(
onPressed: handleCancelAction,
icon: const Icon(Icons.close),
);
detailBottomRow = DetailBottomRow(
onCancel: handleCancelAction,
2021-12-10 05:42:20 +00:00
onAction: handleSaveAction,
onMiddleAction: () => handleSaveAction(close: true),
);
detailBottomRowWhileSaving = DetailBottomRow(
onCancel: handleCancelAction,
2021-12-10 05:42:20 +00:00
onAction: null,
);
2021-10-22 23:08:09 +00:00
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
2021-10-22 23:08:09 +00:00
}
@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;
}
2021-10-22 23:08:09 +00:00
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
Future<void> 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;
2021-12-10 05:42:20 +00:00
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);
2021-12-10 05:42:20 +00:00
} 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);
}
}
}
2021-12-10 05:42:20 +00:00
});
}
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BasalDetailScreen(
basalProfileId: widget.id,
suggestedStartTime: suggestedStartTime,
suggestedEndTime: suggestedEndTime,
);
},
),
2021-12-09 05:14:55 +00:00
).then((result) => reload(message: result?[0]));
}
2021-12-10 05:42:20 +00:00
void handleSaveAction({bool close = false}) async {
setState(() {
bottomNav = detailBottomRowWhileSaving;
});
2021-10-22 23:08:09 +00:00
if (_basalProfileForm.currentState!.validate()) {
await checkActiveProfiles();
2021-12-09 05:14:55 +00:00
BasalProfile basalProfile = BasalProfile(
id: widget.id,
name: _nameController.text,
active: _active,
notes: _notesController.text,
2021-12-09 05:14:55 +00:00
);
BasalProfile.put(basalProfile);
2021-12-10 05:42:20 +00:00
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');
}
}
2021-10-22 23:08:09 +00:00
}
setState(() {
bottomNav = detailBottomRow;
});
2021-10-22 23:08:09 +00:00
}
void handleCancelAction() {
if (Settings.get().showConfirmationDialogOnCancel &&
(_isNew &&
(_active != widget.active ||
2021-10-22 23:08:09 +00:00
_nameController.text != '' ||
_notesController.text != '')) ||
(!_isNew &&
(_basalProfile!.active != _active ||
_basalProfile!.name != _nameController.text ||
(_basalProfile!.notes ?? '') != _notesController.text))) {
DialogUtils.showCancelConfirmationDialog(
2021-10-22 23:08:09 +00:00
context: context,
isNew: _isNew,
2021-10-22 23:08:09 +00:00
onSave: handleSaveAction,
);
} else {
Navigator.pop(context);
}
}
void renderTabButtons(index) {
if (_basalProfile != null) {
2021-10-22 23:08:09 +00:00
setState(() {
switch (index) {
case 1:
actionButton = addBasalButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
2021-10-22 23:08:09 +00:00
break;
default:
actionButton = null;
appBarActions = [closeButton];
bottomNav = detailBottomRow;
2021-10-22 23:08:09 +00:00
}
});
}
}
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: _isNew ? 1 : 2,
2021-10-22 23:08:09 +00:00
child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() {
renderTabButtons(tabController.index);
2021-10-22 23:08:09 +00:00
});
List<Widget> tabs = [
2021-12-09 05:14:55 +00:00
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;
},
),
2021-12-09 05:14:55 +00:00
TextFormField(
keyboardType: TextInputType.multiline,
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes',
),
minLines: 2,
maxLines: 5,
),
2021-12-09 05:14:55 +00:00
BooleanFormField(
value: _active,
onChanged: (value) {
setState(() {
_active = value;
});
},
label: 'active',
),
],
),
],
),
),
),
];
if (!_isNew) {
2021-12-09 05:14:55 +00:00
tabs.add(BasalListScreen(
basalProfile: _basalProfile!,
basalRates: _basalRates,
reload: reload));
}
2021-10-22 23:08:09 +00:00
return Scaffold(
appBar: AppBar(
title: Text(_isNew ? 'New Basal Profile' : _basalProfile!.name),
bottom: _isNew
2021-10-22 23:08:09 +00:00
? 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,
2021-10-22 23:08:09 +00:00
floatingActionButton: actionButton,
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
2021-10-22 23:08:09 +00:00
);
}),
);
}
}