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> watchFoodLogs({int limit = 500}) => _database .watchFoodLogs(limit: limit) .map((records) => records.map(_mapFood).toList(growable: false)); @override Future> 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 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 deleteFoodLog(int id) => _database.deleteFoodLog(id); // --------------------------------------------------------------------------- // Hydration // --------------------------------------------------------------------------- @override Stream> watchHydrationLogs({int limit = 500}) => _database.watchHydrationLogs(limit: limit).map( (records) => records.map(_mapHydration).toList(growable: false), ); @override Future> 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 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 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), ); }