mirror of
https://github.com/kuhyx/diet-guard.git
synced 2026-07-04 13:23:11 +02:00
Milestone 1 of the diet-app-as-wise-balloon plan: a phone-native way to log meals away from the PC, sharing the exact on-disk JSON shape diet_guard already uses (same field names, no translation layer). - lib/models/: 1:1 Dart mirrors of the Python dataclasses (Nutrition, FoodEntry, MealItem, FoodBankRecord, Slot), including the per-100g/ amount-eaten portion scaling that matches _resolve.resolve_nutrition's semantics exactly. - lib/services/log_storage_service.dart: plain-JSON persistence to food_log.json's exact shape (no sqflite -- the canonical format already is this JSON). - lib/services/foodbank_service.dart: ports _foodbank.py's upsert/fuzzy search logic for autocomplete. - lib/screens/: log_meal_screen.dart (single-item logging) and meal_builder_screen.dart (composite multi-item meals, logging full per-component macros via the new components field). Verified end-to-end on a physical device (BL9000): built, installed, logged a real meal through the UI. 77 Flutter tests passing, `flutter analyze` clean against very_good_analysis. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FU3f5KQ1GHXsbbSecfVEyF
62 lines
2.1 KiB
Dart
62 lines
2.1 KiB
Dart
/// Composite "meal" support, mirroring diet_guard's `_meal.py`.
|
|
library;
|
|
|
|
import 'package:diet_guard_app/models/meal_component.dart';
|
|
import 'package:diet_guard_app/models/nutrition.dart';
|
|
|
|
/// Provenance stamped on a summed meal, matching `_meal.MEAL_SOURCE`.
|
|
const String mealSource = 'meal';
|
|
|
|
/// One named component of a composite meal, with its own nutrition.
|
|
///
|
|
/// Mirrors `_meal.MealItem`.
|
|
class MealItem {
|
|
/// Creates a [MealItem] from a component's name and resolved macros.
|
|
const MealItem({required this.name, required this.nutrition});
|
|
|
|
/// The component's food name (e.g. `"chicken"`).
|
|
final String name;
|
|
|
|
/// The component's resolved macros for the amount eaten.
|
|
final Nutrition nutrition;
|
|
}
|
|
|
|
/// Returns the summed nutrition of a meal's [items].
|
|
///
|
|
/// Every macro and the portion weight are added across the items and
|
|
/// rounded to 0.1, and the result is stamped `source: mealSource` so it is
|
|
/// distinguishable from a single food. Mirrors `_meal.meal_total`.
|
|
Nutrition mealTotal(List<MealItem> items) {
|
|
double sumOf(double Function(MealItem) field) {
|
|
var total = 0.0;
|
|
for (final item in items) {
|
|
total += field(item);
|
|
}
|
|
return double.parse(total.toStringAsFixed(1));
|
|
}
|
|
|
|
return Nutrition(
|
|
kcal: sumOf((item) => item.nutrition.kcal),
|
|
proteinG: sumOf((item) => item.nutrition.proteinG),
|
|
carbsG: sumOf((item) => item.nutrition.carbsG),
|
|
fatG: sumOf((item) => item.nutrition.fatG),
|
|
grams: sumOf((item) => item.nutrition.grams),
|
|
source: mealSource,
|
|
);
|
|
}
|
|
|
|
/// Returns a composite meal's per-component log record for [item].
|
|
///
|
|
/// Carries the component's full macros (not just its name) so a food bank
|
|
/// rebuilt purely by replaying the log can recover each component's
|
|
/// standalone nutrition, not just the composite's summed total. Mirrors
|
|
/// `_meal.item_to_component`.
|
|
MealComponent itemToComponent(MealItem item) => MealComponent(
|
|
name: item.name,
|
|
kcal: item.nutrition.kcal,
|
|
proteinG: item.nutrition.proteinG,
|
|
carbsG: item.nutrition.carbsG,
|
|
fatG: item.nutrition.fatG,
|
|
grams: item.nutrition.grams,
|
|
);
|