Merge branch 'main' of https://git.sudo.ca/spinel/diameter into main
This commit is contained in:
		
						commit
						481dc60996
					
				
					 12 changed files with 2773 additions and 1704 deletions
				
			
		|  | @ -68,4 +68,19 @@ class LogEntry { | |||
|   String toString() { | ||||
|     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 | ||||
|   static LogEvent? get(int id) => box.get(id); | ||||
|   static List<LogEvent> getAll() => box.getAll(); | ||||
|   static void put(LogEvent logEvent) => box.put(logEvent); | ||||
| 
 | ||||
|   static void remove(int id) { | ||||
|  |  | |||
|  | @ -1087,44 +1087,19 @@ | |||
|     "lastSequenceId": "0:0", | ||||
|     "modelVersion": 5, | ||||
|     "modelVersionParserMinimum": 5, | ||||
|   "retiredEntityUids": [ | ||||
|     3095978685310268382 | ||||
|   ], | ||||
|   "retiredIndexUids": [ | ||||
|     3670661188280692002, | ||||
|     7379712902406481832 | ||||
|   ], | ||||
|     "retiredEntityUids": [3095978685310268382], | ||||
|     "retiredIndexUids": [3670661188280692002, 7379712902406481832], | ||||
|     "retiredPropertyUids": [ | ||||
|     3455702077061719523, | ||||
|     1048198814030724077, | ||||
|     9003780003858349085, | ||||
|     5421422436108145565, | ||||
|     7741631874181070179, | ||||
|     5471636804765937328, | ||||
|     6855574218883169324, | ||||
|     5313708456544000157, | ||||
|     3678829169126156351, | ||||
|     1568597071506264632, | ||||
|     8795268969829293398, | ||||
|     3247926313599127440, | ||||
|     8789440370359282572, | ||||
|     7838546213550447420, | ||||
|     8031421171668506924, | ||||
|     1614362036318874174, | ||||
|     1675040259141389754, | ||||
|     7518219134349037920, | ||||
|     2172890064639236018, | ||||
|     310032577683835406, | ||||
|     5588897884422150510, | ||||
|     7638848982383620744, | ||||
|     3282706593658092097, | ||||
|     596980591281311896, | ||||
|     3633551763915044903, | ||||
|     2215708755581938580, | ||||
|     241621230513128588, | ||||
|     4678123663117222609, | ||||
|     780211923138281722, | ||||
|     763575433624979013, | ||||
|         3455702077061719523, 1048198814030724077, 9003780003858349085, | ||||
|         5421422436108145565, 7741631874181070179, 5471636804765937328, | ||||
|         6855574218883169324, 5313708456544000157, 3678829169126156351, | ||||
|         1568597071506264632, 8795268969829293398, 3247926313599127440, | ||||
|         8789440370359282572, 7838546213550447420, 8031421171668506924, | ||||
|         1614362036318874174, 1675040259141389754, 7518219134349037920, | ||||
|         2172890064639236018, 310032577683835406, 5588897884422150510, | ||||
|         7638848982383620744, 3282706593658092097, 596980591281311896, | ||||
|         3633551763915044903, 2215708755581938580, 241621230513128588, | ||||
|         4678123663117222609, 780211923138281722, 763575433624979013, | ||||
|         1225271130099322691 | ||||
|     ], | ||||
|     "retiredRelationUids": [], | ||||
|  |  | |||
|  | @ -1234,7 +1234,7 @@ ModelDefinition getObjectBoxModel() { | |||
|           final buffer = fb.BufferContext(fbData); | ||||
|           final rootOffset = buffer.derefObject(0); | ||||
| 
 | ||||
|           final object = Bolus( | ||||
|           final object = LogEventType( | ||||
|               id: const fb.Int64Reader().vTableGet(buffer, rootOffset, 4, 0), | ||||
|               deleted: const fb.BoolReader() | ||||
|                   .vTableGet(buffer, rootOffset, 20, false), | ||||
|  | @ -1250,190 +1250,6 @@ ModelDefinition getObjectBoxModel() { | |||
|                   .vTableGetNullable(buffer, rootOffset, 14), | ||||
|               mmolPerL: const fb.Float64Reader() | ||||
|                   .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 = | ||||
|               const fb.Int64Reader().vTableGet(buffer, rootOffset, 16, 0); | ||||
|           object.bolusProfile.attach(store); | ||||
|  | @ -1487,6 +1303,353 @@ ModelDefinition getObjectBoxModel() { | |||
|           final rootOffset = buffer.derefObject(0); | ||||
| 
 | ||||
|           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), | ||||
|               deleted: const fb.BoolReader() | ||||
|                   .vTableGet(buffer, rootOffset, 36, false), | ||||
|  |  | |||
|  | @ -188,7 +188,7 @@ class _LogEventDetailScreenState extends State<LogEventDetailScreen> { | |||
|   } | ||||
| 
 | ||||
|   Future<void> checkIfActiveEventOfTypeExistsBeforeSaving() async { | ||||
|     if (_isNew && _eventType != null && | ||||
|     if (_eventType != null && | ||||
|         LogEvent.eventTypeExistsForTime(_eventType!.id, _time)) { | ||||
|       await showDialog( | ||||
|           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 | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   flutter_web_plugins: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   frontend_server_client: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  | @ -275,13 +270,6 @@ packages: | |||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.0" | ||||
|   http: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: http | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.13.4" | ||||
|   http_multi_server: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|  |  | |||
		Loading…
	
	Add table
		
		Reference in a new issue