146 lines
5.2 KiB
Dart
146 lines
5.2 KiB
Dart
import 'package:drift/drift.dart';
|
|
|
|
import '../../../../core/database/app_database.dart';
|
|
import '../../domain/entities/food_log_entry.dart';
|
|
import '../../domain/entities/hydration_log_entry.dart';
|
|
import '../../domain/entities/meal_type.dart';
|
|
import '../../domain/repositories/nutrition_repository.dart';
|
|
|
|
/// Drift-backed implementation of [INutritionRepository].
|
|
///
|
|
/// Owns the translation between persistence records ([FoodLogRecord],
|
|
/// [HydrationLogRecord]) and the pure-Dart nutrition entities. The meal slot
|
|
/// is persisted as its stable enum name string so the database schema stays
|
|
/// decoupled from domain types.
|
|
class NutritionRepository implements INutritionRepository {
|
|
NutritionRepository(this._database);
|
|
|
|
final AppDatabase _database;
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Food diary
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@override
|
|
Stream<List<FoodLogEntry>> watchFoodLogs({int limit = 500}) => _database
|
|
.watchFoodLogs(limit: limit)
|
|
.map((records) => records.map(_mapFood).toList(growable: false));
|
|
|
|
@override
|
|
Future<List<FoodLogEntry>> getFoodLogs({
|
|
int limit = 500,
|
|
DateTime? from,
|
|
DateTime? to,
|
|
}) async =>
|
|
(await _database.getFoodLogs(limit: limit, from: from, to: to))
|
|
.map(_mapFood)
|
|
.toList(growable: false);
|
|
|
|
@override
|
|
Future<FoodLogEntry> saveFoodLog(FoodLogEntry entry) async {
|
|
final companion = _toFoodCompanion(entry);
|
|
|
|
final FoodLogRecord record;
|
|
if (entry.id == null) {
|
|
record = await _database.insertFoodLog(companion);
|
|
} else {
|
|
await _database.updateFoodLog(entry.id!, companion);
|
|
record = await (_database.select(_database.foodLogsTable)
|
|
..where((t) => t.id.equals(entry.id!)))
|
|
.getSingle();
|
|
}
|
|
return _mapFood(record);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteFoodLog(int id) => _database.deleteFoodLog(id);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Hydration
|
|
// ---------------------------------------------------------------------------
|
|
|
|
@override
|
|
Stream<List<HydrationLogEntry>> watchHydrationLogs({int limit = 500}) =>
|
|
_database.watchHydrationLogs(limit: limit).map(
|
|
(records) => records.map(_mapHydration).toList(growable: false),
|
|
);
|
|
|
|
@override
|
|
Future<List<HydrationLogEntry>> getHydrationLogs({
|
|
int limit = 500,
|
|
DateTime? from,
|
|
DateTime? to,
|
|
}) async =>
|
|
(await _database.getHydrationLogs(limit: limit, from: from, to: to))
|
|
.map(_mapHydration)
|
|
.toList(growable: false);
|
|
|
|
@override
|
|
Future<HydrationLogEntry> saveHydrationLog(HydrationLogEntry entry) async {
|
|
final companion = _toHydrationCompanion(entry);
|
|
|
|
final HydrationLogRecord record;
|
|
if (entry.id == null) {
|
|
record = await _database.insertHydrationLog(companion);
|
|
} else {
|
|
await _database.updateHydrationLog(entry.id!, companion);
|
|
record = await (_database.select(_database.hydrationLogsTable)
|
|
..where((t) => t.id.equals(entry.id!)))
|
|
.getSingle();
|
|
}
|
|
return _mapHydration(record);
|
|
}
|
|
|
|
@override
|
|
Future<void> deleteHydrationLog(int id) => _database.deleteHydrationLog(id);
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// Mapping
|
|
// ---------------------------------------------------------------------------
|
|
|
|
/// Converts a persistence record into the domain entity. Unknown meal keys
|
|
/// fall back to [MealType.snack] so legacy rows never break the diary.
|
|
FoodLogEntry _mapFood(FoodLogRecord record) => FoodLogEntry(
|
|
id: record.id,
|
|
timestamp: record.timestamp,
|
|
mealType: MealType.tryParse(record.mealType) ?? MealType.snack,
|
|
description: record.description,
|
|
proteinGrams: record.proteinGrams,
|
|
calories: record.calories,
|
|
carbsGrams: record.carbsGrams,
|
|
fatGrams: record.fatGrams,
|
|
);
|
|
|
|
/// Converts a domain entity into an insert/update companion.
|
|
FoodLogsTableCompanion _toFoodCompanion(FoodLogEntry entry) =>
|
|
FoodLogsTableCompanion(
|
|
id: entry.id == null ? const Value.absent() : Value(entry.id!),
|
|
timestamp: Value(entry.timestamp),
|
|
mealType: Value(entry.mealType.persistenceKey),
|
|
description: Value(entry.description),
|
|
proteinGrams: Value(entry.proteinGrams),
|
|
calories: Value(entry.calories),
|
|
carbsGrams: entry.carbsGrams == null
|
|
? const Value.absent()
|
|
: Value(entry.carbsGrams!),
|
|
fatGrams: entry.fatGrams == null
|
|
? const Value.absent()
|
|
: Value(entry.fatGrams!),
|
|
);
|
|
|
|
/// Converts a persistence record into the domain entity.
|
|
HydrationLogEntry _mapHydration(HydrationLogRecord record) =>
|
|
HydrationLogEntry(
|
|
id: record.id,
|
|
timestamp: record.timestamp,
|
|
volumeOz: record.volumeOz,
|
|
);
|
|
|
|
/// Converts a domain entity into an insert/update companion.
|
|
HydrationLogsTableCompanion _toHydrationCompanion(HydrationLogEntry entry) =>
|
|
HydrationLogsTableCompanion(
|
|
id: entry.id == null ? const Value.absent() : Value(entry.id!),
|
|
timestamp: Value(entry.timestamp),
|
|
volumeOz: Value(entry.volumeOz),
|
|
);
|
|
} |