restore: original UI views from first build, keep fixed models/API
All checks were successful
Security Checks / dependency-audit (push) Successful in 12s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 3s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-04-03 01:54:46 -05:00
parent e852e98812
commit fdb8aeba8a
61 changed files with 6652 additions and 1178 deletions

View File

@@ -0,0 +1,359 @@
import Foundation
struct FoodEntry: Codable, Identifiable, Hashable {
let id: String
let foodName: String
let servingDescription: String?
let quantity: Double
let unit: String
let mealType: String
let calories: Double
let protein: Double
let carbs: Double
let fat: Double
let sugar: Double?
let fiber: Double?
let entryType: String?
let method: String?
let foodId: String?
let imageUrl: String?
let note: String?
let loggedAt: String?
enum CodingKeys: String, CodingKey {
case id, quantity, unit, calories, protein, carbs, fat, sugar, fiber, note, method
case foodName = "food_name"
case servingDescription = "serving_description"
case mealType = "meal_type"
case entryType = "entry_type"
case foodId = "food_id"
case imageUrl = "image_url"
case loggedAt = "logged_at"
}
/// Alternate keys from the snapshot-based API response
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: FlexibleCodingKeys.self)
id = try container.decode(String.self, forKey: .init("id"))
quantity = try container.decodeIfPresent(Double.self, forKey: .init("quantity")) ?? 1
unit = try container.decodeIfPresent(String.self, forKey: .init("unit")) ?? "serving"
mealType = try container.decodeIfPresent(String.self, forKey: .init("meal_type")) ?? "snack"
// Handle both "food_name" and "snapshot_food_name"
if let name = try container.decodeIfPresent(String.self, forKey: .init("food_name")) {
foodName = name
} else if let name = try container.decodeIfPresent(String.self, forKey: .init("snapshot_food_name")) {
foodName = name
} else {
foodName = "Unknown"
}
servingDescription = try container.decodeIfPresent(String.self, forKey: .init("serving_description"))
?? container.decodeIfPresent(String.self, forKey: .init("snapshot_serving_label"))
// Handle both direct and snapshot_ prefixed fields
calories = try container.decodeIfPresent(Double.self, forKey: .init("calories"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_calories"))
?? 0
protein = try container.decodeIfPresent(Double.self, forKey: .init("protein"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_protein"))
?? 0
carbs = try container.decodeIfPresent(Double.self, forKey: .init("carbs"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_carbs"))
?? 0
fat = try container.decodeIfPresent(Double.self, forKey: .init("fat"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_fat"))
?? 0
sugar = try container.decodeIfPresent(Double.self, forKey: .init("sugar"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_sugar"))
fiber = try container.decodeIfPresent(Double.self, forKey: .init("fiber"))
?? container.decodeIfPresent(Double.self, forKey: .init("snapshot_fiber"))
entryType = try container.decodeIfPresent(String.self, forKey: .init("entry_type"))
method = try container.decodeIfPresent(String.self, forKey: .init("entry_method"))
?? container.decodeIfPresent(String.self, forKey: .init("method"))
foodId = try container.decodeIfPresent(String.self, forKey: .init("food_id"))
imageUrl = try container.decodeIfPresent(String.self, forKey: .init("image_url"))
?? container.decodeIfPresent(String.self, forKey: .init("food_image_path"))
note = try container.decodeIfPresent(String.self, forKey: .init("note"))
loggedAt = try container.decodeIfPresent(String.self, forKey: .init("logged_at"))
?? container.decodeIfPresent(String.self, forKey: .init("created_at"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(foodName, forKey: .foodName)
try container.encodeIfPresent(servingDescription, forKey: .servingDescription)
try container.encode(quantity, forKey: .quantity)
try container.encode(unit, forKey: .unit)
try container.encode(mealType, forKey: .mealType)
try container.encode(calories, forKey: .calories)
try container.encode(protein, forKey: .protein)
try container.encode(carbs, forKey: .carbs)
try container.encode(fat, forKey: .fat)
try container.encodeIfPresent(sugar, forKey: .sugar)
try container.encodeIfPresent(fiber, forKey: .fiber)
try container.encodeIfPresent(entryType, forKey: .entryType)
try container.encodeIfPresent(method, forKey: .method)
try container.encodeIfPresent(foodId, forKey: .foodId)
try container.encodeIfPresent(imageUrl, forKey: .imageUrl)
try container.encodeIfPresent(note, forKey: .note)
try container.encodeIfPresent(loggedAt, forKey: .loggedAt)
}
static func == (lhs: FoodEntry, rhs: FoodEntry) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
/// Flexible coding keys for handling multiple API shapes
struct FlexibleCodingKeys: CodingKey {
var stringValue: String
var intValue: Int?
init(_ string: String) {
self.stringValue = string
self.intValue = nil
}
init?(stringValue: String) {
self.stringValue = stringValue
self.intValue = nil
}
init?(intValue: Int) {
self.stringValue = "\(intValue)"
self.intValue = intValue
}
}
struct FoodItem: Codable, Identifiable {
let id: String
let name: String
let brand: String?
let baseUnit: String?
let caloriesPerBase: Double
let proteinPerBase: Double?
let carbsPerBase: Double?
let fatPerBase: Double?
let sugarPerBase: Double?
let fiberPerBase: Double?
let status: String?
let imageUrl: String?
let favorite: Bool?
enum CodingKeys: String, CodingKey {
case id, name, brand, status, favorite
case baseUnit = "base_unit"
case caloriesPerBase = "calories_per_base"
case proteinPerBase = "protein_per_base"
case carbsPerBase = "carbs_per_base"
case fatPerBase = "fat_per_base"
case sugarPerBase = "sugar_per_base"
case fiberPerBase = "fiber_per_base"
case imageUrl = "image_url"
}
var displayUnit: String {
baseUnit ?? "serving"
}
var displayInfo: String {
let parts = [brand].compactMap { $0 }
let prefix = parts.isEmpty ? "" : "\(parts.joined(separator: " ")) - "
return "\(prefix)\(displayUnit)"
}
func scaledCalories(quantity: Double) -> Double {
caloriesPerBase * quantity
}
func scaledProtein(quantity: Double) -> Double {
(proteinPerBase ?? 0) * quantity
}
func scaledCarbs(quantity: Double) -> Double {
(carbsPerBase ?? 0) * quantity
}
func scaledFat(quantity: Double) -> Double {
(fatPerBase ?? 0) * quantity
}
}
struct DailyGoal: Codable {
let calories: Double
let protein: Double
let carbs: Double
let fat: Double
let sugar: Double?
let fiber: Double?
static let defaultGoal = DailyGoal(
calories: 2000, protein: 150, carbs: 200, fat: 65, sugar: 50, fiber: 30
)
}
struct MealTemplate: Codable, Identifiable {
let id: String
let name: String
let mealType: String
let calories: Double
let protein: Double?
let carbs: Double?
let fat: Double?
let itemsCount: Int?
// Support flexible decoding for templates with nested items
enum CodingKeys: String, CodingKey {
case id, name, calories, protein, carbs, fat, items
case mealType = "meal_type"
case itemsCount = "items_count"
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decode(String.self, forKey: .id)
name = try container.decode(String.self, forKey: .name)
mealType = try container.decodeIfPresent(String.self, forKey: .mealType) ?? "snack"
protein = try container.decodeIfPresent(Double.self, forKey: .protein)
carbs = try container.decodeIfPresent(Double.self, forKey: .carbs)
fat = try container.decodeIfPresent(Double.self, forKey: .fat)
// Try direct calories first, then compute from items
if let directCals = try? container.decode(Double.self, forKey: .calories) {
calories = directCals
itemsCount = try container.decodeIfPresent(Int.self, forKey: .itemsCount)
} else if let items = try? container.decode([TemplateItem].self, forKey: .items) {
calories = items.reduce(0) { $0 + ($1.snapshotCalories ?? 0) * ($1.quantity ?? 1) }
itemsCount = items.count
} else {
calories = 0
itemsCount = try container.decodeIfPresent(Int.self, forKey: .itemsCount)
}
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(id, forKey: .id)
try container.encode(name, forKey: .name)
try container.encode(mealType, forKey: .mealType)
try container.encode(calories, forKey: .calories)
try container.encodeIfPresent(protein, forKey: .protein)
try container.encodeIfPresent(carbs, forKey: .carbs)
try container.encodeIfPresent(fat, forKey: .fat)
try container.encodeIfPresent(itemsCount, forKey: .itemsCount)
}
}
private struct TemplateItem: Codable {
let snapshotCalories: Double?
let quantity: Double?
enum CodingKeys: String, CodingKey {
case snapshotCalories = "snapshot_calories"
case quantity
}
}
struct CreateEntryRequest: Encodable {
let foodId: String
let quantity: Double
let unit: String
let mealType: String
let entryDate: String
let entryMethod: String
let source: String
enum CodingKeys: String, CodingKey {
case quantity, unit, source
case foodId = "food_id"
case mealType = "meal_type"
case entryDate = "entry_date"
case entryMethod = "entry_method"
}
}
struct UpdateEntryRequest: Encodable {
var quantity: Double?
var unit: String?
var mealType: String?
enum CodingKeys: String, CodingKey {
case quantity, unit
case mealType = "meal_type"
}
}
struct SplitFoodRequest: Encodable {
let text: String
let mealType: String
enum CodingKeys: String, CodingKey {
case text
case mealType = "meal_type"
}
}
// Meals enum for type safety
enum MealType: String, CaseIterable, Identifiable {
case breakfast
case lunch
case dinner
case snack
var id: String { rawValue }
var displayName: String {
rawValue.capitalized
}
var icon: String {
switch self {
case .breakfast: return "sunrise.fill"
case .lunch: return "sun.max.fill"
case .dinner: return "moon.fill"
case .snack: return "leaf.fill"
}
}
static func guess() -> MealType {
let hour = Calendar.current.component(.hour, from: Date())
switch hour {
case 5..<11: return .breakfast
case 11..<15: return .lunch
case 15..<20: return .dinner
default: return .snack
}
}
}
// Helper for grouping entries by meal
struct MealGroup: Identifiable {
let meal: MealType
let entries: [FoodEntry]
var id: String { meal.rawValue }
var totalCalories: Double {
entries.reduce(0) { $0 + $1.calories }
}
var totalProtein: Double {
entries.reduce(0) { $0 + $1.protein }
}
var totalCarbs: Double {
entries.reduce(0) { $0 + $1.carbs }
}
var totalFat: Double {
entries.reduce(0) { $0 + $1.fat }
}
}