434 lines
14 KiB
Dart
434 lines
14 KiB
Dart
import 'package:diameter/components/detail.dart';
|
|
import 'package:diameter/components/forms/boolean_form_field.dart';
|
|
import 'package:diameter/localization_keys.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';
|
|
import 'package:flutter_translate/flutter_translate.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<BasalProfileDetailScreen> {
|
|
BasalProfile? _basalProfile;
|
|
List<Basal> _basalRates = [];
|
|
bool _isNew = true;
|
|
|
|
final GlobalKey<FormState> _basalProfileForm = GlobalKey<FormState>();
|
|
final ScrollController _scrollController = ScrollController();
|
|
|
|
late FloatingActionButton addBasalButton;
|
|
late IconButton refreshButton;
|
|
late IconButton closeButton;
|
|
late DetailBottomRow detailBottomRow;
|
|
late DetailBottomRow detailBottomRowWhileSaving;
|
|
|
|
FloatingActionButton? actionButton;
|
|
List<Widget> 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<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: Text(translate(LocalizationKeys.basalProfile_warnings_activeAlreadySet)),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, 0),
|
|
child: Text(translate(LocalizationKeys.basalProfile_warnings_resolve_ignore).toUpperCase()),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, 1),
|
|
child:
|
|
Text(
|
|
translate(
|
|
LocalizationKeys.basalProfile_warnings_resolve_deactivateProfile,
|
|
args: {
|
|
"profileName": _nameController.text,
|
|
}
|
|
).toUpperCase()
|
|
),
|
|
),
|
|
ElevatedButton(
|
|
onPressed: () => Navigator.pop(context, 2),
|
|
child: Text(
|
|
translate(
|
|
LocalizationKeys.basalProfile_warnings_resolve_deactivateOthers,
|
|
).toUpperCase()),
|
|
)
|
|
],
|
|
);
|
|
}).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: Text(
|
|
translate(
|
|
LocalizationKeys.basalProfile_warnings_noActiveOnCreate,
|
|
).toUpperCase()),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, 0),
|
|
child: Text(
|
|
translate(
|
|
LocalizationKeys.basalProfile_warnings_resolve_ignore,
|
|
).toUpperCase()),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(context, 1),
|
|
child: Text(
|
|
translate(
|
|
LocalizationKeys.basalProfile_warnings_resolve_activateCurrent,
|
|
).toUpperCase()
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}).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,
|
|
[
|
|
translate(LocalizationKeys.basalProfile_saved, args: {
|
|
"status": _isNew ? '${translate(LocalizationKeys.basalProfile_new)} ' : ''
|
|
}),
|
|
]);
|
|
} else {
|
|
if (_isNew) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) =>
|
|
BasalProfileDetailScreen(id: basalProfile.id),
|
|
),
|
|
).then((result) => Navigator.pop(context, result));
|
|
} else {
|
|
reload(message: translate(LocalizationKeys.basalProfile_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<Widget> tabs = [
|
|
Scrollbar(
|
|
controller: _scrollController,
|
|
child: SingleChildScrollView(
|
|
controller: _scrollController,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
FormWrapper(
|
|
formState: _basalProfileForm,
|
|
fields: [
|
|
TextFormField(
|
|
controller: _nameController,
|
|
decoration: InputDecoration(
|
|
labelText: translate(LocalizationKeys.basalProfile_fields_name),
|
|
),
|
|
validator: (value) {
|
|
if (value!.trim().isEmpty) {
|
|
return translate(LocalizationKeys.basalProfile_fields_validators_name);
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
TextFormField(
|
|
keyboardType: TextInputType.multiline,
|
|
controller: _notesController,
|
|
decoration: InputDecoration(
|
|
labelText: translate(LocalizationKeys.basalProfile_fields_notes),
|
|
),
|
|
minLines: 2,
|
|
maxLines: 5,
|
|
),
|
|
BooleanFormField(
|
|
value: _active,
|
|
onChanged: (value) {
|
|
setState(() {
|
|
_active = value;
|
|
});
|
|
},
|
|
label: translate(LocalizationKeys.basalProfile_fields_active),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
];
|
|
|
|
if (!_isNew) {
|
|
tabs.add(BasalListScreen(
|
|
basalProfile: _basalProfile!,
|
|
basalRates: _basalRates,
|
|
reload: reload));
|
|
}
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(translate(LocalizationKeys.basalProfile_detail_title, args: {
|
|
"status": _isNew ? '${translate(LocalizationKeys.basalProfile_new)} ' : '',
|
|
"profileName": _basalProfile!.name,
|
|
})),
|
|
bottom: _isNew
|
|
? PreferredSize(child: Container(), preferredSize: Size.zero)
|
|
: TabBar(
|
|
tabs: [
|
|
Tab(
|
|
text: translate(
|
|
LocalizationKeys.basalProfile_detail_tabs_profile,
|
|
).toUpperCase(),
|
|
),
|
|
Tab(
|
|
text: translate(
|
|
LocalizationKeys.basalProfile_detail_tabs_rates,
|
|
).toUpperCase(),
|
|
),
|
|
],
|
|
),
|
|
actions: appBarActions,
|
|
),
|
|
drawer: const Navigation(
|
|
currentLocation: BasalProfileDetailScreen.routeName),
|
|
body: TabBarView(children: tabs),
|
|
bottomNavigationBar: bottomNav,
|
|
floatingActionButton: actionButton,
|
|
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
|
);
|
|
}),
|
|
);
|
|
}
|
|
}
|