add information to log; separate log events from log entry

This commit is contained in:
spinel 2021-12-01 23:39:06 +01:00
parent 796265b048
commit e42ad61f44
17 changed files with 691 additions and 556 deletions

18
TODO
View File

@ -9,22 +9,18 @@ MAIN TASKS:
☐ replace active profile picking mode with simple dropdown ☐ replace active profile picking mode with simple dropdown
☐ indicate both the default rate and the currently active one (according to event) ☐ indicate both the default rate and the currently active one (according to event)
Log Overview:
☐ display more information
☐ apply target color settings to glucose
☐ total bolus and carbs per entry
Log Entry: Log Entry:
☐ check for multiple active events with temporary basal/bolus profiles (smallest covered time frame counts) ☐ check for multiple active events with temporary basal/bolus profiles (smallest covered time frame counts)
☐ provide splitting functionality for overlapping events ☐ provide splitting functionality for overlapping events
☐ provide percentage functionality for delayed bolus ☐ provide percentage functionality for delayed bolus
☐ get rid of useless cancellation warnings ☐ get rid of useless cancellation warnings
Events:
☐ when adding an event later on, prompt if existing boli should be recalculated
☐ show event start AND end times in list
Settings: Settings:
☐ add objectbox class and use instead of shared preferences ☐ add objectbox class and use instead of shared preferences
☐ add fields for preferred date and time formats
☐ add fields for glucose target (as map of cutoff glucose and colors)
☐ add option to hide warning dialogs on cancel or event stop
FUTURE TASKS: FUTURE TASKS:
General/Framework: General/Framework:
@ -39,14 +35,20 @@ FUTURE TASKS:
☐ find a better way to work with multiple measurements (or disable it?) ☐ find a better way to work with multiple measurements (or disable it?)
Log Overview: Log Overview:
☐ add pagination ☐ add pagination
☐ apply target color settings to glucose
Event Types: Event Types:
☐ add colors as indicators for log entries (and later graphs in reports) ☐ add colors as indicators for log entries (and later graphs in reports)
☐ implement reminders as push notifications ☐ implement reminders as push notifications
Settings: Settings:
☐ add fields for preferred date and time formats
☐ add fields for glucose target (as map of cutoff glucose and colors)
☐ add option to hide warning dialogs on cancel or event stop
☐ add option to hide extra customization options (ie. changing pre calculated values) ☐ add option to hide extra customization options (ie. changing pre calculated values)
☐ add setting for decimal places ☐ add setting for decimal places
Archive: Archive:
✔ separate events from log entries @done(21-12-01 23:37) @project(MAIN TASKS.Events)
✔ show total bolus and carbs per entry @done(21-12-01 19:50) @project(MAIN TASKS.Log Overview)
✔ display boli correctly @done(21-11-30 04:14) @project(MAIN TASKS.Log Entry) ✔ display boli correctly @done(21-11-30 04:14) @project(MAIN TASKS.Log Entry)
✔ replace meal and glucose boli with logbolus entities @done(21-11-30 03:56) @project(MAIN TASKS.Log Entry) ✔ replace meal and glucose boli with logbolus entities @done(21-11-30 03:56) @project(MAIN TASKS.Log Entry)
✔ adjust/debug active events view @done(21-11-26 22:54) @project(MAIN TASKS.Log Overview) ✔ adjust/debug active events view @done(21-11-26 22:54) @project(MAIN TASKS.Log Overview)

View File

@ -115,7 +115,7 @@ class _DateTimeFormFieldState extends State<DateTimeFormField> {
context: context, context: context,
initialDate: widget.date, initialDate: widget.date,
firstDate: widget.minDate ?? DateTime(2000, 1, 1), firstDate: widget.minDate ?? DateTime(2000, 1, 1),
lastDate: widget.maxDate ?? DateTime.now(), lastDate: widget.maxDate ?? DateTime.now().add(const Duration(days: 365)),
); );
widget.onChanged(newTime); widget.onChanged(newTime);
}, },

View File

@ -3,12 +3,12 @@ import 'package:diameter/object_box.dart';
import 'package:diameter/screens/accuracy_detail.dart'; import 'package:diameter/screens/accuracy_detail.dart';
import 'package:diameter/screens/basal/basal_profile_detail.dart'; import 'package:diameter/screens/basal/basal_profile_detail.dart';
import 'package:diameter/screens/bolus/bolus_profile_detail.dart'; import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
import 'package:diameter/screens/log/active_log_event_list.dart';
import 'package:diameter/screens/log/log.dart'; import 'package:diameter/screens/log/log.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_detail.dart';
import 'package:diameter/screens/log/log_event_type_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_list.dart';
import 'package:diameter/screens/log/log_event_type_list.dart'; import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_category_list.dart'; import 'package:diameter/screens/meal/meal_category_list.dart';
import 'package:diameter/screens/meal/meal_detail.dart'; import 'package:diameter/screens/meal/meal_detail.dart';
@ -54,8 +54,8 @@ Future<void> main() async {
Routes.logEntry: (context) => const LogEntryScreen(), Routes.logEntry: (context) => const LogEntryScreen(),
Routes.logEvent: (context) => const LogEventDetailScreen(), Routes.logEvent: (context) => const LogEventDetailScreen(),
Routes.logEventTypes: (context) => const LogEventTypeListScreen(), Routes.logEventTypes: (context) => const LogEventTypeListScreen(),
Routes.logEventType: (context) => const LogEventTypeDetailScreen(), Routes.logEventType: (context) => const EventTypeDetailScreen(),
Routes.activeEvents: (context) => const ActiveEventListScreen(), Routes.events: (context) => const LogEventListScreen(),
Routes.accuracies: (context) => const AccuracyListScreen(), Routes.accuracies: (context) => const AccuracyListScreen(),
Routes.accuracy: (context) => const AccuracyDetailScreen(), Routes.accuracy: (context) => const AccuracyDetailScreen(),
Routes.meals: (context) => const MealListScreen(), Routes.meals: (context) => const MealListScreen(),

View File

@ -56,8 +56,16 @@ class LogBolus {
return builder.build().find(); return builder.build().find();
} }
static double getTotalBolusForEntry(int id) {
QueryBuilder<LogBolus> builder = box.query(LogBolus_.deleted.equals(false));
builder.link(LogBolus_.logEntry, LogEntry_.id.equals(id));
return builder.build().property(LogBolus_.units).sum();
}
static bool glucoseBolusForEntryExists(int id) { static bool glucoseBolusForEntryExists(int id) {
QueryBuilder<LogBolus> builder = box.query(LogBolus_.deleted.equals(false).and(LogBolus_.mgPerDlCorrection.notNull())); QueryBuilder<LogBolus> builder = box.query(LogBolus_.deleted
.equals(false)
.and(LogBolus_.mgPerDlCorrection.notNull()));
builder.link(LogBolus_.logEntry, LogEntry_.id.equals(id)); builder.link(LogBolus_.logEntry, LogEntry_.id.equals(id));
return builder.build().find().isNotEmpty; return builder.build().find().isNotEmpty;
} }

View File

@ -1,10 +1,9 @@
import 'package:diameter/main.dart'; import 'package:diameter/main.dart';
import 'package:diameter/models/basal_profile.dart'; import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart'; import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/models/log_event_type.dart';
import 'package:objectbox/objectbox.dart'; import 'package:objectbox/objectbox.dart';
import 'package:diameter/objectbox.g.dart' show LogEvent_, LogEntry_; import 'package:diameter/objectbox.g.dart' show LogEvent_;
@Entity(uid: 4303325892753185970) @Entity(uid: 4303325892753185970)
class LogEvent { class LogEvent {
@ -22,8 +21,6 @@ class LogEvent {
String? notes; String? notes;
// relations // relations
final logEntry = ToOne<LogEntry>();
final endLogEntry = ToOne<LogEntry>();
final eventType = ToOne<LogEventType>(); final eventType = ToOne<LogEventType>();
final bolusProfile = ToOne<BolusProfile>(); final bolusProfile = ToOne<BolusProfile>();
final basalProfile = ToOne<BasalProfile>(); final basalProfile = ToOne<BasalProfile>();
@ -73,18 +70,21 @@ class LogEvent {
return []; return [];
} }
static List<LogEvent> getAllForEntry(int id) { static Map<DateTime, List<LogEvent>> getDailyEntryMap() {
QueryBuilder<LogEvent> builder = box.query(LogEvent_.deleted.equals(false)) Map<DateTime, List<LogEvent>> dateMap = <DateTime, List<LogEvent>>{};
..order(LogEvent_.time);
builder.link(LogEvent_.logEntry, LogEntry_.id.equals(id));
return builder.build().find();
}
static List<LogEvent> getAllEndedByEntry(int id) { QueryBuilder<LogEvent> allByDate = box
QueryBuilder<LogEvent> builder = box.query(LogEvent_.deleted.equals(false)) .query(LogEvent_.deleted.equals(false))
..order(LogEvent_.time); ..order(LogEvent_.time, flags: Order.descending);
builder.link(LogEvent_.endLogEntry, LogEntry_.id.equals(id)); List<LogEvent> events = allByDate.build().find();
return builder.build().find(); DateTime? date;
for (LogEvent event in events) {
date = DateTime.utc(event.time.year, event.time.month, event.time.day);
dateMap.putIfAbsent(date, () => <LogEvent>[]).add(event);
}
return dateMap;
} }
@override @override

View File

@ -59,6 +59,12 @@ class LogMeal {
return builder.build().find(); return builder.build().find();
} }
static double getTotalCarbsForEntry(int id) {
QueryBuilder<LogMeal> builder = box.query(LogMeal_.deleted.equals(false));
builder.link(LogMeal_.logEntry, LogEntry_.id.equals(id));
return builder.build().property(LogMeal_.carbsPerPortion).sum();
}
@override @override
String toString() { String toString() {
return value; return value;

View File

@ -8,10 +8,10 @@ import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
import 'package:diameter/screens/bolus/bolus_profile_list.dart'; import 'package:diameter/screens/bolus/bolus_profile_list.dart';
import 'package:diameter/screens/log/log.dart'; import 'package:diameter/screens/log/log.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_detail.dart';
import 'package:diameter/screens/log/active_log_event_list.dart'; import 'package:diameter/screens/log/log_event/log_event_list.dart';
import 'package:diameter/screens/log/log_event_type_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
import 'package:diameter/screens/log/log_event_type_list.dart'; import 'package:diameter/screens/log/log_event/log_event_type_list.dart';
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
import 'package:diameter/screens/meal/meal_category_detail.dart'; import 'package:diameter/screens/meal/meal_category_detail.dart';
import 'package:diameter/screens/meal/meal_category_list.dart'; import 'package:diameter/screens/meal/meal_category_list.dart';
@ -40,10 +40,10 @@ class Routes {
static const String logEvent = LogEventDetailScreen.routeName; static const String logEvent = LogEventDetailScreen.routeName;
static const String logMeal = LogMealDetailScreen.routeName; static const String logMeal = LogMealDetailScreen.routeName;
static const List<String> logEntryRoutes = [logEntry, logEvent, logMeal]; static const List<String> logEntryRoutes = [logEntry, logEvent, logMeal];
static const String logEventType = LogEventTypeDetailScreen.routeName; static const String logEventType = EventTypeDetailScreen.routeName;
static const String logEventTypes = LogEventTypeListScreen.routeName; static const String logEventTypes = LogEventTypeListScreen.routeName;
static const List<String> logEventTypeRoutes = [logEventType, logEventTypes]; static const List<String> logEventTypeRoutes = [logEventType, logEventTypes];
static const String activeEvents = ActiveEventListScreen.routeName; static const String events = LogEventListScreen.routeName;
static const String meal = MealDetailScreen.routeName; static const String meal = MealDetailScreen.routeName;
static const String meals = MealListScreen.routeName; static const String meals = MealListScreen.routeName;
@ -108,12 +108,12 @@ class _NavigationState extends State<Navigation> {
selected: Routes.logEntryRoutes.contains(widget.currentLocation), selected: Routes.logEntryRoutes.contains(widget.currentLocation),
), ),
ListTile( ListTile(
title: const Text('Active Events'), title: const Text('Log Events'),
leading: const Icon(Icons.event), leading: const Icon(Icons.event),
onTap: () { onTap: () {
selectDestination(Routes.activeEvents); selectDestination(Routes.events);
}, },
selected: widget.currentLocation == Routes.activeEvents, selected: widget.currentLocation == Routes.events,
), ),
ListTile( ListTile(
title: const Text('Meals'), title: const Text('Meals'),

View File

@ -240,22 +240,6 @@
"name": "notes", "name": "notes",
"type": 9 "type": 9
}, },
{
"id": "6:7838546213550447420",
"name": "logEntryId",
"type": 11,
"flags": 520,
"indexId": "3:3670661188280692002",
"relationTarget": "LogEntry"
},
{
"id": "7:8031421171668506924",
"name": "endLogEntryId",
"type": 11,
"flags": 520,
"indexId": "4:7379712902406481832",
"relationTarget": "LogEntry"
},
{ {
"id": "8:2514297323717317184", "id": "8:2514297323717317184",
"name": "eventTypeId", "name": "eventTypeId",
@ -818,7 +802,10 @@
"retiredEntityUids": [ "retiredEntityUids": [
3095978685310268382 3095978685310268382
], ],
"retiredIndexUids": [], "retiredIndexUids": [
3670661188280692002,
7379712902406481832
],
"retiredPropertyUids": [ "retiredPropertyUids": [
3455702077061719523, 3455702077061719523,
1048198814030724077, 1048198814030724077,
@ -832,7 +819,9 @@
1568597071506264632, 1568597071506264632,
8795268969829293398, 8795268969829293398,
3247926313599127440, 3247926313599127440,
8789440370359282572 8789440370359282572,
7838546213550447420,
8031421171668506924
], ],
"retiredRelationUids": [], "retiredRelationUids": [],
"version": 1 "version": 1

View File

@ -262,20 +262,6 @@ final _entities = <ModelEntity>[
name: 'notes', name: 'notes',
type: 9, type: 9,
flags: 0), flags: 0),
ModelProperty(
id: const IdUid(6, 7838546213550447420),
name: 'logEntryId',
type: 11,
flags: 520,
indexId: const IdUid(3, 3670661188280692002),
relationTarget: 'LogEntry'),
ModelProperty(
id: const IdUid(7, 8031421171668506924),
name: 'endLogEntryId',
type: 11,
flags: 520,
indexId: const IdUid(4, 7379712902406481832),
relationTarget: 'LogEntry'),
ModelProperty( ModelProperty(
id: const IdUid(8, 2514297323717317184), id: const IdUid(8, 2514297323717317184),
name: 'eventTypeId', name: 'eventTypeId',
@ -831,7 +817,7 @@ ModelDefinition getObjectBoxModel() {
lastRelationId: const IdUid(0, 0), lastRelationId: const IdUid(0, 0),
lastSequenceId: const IdUid(0, 0), lastSequenceId: const IdUid(0, 0),
retiredEntityUids: const [3095978685310268382], retiredEntityUids: const [3095978685310268382],
retiredIndexUids: const [], retiredIndexUids: const [3670661188280692002, 7379712902406481832],
retiredPropertyUids: const [ retiredPropertyUids: const [
3455702077061719523, 3455702077061719523,
1048198814030724077, 1048198814030724077,
@ -845,7 +831,9 @@ ModelDefinition getObjectBoxModel() {
1568597071506264632, 1568597071506264632,
8795268969829293398, 8795268969829293398,
3247926313599127440, 3247926313599127440,
8789440370359282572 8789440370359282572,
7838546213550447420,
8031421171668506924
], ],
retiredRelationUids: const [], retiredRelationUids: const [],
modelVersion: 5, modelVersion: 5,
@ -1056,13 +1044,8 @@ ModelDefinition getObjectBoxModel() {
}), }),
LogEvent: EntityDefinition<LogEvent>( LogEvent: EntityDefinition<LogEvent>(
model: _entities[5], model: _entities[5],
toOneRelations: (LogEvent object) => [ toOneRelations: (LogEvent object) =>
object.logEntry, [object.eventType, object.bolusProfile, object.basalProfile],
object.endLogEntry,
object.eventType,
object.bolusProfile,
object.basalProfile
],
toManyRelations: (LogEvent object) => {}, toManyRelations: (LogEvent object) => {},
getId: (LogEvent object) => object.id, getId: (LogEvent object) => object.id,
setId: (LogEvent object, int id) { setId: (LogEvent object, int id) {
@ -1077,8 +1060,6 @@ ModelDefinition getObjectBoxModel() {
fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch); fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch);
fbb.addBool(3, object.hasEndTime); fbb.addBool(3, object.hasEndTime);
fbb.addOffset(4, notesOffset); fbb.addOffset(4, notesOffset);
fbb.addInt64(5, object.logEntry.targetId);
fbb.addInt64(6, object.endLogEntry.targetId);
fbb.addInt64(7, object.eventType.targetId); fbb.addInt64(7, object.eventType.targetId);
fbb.addBool(8, object.deleted); fbb.addBool(8, object.deleted);
fbb.addInt64(9, object.bolusProfile.targetId); fbb.addInt64(9, object.bolusProfile.targetId);
@ -1107,12 +1088,6 @@ ModelDefinition getObjectBoxModel() {
.vTableGetNullable(buffer, rootOffset, 26), .vTableGetNullable(buffer, rootOffset, 26),
notes: const fb.StringReader() notes: const fb.StringReader()
.vTableGetNullable(buffer, rootOffset, 12)); .vTableGetNullable(buffer, rootOffset, 12));
object.logEntry.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 14, 0);
object.logEntry.attach(store);
object.endLogEntry.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
object.endLogEntry.attach(store);
object.eventType.targetId = object.eventType.targetId =
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0); const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
object.eventType.attach(store); object.eventType.attach(store);
@ -1731,33 +1706,25 @@ class LogEvent_ {
static final notes = static final notes =
QueryStringProperty<LogEvent>(_entities[5].properties[4]); QueryStringProperty<LogEvent>(_entities[5].properties[4]);
/// see [LogEvent.logEntry]
static final logEntry =
QueryRelationToOne<LogEvent, LogEntry>(_entities[5].properties[5]);
/// see [LogEvent.endLogEntry]
static final endLogEntry =
QueryRelationToOne<LogEvent, LogEntry>(_entities[5].properties[6]);
/// see [LogEvent.eventType] /// see [LogEvent.eventType]
static final eventType = static final eventType =
QueryRelationToOne<LogEvent, LogEventType>(_entities[5].properties[7]); QueryRelationToOne<LogEvent, LogEventType>(_entities[5].properties[5]);
/// see [LogEvent.deleted] /// see [LogEvent.deleted]
static final deleted = static final deleted =
QueryBooleanProperty<LogEvent>(_entities[5].properties[8]); QueryBooleanProperty<LogEvent>(_entities[5].properties[6]);
/// see [LogEvent.bolusProfile] /// see [LogEvent.bolusProfile]
static final bolusProfile = static final bolusProfile =
QueryRelationToOne<LogEvent, BolusProfile>(_entities[5].properties[9]); QueryRelationToOne<LogEvent, BolusProfile>(_entities[5].properties[7]);
/// see [LogEvent.basalProfile] /// see [LogEvent.basalProfile]
static final basalProfile = static final basalProfile =
QueryRelationToOne<LogEvent, BasalProfile>(_entities[5].properties[10]); QueryRelationToOne<LogEvent, BasalProfile>(_entities[5].properties[8]);
/// see [LogEvent.reminderDuration] /// see [LogEvent.reminderDuration]
static final reminderDuration = static final reminderDuration =
QueryIntegerProperty<LogEvent>(_entities[5].properties[11]); QueryIntegerProperty<LogEvent>(_entities[5].properties[9]);
} }
/// [LogEventType] entity fields to define ObjectBox queries. /// [LogEventType] entity fields to define ObjectBox queries.

View File

@ -1,74 +0,0 @@
import 'package:diameter/models/log_event.dart';
import 'package:diameter/screens/log/log_entry/log_event_detail.dart';
import 'package:diameter/screens/log/log_entry/log_event_list.dart';
import 'package:flutter/material.dart';
import 'package:diameter/navigation.dart';
class ActiveEventListScreen extends StatefulWidget {
static const String routeName = '/active-log-events';
const ActiveEventListScreen({Key? key}) : super(key: key);
@override
_ActiveEventListScreenState createState() => _ActiveEventListScreenState();
}
class _ActiveEventListScreenState extends State<ActiveEventListScreen> {
List<LogEvent> _activeEvents = [];
@override
void initState() {
super.initState();
reload();
}
void reload({String? message}) {
setState(() {
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
});
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void handleAddNewEvent() async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const LogEventDetailScreen();
},
),
).then((message) => reload(message: message));
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Active Events'),
actions: <Widget>[
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
],
),
drawer:
const Navigation(currentLocation: ActiveEventListScreen.routeName),
body: LogEventListScreen(activeEvents: _activeEvents),
floatingActionButton: FloatingActionButton(
onPressed: handleAddNewEvent,
child: const Icon(Icons.add),
),
);
}
}

View File

@ -1,8 +1,11 @@
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dialogs.dart';
import 'package:diameter/config.dart'; import 'package:diameter/config.dart';
import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_meal.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_entry/log_entry.dart'; import 'package:diameter/screens/log/log_entry/log_entry.dart';
import 'package:diameter/settings.dart';
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';
@ -63,10 +66,7 @@ class _LogScreenState extends State<LogScreen> {
appBar: AppBar( appBar: AppBar(
title: const Text('Log Entries'), title: const Text('Log Entries'),
actions: <Widget>[ actions: <Widget>[
IconButton( IconButton(onPressed: reload, icon: const Icon(Icons.refresh)),
onPressed: reload,
icon: const Icon(Icons.refresh)
),
], ],
), ),
drawer: const Navigation(currentLocation: LogScreen.routeName), drawer: const Navigation(currentLocation: LogScreen.routeName),
@ -74,75 +74,169 @@ class _LogScreenState extends State<LogScreen> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: _logEntryDailyMap.isNotEmpty ? SingleChildScrollView( child: _logEntryDailyMap.isNotEmpty
child: ListView.builder( ? SingleChildScrollView(
shrinkWrap: true, child: ListView.builder(
padding: const EdgeInsets.all(10.0), shrinkWrap: true,
itemCount: _logEntryDailyMap.length, padding: const EdgeInsets.all(10.0),
itemBuilder: (context, dateIndex) { itemCount: _logEntryDailyMap.length,
List<DateTime> dateList = _logEntryDailyMap.keys.toList(); itemBuilder: (context, dateIndex) {
final date = dateList[dateIndex]; List<DateTime> dateList =
final entryList = _logEntryDailyMap[date]; _logEntryDailyMap.keys.toList();
return ListBody( final date = dateList[dateIndex];
children: [ final entryList = _logEntryDailyMap[date];
Text(DateTimeUtils.displayDate(date)), return ListBody(
entryList != null && entryList.isNotEmpty children: [
? ListView.builder( Text(DateTimeUtils.displayDate(date)),
shrinkWrap: true, entryList != null && entryList.isNotEmpty
itemCount: entryList.length, ? ListView.builder(
itemBuilder: (context, index) { shrinkWrap: true,
final logEntry = entryList[index]; itemCount: entryList.length,
return ListTile( itemBuilder: (context, index) {
onTap: () { final logEntry = entryList[index];
Navigator.push( double bolus =
context, LogBolus.getTotalBolusForEntry(
MaterialPageRoute( logEntry.id);
builder: (context) => double carbs =
LogEntryScreen( LogMeal.getTotalCarbsForEntry(
id: logEntry.id), logEntry.id);
), return ListTile(
).then((message) => reload( onTap: () {
message: message)); Navigator.push(
}, context,
title: Text( MaterialPageRoute(
DateTimeUtils.displayTime( builder: (context) =>
logEntry.time)), LogEntryScreen(
// ignore: todo id: logEntry.id),
// TODO: add more data (event, glucose color...) ),
subtitle: Text(logEntry ).then((message) =>
.mgPerDl != reload(message: message));
null },
? '${logEntry.mgPerDl.toString()} mg/dl' title: Row(
: ''), mainAxisAlignment: MainAxisAlignment.start,
trailing: Row( crossAxisAlignment:
mainAxisSize: CrossAxisAlignment.start,
MainAxisSize.min, children: [
children: [ Expanded(
IconButton( child: Text(
onPressed: () => DateTimeUtils.displayTime(
handleDeleteAction( logEntry.time),
logEntry), ),
icon: const Icon( ),
Icons.delete, Expanded(
color: Colors.blue), child: Column(
) children: logEntry
], .mgPerDl !=
), null &&
); (glucoseMeasurement == GlucoseMeasurement.mgPerDl ||
} glucoseDisplayMode ==
) : Container(), GlucoseDisplayMode
], .both ||
); glucoseDisplayMode ==
}, GlucoseDisplayMode
), .bothForList)
) : const Center( ? [
child: Text('You have not created any Log Entries yet!'), Text(logEntry
), .mgPerDl
.toString()),
const Text(
'mg/dl',
textScaleFactor:
0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: logEntry
.mmolPerL !=
null &&
(glucoseMeasurement == GlucoseMeasurement.mmolPerL ||
glucoseDisplayMode ==
GlucoseDisplayMode
.both ||
glucoseDisplayMode ==
GlucoseDisplayMode
.bothForList)
? [
Text(logEntry
.mmolPerL
.toString()),
const Text(
'mmol/l',
textScaleFactor:
0.75,
),
]
: [],
),
),
Expanded(
child: Column(
children: (bolus > 0)
? [
Text(bolus
.toStringAsPrecision(
3)),
const Text('U',
textScaleFactor:
0.75),
]
: [],
),
),
Expanded(
child: Column(
children: (carbs > 0)
? [
Text(carbs
.toStringAsPrecision(
3)),
Text(
nutritionMeasurement ==
NutritionMeasurement
.grams
? 'g carbs'
: nutritionMeasurement ==
NutritionMeasurement
.ounces
? 'oz carbs'
: '',
textScaleFactor:
0.75),
]
: [],
),
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () =>
handleDeleteAction(logEntry),
icon: const Icon(Icons.delete,
color: Colors.blue),
)
],
),
);
})
: Container(),
],
);
},
),
)
: const Center(
child: Text('You have not created any Log Entries yet!'),
),
), ),
], ],
), ),
// ignore: todo
// TODO: add button for active events
floatingActionButton: FloatingActionButton( floatingActionButton: FloatingActionButton(
onPressed: () { onPressed: () {
Navigator.push( Navigator.push(

View File

@ -4,13 +4,10 @@ import 'package:diameter/components/forms.dart';
import 'package:diameter/config.dart'; import 'package:diameter/config.dart';
import 'package:diameter/models/log_bolus.dart'; import 'package:diameter/models/log_bolus.dart';
import 'package:diameter/models/log_entry.dart'; import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_meal.dart'; import 'package:diameter/models/log_meal.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_detail.dart';
import 'package:diameter/screens/log/log_entry/log_bolus_list.dart'; import 'package:diameter/screens/log/log_entry/log_bolus_list.dart';
import 'package:diameter/screens/log/log_entry/log_event_list.dart';
import 'package:diameter/screens/log/log_entry/log_event_detail.dart';
import 'package:diameter/screens/log/log_entry/log_meal_detail.dart'; import 'package:diameter/screens/log/log_entry/log_meal_detail.dart';
import 'package:diameter/screens/log/log_entry/log_meal_list.dart'; import 'package:diameter/screens/log/log_entry/log_meal_list.dart';
import 'package:diameter/settings.dart'; import 'package:diameter/settings.dart';
@ -31,15 +28,13 @@ class LogEntryScreen extends StatefulWidget {
class _LogEntryScreenState extends State<LogEntryScreen> { class _LogEntryScreenState extends State<LogEntryScreen> {
LogEntry? _logEntry; LogEntry? _logEntry;
List<LogMeal> _logMeals = []; List<LogMeal> _logMeals = [];
List<LogEvent> _logEvents = [];
List<LogEvent> _activeEvents = [];
List<LogBolus> _logBoli = []; List<LogBolus> _logBoli = [];
bool _isNew = true; bool _isNew = true;
bool _isSaving = false; bool _isSaving = false;
final GlobalKey<FormState> logEntryForm = GlobalKey<FormState>(); final GlobalKey<FormState> logEntryForm = GlobalKey<FormState>();
DateTime _time = DateTime.now(); late DateTime _time;
final _timeController = TextEditingController(text: ''); final _timeController = TextEditingController(text: '');
final _dateController = TextEditingController(text: ''); final _dateController = TextEditingController(text: '');
@ -49,7 +44,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
late FloatingActionButton addMealButton; late FloatingActionButton addMealButton;
late FloatingActionButton addBolusButton; late FloatingActionButton addBolusButton;
late FloatingActionButton addEventButton;
late IconButton refreshButton; late IconButton refreshButton;
late IconButton closeButton; late IconButton closeButton;
late DetailBottomRow detailBottomRow; late DetailBottomRow detailBottomRow;
@ -74,11 +68,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
child: const Icon(Icons.add), child: const Icon(Icons.add),
); );
addEventButton = FloatingActionButton(
onPressed: handleAddNewEvent,
child: const Icon(Icons.add),
);
refreshButton = IconButton( refreshButton = IconButton(
icon: const Icon(Icons.refresh), icon: const Icon(Icons.refresh),
onPressed: reload, onPressed: reload,
@ -103,6 +92,8 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
_mgPerDlController.text = (_logEntry!.mgPerDl ?? '').toString(); _mgPerDlController.text = (_logEntry!.mgPerDl ?? '').toString();
_mmolPerLController.text = (_logEntry!.mmolPerL ?? '').toString(); _mmolPerLController.text = (_logEntry!.mmolPerL ?? '').toString();
_notesController.text = _logEntry!.notes ?? ''; _notesController.text = _logEntry!.notes ?? '';
} else {
_time = DateTime.now();
} }
updateTime(); updateTime();
@ -113,8 +104,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
setState(() { setState(() {
_logEntry = LogEntry.get(widget.id); _logEntry = LogEntry.get(widget.id);
_logMeals = LogMeal.getAllForEntry(widget.id); _logMeals = LogMeal.getAllForEntry(widget.id);
_logEvents = LogEvent.getAllForEntry(widget.id);
_activeEvents = LogEvent.getAllActiveForTime(_logEntry?.time).where((event) => !_logEvents.any((logEvent) => logEvent.id == event.id)).toList();
_logBoli = LogBolus.getAllForEntry(widget.id); _logBoli = LogBolus.getAllForEntry(widget.id);
}); });
_isNew = _logEntry == null; _isNew = _logEntry == null;
@ -230,17 +219,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
).then((message) => reload(message: message)); ).then((message) => reload(message: message));
} }
void handleAddNewEvent() async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return LogEventDetailScreen(logEntryId: _logEntry!.id);
},
),
).then((message) => reload(message: message));
}
void renderTabButtons(index) { void renderTabButtons(index) {
if (_logEntry != null) { if (_logEntry != null) {
setState(() { setState(() {
@ -255,11 +233,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
appBarActions = [refreshButton, closeButton]; appBarActions = [refreshButton, closeButton];
bottomNav = null; bottomNav = null;
break; break;
case 3:
actionButton = addEventButton;
appBarActions = [refreshButton, closeButton];
bottomNav = null;
break;
default: default:
actionButton = null; actionButton = null;
appBarActions = [closeButton]; appBarActions = [closeButton];
@ -272,7 +245,7 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DefaultTabController( return DefaultTabController(
length: _isNew ? 1 : 4, length: _isNew ? 1 : 3,
child: Builder(builder: (BuildContext context) { child: Builder(builder: (BuildContext context) {
final TabController tabController = DefaultTabController.of(context)!; final TabController tabController = DefaultTabController.of(context)!;
tabController.addListener(() { tabController.addListener(() {
@ -442,11 +415,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
logEntry: _logEntry!, logMeals: _logMeals, reload: reload)); logEntry: _logEntry!, logMeals: _logMeals, reload: reload));
tabs.add(LogBolusListScreen( tabs.add(LogBolusListScreen(
logEntry: _logEntry!, logBoli: _logBoli, reload: reload)); logEntry: _logEntry!, logBoli: _logBoli, reload: reload));
tabs.add(LogEventListScreen(
logEntry: _logEntry!,
logEvents: _logEvents,
activeEvents: _activeEvents,
reload: reload));
} }
return Scaffold( return Scaffold(
@ -459,7 +427,6 @@ class _LogEntryScreenState extends State<LogEntryScreen> {
Tab(text: 'GENERAL'), Tab(text: 'GENERAL'),
Tab(text: 'MEALS'), Tab(text: 'MEALS'),
Tab(text: 'BOLI'), Tab(text: 'BOLI'),
Tab(text: 'EVENTS'),
], ],
), ),
actions: appBarActions, actions: appBarActions,

View File

@ -1,228 +0,0 @@
import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart';
import 'package:diameter/components/dropdown.dart';
import 'package:diameter/components/forms.dart';
import 'package:diameter/config.dart';
import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/navigation.dart';
import 'package:flutter/material.dart';
class LogEventDetailScreen extends StatefulWidget {
static const String routeName = '/log-event';
final int logEntryId;
final int endLogEntryId;
final int id;
const LogEventDetailScreen(
{Key? key, this.logEntryId = 0, this.endLogEntryId = 0, this.id = 0})
: super(key: key);
@override
_LogEventDetailScreenState createState() => _LogEventDetailScreenState();
}
class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
LogEvent? _logEvent;
bool _isNew = true;
bool _isSaving = false;
List<BolusProfile> _bolusProfiles = [];
List<BasalProfile> _basalProfiles = [];
final GlobalKey<FormState> _logEventForm = GlobalKey<FormState>();
final _reminderDurationController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
LogEventType? _eventType;
bool _hasEndTime = false;
BolusProfile? _bolusProfile;
BasalProfile? _basalProfile;
List<LogEventType> _logEventTypes = [];
@override
void initState() {
super.initState();
reload();
_bolusProfiles = BolusProfile.getAll();
_basalProfiles = BasalProfile.getAll();
if (_logEvent != null) {
_reminderDurationController.text =
(_logEvent!.reminderDuration ?? '').toString();
_hasEndTime = _logEvent!.hasEndTime;
_notesController.text = _logEvent!.notes ?? '';
_eventType = _logEvent!.eventType.target;
_basalProfile = _logEvent!.basalProfile.target;
_bolusProfile = _logEvent!.bolusProfile.target;
}
_logEventTypes = LogEventType.getAll();
}
void reload() {
if (widget.id != 0) {
setState(() {
_logEvent = LogEvent.get(widget.id);
});
}
_isNew = _logEvent == null;
}
void onSelectEventType(LogEventType eventType) {
setState(() {
_eventType = eventType;
_hasEndTime = eventType.hasEndTime;
if (eventType.basalProfile.target != null) {
_basalProfile = eventType.basalProfile.target;
}
if (eventType.bolusProfile.target != null) {
_bolusProfile = eventType.bolusProfile.target;
}
if (eventType.defaultReminderDuration != null) {
_reminderDurationController.text =
eventType.defaultReminderDuration.toString();
}
});
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logEventForm.currentState!.validate()) {
LogEvent event = LogEvent(
id: widget.id,
time: LogEntry.get(widget.logEntryId)?.time ?? DateTime.now(),
hasEndTime: _hasEndTime,
reminderDuration: int.tryParse(_reminderDurationController.text),
notes: _notesController.text,
);
if (widget.logEntryId != 0) {
event.logEntry.targetId = widget.logEntryId;
} else {
event.logEntry.target = LogEntry(time: DateTime.now());
}
event.eventType.target = _eventType;
event.basalProfile.target = _basalProfile;
event.bolusProfile.target = _bolusProfile;
LogEvent.put(event);
Navigator.pop(context, '${_isNew ? 'New' : ''} Event Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
if (showConfirmationDialogOnCancel &&
((_isNew &&
(_notesController.text != '' ||
_eventType != null ||
_hasEndTime)) ||
(!_isNew &&
(_notesController.text != (_logEvent!.notes ?? '') ||
_eventType != _logEvent!.eventType.target ||
_hasEndTime != _logEvent!.hasEndTime)))) {
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 Event' : 'Edit Event'),
),
drawer: const Navigation(currentLocation: LogEventDetailScreen.routeName),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
FormWrapper(
formState: _logEventForm,
fields: [
AutoCompleteDropdownButton<LogEventType>(
selectedItem: _eventType,
label: 'Event Type',
items: _logEventTypes,
onChanged: (value) {
if (value != null) {
onSelectEventType(value);
}
},
),
BooleanFormField(
value: _hasEndTime,
onChanged: (value) {
setState(() {
_hasEndTime = value;
});
},
label: 'has end time',
),
Column(
children: _hasEndTime ? [
TextFormField(
controller: _reminderDurationController,
keyboardType: const TextInputType.numberWithOptions(),
decoration: InputDecoration(
labelText: 'Default Reminder Duration',
suffixText: ' min',
enabled: _hasEndTime,
),
),
AutoCompleteDropdownButton<BolusProfile>(
selectedItem: _bolusProfile,
label: 'Bolus Profile',
items: _bolusProfiles,
onChanged: (value) {
setState(() {
_bolusProfile = value;
});
},
),
AutoCompleteDropdownButton<BasalProfile>(
selectedItem: _basalProfile,
label: 'Basal Profile',
items: _basalProfiles,
onChanged: (value) {
setState(() {
_basalProfile = value;
});
},
)
] : []),
TextFormField(
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes',
alignLabelWithHint: true,
),
keyboardType: TextInputType.multiline,
),
],
),
],
),
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}
}

View File

@ -0,0 +1,366 @@
import 'package:diameter/components/detail.dart';
import 'package:diameter/components/dialogs.dart';
import 'package:diameter/components/dropdown.dart';
import 'package:diameter/components/forms.dart';
import 'package:diameter/config.dart';
import 'package:diameter/models/basal_profile.dart';
import 'package:diameter/models/bolus_profile.dart';
import 'package:diameter/models/log_event.dart';
import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/navigation.dart';
import 'package:diameter/utils/date_time_utils.dart';
import 'package:flutter/material.dart';
class LogEventDetailScreen extends StatefulWidget {
static const String routeName = '/log-event';
final int logEntryId;
final int endLogEntryId;
final int id;
const LogEventDetailScreen(
{Key? key, this.logEntryId = 0, this.endLogEntryId = 0, this.id = 0})
: super(key: key);
@override
_LogEventDetailScreenState createState() => _LogEventDetailScreenState();
}
class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
LogEvent? _logEvent;
bool _isNew = true;
bool _isSaving = false;
List<BolusProfile> _bolusProfiles = [];
List<BasalProfile> _basalProfiles = [];
final GlobalKey<FormState> _logEventForm = GlobalKey<FormState>();
late DateTime _time;
DateTime? _endTime;
final _timeController = TextEditingController(text: '');
final _endTimeController = TextEditingController(text: '');
final _dateController = TextEditingController(text: '');
final _endDateController = TextEditingController(text: '');
final _reminderDurationController = TextEditingController(text: '');
final _notesController = TextEditingController(text: '');
LogEventType? _eventType;
bool _hasEndTime = false;
BolusProfile? _bolusProfile;
BasalProfile? _basalProfile;
List<LogEventType> _logEventTypes = [];
@override
void initState() {
super.initState();
reload();
_bolusProfiles = BolusProfile.getAll();
_basalProfiles = BasalProfile.getAll();
if (widget.id != 0) {
_reminderDurationController.text =
(_logEvent!.reminderDuration ?? '').toString();
_hasEndTime = _logEvent!.hasEndTime;
_notesController.text = _logEvent!.notes ?? '';
_eventType = _logEvent!.eventType.target;
_basalProfile = _logEvent!.basalProfile.target;
_bolusProfile = _logEvent!.bolusProfile.target;
_time = _logEvent!.time;
_endTime = _logEvent!.endTime;
} else {
_time = DateTime.now();
}
_logEventTypes = LogEventType.getAll();
updateTime();
updateEndTime();
}
void reload({String? message}) {
if (widget.id != 0) {
setState(() {
_logEvent = LogEvent.get(widget.id);
});
}
_isNew = _logEvent == null;
setState(() {
if (message != null) {
var snackBar = SnackBar(
content: Text(message),
duration: const Duration(seconds: 2),
);
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(snackBar);
}
});
}
void updateTime() {
_timeController.text = DateTimeUtils.displayTime(_time);
_dateController.text = DateTimeUtils.displayDate(_time);
}
void updateEndTime() {
_endTimeController.text = DateTimeUtils.displayTime(_endTime);
_endDateController.text = DateTimeUtils.displayDate(_endTime);
}
void onSelectEventType(LogEventType eventType) {
setState(() {
_eventType = eventType;
_hasEndTime = eventType.hasEndTime;
if (eventType.basalProfile.target != null) {
_basalProfile = eventType.basalProfile.target;
}
if (eventType.bolusProfile.target != null) {
_bolusProfile = eventType.bolusProfile.target;
}
if (eventType.defaultReminderDuration != null) {
_reminderDurationController.text =
eventType.defaultReminderDuration.toString();
}
});
}
void handleSaveAction() async {
setState(() {
_isSaving = true;
});
if (_logEventForm.currentState!.validate()) {
LogEvent event = LogEvent(
id: widget.id,
time: _time,
endTime: _endTime,
hasEndTime: _hasEndTime,
reminderDuration: int.tryParse(_reminderDurationController.text),
notes: _notesController.text,
);
event.eventType.target = _eventType;
event.basalProfile.target = _basalProfile;
event.bolusProfile.target = _bolusProfile;
LogEvent.put(event);
Navigator.pop(context, '${_isNew ? 'New' : ''} Event Saved');
}
setState(() {
_isSaving = false;
});
}
void handleCancelAction() {
if (showConfirmationDialogOnCancel &&
((_isNew &&
(_notesController.text != '' ||
_eventType != null ||
_hasEndTime)) ||
(!_isNew &&
(_notesController.text != (_logEvent!.notes ?? '') ||
_eventType != _logEvent!.eventType.target ||
_hasEndTime != _logEvent!.hasEndTime)))) {
Dialogs.showCancelConfirmationDialog(
context: context,
isNew: _isNew,
onSave: handleSaveAction,
);
} else {
Navigator.pop(context);
}
}
@override
Widget build(BuildContext context) {
final now = DateTime.now();
return Scaffold(
appBar: AppBar(
title: Text(_isNew ? 'New Event' : 'Edit Event'),
),
drawer: const Navigation(currentLocation: LogEventDetailScreen.routeName),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
FormWrapper(
formState: _logEventForm,
fields: [
AutoCompleteDropdownButton<LogEventType>(
selectedItem: _eventType,
label: 'Event Type',
items: _logEventTypes,
onChanged: (value) {
if (value != null) {
onSelectEventType(value);
}
},
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: DateTimeFormField(
date: _time,
label: _hasEndTime ? 'Start Date' : 'Date',
controller: _dateController,
onChanged: (newTime) {
if (newTime != null) {
setState(() {
_time = DateTime(
newTime.year,
newTime.month,
newTime.day,
_time.hour,
_time.minute);
});
updateTime();
}
},
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 5),
child: TimeOfDayFormField(
time: TimeOfDay.fromDateTime(_time),
label: _hasEndTime ? 'Start Time' : 'Time',
controller: _timeController,
onChanged: (newTime) {
if (newTime != null) {
setState(() {
_time = DateTime(
_time.year,
_time.month,
_time.day,
newTime.hour,
newTime.minute);
});
updateTime();
}
},
),
),
),
],
),
BooleanFormField(
value: _hasEndTime,
onChanged: (value) {
setState(() {
_hasEndTime = value;
});
},
label: 'has end time',
),
Column(
children: _hasEndTime? [
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 5),
child: DateTimeFormField(
date: _endTime ?? now,
label: 'End Date',
controller: _endDateController,
onChanged: (newTime) {
if (newTime != null) {
setState(() {
_endTime = DateTime(
newTime.year,
newTime.month,
newTime.day,
_endTime?.hour ?? 0,
_endTime?.minute ?? 0);
});
updateEndTime();
}
},
),
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 5),
child: TimeOfDayFormField(
time: TimeOfDay.fromDateTime(_endTime ?? now),
label: 'End Time',
controller: _endTimeController,
onChanged: (newTime) {
if (newTime != null) {
setState(() {
_endTime = DateTime(
_endTime?.year ?? now.year,
_endTime?.month ?? now.month,
_endTime?.day ?? now.day,
newTime.hour,
newTime.minute);
});
updateEndTime();
}
},
),
),
),
],
),
TextFormField(
controller: _reminderDurationController,
keyboardType:
const TextInputType.numberWithOptions(),
decoration: InputDecoration(
labelText: 'Default Reminder Duration',
suffixText: ' min',
enabled: _hasEndTime,
),
),
AutoCompleteDropdownButton<BolusProfile>(
selectedItem: _bolusProfile,
label: 'Bolus Profile',
items: _bolusProfiles,
onChanged: (value) {
setState(() {
_bolusProfile = value;
});
},
),
AutoCompleteDropdownButton<BasalProfile>(
selectedItem: _basalProfile,
label: 'Basal Profile',
items: _basalProfiles,
onChanged: (value) {
setState(() {
_basalProfile = value;
});
},
)
]
: []),
TextFormField(
controller: _notesController,
decoration: const InputDecoration(
labelText: 'Notes',
alignLabelWithHint: true,
),
keyboardType: TextInputType.multiline,
),
],
),
],
),
),
bottomNavigationBar: DetailBottomRow(
onCancel: handleCancelAction,
onSave: _isSaving ? null : handleSaveAction,
),
);
}
}

View File

@ -1,30 +1,34 @@
import 'package:diameter/components/dialogs.dart'; import 'package:diameter/components/dialogs.dart';
import 'package:diameter/config.dart'; import 'package:diameter/config.dart';
import 'package:diameter/models/log_entry.dart';
import 'package:diameter/models/log_event.dart'; import 'package:diameter/models/log_event.dart';
import 'package:diameter/screens/log/log_entry/log_event_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_detail.dart';
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:diameter/navigation.dart';
class LogEventListScreen extends StatefulWidget { class LogEventListScreen extends StatefulWidget {
final LogEntry? logEntry; static const String routeName = '/log-events';
final List<LogEvent> logEvents; const LogEventListScreen({Key? key}) : super(key: key);
final List<LogEvent> activeEvents;
final Function()? reload;
const LogEventListScreen(
{Key? key, this.logEntry, this.logEvents = const [], this.activeEvents = const [], this.reload})
: super(key: key);
@override @override
_LogEventListScreenState createState() => _LogEventListScreenState(); _LogEventListScreenState createState() => _LogEventListScreenState();
} }
class _LogEventListScreenState extends State<LogEventListScreen> { class _LogEventListScreenState extends State<LogEventListScreen> {
List<LogEvent> _activeEvents = [];
late Map<DateTime, List<LogEvent>> _logEventDailyMap;
@override
void initState() {
super.initState();
reload();
}
void reload({String? message}) { void reload({String? message}) {
if (widget.reload != null) { setState(() {
widget.reload!(); _activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
} _logEventDailyMap = LogEvent.getDailyEntryMap();
});
setState(() { setState(() {
if (message != null) { if (message != null) {
@ -39,12 +43,22 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
}); });
} }
void handleAddNewEvent() async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
return const LogEventDetailScreen();
},
),
).then((message) => reload(message: message));
}
void handleEditAction(LogEvent event) { void handleEditAction(LogEvent event) {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => LogEventDetailScreen( builder: (context) => LogEventDetailScreen(
logEntryId: widget.logEntry?.id ?? 0,
id: event.id, id: event.id,
), ),
), ),
@ -70,8 +84,6 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
void onStop(LogEvent event) async { void onStop(LogEvent event) async {
event.endTime = DateTime.now(); event.endTime = DateTime.now();
event.endLogEntry.target =
LogEntry.get(widget.logEntry?.id ?? 0) ?? LogEntry(time: DateTime.now());
LogEvent.put(event); LogEvent.put(event);
reload(message: 'Event ended'); reload(message: 'Event ended');
} }
@ -89,23 +101,43 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
} }
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Scaffold(
crossAxisAlignment: CrossAxisAlignment.center, appBar: AppBar(
title: const Text('Log Events'),
actions: <Widget>[
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
],
),
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: (widget.logEvents.isNotEmpty || child: _logEventDailyMap.isNotEmpty
widget.activeEvents.isNotEmpty) ? SingleChildScrollView(
? ListView.builder( child: ListView.builder(
shrinkWrap: true, shrinkWrap: true,
itemCount: widget.logEvents.length + padding: const EdgeInsets.all(10.0),
widget.activeEvents.length, itemCount: _logEventDailyMap.length,
itemBuilder: (context, index) { itemBuilder: (context, dateIndex) {
final event = (widget.logEvents + List<DateTime?> dateList = <DateTime?>[null] +
widget.activeEvents)[index]; _logEventDailyMap.keys.toList();
return ListTile( final date = dateList[dateIndex];
onTap: () { final eventList = date != null ? _logEventDailyMap[date] : _activeEvents;
return ListBody(
children: [
Text(DateTimeUtils.displayDate(date, fallback: 'Active Events')),
eventList != null && eventList.isNotEmpty
? ListView.builder(
shrinkWrap: true,
itemCount: eventList.length,
itemBuilder: (context, index) {
final event = eventList[index];
return ListTile(
onTap: () {
handleEditAction(event); handleEditAction(event);
}, },
title: Row( title: Row(
@ -138,13 +170,21 @@ class _LogEventListScreenState extends State<LogEventListScreen> {
], ],
), ),
); );
}) }) : Container(),
: Center( ],
child: Text(widget.logEntry == null ? 'There are no active Events!' );
: 'You have not added any Events to this Log Entry yet!'), },
),
), ),
) : const Center(
child: Text('There are no Events!'),
),
),
], ],
),
floatingActionButton: FloatingActionButton(
onPressed: handleAddNewEvent,
child: const Icon(Icons.add),
),
); );
} }
} }

View File

@ -9,17 +9,17 @@ import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class LogEventTypeDetailScreen extends StatefulWidget { class EventTypeDetailScreen extends StatefulWidget {
static const String routeName = '/log-event-type'; static const String routeName = '/log-event-type';
final int id; final int id;
const LogEventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key); const EventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key);
@override @override
_LogEventTypeDetailScreenState createState() => _EventTypeDetailScreenState createState() =>
_LogEventTypeDetailScreenState(); _EventTypeDetailScreenState();
} }
class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> { class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
LogEventType? _logEventType; LogEventType? _logEventType;
bool _isNew = true; bool _isNew = true;
bool _isSaving = false; bool _isSaving = false;
@ -120,7 +120,7 @@ class _LogEventTypeDetailScreenState extends State<LogEventTypeDetailScreen> {
title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value), title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value),
), ),
drawer: drawer:
const Navigation(currentLocation: LogEventTypeDetailScreen.routeName), const Navigation(currentLocation: EventTypeDetailScreen.routeName),
body: SingleChildScrollView( body: SingleChildScrollView(
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,

View File

@ -1,6 +1,6 @@
import 'package:diameter/models/log_event_type.dart'; import 'package:diameter/models/log_event_type.dart';
import 'package:diameter/navigation.dart'; import 'package:diameter/navigation.dart';
import 'package:diameter/screens/log/log_event_type_detail.dart'; import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class LogEventTypeListScreen extends StatefulWidget { class LogEventTypeListScreen extends StatefulWidget {
@ -60,7 +60,7 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
LogEventTypeDetailScreen( EventTypeDetailScreen(
id: logEventType.id), id: logEventType.id),
), ),
).then((message) => reload(message: message)); ).then((message) => reload(message: message));
@ -73,10 +73,8 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
IconButton( IconButton(
onPressed: () async { onPressed: () async {
LogEventType.remove(logEventType.id); LogEventType.remove(logEventType.id);
// await logEventType.delete().then((_) {
reload( reload(
message: 'Log Event Type deleted'); message: 'Log Event Type deleted');
// });
}, },
icon: const Icon(Icons.delete, icon: const Icon(Icons.delete,
color: Colors.blue), color: Colors.blue),
@ -96,7 +94,7 @@ class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => const LogEventTypeDetailScreen(), builder: (context) => const EventTypeDetailScreen(),
), ),
).then((message) => reload(message: message)); ).then((message) => reload(message: message));
}, },