This document describes each screen/view in the application, its purpose, UI components, state management, and navigation.
| Screen | File | ViewModel | Tab / Context |
|---|---|---|---|
| Main Tab Shell | MainTabView.swift |
β | β |
| Onboarding | OnboardingFlowView.swift |
OnboardingViewModel |
fullScreenCover (when not completed) |
| Progressive Profiling Card | ProfileCardView (in MainTabView) |
ProgressiveProfilingService |
Overlay |
| Home | HomeView.swift |
HomeViewModel |
Inicio |
| Add Food | AddFoodSheet.swift |
β (uses parent) | Modal |
| Weekly Plan | WeeklyPlanView.swift |
WeeklyPlanViewModel |
Plan |
| Scan | ScanView.swift |
ScanViewModel |
fullScreenCover (center tab button) |
| Recipes | RecipesView.swift |
RecipesViewModel |
Recetas |
| Recipe Detail | RecipeDetailView.swift |
β (uses parent) | Push |
| Add Recipe | AddRecipeSheet.swift |
β | Modal |
| Shopping List | ShoppingListView.swift |
ShoppingListViewModel |
From Plan (e.g. cart) or tab |
| Assistant | AssistantView.swift |
AssistantViewModel |
Modal (from Home) |
| Profile | ProfileView.swift |
ProfileViewModel |
Perfil |
| App Preferences | AppPreferencesView.swift |
β | Push (theme, etc.) |
| Weight Tracking | WeightTrackingView.swift |
β | Push |
| Notifications Settings | NotificationsSettingsView.swift |
β | Push |
| Legal | LegalView.swift |
β | Push |
| Sign In | SignInView.swift |
β | Onboarding step |
File: Views/MainTabView.swift
The root navigation shell that provides tab-based navigation and the custom tab bar.
TabView with 5 tabs (standard SwiftUI)CustomTabBar - Custom bottom navigation bar
TabBarButton componentsScanTabButton (elevated center button)@State private var selectedTab: Tab = .home
@State private var showScanView: Bool = false
| Action | Handler |
|βββ|βββ|
| Tap tab button | Sets selectedTab |
| Tap scan button | Sets showScanView = true |
| App launches | SeedDataService.seedIfNeeded(); ProgressiveProfilingService.checkTriggers() |
| Onboarding completed | hasCompletedOnboarding = true (from UserProfile via CloudKit) |
File: Views/Home/HomeView.swift
Daily nutrition tracking with calorie/macro progress visualization and meal logging.
| Component | Description |
|ββββ|ββββ-|
| DatePillSegment | Horizontal date picker (7 days) |
| ProgressRing | Large circular calorie progress |
| MacroCard Γ 3 | Protein/Carbs/Fat progress rings |
| MealRow Γ 4 | Desayuno/Comida/Cena/Snack cards |
| Toolbar | βCaluruβ title + Assistant button |
HomeViewModel@Observable
class HomeViewModel {
var selectedDate: Date
var dailyLog: DailyLog?
var userProfile: UserProfile?
var showAddFoodSheet: Bool
var selectedMealType: MealType
}
consumedCalories, targetCaloriesconsumedProtein, targetProteinconsumedCarbs, targetCarbsconsumedFat, targetFat| Action | Method | Effect |
|βββ|βββ|βββ|
| Select date | selectDate(_:context:) | Loads DailyLog for that date |
| Tap β+β on meal | openAddFood(for:) | Opens AddFoodSheet |
| Tap meal row | Local @State toggle | Expands to show items |
| Tap assistant icon | showAssistant = true | Opens AssistantView |
File: Views/Home/AddFoodSheet.swift
Modal sheet for adding food items or recipes to a meal.
| Component | Description |
|ββββ|ββββ-|
| Segmented Picker | βBuscarβ / βRecetasβ / βPersonalizadoβ tabs |
| SearchBar | Filter food items or recipes |
| FoodItemRow | List of food items with β+β button |
| HorizontalRecipeCard | List of recipes with β+β button |
| Custom form | Name + Calories + Protein + Carbs + Fat fields |
@State private var selectedTab: AddFoodTab
@State private var searchText: String
@State private var customName: String
@State private var customCalories: String
// ... more form fields
let mealType: MealType
let onAddFood: (FoodItem, Double) -> Void
let onAddRecipe: (Recipe, Double) -> Void
let onAddCustom: (String, Int, Double, Double, Double) -> Void
| Action | Handler |
|βββ|βββ|
| Tap food item β+β | Calls onAddFood(food, 1.0) |
| Tap recipe β+β | Calls onAddRecipe(recipe, 1.0) |
| Submit custom form | Calls onAddCustom(...) |
File: Views/Plan/WeeklyPlanView.swift
View and edit weekly meal plan with recipe assignments.
| Component | Description |
|ββββ|ββββ-|
| Summary Card | Weekly/daily average calories |
| βGenerar planβ button | Auto-generate random plan |
| DayPlanCard Γ 7 | Card per day with meal slots |
| MealSlotCard Γ 3 | Breakfast/Lunch/Dinner slots |
| Toolbar cart icon | Links to ShoppingListView |
WeeklyPlanViewModel@Observable
class WeeklyPlanViewModel {
var weeklyPlan: WeeklyPlan?
var recipes: [Recipe]
var showRecipePicker: Bool
var selectedDay: Date
var selectedMealSlot: MealType
}
| Action | Method | Effect |
|βββ|βββ|βββ|
| Tap meal slot | openRecipePicker(for:mealType:) | Opens RecipePickerSheet |
| Select recipe | selectRecipe(_:context:) | Assigns to slot, saves |
| Tap βGenerar planβ | generateRandomPlan(context:) | Fills all slots randomly |
| Tap cart icon | NavigationLink | Navigates to ShoppingListView |
File: Views/Scan/ScanView.swift
Camera-based food scanning with barcode and photo recognition modes.
| Component | Description |
|ββββ|ββββ-|
| CameraPreviewView | Live camera feed (AVCaptureSession) |
| Segmented Picker | βCΓ³digo de barrasβ / βFoto de comidaβ |
| ScanFrame | Overlay with scan target area |
| Status indicator | Processing/found/error states |
| Capture button | For photo mode |
ScanViewModel@MainActor @Observable
class ScanViewModel {
var scanMode: ScanMode
var scanState: ScanState
var showAddSheet: Bool
var recognizedFood: RecognizedFood?
var cameraPermissionGranted: Bool
}
enum ScanState {
case idle
case scanning
case processing
case found(RecognizedFood)
case foundMultiple([RecognizedFood])
case error(String)
}
| Action | Method | Effect |
|βββ|βββ|βββ|
| Barcode detected | handleBarcodeDetected(_:) | OpenFoodFactsService.shared.lookupBarcode(barcode) |
| Tap capture button | captureAndRecognize() | FoodRecognitionService.shared.recognizeFood(from: image) (GPT-4o Vision via proxy) |
| Confirm add | addRecognizedFood(to:context:) | Creates FoodItem + logs to DailyLog |
| Tap close | Dismisses | Returns to previous screen |
File: Views/Recipes/RecipesView.swift
Browse recipe library filtered by category.
| Component | Description |
|ββββ|ββββ-|
| PillSegmentButton row | Category filters |
| SearchBar | Filter by title |
| LazyVGrid | 2-column recipe card grid |
| RecipeCard | Image + title + macros |
RecipesViewModel@Observable
class RecipesViewModel {
var recipes: [Recipe]
var selectedCategory: MealType
var searchText: String
var selectedRecipe: Recipe?
var showRecipeDetail: Bool
}
| Action | Method | Effect |
|βββ|βββ|βββ|
| Tap category pill | Sets selectedCategory | Filters grid |
| Type in search | Updates searchText | Filters grid |
| Tap recipe card | selectRecipe(_:) | Navigates to detail |
File: Views/Recipes/RecipeDetailView.swift
Display full recipe with ingredients, steps, and add-to-meal action.
| Component | Description |
|ββββ|ββββ-|
| Hero image | Recipe image/placeholder |
| Title + metadata | Name, meal type, time, servings |
| MacroRow | Calories/Protein/Carbs/Fat |
| βAgregar esta comidaβ button | Add to meal |
| ExpandableSection Γ 2 | Ingredients + PreparaciΓ³n |
@State private var showAddSheet: Bool
@State private var ingredientsExpanded: Bool
@State private var stepsExpanded: Bool
| Action | Handler | Effect |
|βββ|βββ|βββ|
| Tap βAgregarβ | showAddSheet = true | Opens AddToMealSheet |
| Tap section header | Toggle *Expanded | Collapse/expand |
| Tap βRegenerarβ | regenerateRecipe() | Haptic feedback (stub) |
File: Views/ShoppingList/ShoppingListView.swift
View and manage shopping list generated from weekly plan.
| Component | Description |
|ββββ|ββββ-|
| Summary Card | Pending/completed counts |
| βActualizarβ button | Regenerate from plan |
| βLimpiarβ button | Remove checked items |
| ShoppingCategorySection | Grouped by food category |
| ShoppingItemRow | Checkbox + name + quantity |
| Toolbar β+β button | Add manual item |
ShoppingListViewModel@Observable
class ShoppingListViewModel {
var shoppingList: ShoppingList?
var weeklyPlan: WeeklyPlan?
var showAddItemSheet: Bool
// Form fields for new item
}
| Action | Method | Effect |
|βββ|βββ|βββ|
| Tap item row | toggleItem(_:context:) | Toggles checkbox |
| Tap βActualizarβ | regenerateFromPlan(context:) | Rebuilds from plan |
| Tap βLimpiarβ | removeCheckedItems(context:) | Deletes checked |
| Tap β+β | showAddItemSheet = true | Opens form |
File: Views/Assistant/AssistantView.swift
Chat interface for nutrition queries and recommendations.
| Component | Description |
|ββββ|ββββ-|
| ScrollView | Chat message history |
| ChatBubble | User/assistant messages |
| TypingIndicator | Animated dots while processing |
| QuickActionChip row | Preset questions |
| ChatInputBar | Text input + send button |
AssistantViewModel@Observable
class AssistantViewModel {
var messages: [ChatMessage]
var inputText: String
var isTyping: Bool
var dailyLog: DailyLog?
var userProfile: UserProfile?
var recipes: [Recipe]
}
| Action | Method | Effect |
|βββ|βββ|βββ|
| Type message + send | sendMessage() | Adds message, generates response |
| Tap quick action | askAboutCalories() etc. | Sends preset question |
| Scroll to bottom | Auto on new message | Keeps latest visible |
messagesisTyping = trueAIAssistantService.shared.generateResponse() called (OpenAI via Supabase openai-proxy)ActionParser.parse(from: content) extracts validated structured actions (if any) and strips the machine block from display textsuggestedActions) added to messagesisTyping = falseshowLogMealSheet; confirm β create LoggedItem and add to DailyLogshowAddRecipeSheet; confirm β insert RecipeshowGeneratePlanSheet; confirm β MealPlanGeneratorshowAdjustTargetSheet; confirm β update UserProfile targets and save (shows old β new + estimates disclaimer)File: Views/Onboarding/OnboardingFlowView.swift + Views/Onboarding/Screens/*.swift
ViewModel: OnboardingViewModel
State: OnboardingState (UserDefaults draft); final result β UserProfile in SwiftData + onboardingCompletedAt set.
| Step | Screen | Purpose |
|---|---|---|
| 1 | Welcome (S0Welcome) |
Brand-first intro; advance notice of Privacy Policy / Terms |
| 1a | Privacy Consent (S1aPrivacyPolicy) |
Explicit checkbox acceptance before health/AI data collection |
| 2 | Goal (S1WelcomeGoal) |
GoalType; gain-muscle pace inline |
| 3 | Motivations (SMotivations) |
Why this goal β multi-select OtherGoalTag for everyone |
| 4 | Skill (S15Confidence) |
Required experience/skill level (1β5) |
| 4a | Encouragement (encouragementGoals) |
Short milestone beat after skill |
| 5 | Quick Basics (SQuickBasics) |
Units + age + sex; age under 16 blocks Continue (Privacy Policy Β§11) |
| 6 | Body Metrics (SBodyMetrics) |
Height + weight (weight skippable) |
| 7 | Activity (S8Activity) |
ActivityLevel |
| 7a | Training Days (S9) |
If veryActive / athlete |
| 7b | Goal Pace (S10) |
If lose-weight and pace not yet set |
| 8 | Dietary Preferences (S12) |
Skippable |
| 9 | Allergies (S13) |
Skippable |
| 9a | Meals & Dessert (SMealsDessert) |
Meals/day + dessert preference |
| 10 | Targets Reveal (STargetsReveal) |
CalculatedTargets aha; adjust sheet |
| 11 | Sign In | Sign in with Apple (required if logged out) |
| 12 | Name (S0aName) |
Display name + optional photo |
| 13 | Username (S0bUsername) |
Valid + available username |
| 14 | First Log Nudge (SFirstLogNudge) |
Completes onboarding β hard paywall if not Pro |
Legacy enum cases (S3βS7, encouragementBasicInfo / almostDone, S11a, S14 cooking time, S16/S17) remain for draft decode only and are remapped on resume. s1aPrivacyPolicy is live again (explicit consent). encouragementGoals is live as a post-skill beat. Mid-flow drafts without privacyPolicyAccepted are redirected to privacy consent before health steps.
!hasCompletedOnboarding)navigateBack(); path persists in draft; edit-from-targets returns via Done / ContinuecompleteOnboarding saves profile, clears draft; hasCompletedOnboarding = true; progressive profiling initializedProfileView β Account (Sign in with Apple), name, photo, preferences link, weight tracking link, notifications link, theme (AppPreferencesView), legal (LegalView), account reset (AccountResetService).
AppPreferencesView β Theme: light / dark / auto (persisted to appTheme).
WeightTrackingView β List of WeightEntry; add/edit/delete; display by unit system.
NotificationsSettingsView β Meal reminders, goal reminders, streak, weekly report, weight reminders; times and toggles; uses NotificationService.
LegalView β Privacy policy / terms (content as implemented).
SignInView β Sign in with Apple (onboarding step); calls AuthService.handleAppleSignIn(result:nonce:).
DesignSystem/Components/:| Component | File | Usage |
|---|---|---|
Card |
Card.swift |
White rounded container |
GlassCard |
Card.swift |
Frosted glass effect |
PillSegmentButton |
PillSegment.swift |
Category filter pills |
DatePillSegment |
PillSegment.swift |
Date picker pills |
MacroCard |
MacroChip.swift |
Progress ring + label |
MacroChip |
MacroChip.swift |
Small inline macro tag |
MacroRow |
MacroChip.swift |
4-column macro display |
ProgressRing |
ProgressRing.swift |
Circular progress |
PrimaryButton |
PrimaryButton.swift |
Green pill button |
SecondaryButton |
PrimaryButton.swift |
Outlined button |
IconButton |
IconButton.swift |
Circular icon button |
CircleAddButton |
IconButton.swift |
β+β button |
ScanTabButton |
IconButton.swift |
Elevated scan button |
CheckboxButton |
IconButton.swift |
Toggle checkbox |
SearchBar |
SearchBar.swift |
Search input |
ChatInputBar |
SearchBar.swift |
Message input |
RecipeCard |
RecipeCard.swift |
Grid recipe card |
HorizontalRecipeCard |
RecipeCard.swift |
List recipe card |
MealSlotCard |
RecipeCard.swift |
Plan slot card |
EmptyState |
EmptyState.swift |
No-data placeholder |
LoadingState |
EmptyState.swift |
Loading spinner |
ErrorState |
EmptyState.swift |
Error message |