Caluru

Caluru - Screens Reference

This document describes each screen/view in the application, its purpose, UI components, state management, and navigation.


πŸ—‚ Screen Overview

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

1️⃣ MainTabView

File: Views/MainTabView.swift

Purpose

The root navigation shell that provides tab-based navigation and the custom tab bar.

UI Components

State

@State private var selectedTab: Tab = .home
@State private var showScanView: Bool = false

Key Actions

| 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) |


2️⃣ HomeView (Inicio)

File: Views/Home/HomeView.swift

Purpose

Daily nutrition tracking with calorie/macro progress visualization and meal logging.

UI Components

| 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 |

ViewModel: HomeViewModel

@Observable
class HomeViewModel {
    var selectedDate: Date
    var dailyLog: DailyLog?
    var userProfile: UserProfile?
    var showAddFoodSheet: Bool
    var selectedMealType: MealType
}

Key Computed Properties

Key Actions

| 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 |


3️⃣ AddFoodSheet

File: Views/Home/AddFoodSheet.swift

Purpose

Modal sheet for adding food items or recipes to a meal.

UI Components

| 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

@State private var selectedTab: AddFoodTab
@State private var searchText: String
@State private var customName: String
@State private var customCalories: String
// ... more form fields

Props (Callbacks)

let mealType: MealType
let onAddFood: (FoodItem, Double) -> Void
let onAddRecipe: (Recipe, Double) -> Void
let onAddCustom: (String, Int, Double, Double, Double) -> Void

Key Actions

| Action | Handler | |——–|β€”β€”β€”| | Tap food item β€œ+” | Calls onAddFood(food, 1.0) | | Tap recipe β€œ+” | Calls onAddRecipe(recipe, 1.0) | | Submit custom form | Calls onAddCustom(...) |


4️⃣ WeeklyPlanView (Plan)

File: Views/Plan/WeeklyPlanView.swift

Purpose

View and edit weekly meal plan with recipe assignments.

UI Components

| 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 |

ViewModel: WeeklyPlanViewModel

@Observable
class WeeklyPlanViewModel {
    var weeklyPlan: WeeklyPlan?
    var recipes: [Recipe]
    var showRecipePicker: Bool
    var selectedDay: Date
    var selectedMealSlot: MealType
}

Key Actions

| 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 |


5️⃣ ScanView

File: Views/Scan/ScanView.swift

Purpose

Camera-based food scanning with barcode and photo recognition modes.

UI Components

| 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 |

ViewModel: ScanViewModel

@MainActor @Observable
class ScanViewModel {
    var scanMode: ScanMode
    var scanState: ScanState
    var showAddSheet: Bool
    var recognizedFood: RecognizedFood?
    var cameraPermissionGranted: Bool
}

ScanState Enum

enum ScanState {
    case idle
    case scanning
    case processing
    case found(RecognizedFood)
    case foundMultiple([RecognizedFood])
    case error(String)
}

Key Actions

| 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 |


6️⃣ RecipesView (Recetas)

File: Views/Recipes/RecipesView.swift

Purpose

Browse recipe library filtered by category.

UI Components

| Component | Description | |———–|β€”β€”β€”β€”-| | PillSegmentButton row | Category filters | | SearchBar | Filter by title | | LazyVGrid | 2-column recipe card grid | | RecipeCard | Image + title + macros |

ViewModel: RecipesViewModel

@Observable
class RecipesViewModel {
    var recipes: [Recipe]
    var selectedCategory: MealType
    var searchText: String
    var selectedRecipe: Recipe?
    var showRecipeDetail: Bool
}

Key Actions

| Action | Method | Effect | |——–|——–|——–| | Tap category pill | Sets selectedCategory | Filters grid | | Type in search | Updates searchText | Filters grid | | Tap recipe card | selectRecipe(_:) | Navigates to detail |


7️⃣ RecipeDetailView

File: Views/Recipes/RecipeDetailView.swift

Purpose

Display full recipe with ingredients, steps, and add-to-meal action.

UI Components

| 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

@State private var showAddSheet: Bool
@State private var ingredientsExpanded: Bool
@State private var stepsExpanded: Bool

Key Actions

| Action | Handler | Effect | |——–|β€”β€”β€”|——–| | Tap β€œAgregar” | showAddSheet = true | Opens AddToMealSheet | | Tap section header | Toggle *Expanded | Collapse/expand | | Tap β€œRegenerar” | regenerateRecipe() | Haptic feedback (stub) |


8️⃣ ShoppingListView (Lista)

File: Views/ShoppingList/ShoppingListView.swift

Purpose

View and manage shopping list generated from weekly plan.

UI Components

| 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 |

ViewModel: ShoppingListViewModel

@Observable
class ShoppingListViewModel {
    var shoppingList: ShoppingList?
    var weeklyPlan: WeeklyPlan?
    var showAddItemSheet: Bool
    // Form fields for new item
}

Key Actions

| 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 |


9️⃣ AssistantView

File: Views/Assistant/AssistantView.swift

Purpose

Chat interface for nutrition queries and recommendations.

UI Components

| Component | Description | |———–|β€”β€”β€”β€”-| | ScrollView | Chat message history | | ChatBubble | User/assistant messages | | TypingIndicator | Animated dots while processing | | QuickActionChip row | Preset questions | | ChatInputBar | Text input + send button |

ViewModel: AssistantViewModel

@Observable
class AssistantViewModel {
    var messages: [ChatMessage]
    var inputText: String
    var isTyping: Bool
    var dailyLog: DailyLog?
    var userProfile: UserProfile?
    var recipes: [Recipe]
}

Key Actions

| 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 |

Chat Flow

  1. User types message
  2. User message added to messages
  3. isTyping = true
  4. AIAssistantService.shared.generateResponse() called (OpenAI via Supabase openai-proxy)
  5. Response text parsed; ActionParser.parse(from: content) extracts validated structured actions (if any) and strips the machine block from display text
  6. Assistant message (with suggestedActions) added to messages
  7. isTyping = false
  8. User can tap suggested action β†’ execute (log meal, create recipe, generate plan, adjust target, etc.)

Assistant Actions (from AssistantViewModel)



πŸ”Ÿ Onboarding Flow (Duolingo-style quiz)

File: Views/Onboarding/OnboardingFlowView.swift + Views/Onboarding/Screens/*.swift
ViewModel: OnboardingViewModel
State: OnboardingState (UserDefaults draft); final result β†’ UserProfile in SwiftData + onboardingCompletedAt set.

Screen Order (live path)

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.


ProfileView – 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:).


🧩 Reusable View Components

From 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