validate time periods for bolus and basal and make auto suggestions

This commit is contained in:
spinel 2021-10-27 01:25:40 +02:00
parent db023a94cf
commit 53fb4ac7fc
13 changed files with 555 additions and 353 deletions

View File

@ -1,5 +1,5 @@
import 'package:diameter/utils/date_time_utils.dart'; // import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk.dart'; import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';
import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/components/data_table.dart'; import 'package:diameter/components/data_table.dart';
@ -14,8 +14,8 @@ class Basal extends DataTableContent {
Basal(ParseObject? object) { Basal(ParseObject? object) {
if (object != null) { if (object != null) {
objectId = object.get<String>('objectId'); objectId = object.get<String>('objectId');
startTime = object.get<DateTime>('startTime')!; startTime = object.get<DateTime>('startTime')!.toLocal();
endTime = object.get<DateTime>('endTime')!; endTime = object.get<DateTime>('endTime')!.toLocal();
units = object.get<num>('units')! / 100; units = object.get<num>('units')! / 100;
basalProfile = basalProfile =
object.get<ParseObject>('basalProfile')!.get<String>('objectId')!; object.get<ParseObject>('basalProfile')!.get<String>('objectId')!;
@ -40,7 +40,8 @@ class Basal extends DataTableContent {
..whereEqualTo( ..whereEqualTo(
'basalProfile', 'basalProfile',
(ParseObject('BasalProfile')..objectId = basalProfile.objectId!) (ParseObject('BasalProfile')..objectId = basalProfile.objectId!)
.toPointer()); .toPointer())
..orderByAscending('startTime');
final ParseResponse apiResponse = await query.query(); final ParseResponse apiResponse = await query.query();
if (apiResponse.success && apiResponse.results != null) { if (apiResponse.success && apiResponse.results != null) {
@ -57,8 +58,8 @@ class Basal extends DataTableContent {
required String basalProfile, required String basalProfile,
}) async { }) async {
final basal = ParseObject('Basal') final basal = ParseObject('Basal')
..set('startTime', startTime) ..set('startTime', startTime.toUtc())
..set('endTime', endTime) ..set('endTime', endTime.toUtc())
..set('units', units * 100) ..set('units', units * 100)
..set('basalProfile', ..set('basalProfile',
(ParseObject('BasalProfile')..objectId = basalProfile).toPointer()); (ParseObject('BasalProfile')..objectId = basalProfile).toPointer());
@ -73,10 +74,10 @@ class Basal extends DataTableContent {
}) async { }) async {
var basal = ParseObject('Basal')..objectId = objectId; var basal = ParseObject('Basal')..objectId = objectId;
if (startTime != null) { if (startTime != null) {
basal.set('startTime', startTime); basal.set('startTime', startTime.toUtc());
} }
if (endTime != null) { if (endTime != null) {
basal.set('endTime', endTime); basal.set('endTime', endTime.toUtc());
} }
if (units != null) { if (units != null) {
basal.set('units', units * 100); basal.set('units', units * 100);
@ -88,27 +89,27 @@ class Basal extends DataTableContent {
var basal = ParseObject('Basal')..objectId = objectId; var basal = ParseObject('Basal')..objectId = objectId;
await basal.delete(); await basal.delete();
} }
//
@override // @override
List<DataCell> asDataTableCells(List<Widget>? actions) { // List<DataCell> asDataTableCells(List<Widget>? actions) {
return [ // return [
DataCell(Text(DateTimeUtils.displayTime(startTime))), // DataCell(Text(DateTimeUtils.displayTime(startTime))),
DataCell(Text(DateTimeUtils.displayTime(endTime))), // DataCell(Text(DateTimeUtils.displayTime(endTime))),
DataCell(Text('${units.toString()} U')), // DataCell(Text('${units.toString()} U')),
DataCell( // DataCell(
Row( // Row(
children: actions ?? [], // children: actions ?? [],
), // ),
), // ),
]; // ];
} // }
//
static List<DataColumn> asDataTableColumns() { // static List<DataColumn> asDataTableColumns() {
return [ // return [
const DataColumn(label: Expanded(child: Text('Start Time'))), // const DataColumn(label: Expanded(child: Text('Start Time'))),
const DataColumn(label: Expanded(child: Text('End Time'))), // const DataColumn(label: Expanded(child: Text('End Time'))),
const DataColumn(label: Expanded(child: Text('Units'))), // const DataColumn(label: Expanded(child: Text('Units'))),
const DataColumn(label: Expanded(child: Text('Actions'))), // const DataColumn(label: Expanded(child: Text('Actions'))),
]; // ];
} // }
} }

View File

@ -1,8 +1,8 @@
import 'package:diameter/config.dart'; // import 'package:diameter/config.dart';
import 'package:diameter/settings.dart'; // import 'package:diameter/settings.dart';
import 'package:diameter/utils/date_time_utils.dart'; // import 'package:diameter/utils/date_time_utils.dart';
import 'package:diameter/utils/utils.dart'; import 'package:diameter/utils/utils.dart';
import 'package:flutter/material.dart'; // import 'package:flutter/material.dart';
import 'package:parse_server_sdk_flutter/parse_server_sdk.dart'; import 'package:parse_server_sdk_flutter/parse_server_sdk.dart';
import 'package:diameter/components/data_table.dart'; import 'package:diameter/components/data_table.dart';
import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/bolus_profile.dart';
@ -20,8 +20,8 @@ class Bolus extends DataTableContent {
Bolus(ParseObject? object) { Bolus(ParseObject? object) {
if (object != null) { if (object != null) {
objectId = object.get<String>('objectId'); objectId = object.get<String>('objectId');
startTime = object.get<DateTime>('startTime')!; startTime = object.get<DateTime>('startTime')!.toLocal();
endTime = object.get<DateTime>('endTime')!; endTime = object.get<DateTime>('endTime')!.toLocal();
units = object.get<num>('units')! / 100; units = object.get<num>('units')! / 100;
carbs = object.get<num>('carbs')!.toDouble(); carbs = object.get<num>('carbs')!.toDouble();
mgPerDl = object.get<num>('mgPerDl') != null mgPerDl = object.get<num>('mgPerDl') != null
@ -39,6 +39,7 @@ class Bolus extends DataTableContent {
QueryBuilder<ParseObject> query = QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('Bolus')) QueryBuilder<ParseObject>(ParseObject('Bolus'))
..whereEqualTo('objectId', objectId); ..whereEqualTo('objectId', objectId);
final ParseResponse apiResponse = await query.query(); final ParseResponse apiResponse = await query.query();
if (apiResponse.success && apiResponse.results != null) { if (apiResponse.success && apiResponse.results != null) {
@ -53,7 +54,8 @@ class Bolus extends DataTableContent {
..whereEqualTo( ..whereEqualTo(
'bolusProfile', 'bolusProfile',
(ParseObject('BolusProfile')..objectId = bolusProfile.objectId!) (ParseObject('BolusProfile')..objectId = bolusProfile.objectId!)
.toPointer()); .toPointer())
..orderByAscending('startTime');
final ParseResponse apiResponse = await query.query(); final ParseResponse apiResponse = await query.query();
if (apiResponse.success && apiResponse.results != null) { if (apiResponse.success && apiResponse.results != null) {
@ -73,8 +75,8 @@ class Bolus extends DataTableContent {
required String bolusProfile, required String bolusProfile,
}) async { }) async {
final bolus = ParseObject('Bolus') final bolus = ParseObject('Bolus')
..set('startTime', startTime) ..set('startTime', startTime.toUtc())
..set('endTime', endTime) ..set('endTime', endTime.toUtc())
..set('units', units * 100) ..set('units', units * 100)
..set('carbs', carbs.round()) ..set('carbs', carbs.round())
..set('bolusProfile', ..set('bolusProfile',
@ -105,10 +107,10 @@ class Bolus extends DataTableContent {
}) async { }) async {
var bolus = ParseObject('Bolus')..objectId = objectId; var bolus = ParseObject('Bolus')..objectId = objectId;
if (startTime != null) { if (startTime != null) {
bolus.set('startTime', startTime); bolus.set('startTime', startTime.toUtc());
} }
if (endTime != null) { if (endTime != null) {
bolus.set('endTime', endTime); bolus.set('endTime', endTime.toUtc());
} }
if (units != null) { if (units != null) {
bolus.set('units', units * 100); bolus.set('units', units * 100);
@ -137,63 +139,63 @@ class Bolus extends DataTableContent {
var bolus = ParseObject('Bolus')..objectId = objectId; var bolus = ParseObject('Bolus')..objectId = objectId;
await bolus.delete(); await bolus.delete();
} }
//
@override // @override
List<DataCell> asDataTableCells(List<Widget>? actions) { // List<DataCell> asDataTableCells(List<Widget>? actions) {
var cols = [ // var cols = [
DataCell(Text(DateTimeUtils.displayTime(startTime))), // DataCell(Text(DateTimeUtils.displayTime(startTime))),
DataCell(Text(DateTimeUtils.displayTime(endTime))), // DataCell(Text(DateTimeUtils.displayTime(endTime))),
DataCell(Text('${units.toString()} U')), // DataCell(Text('${units.toString()} U')),
DataCell(Text('${carbs.toString()} g')), // DataCell(Text('${carbs.toString()} g')),
]; // ];
//
if (glucoseMeasurement == GlucoseMeasurement.mgPerDl || // if (glucoseMeasurement == GlucoseMeasurement.mgPerDl ||
glucoseDisplayMode == GlucoseDisplayMode.both || // glucoseDisplayMode == GlucoseDisplayMode.both ||
glucoseDisplayMode == GlucoseDisplayMode.bothForList) { // glucoseDisplayMode == GlucoseDisplayMode.bothForList) {
cols.add(DataCell(Text('${mgPerDl.toString()} mg/dl'))); // cols.add(DataCell(Text('${mgPerDl.toString()} mg/dl')));
} // }
//
if (glucoseMeasurement == GlucoseMeasurement.mmolPerL || // if (glucoseMeasurement == GlucoseMeasurement.mmolPerL ||
glucoseDisplayMode == GlucoseDisplayMode.both || // glucoseDisplayMode == GlucoseDisplayMode.both ||
glucoseDisplayMode == GlucoseDisplayMode.bothForList) { // glucoseDisplayMode == GlucoseDisplayMode.bothForList) {
cols.add(DataCell(Text('${mmolPerL.toString()} mmol/l'))); // cols.add(DataCell(Text('${mmolPerL.toString()} mmol/l')));
} // }
//
cols.add( // cols.add(
DataCell( // DataCell(
Row( // Row(
children: actions ?? [], // children: actions ?? [],
), // ),
), // ),
); // );
//
return cols; // return cols;
} // }
//
static List<DataColumn> asDataTableColumns() { // static List<DataColumn> asDataTableColumns() {
var cols = [ // var cols = [
const DataColumn(label: Expanded(child: Text('Start Time'))), // const DataColumn(label: Expanded(child: Text('Start Time'))),
const DataColumn(label: Expanded(child: Text('End Time'))), // const DataColumn(label: Expanded(child: Text('End Time'))),
const DataColumn(label: Expanded(child: Text('Units'))), // const DataColumn(label: Expanded(child: Text('Units'))),
const DataColumn(label: Expanded(child: Text('per Carbs'))), // const DataColumn(label: Expanded(child: Text('per Carbs'))),
]; // ];
//
if (glucoseMeasurement == GlucoseMeasurement.mgPerDl || // if (glucoseMeasurement == GlucoseMeasurement.mgPerDl ||
glucoseDisplayMode == GlucoseDisplayMode.both || // glucoseDisplayMode == GlucoseDisplayMode.both ||
glucoseDisplayMode == GlucoseDisplayMode.bothForList) { // glucoseDisplayMode == GlucoseDisplayMode.bothForList) {
cols.add(const DataColumn(label: Expanded(child: Text('per mg/dl')))); // cols.add(const DataColumn(label: Expanded(child: Text('per mg/dl'))));
} // }
//
if (glucoseMeasurement == GlucoseMeasurement.mmolPerL || // if (glucoseMeasurement == GlucoseMeasurement.mmolPerL ||
glucoseDisplayMode == GlucoseDisplayMode.both || // glucoseDisplayMode == GlucoseDisplayMode.both ||
glucoseDisplayMode == GlucoseDisplayMode.bothForList) { // glucoseDisplayMode == GlucoseDisplayMode.bothForList) {
cols.add(const DataColumn(label: Expanded(child: Text('per mmol/l')))); // cols.add(const DataColumn(label: Expanded(child: Text('per mmol/l'))));
} // }
//
cols.add( // cols.add(
const DataColumn(label: Expanded(child: Text('Actions'))), // const DataColumn(label: Expanded(child: Text('Actions'))),
); // );
//
return cols; // return cols;
} // }
} }

View File

@ -20,7 +20,7 @@ class LogEntry {
LogEntry(ParseObject object) { LogEntry(ParseObject object) {
objectId = object.get<String>('objectId'); objectId = object.get<String>('objectId');
time = object.get<DateTime>('time')!; time = object.get<DateTime>('time')!.toLocal();
mgPerDl = object.get<num>('mgPerDl') != null mgPerDl = object.get<num>('mgPerDl') != null
? object.get<num>('mgPerDl')!.toInt() ? object.get<num>('mgPerDl')!.toInt()
: null; : null;
@ -58,8 +58,8 @@ class LogEntry {
static Future<List<LogEntry>> fetchAllForRange(DateTimeRange range) async { static Future<List<LogEntry>> fetchAllForRange(DateTimeRange range) async {
QueryBuilder<ParseObject> query = QueryBuilder<ParseObject> query =
QueryBuilder<ParseObject>(ParseObject('LogEntry')) QueryBuilder<ParseObject>(ParseObject('LogEntry'))
..whereGreaterThanOrEqualsTo('time', range.start) ..whereGreaterThanOrEqualsTo('time', range.start.toUtc())
..whereLessThanOrEqualTo('time', range.end) ..whereLessThanOrEqualTo('time', range.end.toUtc())
..orderByAscending('time'); ..orderByAscending('time');
final ParseResponse apiResponse = await query.query(); final ParseResponse apiResponse = await query.query();
@ -110,7 +110,7 @@ class LogEntry {
String? notes, String? notes,
}) async { }) async {
final logEntry = ParseObject('LogEntry') final logEntry = ParseObject('LogEntry')
..set('time', time) ..set('time', time.toUtc())
..set('bolusGlucose', bolusGlucose) ..set('bolusGlucose', bolusGlucose)
..set('delayedBolusDuration', delayedBolusDuration) ..set('delayedBolusDuration', delayedBolusDuration)
..set('delayedBolusRatio', delayedBolusRatio) ..set('delayedBolusRatio', delayedBolusRatio)
@ -145,7 +145,7 @@ class LogEntry {
final logEntry = ParseObject('LogEntry'); final logEntry = ParseObject('LogEntry');
if (time != null) { if (time != null) {
logEntry.set('time', time); logEntry.set('time', time.toUtc());
} }
if (bolusGlucose != null) { if (bolusGlucose != null) {

View File

@ -23,8 +23,8 @@ class LogEvent extends DataTableContent {
object.get<ParseObject>('endLogEntry')?.get<String>('objectId'); object.get<ParseObject>('endLogEntry')?.get<String>('objectId');
eventType = eventType =
object.get<ParseObject>('eventType')!.get<String>('objectId')!; object.get<ParseObject>('eventType')!.get<String>('objectId')!;
time = object.get<DateTime>('time')!; time = object.get<DateTime>('time')!.toLocal();
endTime = object.get<DateTime>('endTime'); endTime = object.get<DateTime>('endTime')?.toLocal();
hasEndTime = object.get<bool>('hasEndTime')!; hasEndTime = object.get<bool>('hasEndTime')!;
notes = object.get<String>('notes'); notes = object.get<String>('notes');
} }
@ -101,7 +101,7 @@ class LogEvent extends DataTableContent {
(ParseObject('LogEntry')..objectId = logEntry).toPointer()) (ParseObject('LogEntry')..objectId = logEntry).toPointer())
..set('eventType', ..set('eventType',
(ParseObject('LogEventType')..objectId = eventType).toPointer()) (ParseObject('LogEventType')..objectId = eventType).toPointer())
..set('time', time) ..set('time', time.toUtc())
..set('hasEndTime', hasEndTime) ..set('hasEndTime', hasEndTime)
..set('notes', notes); ..set('notes', notes);
await logEvent.save(); await logEvent.save();
@ -126,10 +126,10 @@ class LogEvent extends DataTableContent {
(ParseObject('LogEntry')..objectId = endLogEntry).toPointer()); (ParseObject('LogEntry')..objectId = endLogEntry).toPointer());
} }
if (time != null) { if (time != null) {
logEvent.set('time', time); logEvent.set('time', time.toUtc());
} }
if (endTime != null) { if (endTime != null) {
logEvent.set('endTime', endTime); logEvent.set('endTime', endTime.toUtc());
} }
if (hasEndTime != null) { if (hasEndTime != null) {
logEvent.set('hasEndTime', hasEndTime); logEvent.set('hasEndTime', hasEndTime);
@ -144,33 +144,33 @@ class LogEvent extends DataTableContent {
var logEvent = ParseObject('LogEvent')..objectId = objectId; var logEvent = ParseObject('LogEvent')..objectId = objectId;
await logEvent.delete(); await logEvent.delete();
} }
//
@override // @override
List<DataCell> asDataTableCells(List<Widget> actions, // List<DataCell> asDataTableCells(List<Widget> actions,
{List<LogEventType>? types}) { // {List<LogEventType>? types}) {
return [ // return [
DataCell(Text( // DataCell(Text(
types?.firstWhere((element) => element.objectId == eventType).value ?? // types?.firstWhere((element) => element.objectId == eventType).value ??
types?.length.toString() ?? // types?.length.toString() ??
'')), // '')),
DataCell(Text(DateTimeUtils.displayDateTime(time))), // DataCell(Text(DateTimeUtils.displayDateTime(time))),
DataCell(Text(hasEndTime // DataCell(Text(hasEndTime
? DateTimeUtils.displayDateTime(endTime, fallback: 'ongoing') // ? DateTimeUtils.displayDateTime(endTime, fallback: 'ongoing')
: '-')), // : '-')),
DataCell( // DataCell(
Row( // Row(
children: actions, // children: actions,
), // ),
), // ),
]; // ];
} // }
//
static List<DataColumn> asDataTableColumns() { // static List<DataColumn> asDataTableColumns() {
return [ // return [
const DataColumn(label: Expanded(child: Text('Event Type'))), // const DataColumn(label: Expanded(child: Text('Event Type'))),
const DataColumn(label: Expanded(child: Text('Start Time'))), // const DataColumn(label: Expanded(child: Text('Start Time'))),
const DataColumn(label: Expanded(child: Text('End Time'))), // const DataColumn(label: Expanded(child: Text('End Time'))),
const DataColumn(label: Expanded(child: Text('Actions'))), // const DataColumn(label: Expanded(child: Text('Actions'))),
]; // ];
} // }
} }

View File

@ -13,8 +13,15 @@ class BasalDetailScreen extends StatefulWidget {
final BasalProfile basalProfile; final BasalProfile basalProfile;
final Basal? basal; final Basal? basal;
final TimeOfDay? suggestedStartTime;
final TimeOfDay? suggestedEndTime;
const BasalDetailScreen({Key? key, required this.basalProfile, this.basal}) const BasalDetailScreen(
{Key? key,
required this.basalProfile,
this.basal,
this.suggestedStartTime,
this.suggestedEndTime})
: super(key: key); : super(key: key);
@override @override
@ -33,6 +40,12 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.suggestedStartTime != null) {
_startTime = widget.suggestedStartTime!;
}
if (widget.suggestedEndTime != null) {
_endTime = widget.suggestedEndTime!;
}
if (widget.basal != null) { if (widget.basal != null) {
_startTime = TimeOfDay.fromDateTime(widget.basal!.startTime); _startTime = TimeOfDay.fromDateTime(widget.basal!.startTime);
_endTime = TimeOfDay.fromDateTime(widget.basal!.endTime); _endTime = TimeOfDay.fromDateTime(widget.basal!.endTime);
@ -51,24 +64,78 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
} }
Future<String?> validateTimePeriod() async {
String? error;
List<Basal> basalRates =
await Basal.fetchAllForBasalProfile(widget.basalProfile);
// check for duplicates
if (basalRates
.where((other) =>
(widget.basal == null ||
widget.basal!.objectId != other.objectId) &&
_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.basal == null ||
widget.basal!.objectId != other.objectId) &&
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: <Widget>[
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 { void handleSaveAction() async {
// TODO: add confirmation dialog in case time period is already covered
if (_basalForm.currentState!.validate()) { if (_basalForm.currentState!.validate()) {
bool isNew = widget.basal == null; await validateTimePeriod().then((value) async {
isNew if (value != 'CANCEL') {
? await Basal.save( bool isNew = widget.basal == null;
startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime), isNew
endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime), ? await Basal.save(
units: double.parse(_unitsController.text), startTime:
basalProfile: widget.basalProfile.objectId!, DateTimeUtils.convertTimeOfDayToDateTime(_startTime),
) endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime),
: await Basal.update( units: double.parse(_unitsController.text),
widget.basal!.objectId!, basalProfile: widget.basalProfile.objectId!,
startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime), )
endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime), : await Basal.update(
units: double.parse(_unitsController.text), widget.basal!.objectId!,
); startTime:
Navigator.pop(context, '${isNew ? 'New' : ''} Basal Rate saved'); DateTimeUtils.convertTimeOfDayToDateTime(_startTime),
endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime),
units: double.parse(_unitsController.text),
);
Navigator.pop(context, '${isNew ? 'New' : ''} Basal Rate saved');
}
});
} }
} }
@ -76,10 +143,11 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
bool isNew = widget.basal == null; bool isNew = widget.basal == null;
if (showConfirmationDialogOnCancel && if (showConfirmationDialogOnCancel &&
((isNew && ((isNew &&
(_startTime.hour != 0 || (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) ||
_endTime.hour != 0 || _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) ||
_startTime.minute != 0 || _startTime.minute !=
_endTime.minute != 0 || (widget.suggestedStartTime?.minute ?? 0) ||
_endTime.minute != (widget.suggestedEndTime?.minute ?? 0) ||
double.tryParse(_unitsController.text) != null)) || double.tryParse(_unitsController.text) != null)) ||
(!isNew && (!isNew &&
(TimeOfDay.fromDateTime(widget.basal!.startTime) != (TimeOfDay.fromDateTime(widget.basal!.startTime) !=
@ -115,7 +183,6 @@ class _BasalDetailScreenState extends State<BasalDetailScreen> {
Row( Row(
children: [ children: [
Expanded( Expanded(
// TODO fix handling of time zones!
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 5), padding: const EdgeInsets.only(right: 5),
child: StyledTimeOfDayFormField( child: StyledTimeOfDayFormField(

View File

@ -65,12 +65,12 @@ class _BasalListScreenState extends State<BasalListScreen> {
} }
} }
String? checkBasalValidity(List<Basal> basalRates, int index) { String? validateTimePeriod(List<Basal> basalRates, int index) {
Basal basal = basalRates[index]; Basal basal = basalRates[index];
// check for gaps // check for gaps
if (index == 0 && if (index == 0 &&
(basal.startTime.toLocal().hour != 0 || basal.startTime.minute != 0)) { (basal.startTime.hour != 0 || basal.startTime.minute != 0)) {
return 'First Basal of the day needs to start at 00:00'; return 'First Basal of the day needs to start at 00:00';
} }
@ -82,12 +82,11 @@ class _BasalListScreenState extends State<BasalListScreen> {
} }
if (index == basalRates.length - 1 && if (index == basalRates.length - 1 &&
(basal.endTime.toLocal().hour != 0 || basal.endTime.minute != 0)) { (basal.endTime.hour != 0 || basal.endTime.minute != 0)) {
return 'Last Basal of the day needs to end at 00:00'; return 'Last Basal of the day needs to end at 00:00';
} }
// check for duplicates // check for duplicates
if (basalRates if (basalRates
.where((other) => basal != other && basal.startTime == other.startTime) .where((other) => basal != other && basal.startTime == other.startTime)
.isNotEmpty) { .isNotEmpty) {
@ -131,7 +130,7 @@ class _BasalListScreenState extends State<BasalListScreen> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final basal = snapshot.data![index]; final basal = snapshot.data![index];
final error = final error =
checkBasalValidity(snapshot.data!, index); validateTimePeriod(snapshot.data!, index);
return ListTile( return ListTile(
tileColor: tileColor:
error != null ? Colors.red.shade100 : null, error != null ? Colors.red.shade100 : null,

View File

@ -48,16 +48,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
} }
addBasalButton = FloatingActionButton( addBasalButton = FloatingActionButton(
onPressed: () { onPressed: handleAddNew,
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BasalDetailScreen(basalProfile: widget.basalProfile!);
},
),
).then((message) => refresh(message: message));
},
child: const Icon(Icons.add), child: const Icon(Icons.add),
); );
@ -163,6 +154,46 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
} }
} }
void handleAddNew() async {
List<Basal> basalRates =
await Basal.fetchAllForBasalProfile(widget.basalProfile!);
TimeOfDay? suggestedStartTime;
TimeOfDay? suggestedEndTime;
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(
basalProfile: widget.basalProfile!,
suggestedStartTime: suggestedStartTime,
suggestedEndTime: suggestedEndTime,
);
},
),
).then((message) => refresh(message: message));
}
void handleSaveAction() async { void handleSaveAction() async {
if (_basalProfileForm.currentState!.validate()) { if (_basalProfileForm.currentState!.validate()) {
await checkActiveProfiles(); await checkActiveProfiles();
@ -187,7 +218,7 @@ class _BasalProfileDetailScreenState extends State<BasalProfileDetailScreen> {
if (showConfirmationDialogOnCancel && if (showConfirmationDialogOnCancel &&
(isNew && (isNew &&
(_active || (_active != widget.active ||
_nameController.text != '' || _nameController.text != '' ||
_notesController.text != '')) || _notesController.text != '')) ||
(!isNew && (!isNew &&

View File

@ -15,8 +15,15 @@ class BolusDetailScreen extends StatefulWidget {
final BolusProfile bolusProfile; final BolusProfile bolusProfile;
final Bolus? bolus; final Bolus? bolus;
final TimeOfDay? suggestedStartTime;
final TimeOfDay? suggestedEndTime;
const BolusDetailScreen({Key? key, required this.bolusProfile, this.bolus}) const BolusDetailScreen(
{Key? key,
required this.bolusProfile,
this.bolus,
this.suggestedStartTime,
this.suggestedEndTime})
: super(key: key); : super(key: key);
@override @override
@ -39,6 +46,12 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
if (widget.suggestedStartTime != null) {
_startTime = widget.suggestedStartTime!;
}
if (widget.suggestedEndTime != null) {
_endTime = widget.suggestedEndTime!;
}
if (widget.bolus != null) { if (widget.bolus != null) {
_startTime = TimeOfDay.fromDateTime(widget.bolus!.startTime); _startTime = TimeOfDay.fromDateTime(widget.bolus!.startTime);
_endTime = TimeOfDay.fromDateTime(widget.bolus!.endTime); _endTime = TimeOfDay.fromDateTime(widget.bolus!.endTime);
@ -60,42 +73,84 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
_endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime); _endTimeController.text = DateTimeUtils.displayTimeOfDay(_endTime);
} }
void updateMgPerDl() { Future<String?> validateTimePeriod() async {
_mgPerDlController.text = Utils.convertMmolPerLToMgPerDl( String? error;
double.tryParse(_mmolPerLController.text) ?? 0) List<Bolus> bolusRates =
.toString(); await Bolus.fetchAllForBolusProfile(widget.bolusProfile);
}
void updateMmolPerL() { // check for duplicates
_mmolPerLController.text = Utils.convertMgPerDlToMmolPerL( if (bolusRates
int.tryParse(_mgPerDlController.text) ?? 0) .where((other) =>
.toString(); (widget.bolus == null ||
widget.bolus!.objectId != other.objectId) &&
_startTime.hour == other.startTime.hour &&
_startTime.minute == other.startTime.minute)
.isNotEmpty) {
error = 'There\'s already a rate with this start time.';
}
if (bolusRates
.where((other) =>
(widget.bolus == null ||
widget.bolus!.objectId != other.objectId) &&
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: <Widget>[
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 { void handleSaveAction() async {
// TODO: add confirmation dialog in case time period is already covered
if (_bolusForm.currentState!.validate()) { if (_bolusForm.currentState!.validate()) {
bool isNew = widget.bolus == null; await validateTimePeriod().then((value) async {
isNew if (value != 'CANCEL') {
? await Bolus.save( bool isNew = widget.bolus == null;
startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime), isNew
endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime), ? await Bolus.save(
units: double.parse(_unitsController.text), startTime:
bolusProfile: widget.bolusProfile.objectId!, DateTimeUtils.convertTimeOfDayToDateTime(_startTime),
carbs: double.parse(_carbsController.text), endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime),
mgPerDl: int.tryParse(_mgPerDlController.text), units: double.parse(_unitsController.text),
mmolPerL: double.tryParse(_mmolPerLController.text), bolusProfile: widget.bolusProfile.objectId!,
) carbs: double.parse(_carbsController.text),
: await Bolus.update( mgPerDl: int.tryParse(_mgPerDlController.text),
widget.bolus!.objectId!, mmolPerL: double.tryParse(_mmolPerLController.text),
startTime: DateTimeUtils.convertTimeOfDayToDateTime(_startTime), )
endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime), : await Bolus.update(
units: double.tryParse(_unitsController.text), widget.bolus!.objectId!,
carbs: double.tryParse(_carbsController.text), startTime:
mgPerDl: int.tryParse(_mgPerDlController.text), DateTimeUtils.convertTimeOfDayToDateTime(_startTime),
mmolPerL: double.parse(_mmolPerLController.text), endTime: DateTimeUtils.convertTimeOfDayToDateTime(_endTime),
); units: double.tryParse(_unitsController.text),
Navigator.pop(context, '${isNew ? 'New' : ''} Bolus Rate saved'); carbs: double.tryParse(_carbsController.text),
mgPerDl: int.tryParse(_mgPerDlController.text),
mmolPerL: double.parse(_mmolPerLController.text),
);
Navigator.pop(context, '${isNew ? 'New' : ''} Bolus Rate saved');
}
});
} }
} }
@ -103,10 +158,11 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
bool isNew = widget.bolus == null; bool isNew = widget.bolus == null;
if (showConfirmationDialogOnCancel && if (showConfirmationDialogOnCancel &&
((isNew && ((isNew &&
(_startTime.hour != 0 || (_startTime.hour != (widget.suggestedStartTime?.hour ?? 0) ||
_endTime.hour != 0 || _endTime.hour != (widget.suggestedEndTime?.hour ?? 0) ||
_startTime.minute != 0 || _startTime.minute !=
_endTime.minute != 0 || (widget.suggestedStartTime?.minute ?? 0) ||
_endTime.minute != (widget.suggestedEndTime?.minute ?? 0) ||
(double.tryParse(_unitsController.text) ?? 0) != 0.0 || (double.tryParse(_unitsController.text) ?? 0) != 0.0 ||
(double.tryParse(_carbsController.text) ?? 0) != 0.0 || (double.tryParse(_carbsController.text) ?? 0) != 0.0 ||
(int.tryParse(_mgPerDlController.text) ?? 0) != 0 || (int.tryParse(_mgPerDlController.text) ?? 0) != 0 ||
@ -181,7 +237,6 @@ class _BolusDetailScreenState extends State<BolusDetailScreen> {
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.only(right: 5), padding: const EdgeInsets.only(right: 5),
// TODO fix handling of time zones!
child: StyledTimeOfDayFormField( child: StyledTimeOfDayFormField(
label: 'Start Time', label: 'Start Time',
controller: _startTimeController, controller: _startTimeController,

View File

@ -66,7 +66,7 @@ class _BolusListScreenState extends State<BolusListScreen> {
} }
} }
String? checkBolusValidity(List<Bolus> bolusRates, int index) { String? validateTimePeriod(List<Bolus> bolusRates, int index) {
Bolus bolus = bolusRates[index]; Bolus bolus = bolusRates[index];
// check for gaps // check for gaps
@ -88,7 +88,6 @@ class _BolusListScreenState extends State<BolusListScreen> {
} }
// check for duplicates // check for duplicates
if (bolusRates if (bolusRates
.where((other) => bolus != other && bolus.startTime == other.startTime) .where((other) => bolus != other && bolus.startTime == other.startTime)
.isNotEmpty) { .isNotEmpty) {
@ -133,31 +132,28 @@ class _BolusListScreenState extends State<BolusListScreen> {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final bolus = snapshot.data![index]; final bolus = snapshot.data![index];
final error = final error =
checkBolusValidity(snapshot.data!, index); validateTimePeriod(snapshot.data!, index);
return ListTile( return ListTile(
isThreeLine: true,
tileColor: tileColor:
error != null ? Colors.red.shade100 : null, error != null ? Colors.red.shade100 : null,
onTap: () { onTap: () {
handleEditAction(bolus); handleEditAction(bolus);
}, },
title: Row( title: Text(
mainAxisSize: MainAxisSize.max, '${DateTimeUtils.displayTime(bolus.startTime)} - ${DateTimeUtils.displayTime(bolus.endTime)}'),
children: [ subtitle: Column(
Expanded( mainAxisSize: MainAxisSize.max,
child: Text( crossAxisAlignment: CrossAxisAlignment.start,
'${DateTimeUtils.displayTime(bolus.startTime)} - ${DateTimeUtils.displayTime(bolus.endTime)}')), children: [
// TODO: style this Text(
Expanded( '${bolus.units} U per ${bolus.carbs}${nutritionMeasurement == NutritionMeasurement.grams ? ' g' : ' oz'} carbs/${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL} ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 'mg/dl' : 'mmol/l'}'),
child: Text( error != null
'${bolus.units} U per ${bolus.carbs}${nutritionMeasurement == NutritionMeasurement.grams ? ' g' : ' oz'} carbs/${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? bolus.mgPerDl : bolus.mmolPerL} ${glucoseMeasurement == GlucoseMeasurement.mgPerDl ? 'mg/dl' : 'mmol/l'}', ? Text(error,
style: const TextStyle(fontSize: 12.0)), style: const TextStyle(
), color: Colors.red))
], : const Text('')
), ]),
subtitle: error != null
? Text(error,
style: const TextStyle(color: Colors.red))
: Container(),
trailing: Row( trailing: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [

View File

@ -48,16 +48,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
} }
addBolusButton = FloatingActionButton( addBolusButton = FloatingActionButton(
onPressed: () { onPressed: handleAddNew,
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BolusDetailScreen(bolusProfile: widget.bolusProfile!);
},
),
).then((message) => refresh(message: message));
},
child: const Icon(Icons.add), child: const Icon(Icons.add),
); );
@ -163,6 +154,46 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
} }
} }
void handleAddNew() async {
List<Bolus> bolusRates =
await Bolus.fetchAllForBolusProfile(widget.bolusProfile!);
TimeOfDay? suggestedStartTime;
TimeOfDay? suggestedEndTime;
bolusRates.asMap().forEach((index, bolus) {
if (suggestedStartTime == null && suggestedEndTime == null) {
if (index == 0 &&
(bolus.startTime.hour != 0 || bolus.startTime.minute != 0)) {
suggestedStartTime = const TimeOfDay(hour: 0, minute: 0);
suggestedEndTime = TimeOfDay.fromDateTime(bolus.startTime);
} else if ((index == bolusRates.length - 1) &&
(bolus.endTime.hour != 0 || bolus.endTime.minute != 0)) {
suggestedStartTime = TimeOfDay.fromDateTime(bolus.endTime);
suggestedEndTime = const TimeOfDay(hour: 0, minute: 0);
} else if (index != 0) {
var lastEndTime = bolusRates[index - 1].endTime;
if (bolus.startTime.isAfter(lastEndTime)) {
suggestedStartTime = TimeOfDay.fromDateTime(lastEndTime);
suggestedEndTime = TimeOfDay.fromDateTime(bolus.startTime);
}
}
}
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return BolusDetailScreen(
bolusProfile: widget.bolusProfile!,
suggestedStartTime: suggestedStartTime,
suggestedEndTime: suggestedEndTime,
);
},
),
).then((message) => refresh(message: message));
}
void handleSaveAction() async { void handleSaveAction() async {
if (_bolusProfileForm.currentState!.validate()) { if (_bolusProfileForm.currentState!.validate()) {
await checkActiveProfiles(); await checkActiveProfiles();
@ -187,7 +218,7 @@ class _BolusProfileDetailScreenState extends State<BolusProfileDetailScreen> {
if (showConfirmationDialogOnCancel && if (showConfirmationDialogOnCancel &&
(isNew && (isNew &&
(_active || (_active != widget.active ||
_nameController.text != '' || _nameController.text != '' ||
_notesController.text != '')) || _notesController.text != '')) ||
(!isNew && (!isNew &&

View File

@ -133,48 +133,46 @@ class _ActiveLogEventListScreenState extends State<ActiveLogEventListScreen> {
return ViewWithProgressIndicator( return ViewWithProgressIndicator(
snapshot: snapshot, snapshot: snapshot,
child: snapshot.data == null || snapshot.data!.isEmpty child: snapshot.data == null || snapshot.data!.isEmpty
? const Padding( ? Container()
padding: EdgeInsets.all(10.0),
child: Text('No Active Events'),
)
: ListBody( : ListBody(
children: [ children: [
// TODO: fix problems that this futurebuilder in futurebuilder creates // TODO: fix problems that this futurebuilder in futurebuilder creates
FutureBuilder<List<LogEventType>>( FutureBuilder<List<LogEventType>>(
future: _logEventTypes, future: _logEventTypes,
builder: (context, types) { builder: (context, types) {
return DataTable( // return DataTable(
columnSpacing: 10.0, // columnSpacing: 10.0,
showCheckboxColumn: false, // showCheckboxColumn: false,
rows: snapshot.data != null // rows: snapshot.data != null
? snapshot.data!.map((event) { // ? snapshot.data!.map((event) {
return DataRow( // return DataRow(
cells: event.asDataTableCells( // cells: event.asDataTableCells(
[ // [
IconButton( // IconButton(
icon: const Icon( // icon: const Icon(
Icons.stop), // Icons.stop),
iconSize: 16.0, // iconSize: 16.0,
onPressed: () => // onPressed: () =>
handleStopAction( // handleStopAction(
event), // event),
), // ),
IconButton( // IconButton(
icon: const Icon( // icon: const Icon(
Icons.delete), // Icons.delete),
iconSize: 16.0, // iconSize: 16.0,
onPressed: () => // onPressed: () =>
handleDeleteAction( // handleDeleteAction(
event), // event),
), // ),
], // ],
types: types.data, // types: types.data,
), // ),
); // );
}).toList() // }).toList()
: [], // : [],
columns: LogEvent.asDataTableColumns(), // columns: LogEvent.asDataTableColumns(),
); // );
return Container();
}) })
], ],
), ),

View File

@ -5,6 +5,7 @@ import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/screens/log/active_log_event_list.dart'; import 'package:diameter/screens/log/active_log_event_list.dart';
import 'package:diameter/screens/log/log_event_detail.dart'; import 'package:diameter/screens/log/log_event_detail.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flex_color_scheme/flex_color_scheme.dart'; import 'package:flex_color_scheme/flex_color_scheme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:diameter/components/progress_indicator.dart'; import 'package:diameter/components/progress_indicator.dart';
@ -80,7 +81,6 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
bool isNew = widget.logEntry == null;
return SingleChildScrollView( return SingleChildScrollView(
padding: const EdgeInsets.only(top: 10.0), padding: const EdgeInsets.only(top: 10.0),
child: Column( child: Column(
@ -97,39 +97,51 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
padding: EdgeInsets.all(10.0), padding: EdgeInsets.all(10.0),
child: Text('No Events for this Log Entry'), child: Text('No Events for this Log Entry'),
) )
: ListBody( : FutureBuilder<List<LogEventType>>(
children: [ future: _logEventTypes,
FutureBuilder<List<LogEventType>>( builder: (context, types) {
future: _logEventTypes, return ListView.builder(
builder: (context, types) { shrinkWrap: true,
return DataTable( itemCount: snapshot.data != null
columnSpacing: 10.0, ? snapshot.data!.length
showCheckboxColumn: false, : 0,
rows: snapshot.data != null itemBuilder: (context, index) {
? snapshot.data!.map((event) { final event = snapshot.data![index];
return DataRow( return ListTile(
cells: event.asDataTableCells([ onTap: () {
IconButton( handleEditAction(event);
icon: const Icon(Icons.edit), },
iconSize: 16.0, title: Row(
onPressed: () => mainAxisSize: MainAxisSize.max,
handleEditAction(event)), children: [
IconButton( Expanded(
icon: child: Text(types.data
const Icon(Icons.delete), ?.firstWhere((element) =>
iconSize: 16.0, element.objectId ==
onPressed: () => event.eventType)
handleDeleteAction( .value ??
event)), '')),
], types: types.data), ],
); ),
}).toList() subtitle: Text(
: [], '${DateTimeUtils.displayDateTime(event.time)}${event.hasEndTime ? ' - ${DateTimeUtils.displayDateTime(event.endTime, fallback: '(ongoing)')}' : ''}'),
columns: LogEvent.asDataTableColumns(), trailing: Row(
); mainAxisSize: MainAxisSize.min,
}) children: [
], IconButton(
), icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () =>
handleDeleteAction(event),
),
],
),
);
},
);
}),
); );
}, },
), ),
@ -140,39 +152,51 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
snapshot: snapshot, snapshot: snapshot,
child: snapshot.data == null || snapshot.data!.isEmpty child: snapshot.data == null || snapshot.data!.isEmpty
? Container() ? Container()
: ListBody( : FutureBuilder<List<LogEventType>>(
children: [ future: _logEventTypes,
FutureBuilder<List<LogEventType>>( builder: (context, types) {
future: _logEventTypes, return ListView.builder(
builder: (context, types) { shrinkWrap: true,
return DataTable( itemCount: snapshot.data != null
columnSpacing: 10.0, ? snapshot.data!.length
showCheckboxColumn: false, : 0,
rows: snapshot.data != null itemBuilder: (context, index) {
? snapshot.data!.map((event) { final event = snapshot.data![index];
return DataRow( return ListTile(
cells: event.asDataTableCells([ onTap: () {
IconButton( handleEditAction(event);
icon: const Icon(Icons.edit), },
iconSize: 16.0, title: Row(
onPressed: () => mainAxisSize: MainAxisSize.max,
handleEditAction(event)), children: [
IconButton( Expanded(
icon: child: Text(types.data
const Icon(Icons.delete), ?.firstWhere((element) =>
iconSize: 16.0, element.objectId ==
onPressed: () => event.eventType)
handleDeleteAction( .value ??
event)), '')),
], types: types.data), ],
); ),
}).toList() subtitle: Text(
: [], '${DateTimeUtils.displayDateTime(event.time)}${event.hasEndTime ? ' - ${DateTimeUtils.displayDateTime(event.endTime)}' : ''}'),
columns: LogEvent.asDataTableColumns(), trailing: Row(
); mainAxisSize: MainAxisSize.min,
}) children: [
], IconButton(
), icon: const Icon(
Icons.delete,
color: Colors.blue,
),
onPressed: () =>
handleDeleteAction(event),
),
],
),
);
},
);
}),
); );
}, },
), ),

View File

@ -3,8 +3,6 @@ import 'package:flutter/material.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
class DateTimeUtils { class DateTimeUtils {
// TODO fix handling of time zones!
static String displayDateTime(DateTime? date, {String fallback = ''}) { static String displayDateTime(DateTime? date, {String fallback = ''}) {
if (date == null) { if (date == null) {
return fallback; return fallback;