Skip to content

Commit 5aeda3b

Browse files
Bugfixes - Favorite Foods and math fix for Carb Entry
1. bug-saving as favorite food does not actually save as favorite - Fixed 2. bug-math problem when calculating carbs for a portion using AI - Fixed
1 parent 2c6a78e commit 5aeda3b

File tree

5 files changed

+192
-71
lines changed

5 files changed

+192
-71
lines changed

Loop/Extensions/UserDefaults+Loop.swift

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,21 @@ extension UserDefaults {
123123
}
124124
}
125125
}
126+
127+
/// Persist favorite foods with explicit call and lightweight logging.
128+
/// Use this after user-initiated changes to avoid race conditions between multiple view models.
129+
func writeFavoriteFoods(_ newValue: [StoredFavoriteFood]) {
130+
do {
131+
let encoder = JSONEncoder()
132+
let data = try encoder.encode(newValue)
133+
set(data, forKey: Key.favoriteFoods.rawValue)
134+
#if DEBUG
135+
print("💾 Saved favorite foods count: \(newValue.count)")
136+
#endif
137+
} catch {
138+
assertionFailure("Unable to encode stored favorite foods (explicit write)")
139+
}
140+
}
126141

127142
var aiProvider: String {
128143
get {
@@ -340,7 +355,7 @@ MANDATORY REQUIREMENTS:
340355

341356
var textSearchProvider: String {
342357
get {
343-
return string(forKey: Key.textSearchProvider.rawValue) ?? "USDA FoodData Central"
358+
return string(forKey: Key.textSearchProvider.rawValue) ?? "OpenFoodFacts (Default)"
344359
}
345360
set {
346361
set(newValue, forKey: Key.textSearchProvider.rawValue)
@@ -349,7 +364,7 @@ MANDATORY REQUIREMENTS:
349364

350365
var barcodeSearchProvider: String {
351366
get {
352-
return string(forKey: Key.barcodeSearchProvider.rawValue) ?? "OpenFoodFacts"
367+
return string(forKey: Key.barcodeSearchProvider.rawValue) ?? "OpenFoodFacts (Default)"
353368
}
354369
set {
355370
set(newValue, forKey: Key.barcodeSearchProvider.rawValue)
@@ -358,7 +373,7 @@ MANDATORY REQUIREMENTS:
358373

359374
var aiImageProvider: String {
360375
get {
361-
return string(forKey: Key.aiImageProvider.rawValue) ?? "OpenAI (ChatGPT API)"
376+
return string(forKey: Key.aiImageProvider.rawValue) ?? "Google (Gemini API)"
362377
}
363378
set {
364379
set(newValue, forKey: Key.aiImageProvider.rawValue)

Loop/Services/AIFoodAnalysis.swift

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -229,8 +229,7 @@ FOR MENU AND RECIPE ITEMS:
229229
❌ NEVER multiply nutrition values by assumed restaurant portion sizes
230230
231231
✅ ALWAYS set image_type to "menu_item" when analyzing menu text
232-
✅ When analyzing a MENU, ALWAYS set portion_estimate to "CANNOT DETERMINE PORTION - menu text only"
233-
✅ When analyzing a RECIPE, ALWAYS set portion_estimate to "CANNOT DETERMINE PORTION - recipe text only"
232+
✅ ALWAYS set portion_estimate to "CANNOT DETERMINE - menu text only"
234233
✅ ALWAYS set serving_multiplier to 1.0 for menu items (USDA standard only)
235234
✅ ALWAYS set visual_cues to "NONE - menu text analysis only"
236235
✅ ALWAYS mark assessment_notes as "ESTIMATE ONLY - Based on USDA standard serving size"
@@ -472,7 +471,7 @@ FOR MENU ITEMS:
472471
"food_items": [
473472
{
474473
"name": "menu item name as written on menu",
475-
"portion_estimate": "CANNOT DETERMINE PORTION - menu text only, no actual food visible",
474+
"portion_estimate": "CANNOT DETERMINE - menu text only, no actual food visible",
476475
"usda_serving_size": "standard USDA serving size for this food type (e.g., '3 oz for chicken breast', '1/2 cup for cooked rice')",
477476
"serving_multiplier": 1.0,
478477
"preparation_method": "method described on menu (if any)",
@@ -515,7 +514,7 @@ If menu shows "Grilled Chicken Caesar Salad", respond:
515514
"food_items": [
516515
{
517516
"name": "Grilled Chicken Caesar Salad",
518-
"portion_estimate": "CANNOT DETERMINE PORTION - menu text only, no actual food visible",
517+
"portion_estimate": "CANNOT DETERMINE - menu text only, no actual food visible",
519518
"usda_serving_size": "3 oz chicken breast + 2 cups mixed greens",
520519
"serving_multiplier": 1.0,
521520
"preparation_method": "grilled chicken as described on menu",
@@ -557,7 +556,7 @@ If menu shows "Teriyaki Chicken Bowl with White Rice", respond:
557556
"food_items": [
558557
{
559558
"name": "Teriyaki Chicken with White Rice",
560-
"portion_estimate": "CANNOT DETERMINE PORTION - menu text only, no actual food visible",
559+
"portion_estimate": "CANNOT DETERMINE - menu text only, no actual food visible",
561560
"usda_serving_size": "3 oz chicken breast + 1/2 cup cooked white rice",
562561
"serving_multiplier": 1.0,
563562
"preparation_method": "teriyaki glazed chicken with steamed white rice as described on menu",
@@ -597,7 +596,7 @@ If menu shows "Quinoa Bowl with Sweet Potato and Black Beans", respond:
597596
"food_items": [
598597
{
599598
"name": "Quinoa Bowl with Sweet Potato and Black Beans",
600-
"portion_estimate": "CANNOT DETERMINE PORTION - menu text only, no actual food visible",
599+
"portion_estimate": "CANNOT DETERMINE - menu text only, no actual food visible",
601600
"usda_serving_size": "1/2 cup cooked quinoa + 1/2 cup sweet potato + 1/2 cup black beans",
602601
"serving_multiplier": 1.0,
603602
"preparation_method": "cooked quinoa, roasted sweet potato, and seasoned black beans as described on menu",
@@ -676,8 +675,7 @@ FOR MENU AND RECIPE ITEMS:
676675
❌ NEVER multiply nutrition values by assumed restaurant portion sizes
677676
678677
✅ ALWAYS set image_type to "menu_item" when analyzing menu text
679-
✅ When analyzing a MENU, ALWAYS set portion_estimate to "CANNOT DETERMINE PORTION - menu text only"
680-
✅ When analyzing a RECIPE, ALWAYS set portion_estimate to "CANNOT DETERMINE PORTION - recipe text only"
678+
✅ ALWAYS set portion_estimate to "CANNOT DETERMINE - menu text only"
681679
✅ ALWAYS set serving_multiplier to 1.0 for menu items (USDA standard only)
682680
✅ ALWAYS set visual_cues to "NONE - menu text analysis only"
683681
✅ ALWAYS mark assessment_notes as "ESTIMATE ONLY - Based on USDA standard serving size"
@@ -917,7 +915,7 @@ enum SearchProvider: String, CaseIterable {
917915
case claude = "Anthropic (Claude API)"
918916
case googleGemini = "Google (Gemini API)"
919917
case openAI = "OpenAI (ChatGPT API)"
920-
case openFoodFacts = "OpenFoodFacts"
918+
case openFoodFacts = "OpenFoodFacts (Default)"
921919
case usdaFoodData = "USDA FoodData Central"
922920

923921

Loop/View Models/CarbEntryViewModel.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,8 @@ final class CarbEntryViewModel: ObservableObject {
320320
func onFavoriteFoodSave(_ food: NewFavoriteFood) {
321321
let newStoredFood = StoredFavoriteFood(name: food.name, carbsQuantity: food.carbsQuantity, foodType: food.foodType, absorptionTime: food.absorptionTime)
322322
favoriteFoods.append(newStoredFood)
323+
// Explicitly persist to avoid race with other view models' sinks
324+
UserDefaults.standard.writeFavoriteFoods(favoriteFoods)
323325
selectedFavoriteFoodIndex = favoriteFoods.count - 1
324326
}
325327

@@ -1787,4 +1789,3 @@ extension CarbEntryViewModel {
17871789
return (totalHours, reasoning)
17881790
}
17891791
}
1790-

Loop/View Models/FavoriteFoodsViewModel.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ final class FavoriteFoodsViewModel: ObservableObject {
4040
withAnimation {
4141
favoriteFoods.append(newStoredFood)
4242
}
43+
// Explicitly persist after add
44+
UserDefaults.standard.writeFavoriteFoods(favoriteFoods)
4345
isAddViewActive = false
4446
}
4547
else if var selectedFood, let selectedFooxIndex = favoriteFoods.firstIndex(of: selectedFood) {
@@ -48,6 +50,8 @@ final class FavoriteFoodsViewModel: ObservableObject {
4850
selectedFood.foodType = newFood.foodType
4951
selectedFood.absorptionTime = newFood.absorptionTime
5052
favoriteFoods[selectedFooxIndex] = selectedFood
53+
// Explicitly persist after edit
54+
UserDefaults.standard.writeFavoriteFoods(favoriteFoods)
5155
isEditViewActive = false
5256
}
5357
}
@@ -59,12 +63,16 @@ final class FavoriteFoodsViewModel: ObservableObject {
5963
withAnimation {
6064
_ = favoriteFoods.remove(food)
6165
}
66+
// Explicitly persist after delete
67+
UserDefaults.standard.writeFavoriteFoods(favoriteFoods)
6268
}
6369

6470
func onFoodReorder(from: IndexSet, to: Int) {
6571
withAnimation {
6672
favoriteFoods.move(fromOffsets: from, toOffset: to)
6773
}
74+
// Explicitly persist after reorder
75+
UserDefaults.standard.writeFavoriteFoods(favoriteFoods)
6876
}
6977

7078
func addFoodTapped() {

0 commit comments

Comments
 (0)