Merge branch 'main' of https://git.sudo.ca/spinel/diameter into main
This commit is contained in:
commit
481dc60996
@ -68,4 +68,19 @@ class LogEntry {
|
|||||||
String toString() {
|
String toString() {
|
||||||
return DateTimeUtils.displayDateTime(time);
|
return DateTimeUtils.displayDateTime(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<LogEntry> getAllForDate(DateTime date) {
|
||||||
|
DateTime startOfDay = DateTime(date.year, date.month, date.day);
|
||||||
|
DateTime endOfDay = startOfDay.add(const Duration(days: 1));
|
||||||
|
QueryBuilder<LogEntry> builder = box.query(LogEntry_.deleted.equals(false))
|
||||||
|
..order(LogEntry_.time, flags: Order.descending);
|
||||||
|
return builder.build().find().where((entry) {
|
||||||
|
return (entry.time.compareTo(startOfDay) >= 0 && entry.time.isBefore(endOfDay));
|
||||||
|
}).toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return DateTimeUtils.displayDateTime(time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,6 @@ class LogEvent {
|
|||||||
|
|
||||||
// methods
|
// methods
|
||||||
static LogEvent? get(int id) => box.get(id);
|
static LogEvent? get(int id) => box.get(id);
|
||||||
static List<LogEvent> getAll() => box.getAll();
|
|
||||||
static void put(LogEvent logEvent) => box.put(logEvent);
|
static void put(LogEvent logEvent) => box.put(logEvent);
|
||||||
|
|
||||||
static void remove(int id) {
|
static void remove(int id) {
|
||||||
|
@ -1087,44 +1087,19 @@
|
|||||||
"lastSequenceId": "0:0",
|
"lastSequenceId": "0:0",
|
||||||
"modelVersion": 5,
|
"modelVersion": 5,
|
||||||
"modelVersionParserMinimum": 5,
|
"modelVersionParserMinimum": 5,
|
||||||
"retiredEntityUids": [
|
"retiredEntityUids": [3095978685310268382],
|
||||||
3095978685310268382
|
"retiredIndexUids": [3670661188280692002, 7379712902406481832],
|
||||||
],
|
|
||||||
"retiredIndexUids": [
|
|
||||||
3670661188280692002,
|
|
||||||
7379712902406481832
|
|
||||||
],
|
|
||||||
"retiredPropertyUids": [
|
"retiredPropertyUids": [
|
||||||
3455702077061719523,
|
3455702077061719523, 1048198814030724077, 9003780003858349085,
|
||||||
1048198814030724077,
|
5421422436108145565, 7741631874181070179, 5471636804765937328,
|
||||||
9003780003858349085,
|
6855574218883169324, 5313708456544000157, 3678829169126156351,
|
||||||
5421422436108145565,
|
1568597071506264632, 8795268969829293398, 3247926313599127440,
|
||||||
7741631874181070179,
|
8789440370359282572, 7838546213550447420, 8031421171668506924,
|
||||||
5471636804765937328,
|
1614362036318874174, 1675040259141389754, 7518219134349037920,
|
||||||
6855574218883169324,
|
2172890064639236018, 310032577683835406, 5588897884422150510,
|
||||||
5313708456544000157,
|
7638848982383620744, 3282706593658092097, 596980591281311896,
|
||||||
3678829169126156351,
|
3633551763915044903, 2215708755581938580, 241621230513128588,
|
||||||
1568597071506264632,
|
4678123663117222609, 780211923138281722, 763575433624979013,
|
||||||
8795268969829293398,
|
|
||||||
3247926313599127440,
|
|
||||||
8789440370359282572,
|
|
||||||
7838546213550447420,
|
|
||||||
8031421171668506924,
|
|
||||||
1614362036318874174,
|
|
||||||
1675040259141389754,
|
|
||||||
7518219134349037920,
|
|
||||||
2172890064639236018,
|
|
||||||
310032577683835406,
|
|
||||||
5588897884422150510,
|
|
||||||
7638848982383620744,
|
|
||||||
3282706593658092097,
|
|
||||||
596980591281311896,
|
|
||||||
3633551763915044903,
|
|
||||||
2215708755581938580,
|
|
||||||
241621230513128588,
|
|
||||||
4678123663117222609,
|
|
||||||
780211923138281722,
|
|
||||||
763575433624979013,
|
|
||||||
1225271130099322691
|
1225271130099322691
|
||||||
],
|
],
|
||||||
"retiredRelationUids": [],
|
"retiredRelationUids": [],
|
||||||
|
@ -1234,7 +1234,7 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
final buffer = fb.BufferContext(fbData);
|
final buffer = fb.BufferContext(fbData);
|
||||||
final rootOffset = buffer.derefObject(0);
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
final object = Bolus(
|
final object = LogEventType(
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 20, false),
|
.vTableGet(buffer, rootOffset, 20, false),
|
||||||
@ -1250,190 +1250,6 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
.vTableGetNullable(buffer, rootOffset, 14),
|
.vTableGetNullable(buffer, rootOffset, 14),
|
||||||
mmolPerL: const fb.Float64Reader()
|
mmolPerL: const fb.Float64Reader()
|
||||||
.vTableGetNullable(buffer, rootOffset, 16));
|
.vTableGetNullable(buffer, rootOffset, 16));
|
||||||
object.bolusProfile.targetId =
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
|
||||||
object.bolusProfile.attach(store);
|
|
||||||
return object;
|
|
||||||
}),
|
|
||||||
BolusProfile: EntityDefinition<BolusProfile>(
|
|
||||||
model: _entities[3],
|
|
||||||
toOneRelations: (BolusProfile object) => [],
|
|
||||||
toManyRelations: (BolusProfile object) => {},
|
|
||||||
getId: (BolusProfile object) => object.id,
|
|
||||||
setId: (BolusProfile object, int id) {
|
|
||||||
object.id = id;
|
|
||||||
},
|
|
||||||
objectToFB: (BolusProfile object, fb.Builder fbb) {
|
|
||||||
final nameOffset = fbb.writeString(object.name);
|
|
||||||
final notesOffset =
|
|
||||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
|
||||||
fbb.startTable(6);
|
|
||||||
fbb.addInt64(0, object.id);
|
|
||||||
fbb.addOffset(1, nameOffset);
|
|
||||||
fbb.addBool(2, object.active);
|
|
||||||
fbb.addOffset(3, notesOffset);
|
|
||||||
fbb.addBool(4, object.deleted);
|
|
||||||
fbb.finish(fbb.endTable());
|
|
||||||
return object.id;
|
|
||||||
},
|
|
||||||
objectFromFB: (Store store, ByteData fbData) {
|
|
||||||
final buffer = fb.BufferContext(fbData);
|
|
||||||
final rootOffset = buffer.derefObject(0);
|
|
||||||
|
|
||||||
final object = BolusProfile(
|
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
|
||||||
deleted: const fb.BoolReader()
|
|
||||||
.vTableGet(buffer, rootOffset, 12, false),
|
|
||||||
name: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGet(buffer, rootOffset, 6, ''),
|
|
||||||
active:
|
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
|
||||||
notes: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 10));
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}),
|
|
||||||
LogEntry: EntityDefinition<LogEntry>(
|
|
||||||
model: _entities[4],
|
|
||||||
toOneRelations: (LogEntry object) => [],
|
|
||||||
toManyRelations: (LogEntry object) => {},
|
|
||||||
getId: (LogEntry object) => object.id,
|
|
||||||
setId: (LogEntry object, int id) {
|
|
||||||
object.id = id;
|
|
||||||
},
|
|
||||||
objectToFB: (LogEntry object, fb.Builder fbb) {
|
|
||||||
final notesOffset =
|
|
||||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
|
||||||
fbb.startTable(11);
|
|
||||||
fbb.addInt64(0, object.id);
|
|
||||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
|
||||||
fbb.addInt64(2, object.mgPerDl);
|
|
||||||
fbb.addFloat64(3, object.mmolPerL);
|
|
||||||
fbb.addOffset(7, notesOffset);
|
|
||||||
fbb.addBool(8, object.deleted);
|
|
||||||
fbb.addFloat64(9, object.glucoseTrend);
|
|
||||||
fbb.finish(fbb.endTable());
|
|
||||||
return object.id;
|
|
||||||
},
|
|
||||||
objectFromFB: (Store store, ByteData fbData) {
|
|
||||||
final buffer = fb.BufferContext(fbData);
|
|
||||||
final rootOffset = buffer.derefObject(0);
|
|
||||||
|
|
||||||
final object = LogEntry(
|
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
|
||||||
deleted: const fb.BoolReader()
|
|
||||||
.vTableGet(buffer, rootOffset, 20, false),
|
|
||||||
time: DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
|
||||||
mgPerDl: const fb.Int64Reader()
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 8),
|
|
||||||
mmolPerL: const fb.Float64Reader()
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 10),
|
|
||||||
glucoseTrend: const fb.Float64Reader()
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 22),
|
|
||||||
notes: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 18));
|
|
||||||
|
|
||||||
return object;
|
|
||||||
}),
|
|
||||||
LogEvent: EntityDefinition<LogEvent>(
|
|
||||||
model: _entities[5],
|
|
||||||
toOneRelations: (LogEvent object) =>
|
|
||||||
[object.eventType, object.bolusProfile, object.basalProfile],
|
|
||||||
toManyRelations: (LogEvent object) => {},
|
|
||||||
getId: (LogEvent object) => object.id,
|
|
||||||
setId: (LogEvent object, int id) {
|
|
||||||
object.id = id;
|
|
||||||
},
|
|
||||||
objectToFB: (LogEvent object, fb.Builder fbb) {
|
|
||||||
final notesOffset =
|
|
||||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
|
||||||
fbb.startTable(13);
|
|
||||||
fbb.addInt64(0, object.id);
|
|
||||||
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
|
||||||
fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch);
|
|
||||||
fbb.addBool(3, object.hasEndTime);
|
|
||||||
fbb.addOffset(4, notesOffset);
|
|
||||||
fbb.addInt64(7, object.eventType.targetId);
|
|
||||||
fbb.addBool(8, object.deleted);
|
|
||||||
fbb.addInt64(9, object.bolusProfile.targetId);
|
|
||||||
fbb.addInt64(10, object.basalProfile.targetId);
|
|
||||||
fbb.addInt64(11, object.reminderDuration);
|
|
||||||
fbb.finish(fbb.endTable());
|
|
||||||
return object.id;
|
|
||||||
},
|
|
||||||
objectFromFB: (Store store, ByteData fbData) {
|
|
||||||
final buffer = fb.BufferContext(fbData);
|
|
||||||
final rootOffset = buffer.derefObject(0);
|
|
||||||
final endTimeValue =
|
|
||||||
const fb.Int64Reader().vTableGetNullable(buffer, rootOffset, 8);
|
|
||||||
final object = LogEvent(
|
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
|
||||||
deleted: const fb.BoolReader()
|
|
||||||
.vTableGet(buffer, rootOffset, 20, false),
|
|
||||||
time: DateTime.fromMillisecondsSinceEpoch(
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
|
||||||
endTime: endTimeValue == null
|
|
||||||
? null
|
|
||||||
: DateTime.fromMillisecondsSinceEpoch(endTimeValue),
|
|
||||||
hasEndTime: const fb.BoolReader()
|
|
||||||
.vTableGet(buffer, rootOffset, 10, false),
|
|
||||||
reminderDuration: const fb.Int64Reader()
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 26),
|
|
||||||
notes: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 12));
|
|
||||||
object.eventType.targetId =
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
|
||||||
object.eventType.attach(store);
|
|
||||||
object.bolusProfile.targetId =
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
|
||||||
object.bolusProfile.attach(store);
|
|
||||||
object.basalProfile.targetId =
|
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
|
||||||
object.basalProfile.attach(store);
|
|
||||||
return object;
|
|
||||||
}),
|
|
||||||
LogEventType: EntityDefinition<LogEventType>(
|
|
||||||
model: _entities[6],
|
|
||||||
toOneRelations: (LogEventType object) =>
|
|
||||||
[object.bolusProfile, object.basalProfile],
|
|
||||||
toManyRelations: (LogEventType object) => {},
|
|
||||||
getId: (LogEventType object) => object.id,
|
|
||||||
setId: (LogEventType object, int id) {
|
|
||||||
object.id = id;
|
|
||||||
},
|
|
||||||
objectToFB: (LogEventType object, fb.Builder fbb) {
|
|
||||||
final valueOffset = fbb.writeString(object.value);
|
|
||||||
final notesOffset =
|
|
||||||
object.notes == null ? null : fbb.writeString(object.notes!);
|
|
||||||
fbb.startTable(9);
|
|
||||||
fbb.addInt64(0, object.id);
|
|
||||||
fbb.addOffset(1, valueOffset);
|
|
||||||
fbb.addBool(2, object.hasEndTime);
|
|
||||||
fbb.addInt64(3, object.defaultReminderDuration);
|
|
||||||
fbb.addOffset(4, notesOffset);
|
|
||||||
fbb.addBool(5, object.deleted);
|
|
||||||
fbb.addInt64(6, object.bolusProfile.targetId);
|
|
||||||
fbb.addInt64(7, object.basalProfile.targetId);
|
|
||||||
fbb.finish(fbb.endTable());
|
|
||||||
return object.id;
|
|
||||||
},
|
|
||||||
objectFromFB: (Store store, ByteData fbData) {
|
|
||||||
final buffer = fb.BufferContext(fbData);
|
|
||||||
final rootOffset = buffer.derefObject(0);
|
|
||||||
|
|
||||||
final object = LogEventType(
|
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
|
||||||
deleted: const fb.BoolReader()
|
|
||||||
.vTableGet(buffer, rootOffset, 14, false),
|
|
||||||
value: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGet(buffer, rootOffset, 6, ''),
|
|
||||||
hasEndTime:
|
|
||||||
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
|
||||||
defaultReminderDuration: const fb.Int64Reader()
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 10),
|
|
||||||
notes: const fb.StringReader(asciiOptimization: true)
|
|
||||||
.vTableGetNullable(buffer, rootOffset, 12));
|
|
||||||
object.bolusProfile.targetId =
|
object.bolusProfile.targetId =
|
||||||
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
||||||
object.bolusProfile.attach(store);
|
object.bolusProfile.attach(store);
|
||||||
@ -1487,6 +1303,353 @@ ModelDefinition getObjectBoxModel() {
|
|||||||
final rootOffset = buffer.derefObject(0);
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
final object = LogMeal(
|
final object = LogMeal(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 36, false),
|
||||||
|
value:
|
||||||
|
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||||
|
amount:
|
||||||
|
const fb.Float64Reader().vTableGet(buffer, rootOffset, 38, 0),
|
||||||
|
carbsRatio: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 8),
|
||||||
|
portionSize: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
|
totalCarbs: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 40),
|
||||||
|
notes: const fb.StringReader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 20))
|
||||||
|
..bolus = const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 14);
|
||||||
|
object.logEntry.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||||
|
object.logEntry.attach(store);
|
||||||
|
object.meal.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||||
|
object.meal.attach(store);
|
||||||
|
object.mealSource.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 26, 0);
|
||||||
|
object.mealSource.attach(store);
|
||||||
|
object.mealCategory.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 28, 0);
|
||||||
|
object.mealCategory.attach(store);
|
||||||
|
object.mealPortionType.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 30, 0);
|
||||||
|
object.mealPortionType.attach(store);
|
||||||
|
object.portionSizeAccuracy.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 32, 0);
|
||||||
|
object.portionSizeAccuracy.attach(store);
|
||||||
|
object.carbsRatioAccuracy.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 34, 0);
|
||||||
|
object.carbsRatioAccuracy.attach(store);
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
Meal: EntityDefinition<Meal>(
|
||||||
|
model: _entities[8],
|
||||||
|
toOneRelations: (Meal object) => [
|
||||||
|
object.mealSource,
|
||||||
|
object.mealCategory,
|
||||||
|
object.mealPortionType,
|
||||||
|
object.portionSizeAccuracy,
|
||||||
|
object.carbsRatioAccuracy
|
||||||
|
],
|
||||||
|
toManyRelations: (Meal object) => {},
|
||||||
|
getId: (Meal object) => object.id,
|
||||||
|
setId: (Meal object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (Meal object, fb.Builder fbb) {
|
||||||
|
final valueOffset = fbb.writeString(object.value);
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(16);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addOffset(1, valueOffset);
|
||||||
|
fbb.addFloat64(2, object.carbsRatio);
|
||||||
|
fbb.addFloat64(3, object.portionSize);
|
||||||
|
fbb.addFloat64(4, object.carbsPerPortion);
|
||||||
|
fbb.addInt64(5, object.delayedBolusDuration);
|
||||||
|
fbb.addOffset(7, notesOffset);
|
||||||
|
fbb.addInt64(8, object.mealSource.targetId);
|
||||||
|
fbb.addInt64(9, object.mealCategory.targetId);
|
||||||
|
fbb.addInt64(10, object.mealPortionType.targetId);
|
||||||
|
fbb.addInt64(11, object.portionSizeAccuracy.targetId);
|
||||||
|
fbb.addInt64(12, object.carbsRatioAccuracy.targetId);
|
||||||
|
fbb.addBool(13, object.deleted);
|
||||||
|
fbb.addFloat64(14, object.delayedBolusPercentage);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = Meal(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 30, false),
|
||||||
|
value:
|
||||||
|
const fb.StringReader().vTableGet(buffer, rootOffset, 6, ''),
|
||||||
|
carbsRatio: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 8),
|
||||||
|
portionSize: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
|
carbsPerPortion: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 12),
|
||||||
|
delayedBolusDuration: const fb.Int64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 14),
|
||||||
|
delayedBolusPercentage: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 32),
|
||||||
|
notes: const fb.StringReader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 18));
|
||||||
|
object.mealSource.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 20, 0);
|
||||||
|
object.mealSource.attach(store);
|
||||||
|
object.mealCategory.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||||
|
object.mealCategory.attach(store);
|
||||||
|
object.mealPortionType.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||||
|
object.mealPortionType.attach(store);
|
||||||
|
object.portionSizeAccuracy.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 26, 0);
|
||||||
|
object.portionSizeAccuracy.attach(store);
|
||||||
|
object.carbsRatioAccuracy.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 28, 0);
|
||||||
|
object.carbsRatioAccuracy.attach(store);
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
BolusProfile: EntityDefinition<BolusProfile>(
|
||||||
|
model: _entities[3],
|
||||||
|
toOneRelations: (BolusProfile object) => [],
|
||||||
|
toManyRelations: (BolusProfile object) => {},
|
||||||
|
getId: (BolusProfile object) => object.id,
|
||||||
|
setId: (BolusProfile object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (MealCategory object, fb.Builder fbb) {
|
||||||
|
final valueOffset = fbb.writeString(object.value);
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(6);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addOffset(1, nameOffset);
|
||||||
|
fbb.addBool(2, object.active);
|
||||||
|
fbb.addOffset(3, notesOffset);
|
||||||
|
fbb.addBool(4, object.deleted);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = MealCategory(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 12, false),
|
||||||
|
name: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
|
active:
|
||||||
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 10));
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
LogEntry: EntityDefinition<LogEntry>(
|
||||||
|
model: _entities[4],
|
||||||
|
toOneRelations: (LogEntry object) => [],
|
||||||
|
toManyRelations: (LogEntry object) => {},
|
||||||
|
getId: (LogEntry object) => object.id,
|
||||||
|
setId: (LogEntry object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (MealPortionType object, fb.Builder fbb) {
|
||||||
|
final valueOffset = fbb.writeString(object.value);
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(11);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||||
|
fbb.addInt64(2, object.mgPerDl);
|
||||||
|
fbb.addFloat64(3, object.mmolPerL);
|
||||||
|
fbb.addOffset(7, notesOffset);
|
||||||
|
fbb.addBool(8, object.deleted);
|
||||||
|
fbb.addFloat64(9, object.glucoseTrend);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = MealPortionType(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 20, false),
|
||||||
|
time: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||||
|
mgPerDl: const fb.Int64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 8),
|
||||||
|
mmolPerL: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
|
glucoseTrend: const fb.Float64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 22),
|
||||||
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 18));
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
LogEvent: EntityDefinition<LogEvent>(
|
||||||
|
model: _entities[5],
|
||||||
|
toOneRelations: (LogEvent object) =>
|
||||||
|
[object.eventType, object.bolusProfile, object.basalProfile],
|
||||||
|
toManyRelations: (LogEvent object) => {},
|
||||||
|
getId: (LogEvent object) => object.id,
|
||||||
|
setId: (LogEvent object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (MealSource object, fb.Builder fbb) {
|
||||||
|
final valueOffset = fbb.writeString(object.value);
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(13);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addInt64(1, object.time.millisecondsSinceEpoch);
|
||||||
|
fbb.addInt64(2, object.endTime?.millisecondsSinceEpoch);
|
||||||
|
fbb.addBool(3, object.hasEndTime);
|
||||||
|
fbb.addOffset(4, notesOffset);
|
||||||
|
fbb.addInt64(7, object.eventType.targetId);
|
||||||
|
fbb.addBool(8, object.deleted);
|
||||||
|
fbb.addInt64(9, object.bolusProfile.targetId);
|
||||||
|
fbb.addInt64(10, object.basalProfile.targetId);
|
||||||
|
fbb.addInt64(11, object.reminderDuration);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = MealSource(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 20, false),
|
||||||
|
time: DateTime.fromMillisecondsSinceEpoch(
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 6, 0)),
|
||||||
|
endTime: endTimeValue == null
|
||||||
|
? null
|
||||||
|
: DateTime.fromMillisecondsSinceEpoch(endTimeValue),
|
||||||
|
hasEndTime: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 10, false),
|
||||||
|
reminderDuration: const fb.Int64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 26),
|
||||||
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 12));
|
||||||
|
object.eventType.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||||
|
object.eventType.attach(store);
|
||||||
|
object.bolusProfile.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 22, 0);
|
||||||
|
object.bolusProfile.attach(store);
|
||||||
|
object.basalProfile.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 24, 0);
|
||||||
|
object.basalProfile.attach(store);
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
LogEventType: EntityDefinition<LogEventType>(
|
||||||
|
model: _entities[6],
|
||||||
|
toOneRelations: (LogEventType object) =>
|
||||||
|
[object.bolusProfile, object.basalProfile],
|
||||||
|
toManyRelations: (LogEventType object) => {},
|
||||||
|
getId: (LogEventType object) => object.id,
|
||||||
|
setId: (LogEventType object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (LogBolus object, fb.Builder fbb) {
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(9);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addOffset(1, valueOffset);
|
||||||
|
fbb.addBool(2, object.hasEndTime);
|
||||||
|
fbb.addInt64(3, object.defaultReminderDuration);
|
||||||
|
fbb.addOffset(4, notesOffset);
|
||||||
|
fbb.addBool(5, object.deleted);
|
||||||
|
fbb.addInt64(6, object.bolusProfile.targetId);
|
||||||
|
fbb.addInt64(7, object.basalProfile.targetId);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = LogBolus(
|
||||||
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
|
deleted: const fb.BoolReader()
|
||||||
|
.vTableGet(buffer, rootOffset, 14, false),
|
||||||
|
value: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGet(buffer, rootOffset, 6, ''),
|
||||||
|
hasEndTime:
|
||||||
|
const fb.BoolReader().vTableGet(buffer, rootOffset, 8, false),
|
||||||
|
defaultReminderDuration: const fb.Int64Reader()
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 10),
|
||||||
|
notes: const fb.StringReader(asciiOptimization: true)
|
||||||
|
.vTableGetNullable(buffer, rootOffset, 12));
|
||||||
|
object.bolusProfile.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0);
|
||||||
|
object.bolusProfile.attach(store);
|
||||||
|
object.basalProfile.targetId =
|
||||||
|
const fb.Int64Reader().vTableGet(buffer, rootOffset, 18, 0);
|
||||||
|
object.basalProfile.attach(store);
|
||||||
|
return object;
|
||||||
|
}),
|
||||||
|
LogMeal: EntityDefinition<LogMeal>(
|
||||||
|
model: _entities[7],
|
||||||
|
toOneRelations: (LogMeal object) => [
|
||||||
|
object.logEntry,
|
||||||
|
object.meal,
|
||||||
|
object.mealSource,
|
||||||
|
object.mealCategory,
|
||||||
|
object.mealPortionType,
|
||||||
|
object.portionSizeAccuracy,
|
||||||
|
object.carbsRatioAccuracy
|
||||||
|
],
|
||||||
|
toManyRelations: (LogMeal object) => {},
|
||||||
|
getId: (LogMeal object) => object.id,
|
||||||
|
setId: (LogMeal object, int id) {
|
||||||
|
object.id = id;
|
||||||
|
},
|
||||||
|
objectToFB: (Accuracy object, fb.Builder fbb) {
|
||||||
|
final valueOffset = fbb.writeString(object.value);
|
||||||
|
final notesOffset =
|
||||||
|
object.notes == null ? null : fbb.writeString(object.notes!);
|
||||||
|
fbb.startTable(20);
|
||||||
|
fbb.addInt64(0, object.id);
|
||||||
|
fbb.addOffset(1, valueOffset);
|
||||||
|
fbb.addFloat64(2, object.carbsRatio);
|
||||||
|
fbb.addFloat64(3, object.portionSize);
|
||||||
|
fbb.addFloat64(5, object.bolus);
|
||||||
|
fbb.addOffset(8, notesOffset);
|
||||||
|
fbb.addInt64(9, object.logEntry.targetId);
|
||||||
|
fbb.addInt64(10, object.meal.targetId);
|
||||||
|
fbb.addInt64(11, object.mealSource.targetId);
|
||||||
|
fbb.addInt64(12, object.mealCategory.targetId);
|
||||||
|
fbb.addInt64(13, object.mealPortionType.targetId);
|
||||||
|
fbb.addInt64(14, object.portionSizeAccuracy.targetId);
|
||||||
|
fbb.addInt64(15, object.carbsRatioAccuracy.targetId);
|
||||||
|
fbb.addBool(16, object.deleted);
|
||||||
|
fbb.addFloat64(17, object.amount);
|
||||||
|
fbb.addFloat64(18, object.totalCarbs);
|
||||||
|
fbb.finish(fbb.endTable());
|
||||||
|
return object.id;
|
||||||
|
},
|
||||||
|
objectFromFB: (Store store, ByteData fbData) {
|
||||||
|
final buffer = fb.BufferContext(fbData);
|
||||||
|
final rootOffset = buffer.derefObject(0);
|
||||||
|
|
||||||
|
final object = Accuracy(
|
||||||
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0),
|
||||||
deleted: const fb.BoolReader()
|
deleted: const fb.BoolReader()
|
||||||
.vTableGet(buffer, rootOffset, 36, false),
|
.vTableGet(buffer, rootOffset, 36, false),
|
||||||
|
@ -188,7 +188,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async {
|
Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async {
|
||||||
if (_isNew && _eventType != null &&
|
if (_eventType != null &&
|
||||||
LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) {
|
LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) {
|
||||||
await showDialog(
|
await showDialog(
|
||||||
context: context,
|
context: context,
|
366
lib/screens/log/log_event/log_event_list.dart
Normal file
366
lib/screens/log/log_event/log_event_list.dart
Normal file
@ -0,0 +1,366 @@
|
|||||||
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
|
import 'package:diameter/models/log_event.dart';
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
import 'package:diameter/screens/log/log_event/log_event_detail.dart';
|
||||||
|
import 'package:diameter/utils/date_time_utils.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
|
||||||
|
class LogEventListScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/log-events';
|
||||||
|
const LogEventListScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LogEventListScreenState createState() => _LogEventListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LogEventListScreenState extends State<LogEventListScreen> {
|
||||||
|
List<LogEvent> _activeEvents = [];
|
||||||
|
late List<LogEvent> _logEvents;
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
final TextEditingController _dateController = TextEditingController(text: '');
|
||||||
|
|
||||||
|
late DateTime _date;
|
||||||
|
bool _showActive = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
_date = DateTime.now();
|
||||||
|
_dateController.text = DateTimeUtils.displayDate(_date);
|
||||||
|
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
_dateController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
setState(() {
|
||||||
|
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
|
||||||
|
_logEvents = LogEvent.getAllForDate(_date);
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleAddNewEvent() async {
|
||||||
|
final now = DateTime.now();
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) {
|
||||||
|
return LogEventDetailScreen(
|
||||||
|
suggestedDate: _date.isAtSameMomentAs(DateTime(now.year, now.month, now.day)) ? now : _date,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleEditAction(LogEvent event) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => LogEventDetailScreen(
|
||||||
|
id: event.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDelete(LogEvent logEvent) {
|
||||||
|
LogEvent.remove(logEvent.id);
|
||||||
|
reload(message: 'Event deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDeleteAction(LogEvent logEvent) async {
|
||||||
|
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||||
|
DialogUtils.showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
onConfirm: () => onDelete(logEvent),
|
||||||
|
message: 'Are you sure you want to delete this Event?',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onDelete(logEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onStop(LogEvent event) async {
|
||||||
|
event.endTime = DateTime.now();
|
||||||
|
LogEvent.put(event);
|
||||||
|
reload(message: 'Event ended');
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleStopAction(LogEvent event) async {
|
||||||
|
if (Settings.get().showConfirmationDialogOnStopEvent) {
|
||||||
|
DialogUtils.showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
onConfirm: () => onStop(event),
|
||||||
|
message: 'Are you sure you want to end this Event?',
|
||||||
|
confirmationLabel: 'END EVENT',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onStop(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void onChangeDate(DateTime? date) {
|
||||||
|
if (date != null) {
|
||||||
|
setState(() {
|
||||||
|
_date = DateTime(date.year, date.month, date.day);
|
||||||
|
_dateController.text = DateTimeUtils.displayDate(date);
|
||||||
|
});
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('Log Events'),
|
||||||
|
actions: <Widget>[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => onChangeDate(DateTime.now()),
|
||||||
|
icon: const Icon(Icons.today)),
|
||||||
|
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
|
||||||
|
body: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () => setState(() {
|
||||||
|
_showActive = !_showActive;
|
||||||
|
}),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
'ACTIVE EVENTS',
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(_showActive
|
||||||
|
? Icons.expand_less
|
||||||
|
: Icons.expand_more),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
!_showActive ? Container() :
|
||||||
|
_activeEvents.isNotEmpty
|
||||||
|
? ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _activeEvents.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
LogEvent event = _activeEvents[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
handleEditAction(event);
|
||||||
|
},
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateTimeUtils.displayDateTime(event.time),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(event.title ??
|
||||||
|
event.eventType.target?.value ??
|
||||||
|
'')
|
||||||
|
.toUpperCase(),
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
event.hasEndTime && event.endTime == null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.stop,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleStopAction(event),
|
||||||
|
)
|
||||||
|
: const SizedBox(width: 50),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleEditAction(event),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleDeleteAction(event),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Text('There are no Active Events!'),
|
||||||
|
),
|
||||||
|
const Padding(
|
||||||
|
padding: EdgeInsets.all(10.0),
|
||||||
|
child: Divider(),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
|
||||||
|
? null
|
||||||
|
: () =>
|
||||||
|
onChangeDate(_date.subtract(const Duration(days: 1))),
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
final newTime = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _date,
|
||||||
|
firstDate: DateTime(2000, 1, 1),
|
||||||
|
lastDate: DateTime.now().add(const Duration(days: 365)),
|
||||||
|
);
|
||||||
|
onChangeDate(newTime);
|
||||||
|
},
|
||||||
|
child: Expanded(
|
||||||
|
child: Text(
|
||||||
|
DateTimeUtils.displayDate(_date).toUpperCase(),
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed:
|
||||||
|
_date.add(const Duration(days: 1)).isBefore(DateTime.now())
|
||||||
|
? () => onChangeDate(_date.add(const Duration(days: 1)))
|
||||||
|
: null,
|
||||||
|
icon: const Icon(Icons.arrow_forward),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: _logEvents.isNotEmpty
|
||||||
|
? Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
shrinkWrap: true,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _logEvents.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
LogEvent event = _logEvents[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
handleEditAction(event);
|
||||||
|
},
|
||||||
|
title: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
DateTimeUtils.displayTime(event.isEndEvent
|
||||||
|
? event.endTime
|
||||||
|
: event.time),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Text(
|
||||||
|
(event.title ??
|
||||||
|
event.eventType.target?.value ??
|
||||||
|
'')
|
||||||
|
.toUpperCase(),
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
event.hasEndTime && event.endTime == null
|
||||||
|
? IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.stop,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () =>
|
||||||
|
handleStopAction(event),
|
||||||
|
)
|
||||||
|
: const SizedBox(width: 50),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.edit,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleEditAction(event),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.blue,
|
||||||
|
),
|
||||||
|
onPressed: () => handleDeleteAction(event),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
))
|
||||||
|
: const Center(
|
||||||
|
child: Text('There are no Events for that date!'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: handleAddNewEvent,
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
304
lib/screens/log/log_event/log_event_type_detail.dart
Normal file
304
lib/screens/log/log_event/log_event_type_detail.dart
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import 'package:diameter/components/detail.dart';
|
||||||
|
import 'package:diameter/components/forms/boolean_form_field.dart';
|
||||||
|
import 'package:diameter/components/forms/duration_form_field.dart';
|
||||||
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
|
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||||
|
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||||
|
import 'package:diameter/models/basal_profile.dart';
|
||||||
|
import 'package:diameter/models/bolus_profile.dart';
|
||||||
|
import 'package:diameter/models/log_event_type.dart';
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
import 'package:diameter/screens/basal/basal_profile_detail.dart';
|
||||||
|
import 'package:diameter/screens/bolus/bolus_profile_detail.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class EventTypeDetailScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/log-event-type';
|
||||||
|
final int id;
|
||||||
|
const EventTypeDetailScreen({Key? key, this.id = 0}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_EventTypeDetailScreenState createState() => _EventTypeDetailScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _EventTypeDetailScreenState extends State<EventTypeDetailScreen> {
|
||||||
|
LogEventType? _logEventType;
|
||||||
|
bool _isNew = true;
|
||||||
|
bool _isSaving = false;
|
||||||
|
|
||||||
|
List<BolusProfile> _bolusProfiles = [];
|
||||||
|
List<BasalProfile> _basalProfiles = [];
|
||||||
|
|
||||||
|
final GlobalKey<FormState> _logEventTypeForm = GlobalKey<FormState>();
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
final _valueController = TextEditingController(text: '');
|
||||||
|
final _notesController = TextEditingController(text: '');
|
||||||
|
|
||||||
|
bool _hasEndTime = false;
|
||||||
|
int _defaultReminderDuration = 0;
|
||||||
|
BolusProfile? _bolusProfile;
|
||||||
|
BasalProfile? _basalProfile;
|
||||||
|
final _bolusProfileController = TextEditingController(text: '');
|
||||||
|
final _basalProfileController = TextEditingController(text: '');
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
reload();
|
||||||
|
|
||||||
|
_bolusProfiles = BolusProfile.getAll();
|
||||||
|
_basalProfiles = BasalProfile.getAll();
|
||||||
|
|
||||||
|
if (_logEventType != null) {
|
||||||
|
_valueController.text = _logEventType!.value;
|
||||||
|
_defaultReminderDuration =
|
||||||
|
_logEventType!.defaultReminderDuration ?? 0;
|
||||||
|
_hasEndTime = _logEventType!.hasEndTime;
|
||||||
|
_notesController.text = _logEventType!.notes ?? '';
|
||||||
|
_basalProfile = _logEventType!.basalProfile.target;
|
||||||
|
_basalProfileController.text = (_basalProfile ?? '').toString();
|
||||||
|
_bolusProfile = _logEventType!.bolusProfile.target;
|
||||||
|
_bolusProfileController.text = (_bolusProfile ?? '').toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
_valueController.dispose();
|
||||||
|
_notesController.dispose();
|
||||||
|
_bolusProfileController.dispose();
|
||||||
|
_basalProfileController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
if (widget.id != 0) {
|
||||||
|
setState(() {
|
||||||
|
_logEventType = LogEventType.get(widget.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_isNew = _logEventType == null;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBasalProfile(BasalProfile? value) {
|
||||||
|
setState(() {
|
||||||
|
_basalProfile = value;
|
||||||
|
_basalProfileController.text = (_basalProfile ?? '').toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateBolusProfile(BolusProfile? value) {
|
||||||
|
setState(() {
|
||||||
|
_bolusProfile = value;
|
||||||
|
_bolusProfileController.text = (_bolusProfile ?? '').toString();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSaveAction() async {
|
||||||
|
setState(() {
|
||||||
|
_isSaving = true;
|
||||||
|
});
|
||||||
|
if (_logEventTypeForm.currentState!.validate()) {
|
||||||
|
LogEventType eventType = LogEventType(
|
||||||
|
id: widget.id,
|
||||||
|
value: _valueController.text,
|
||||||
|
notes: _notesController.text,
|
||||||
|
defaultReminderDuration: _defaultReminderDuration,
|
||||||
|
hasEndTime: _hasEndTime,
|
||||||
|
);
|
||||||
|
eventType.basalProfile.target = _basalProfile;
|
||||||
|
eventType.bolusProfile.target = _bolusProfile;
|
||||||
|
LogEventType.put(eventType);
|
||||||
|
Navigator.pop(
|
||||||
|
context, ['${_isNew ? 'New' : ''} Log Event Type Saved', eventType]);
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_isSaving = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleCancelAction() {
|
||||||
|
bool isNew = _logEventType == null;
|
||||||
|
if (Settings.get().showConfirmationDialogOnCancel &&
|
||||||
|
((isNew &&
|
||||||
|
(_valueController.text != '' ||
|
||||||
|
_defaultReminderDuration != 0 ||
|
||||||
|
_notesController.text != '' ||
|
||||||
|
_hasEndTime)) ||
|
||||||
|
(!isNew &&
|
||||||
|
(_valueController.text != _logEventType!.value ||
|
||||||
|
_defaultReminderDuration !=
|
||||||
|
_logEventType!.defaultReminderDuration ||
|
||||||
|
_notesController.text != (_logEventType!.notes ?? '') ||
|
||||||
|
_hasEndTime != _logEventType!.hasEndTime)))) {
|
||||||
|
DialogUtils.showCancelConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
isNew: isNew,
|
||||||
|
onSave: handleSaveAction,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(_isNew ? 'New Log Event Type' : _logEventType!.value),
|
||||||
|
),
|
||||||
|
drawer:
|
||||||
|
const Navigation(currentLocation: EventTypeDetailScreen.routeName),
|
||||||
|
body: Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
FormWrapper(formState: _logEventTypeForm, fields: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _valueController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Name',
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value!.trim().isEmpty) {
|
||||||
|
return 'Empty name';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
BooleanFormField(
|
||||||
|
value: _hasEndTime,
|
||||||
|
label: 'has end time',
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_hasEndTime = value;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: _hasEndTime
|
||||||
|
? [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10.0),
|
||||||
|
child: DurationFormField(
|
||||||
|
minutes: _defaultReminderDuration,
|
||||||
|
label: 'Default Reminder Duration',
|
||||||
|
onChanged: (value) => _defaultReminderDuration = value ?? 0,
|
||||||
|
showSteppers: true,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(bottom: 10.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AutoCompleteDropdownButton<
|
||||||
|
BolusProfile>(
|
||||||
|
selectedItem: _bolusProfile,
|
||||||
|
controller: _bolusProfileController,
|
||||||
|
label: 'Bolus Profile',
|
||||||
|
items: _bolusProfiles,
|
||||||
|
onChanged: updateBolusProfile,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => _bolusProfile ==
|
||||||
|
null
|
||||||
|
? const BolusProfileDetailScreen()
|
||||||
|
: BolusProfileDetailScreen(
|
||||||
|
id: _bolusProfile!.id),
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
setState(() {
|
||||||
|
updateBolusProfile(result?[1]);
|
||||||
|
});
|
||||||
|
reload(message: result?[0]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(_bolusProfile == null
|
||||||
|
? Icons.add
|
||||||
|
: Icons.edit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child:
|
||||||
|
AutoCompleteDropdownButton<BasalProfile>(
|
||||||
|
controller: _basalProfileController,
|
||||||
|
selectedItem: _basalProfile,
|
||||||
|
label: 'Basal Profile',
|
||||||
|
items: _basalProfiles,
|
||||||
|
onChanged: updateBasalProfile,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => _basalProfile ==
|
||||||
|
null
|
||||||
|
? const BasalProfileDetailScreen()
|
||||||
|
: BasalProfileDetailScreen(
|
||||||
|
id: _basalProfile!.id),
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
updateBasalProfile(result?[1]);
|
||||||
|
reload(message: result?[0]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(_basalProfile == null
|
||||||
|
? Icons.add
|
||||||
|
: Icons.edit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: []),
|
||||||
|
TextFormField(
|
||||||
|
controller: _notesController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Notes',
|
||||||
|
),
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
minLines: 2,
|
||||||
|
maxLines: 5,
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: DetailBottomRow(
|
||||||
|
onCancel: handleCancelAction,
|
||||||
|
onAction: _isSaving ? null : handleSaveAction,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
123
lib/screens/log/log_event/log_event_type_list.dart
Normal file
123
lib/screens/log/log_event/log_event_type_list.dart
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
import 'package:diameter/models/log_event_type.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
import 'package:diameter/screens/log/log_event/log_event_type_detail.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class LogEventTypeListScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/log-event-types';
|
||||||
|
const LogEventTypeListScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_LogEventTypeListScreenState createState() => _LogEventTypeListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _LogEventTypeListScreenState extends State<LogEventTypeListScreen> {
|
||||||
|
List<LogEventType> _logEventTypes = [];
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
setState(() {
|
||||||
|
_logEventTypes = LogEventType.getAll();
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Log Event Types'), actions: <Widget>[
|
||||||
|
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||||
|
]),
|
||||||
|
drawer:
|
||||||
|
const Navigation(currentLocation: LogEventTypeListScreen.routeName),
|
||||||
|
body: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: _logEventTypes.isNotEmpty
|
||||||
|
? Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _logEventTypes.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final logEventType = _logEventTypes[index];
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => EventTypeDetailScreen(
|
||||||
|
id: logEventType.id),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
logEventType.value.toUpperCase(),
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
subtitle: Text(logEventType.notes ?? ''),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
LogEventType.remove(logEventType.id);
|
||||||
|
reload(message: 'Log Event Type deleted');
|
||||||
|
},
|
||||||
|
icon: const Icon(Icons.delete,
|
||||||
|
color: Colors.blue),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child:
|
||||||
|
Text('You have not created any Log Event Types yet!'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const EventTypeDetailScreen(),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,386 +0,0 @@
|
|||||||
import 'package:diameter/utils/dialog_utils.dart';
|
|
||||||
import 'package:diameter/models/log_event.dart';
|
|
||||||
import 'package:diameter/models/settings.dart';
|
|
||||||
import 'package:diameter/screens/log/log_event_detail.dart';
|
|
||||||
import 'package:diameter/utils/date_time_utils.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:diameter/navigation.dart';
|
|
||||||
|
|
||||||
class LogEventListScreen extends StatefulWidget {
|
|
||||||
static const String routeName = '/log-events';
|
|
||||||
const LogEventListScreen({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
_LogEventListScreenState createState() => _LogEventListScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LogEventListScreenState extends State<LogEventListScreen> {
|
|
||||||
List<LogEvent> _activeEvents = [];
|
|
||||||
late List<LogEvent> _logEvents;
|
|
||||||
|
|
||||||
final ScrollController _scrollController = ScrollController();
|
|
||||||
|
|
||||||
final TextEditingController _dateController = TextEditingController(text: '');
|
|
||||||
|
|
||||||
late DateTime _date;
|
|
||||||
bool _showActive = true;
|
|
||||||
String? swipeDirection;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
_date = DateTime.now();
|
|
||||||
_dateController.text = DateTimeUtils.displayDate(_date);
|
|
||||||
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_scrollController.dispose();
|
|
||||||
_dateController.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reload({String? message}) {
|
|
||||||
setState(() {
|
|
||||||
_activeEvents = LogEvent.getAllActiveForTime(DateTime.now());
|
|
||||||
_logEvents = LogEvent.getAllForDate(_date);
|
|
||||||
});
|
|
||||||
|
|
||||||
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 _date.isAtSameMomentAs(DateTimeUtils.today())
|
|
||||||
? const LogEventDetailScreen()
|
|
||||||
: LogEventDetailScreen(
|
|
||||||
suggestedDate: _date,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleEditAction(LogEvent event) {
|
|
||||||
Navigator.push(
|
|
||||||
context,
|
|
||||||
MaterialPageRoute(
|
|
||||||
builder: (context) => LogEventDetailScreen(
|
|
||||||
id: event.id,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).then((result) => reload(message: result?[0]));
|
|
||||||
}
|
|
||||||
|
|
||||||
void onDelete(LogEvent logEvent) {
|
|
||||||
LogEvent.remove(logEvent.id);
|
|
||||||
reload(message: 'Event deleted');
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleDeleteAction(LogEvent logEvent) async {
|
|
||||||
if (Settings.get().showConfirmationDialogOnDelete) {
|
|
||||||
DialogUtils.showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
onConfirm: () => onDelete(logEvent),
|
|
||||||
message: 'Are you sure you want to delete this Event?',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onDelete(logEvent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onStop(LogEvent event) async {
|
|
||||||
event.endTime = DateTime.now();
|
|
||||||
LogEvent.put(event);
|
|
||||||
reload(message: 'Event ended');
|
|
||||||
}
|
|
||||||
|
|
||||||
void handleStopAction(LogEvent event) async {
|
|
||||||
if (Settings.get().showConfirmationDialogOnStopEvent) {
|
|
||||||
DialogUtils.showConfirmationDialog(
|
|
||||||
context: context,
|
|
||||||
onConfirm: () => onStop(event),
|
|
||||||
message: 'Are you sure you want to end this Event?',
|
|
||||||
confirmationLabel: 'END EVENT',
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
onStop(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void onChangeDate(DateTime? date) {
|
|
||||||
if (date != null) {
|
|
||||||
setState(() {
|
|
||||||
_date = DateTime(date.year, date.month, date.day);
|
|
||||||
_dateController.text = DateTimeUtils.displayDate(date);
|
|
||||||
});
|
|
||||||
reload();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
|
||||||
title: const Text('Log Events'),
|
|
||||||
actions: <Widget>[
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => onChangeDate(DateTime.now()),
|
|
||||||
icon: const Icon(Icons.today)),
|
|
||||||
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
drawer: const Navigation(currentLocation: LogEventListScreen.routeName),
|
|
||||||
body: GestureDetector(
|
|
||||||
onPanUpdate: (details) {
|
|
||||||
swipeDirection = details.delta.dx < 0 ? 'left' : 'right';
|
|
||||||
},
|
|
||||||
onPanEnd: (details) {
|
|
||||||
if (swipeDirection == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (swipeDirection == 'right' &&
|
|
||||||
!_date.isAtSameMomentAs(DateTime(2000, 1, 1))) {
|
|
||||||
onChangeDate(_date.subtract(const Duration(days: 1)));
|
|
||||||
}
|
|
||||||
if (swipeDirection == 'left' &&
|
|
||||||
_date.add(const Duration(days: 1)).isBefore(DateTime.now())) {
|
|
||||||
onChangeDate(_date.add(const Duration(days: 1)));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
GestureDetector(
|
|
||||||
onTap: () => setState(() {
|
|
||||||
_showActive = !_showActive;
|
|
||||||
}),
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.max,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
'ACTIVE EVENTS',
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Icon(_showActive ? Icons.expand_less : Icons.expand_more),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
!_showActive
|
|
||||||
? Container()
|
|
||||||
: _activeEvents.isNotEmpty
|
|
||||||
? ListView.builder(
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
itemCount: _activeEvents.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
LogEvent event = _activeEvents[index];
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () {
|
|
||||||
handleEditAction(event);
|
|
||||||
},
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateTimeUtils.displayDateTime(event.time),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
(event.title ??
|
|
||||||
event.eventType.target?.value ??
|
|
||||||
'')
|
|
||||||
.toUpperCase(),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
event.hasEndTime && event.endTime == null
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.stop,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
handleStopAction(event),
|
|
||||||
)
|
|
||||||
: const SizedBox(width: 50),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleEditAction(event),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleDeleteAction(event),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
: const Center(
|
|
||||||
child: Text('There are no Active Events!'),
|
|
||||||
),
|
|
||||||
const Padding(
|
|
||||||
padding: EdgeInsets.all(10.0),
|
|
||||||
child: Divider(),
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: _date.isAtSameMomentAs(DateTime(2000, 1, 1))
|
|
||||||
? null
|
|
||||||
: () =>
|
|
||||||
onChangeDate(_date.subtract(const Duration(days: 1))),
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
final newTime = await showDatePicker(
|
|
||||||
context: context,
|
|
||||||
initialDate: _date,
|
|
||||||
firstDate: DateTime(2000, 1, 1),
|
|
||||||
lastDate: DateTime.now().add(const Duration(days: 365)),
|
|
||||||
);
|
|
||||||
onChangeDate(newTime);
|
|
||||||
},
|
|
||||||
child: Text(
|
|
||||||
DateTimeUtils.displayDate(_date).toUpperCase(),
|
|
||||||
style: Theme.of(context).textTheme.subtitle2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: _date
|
|
||||||
.add(const Duration(days: 1))
|
|
||||||
.isBefore(DateTime.now())
|
|
||||||
? () => onChangeDate(_date.add(const Duration(days: 1)))
|
|
||||||
: null,
|
|
||||||
icon: const Icon(Icons.arrow_forward),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: _logEvents.isNotEmpty
|
|
||||||
? Scrollbar(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: ListView.builder(
|
|
||||||
controller: _scrollController,
|
|
||||||
shrinkWrap: true,
|
|
||||||
padding: const EdgeInsets.all(10.0),
|
|
||||||
itemCount: _logEvents.length,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
LogEvent event = _logEvents[index];
|
|
||||||
return Card(
|
|
||||||
child: ListTile(
|
|
||||||
onTap: () {
|
|
||||||
handleEditAction(event);
|
|
||||||
},
|
|
||||||
title: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
DateTimeUtils.displayTime(event.isEndEvent
|
|
||||||
? event.endTime
|
|
||||||
: event.time),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 24),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
(event.title ??
|
|
||||||
event.eventType.target?.value ??
|
|
||||||
'')
|
|
||||||
.toUpperCase(),
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.subtitle2,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
event.hasEndTime && event.endTime == null
|
|
||||||
? IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.stop,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
handleStopAction(event),
|
|
||||||
)
|
|
||||||
: const SizedBox(width: 50),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.edit,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleEditAction(event),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(
|
|
||||||
Icons.delete,
|
|
||||||
color: Colors.blue,
|
|
||||||
),
|
|
||||||
onPressed: () => handleDeleteAction(event),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
))
|
|
||||||
: const Center(
|
|
||||||
child: Text('There are no Events for that date!'),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
onPressed: handleAddNewEvent,
|
|
||||||
child: const Icon(Icons.add),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
322
lib/screens/recipe/recipe_detail.dart
Normal file
322
lib/screens/recipe/recipe_detail.dart
Normal file
@ -0,0 +1,322 @@
|
|||||||
|
import 'package:diameter/components/detail.dart';
|
||||||
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
|
import 'package:diameter/components/forms/auto_complete_dropdown_button.dart';
|
||||||
|
import 'package:diameter/components/forms/form_wrapper.dart';
|
||||||
|
import 'package:diameter/models/ingredient.dart';
|
||||||
|
import 'package:diameter/models/meal.dart';
|
||||||
|
import 'package:diameter/models/recipe.dart';
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
import 'package:diameter/screens/meal/meal_detail.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RecipeDetailScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/recipe';
|
||||||
|
final int id;
|
||||||
|
|
||||||
|
const RecipeDetailScreen({Key? key, this.id = 0}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RecipeDetailScreenState createState() => _RecipeDetailScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecipeDetailScreenState extends State<RecipeDetailScreen> {
|
||||||
|
Recipe? _recipe;
|
||||||
|
List<Ingredient> _ingredients = [];
|
||||||
|
|
||||||
|
bool _isNew = true;
|
||||||
|
bool _isSaving = false;
|
||||||
|
|
||||||
|
final GlobalKey<FormState> _recipeForm = GlobalKey<FormState>();
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
final _nameController = TextEditingController(text: '');
|
||||||
|
final _notesController = TextEditingController(text: '');
|
||||||
|
|
||||||
|
double _servings = 1;
|
||||||
|
|
||||||
|
final List<TextEditingController> _ingredientControllers = [];
|
||||||
|
|
||||||
|
List<Meal> _meals = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
reload();
|
||||||
|
|
||||||
|
_meals = Meal.getAll();
|
||||||
|
|
||||||
|
if (_recipe != null) {
|
||||||
|
_nameController.text = _recipe!.name;
|
||||||
|
_servings = _recipe!.servings ?? 1;
|
||||||
|
_notesController.text = _recipe!.notes ?? '';
|
||||||
|
|
||||||
|
if (_ingredients.isNotEmpty) {
|
||||||
|
for (Ingredient ingredient in _ingredients) {
|
||||||
|
_ingredientControllers.add(
|
||||||
|
TextEditingController(text: ingredient.ingredient.target?.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
if (widget.id != 0) {
|
||||||
|
setState(() {
|
||||||
|
_recipe = Recipe.get(widget.id);
|
||||||
|
_ingredients = Ingredient.getAllForRecipe(widget.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_isNew = _recipe == null;
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onAddIngredient() {
|
||||||
|
final newIngredient = Ingredient(amount: 0);
|
||||||
|
setState(() {
|
||||||
|
newIngredient.recipe.target = _recipe;
|
||||||
|
_ingredients.add(newIngredient);
|
||||||
|
_ingredientControllers.add(TextEditingController(text: ''));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleSaveAction({bool close = false}) async {
|
||||||
|
setState(() {
|
||||||
|
_isSaving = true;
|
||||||
|
});
|
||||||
|
if (_recipeForm.currentState!.validate()) {
|
||||||
|
Recipe recipe = Recipe(
|
||||||
|
id: widget.id,
|
||||||
|
name: _nameController.text,
|
||||||
|
servings: _servings,
|
||||||
|
notes: _notesController.text,
|
||||||
|
);
|
||||||
|
Recipe.put(recipe);
|
||||||
|
List<Ingredient> ingredients = _ingredients.map((ingredient) {
|
||||||
|
if (ingredient.id != 0 &&
|
||||||
|
(!ingredient.ingredient.hasValue || ingredient.amount == 0)) {
|
||||||
|
ingredient.deleted = true;
|
||||||
|
}
|
||||||
|
return ingredient;
|
||||||
|
}).toList();
|
||||||
|
ingredients.retainWhere((ingredient) {
|
||||||
|
return ingredient.id != 0 ||
|
||||||
|
(ingredient.amount > 0 && ingredient.ingredient.hasValue);
|
||||||
|
});
|
||||||
|
Ingredient.putMany(ingredients);
|
||||||
|
|
||||||
|
if (close) {
|
||||||
|
Navigator.pop(context, ['${_isNew ? 'New' : ''} Recipe Saved', recipe]);
|
||||||
|
} else {
|
||||||
|
if (_isNew) {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => RecipeDetailScreen(id: recipe.id),
|
||||||
|
),
|
||||||
|
).then((result) => Navigator.pop(context, result));
|
||||||
|
} else {
|
||||||
|
reload(message: 'Recipe saved');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
_isSaving = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleCancelAction() {
|
||||||
|
if (Settings.get().showConfirmationDialogOnCancel &&
|
||||||
|
((_isNew &&
|
||||||
|
(_nameController.text != '' ||
|
||||||
|
_servings != 1 ||
|
||||||
|
_notesController.text != '')) ||
|
||||||
|
(!_isNew &&
|
||||||
|
(_nameController.text != _recipe!.name ||
|
||||||
|
_servings != _recipe!.servings ||
|
||||||
|
_notesController.text != (_recipe!.notes ?? ''))))) {
|
||||||
|
DialogUtils.showCancelConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
isNew: _isNew,
|
||||||
|
onSave: handleSaveAction,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: Text(_isNew ? 'New Recipe' : _recipe!.name),
|
||||||
|
),
|
||||||
|
drawer: const Navigation(currentLocation: RecipeDetailScreen.routeName),
|
||||||
|
body: Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
FormWrapper(
|
||||||
|
formState: _recipeForm,
|
||||||
|
fields: [
|
||||||
|
TextFormField(
|
||||||
|
controller: _nameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Name',
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value!.trim().isEmpty) {
|
||||||
|
return 'Empty title';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// NumberFormField(
|
||||||
|
// value: _servings,
|
||||||
|
// label: 'Servings',
|
||||||
|
// suffix: ' portions',
|
||||||
|
// min: 0,
|
||||||
|
// onChanged: (value) {
|
||||||
|
// if (value != null && value >= 0) {
|
||||||
|
// setState(() {
|
||||||
|
// _servings = value.toDouble();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
TextFormField(
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
controller: _notesController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Notes',
|
||||||
|
),
|
||||||
|
minLines: 2,
|
||||||
|
maxLines: 5,
|
||||||
|
),
|
||||||
|
const Divider(),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: onAddIngredient,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
'INGREDIENTS',
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: onAddIngredient,
|
||||||
|
icon: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
!_isNew && _ingredients.isNotEmpty
|
||||||
|
? ListBody(
|
||||||
|
children: _ingredients.map((item) {
|
||||||
|
final ingredient = item.ingredient.target;
|
||||||
|
final index = _ingredients.indexOf(item);
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 10.0, vertical: 5.0),
|
||||||
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: AutoCompleteDropdownButton<Meal>(
|
||||||
|
controller: _ingredientControllers[index],
|
||||||
|
selectedItem: ingredient,
|
||||||
|
label: 'Meal Category',
|
||||||
|
items: _meals,
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {
|
||||||
|
_ingredients[index]
|
||||||
|
.ingredient
|
||||||
|
.target = value;
|
||||||
|
_ingredientControllers[index].text =
|
||||||
|
value?.value ?? '';
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
ingredient == null
|
||||||
|
? const MealDetailScreen()
|
||||||
|
: MealDetailScreen(
|
||||||
|
id: ingredient.id),
|
||||||
|
),
|
||||||
|
).then((result) {
|
||||||
|
_ingredients[index].ingredient.target =
|
||||||
|
result?[1];
|
||||||
|
_ingredientControllers[index].text =
|
||||||
|
result?[1].value ?? '';
|
||||||
|
reload(message: result?[0]);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
icon: Icon(ingredient == null
|
||||||
|
? Icons.add
|
||||||
|
: Icons.edit),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.only(top: 10.0),
|
||||||
|
// child: NumberFormField(
|
||||||
|
// controller:
|
||||||
|
// _ingredients[index].amount,
|
||||||
|
// label: 'Amount',
|
||||||
|
// suffix: Settings.nutritionMeasurementSuffix,
|
||||||
|
// min: 0,
|
||||||
|
// onChanged: (value) {
|
||||||
|
// if (value != null && value >= 0) {
|
||||||
|
// setState(() {
|
||||||
|
// _ingredients[index].amount = value.toDouble();
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
)
|
||||||
|
: Center(
|
||||||
|
child: Text(_isNew
|
||||||
|
? 'Save the Recipe in order to add ingredients!'
|
||||||
|
: 'You have not added any Ingredients yet!'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
bottomNavigationBar: DetailBottomRow(
|
||||||
|
onCancel: handleCancelAction,
|
||||||
|
onAction: _isSaving ? null : handleSaveAction,
|
||||||
|
onMiddleAction: _isSaving ? null : () => handleSaveAction(close: true),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
200
lib/screens/recipe/recipe_list.dart
Normal file
200
lib/screens/recipe/recipe_list.dart
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import 'package:diameter/utils/dialog_utils.dart';
|
||||||
|
import 'package:diameter/models/ingredient.dart';
|
||||||
|
import 'package:diameter/models/recipe.dart';
|
||||||
|
import 'package:diameter/models/settings.dart';
|
||||||
|
import 'package:diameter/navigation.dart';
|
||||||
|
import 'package:diameter/screens/recipe/recipe_detail.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class RecipeListScreen extends StatefulWidget {
|
||||||
|
static const String routeName = '/recipes';
|
||||||
|
|
||||||
|
const RecipeListScreen({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
_RecipeListScreenState createState() => _RecipeListScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _RecipeListScreenState extends State<RecipeListScreen> {
|
||||||
|
List<Recipe> _recipes = [];
|
||||||
|
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
reload();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reload({String? message}) {
|
||||||
|
setState(() {
|
||||||
|
_recipes = Recipe.getAll();
|
||||||
|
});
|
||||||
|
setState(() {
|
||||||
|
if (message != null) {
|
||||||
|
var snackBar = SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
duration: const Duration(seconds: 2),
|
||||||
|
);
|
||||||
|
ScaffoldMessenger.of(context)
|
||||||
|
..removeCurrentSnackBar()
|
||||||
|
..showSnackBar(snackBar);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onDelete(Recipe recipe) {
|
||||||
|
Recipe.remove(recipe.id);
|
||||||
|
reload(message: 'Recipe deleted');
|
||||||
|
}
|
||||||
|
|
||||||
|
void handleDeleteAction(Recipe recipe) async {
|
||||||
|
if (Settings.get().showConfirmationDialogOnDelete) {
|
||||||
|
DialogUtils.showConfirmationDialog(
|
||||||
|
context: context,
|
||||||
|
onConfirm: () => onDelete(recipe),
|
||||||
|
message: 'Are you sure you want to delete this Recipe?',
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
onDelete(recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Recipes'), actions: <Widget>[
|
||||||
|
IconButton(onPressed: reload, icon: const Icon(Icons.refresh))
|
||||||
|
]),
|
||||||
|
drawer: const Navigation(currentLocation: RecipeListScreen.routeName),
|
||||||
|
body: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: <Widget>[
|
||||||
|
Expanded(
|
||||||
|
child: _recipes.isNotEmpty
|
||||||
|
? Scrollbar(
|
||||||
|
controller: _scrollController,
|
||||||
|
child: ListView.builder(
|
||||||
|
controller: _scrollController,
|
||||||
|
padding: const EdgeInsets.all(10.0),
|
||||||
|
itemCount: _recipes.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final recipe = _recipes[index];
|
||||||
|
final carbsRatio =
|
||||||
|
Ingredient.getCarbsRatioForRecipe(recipe.id);
|
||||||
|
final carbsPerPortion = Recipe.getCarbsPerPortion(recipe.id);
|
||||||
|
return Card(
|
||||||
|
child: ListTile(
|
||||||
|
onTap: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) =>
|
||||||
|
RecipeDetailScreen(id: recipe.id),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
},
|
||||||
|
title: Text(
|
||||||
|
recipe.name.toUpperCase(),
|
||||||
|
style: Theme.of(context).textTheme.subtitle2,
|
||||||
|
),
|
||||||
|
subtitle: Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 10.0),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
Text(recipe.notes ?? ''),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
((carbsRatio ?? 0) > 0)
|
||||||
|
? [
|
||||||
|
Text(carbsRatio!.toString()),
|
||||||
|
const Text('% carbs',
|
||||||
|
textScaleFactor: 0.75),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
(recipe.servings != null)
|
||||||
|
? [
|
||||||
|
Text(recipe.servings!
|
||||||
|
.toStringAsPrecision(3)),
|
||||||
|
const Text('servings',
|
||||||
|
textScaleFactor: 0.75),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children:
|
||||||
|
((carbsPerPortion ?? 0) > 0)
|
||||||
|
? [
|
||||||
|
Text(carbsPerPortion!
|
||||||
|
.toStringAsPrecision(3)),
|
||||||
|
Text(
|
||||||
|
'${Settings.nutritionMeasurementSuffix} carbs per serving',
|
||||||
|
textScaleFactor: 0.75),
|
||||||
|
]
|
||||||
|
: [],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
trailing: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => handleDeleteAction(recipe),
|
||||||
|
icon: const Icon(Icons.delete,
|
||||||
|
color: Colors.blue),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: const Center(
|
||||||
|
child: Text('You have not created any Recipes yet!'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.push(
|
||||||
|
context,
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => const RecipeDetailScreen(),
|
||||||
|
),
|
||||||
|
).then((result) => reload(message: result?[0]));
|
||||||
|
},
|
||||||
|
child: const Icon(Icons.add),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
12
pubspec.lock
12
pubspec.lock
@ -249,11 +249,6 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
version: "0.0.0"
|
||||||
flutter_web_plugins:
|
|
||||||
dependency: transitive
|
|
||||||
description: flutter
|
|
||||||
source: sdk
|
|
||||||
version: "0.0.0"
|
|
||||||
frontend_server_client:
|
frontend_server_client:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -275,13 +270,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
version: "2.1.0"
|
||||||
http:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: http
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "0.13.4"
|
|
||||||
http_multi_server:
|
http_multi_server:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
Loading…
Reference in New Issue
Block a user