Files

1 line
82 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"repo": "aadijha14/nutritionapp", "files": {"project/app/(app)/plan-my-day.tsx": {"e85c1360f40beeb0b2c31f1a6e47498a0c8b24dc": {"t": 1785746533.368824, "n": 41830, "h": "7f34a25a1153d013", "c": "// PlanMyDayScreen.tsx\nimport React, { useState, useEffect, useContext } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n TouchableOpacity,\n ScrollView,\n ActivityIndicator,\n Alert,\n Platform,\n TextInput,\n KeyboardAvoidingView\n} from 'react-native';\nimport { router } from 'expo-router';\nimport { auth, db } from '../../firebaseConfig';\nimport {\n doc,\n getDoc,\n collection,\n serverTimestamp,\n updateDoc,\n setDoc,\n getDocs\n} from 'firebase/firestore';\nimport {\n ArrowLeft,\n LayoutGrid,\n Calendar,\n Check,\n FileCheck,\n Heart,\n CircleAlert as AlertCircle,\n Utensils\n} from 'lucide-react-native';\nimport * as Notifications from 'expo-notifications';\nimport { ThemeContext } from '../../context/ThemeContext';\nimport MealPlanCard from '../../components/MealPlanCard';\nimport { MealSlot } from '../../types/mealPlanner';\n\n//////////////////////////////////////////////////////////////////////////\n// DeepSeek Constants (Using DeepSeek API)\n//////////////////////////////////////////////////////////////////////////\nconst DEEPSEEK_BASE_URL = 'https://api.deepseek.com';\nconst DEEPSEEK_API_KEY = 'sk-95593a35636148368ec4a6d868bf1bb8';\n\n// Set notification handler for foreground notifications\nNotifications.setNotificationHandler({\n handleNotification: async () => ({\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: false,\n }),\n});\n\nconst PlanMyDayScreen: React.FC = () => {\n const { theme } = useContext(ThemeContext);\n const isDark = theme === 'dark';\n\n //////////////////////////////////////////////////////////////////////\n // Local State\n //////////////////////////////////////////////////////////////////////\n const [dietaryPreferences, setDietaryPreferences] = useState<string[]>([]);\n const [availablePreferences] = useState<{ id: string; label: string }[]>([\n { id: 'vegetarian', label: 'Vegetarian' },\n { id: 'vegan', label: 'Vegan' },\n { id: 'glutenFree', label: 'Gluten Free' },\n { id: 'dairyFree', label: 'Dairy Free' },\n { id: 'ketogenic', label: 'Ketogenic' },\n { id: 'paleo', label: 'Paleo' },\n ]);\n\n const [dailyCalorieTarget, setDailyCalorieTarget] = useState<number>(2000);\n const [todayCalories, setTodayCalories] = useState<number>(0);\n const remainingCalories = Math.max(0, dailyCalorieTarget - todayCalories);\n\n // Meal settings: which mode per meal.\n const [mealSettings, setMealSettings] = useState<{\n breakfast: 'home' | 'restaurant';\n lunch: 'home' | 'restaurant';\n snack: 'home' | 'restaurant';\n dinner: 'home' | 'restaurant';\n }>({\n breakfast: 'home',\n lunch: 'home',\n snack: 'home',\n dinner: 'home'\n });\n\n // Final meal slots and UI states.\n const [slots, setSlots] = useState<MealSlot[]>([]);\n const [loading, setLoading] = useState<boolean>(true);\n const [isGenerating, setIsGenerating] = useState<boolean>(false);\n // isPlanSaved is used to indicate if the plan as loaded from Firestore is saved.\n // It will be marked false on any user edit/regeneration to allow saving.\n const [isPlanSaved, setIsPlanSaved] = useState<boolean>(false);\n const [error, setError] = useState<string | null>(null);\n\n // Plan feedback and single-meal swap reasons.\n const [planFeedback, setPlanFeedback] = useState<string>('');\n const [swapReasons, setSwapReasons] = useState<{ [key: string]: string }>({});\n const [swappingMeal, setSwappingMeal] = useState<string | null>(null);\n\n //////////////////////////////////////////////////////////////////////\n // Initialization: load user data and saved plan (if any) for today\n //////////////////////////////////////////////////////////////////////\n useEffect(() => {\n const init = async () => {\n if (!auth.currentUser) {\n setLoading(false);\n setError('You must be logged in to use PlanMyDay.');\n return;\n }\n try {\n // Load user document data.\n const userRef = doc(db, 'users', auth.currentUser.uid);\n const userSnap = await getDoc(userRef);\n if (userSnap.exists()) {\n const userData = userSnap.data();\n if (userData.dietaryPreferences) {\n setDietaryPreferences(userData.dietaryPreferences);\n }\n if (typeof userData.dailyCalorieTarget === 'number') {\n setDailyCalorieTarget(userData.dailyCalorieTarget);\n }\n if (typeof userData.todayCalories === 'number') {\n setTodayCalories(userData.todayCalories);\n }\n }\n // Check if plan exists for today.\n const today = new Date().toISOString().split('T')[0];\n const planRef = doc(db, `users/${auth.currentUser.uid}/plans`, today);\n const planSnap = await getDoc(planRef);\n if (planSnap.exists()) {\n const savedPlan = planSnap.data();\n setSlots(savedPlan.slots || []);\n setIsPlanSaved(true);\n }\n } catch (err) {\n console.error('PlanMyDayScreen init error:', err);\n setError('Initialization failed. Please try again.');\n } finally {\n setLoading(false);\n }\n };\n init();\n }, []);\n\n //////////////////////////////////////////////////////////////////////\n // Utility: Convert degrees to radians and calculate distance.\n //////////////////////////////////////////////////////////////////////\n const toRad = (deg: number) => deg * (Math.PI / 180);\n const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {\n const R = 6371; // in km\n const dLat = toRad(lat2 - lat1);\n const dLon = toRad(lon2 - lon1);\n const a =\n Math.sin(dLat / 2) ** 2 +\n Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n return R * c;\n };\n\n // Default location (for example, near NTU)\n const LATITUDE = 1.355049655134308;\n const LONGITUDE = 103.68518139204353;\n\n //////////////////////////////////////////////////////////////////////\n // Fetch nearby restaurant items from Firestore (all restaurants within 2 km)\n //////////////////////////////////////////////////////////////////////\n const fetchNearbyMenuItems = async (): Promise<any[]> => {\n try {\n const snapshot = await getDocs(collection(db, 'restaurants'));\n const nearbyItems: any[] = [];\n snapshot.forEach((docSnap) => {\n const data = docSnap.data();\n if (data.location && data.items) {\n const dist = getDistance(\n data.location.lat,\n data.location.lng,\n LATITUDE,\n LONGITUDE\n );\n if (dist < 2.0) {\n data.items.forEach((item: any) => {\n nearbyItems.push({\n ...item,\n restaurantName: data.name,\n restaurantAddress: data.address || ''\n });\n });\n }\n }\n });\n return nearbyItems;\n } catch (error) {\n console.error('fetchNearbyMenuItems error:', error);\n return [];\n }\n };\n\n //////////////////////////////////////////////////////////////////////\n // Group restaurant items by restaurant (using \"restaurantName\" and \"restaurantAddress\")\n //////////////////////////////////////////////////////////////////////\n const groupItemsByRestaurant = (items: any[]): { [key: string]: any[] } => {\n const grouped: { [key: string]: any[] } = {};\n items.forEach((item) => {\n const rName = item.restaurantName || 'Unknown';\n const rAddr = item.restaurantAddress || 'N/A';\n const key = `${rName}__${rAddr}`;\n if (!grouped[key]) grouped[key] = [];\n grouped[key].push(item);\n });\n return grouped;\n };\n\n //////////////////////////////////////////////////////////////////////\n // Generate Meal Plan using DeepSeek API (with grouping for restaurants)\n //////////////////////////////////////////////////////////////////////\n const generateMealPlan = async (feedback: string = ''): Promise<MealSlot[]> => {\n // Five guaranteed meals.\n const guaranteedMeals = ['breakfast', 'lunch', 'snack', 'dinner', 'snack'];\n\n // Gather all restaurant menu items.\n const nearbyMenuItems = await fetchNearbyMenuItems();\n // Group restaurant items to avoid repeating restaurant name/address.\n const restaurantGroups = groupItemsByRestaurant(nearbyMenuItems);\n\n // Build available dishes text for each meal.\n const availableByMeal: { [key: string]: string } = {};\n guaranteedMeals.forEach((meal) => {\n const setting = mealSettings[meal as keyof typeof mealSettings] || 'home';\n if (setting === 'restaurant') {\n if (Object.keys(restaurantGroups).length === 0) {\n availableByMeal[meal] = 'No restaurant data found.';\n } else {\n let block = '';\n for (const key in restaurantGroups) {\n const [rName, rAddr] = key.split('__');\n block += `\\nRestaurant: ${rName}, ${rAddr}\\n`;\n const dishes = restaurantGroups[key];\n for (const dish of dishes) {\n block += ` - ${dish.foodName} (${dish.calories} cal, P:${dish.protein || 0}g C:${dish.carbs || 0}g F:${dish.fat || 0}g)\\n`;\n }\n }\n availableByMeal[meal] = block.trim();\n }\n } else {\n availableByMeal[meal] =\n 'User will cook at home. You can invent a dish with realistic macros.';\n }\n });\n\n const dietLabels = dietaryPreferences.length ? dietaryPreferences.join(', ') : 'None';\n\n // Create the system and user prompts for DeepSeek.\n const systemPrompt = `\nYou are a meal recommendation assistant.\nOnly return answers in the exact format specified below.\nNever guess the calories for restaurant items; use the data provided.\nInclude macros (protein, carbs, fat) for each dish.\nIgnore meal timing constraints.\n `;\n\n let userPrompt = `\nGenerate a meal plan for today with these constraints:\n\n**Calories Remaining**: ${remainingCalories}\n**Dietary Preferences**: ${dietLabels}\n**User Feedback**: ${feedback || 'None'}\n\nWe have five guaranteed meals:\n1) Breakfast\n2) Lunch\n3) Snack\n4) Dinner\n5) Snack\n\nIf needed, feel free to add an extra snack.\n\nFor each meal, use exactly this format (no time required):\n\n---\n**Meal**: <meal name>\n**Dish**: <dish name>\n**Calories**: <kcal>\n**Protein**: <g>\n**Carbs**: <g>\n**Fat**: <g>\n**Restaurant**: <restaurant name or 'home'>\n**Address**: <restaurant address or 'N/A'>\n**Why this dish**: <short reason>\n---\n\nAvailable dishes per meal:\n `;\n\n guaranteedMeals.forEach((meal) => {\n userPrompt += `\\n🍽️ ${meal.toUpperCase()} (${mealSettings[meal as keyof typeof mealSettings] || 'home'}):\\n${availableByMeal[meal]}\\n`;\n });\n\n // Log for debugging.\n console.log('Sending Plan Generation Request to DeepSeek...');\n console.log('System Prompt:', systemPrompt);\n console.log('User Prompt:', userPrompt);\n\n try {\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${DEEPSEEK_API_KEY}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages: [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt }\n ],\n temperature: 0.7\n })\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.error('DeepSeek API response not ok:', text);\n throw new Error(`DeepSeek API Error: ${response.status} - ${text}`);\n }\n\n const completionData = await response.json();\n console.log('DeepSeek API response:', completionData);\n\n const rawResponse = completionData.choices?.[0]?.message?.content || '';\n console.log('Raw GPT response:', rawResponse);\n\n const sections = rawResponse.split('---').filter((sec: string) => sec.trim() !== '');\n const newSlots: MealSlot[] = [];\n\n sections.forEach((section, idx) => {\n const mealMatch = section.match(/\\*\\*Meal\\*\\*:\\s*(.+)/);\n const dishMatch = section.match(/\\*\\*Dish\\*\\*:\\s*(.+)/);\n const calMatch = section.match(/\\*\\*Calories\\*\\*:\\s*(.+)/);\n const proteinMatch = section.match(/\\*\\*Protein\\*\\*:\\s*(.+)/);\n const carbsMatch = section.match(/\\*\\*Carbs\\*\\*:\\s*(.+)/);\n const fatMatch = section.match(/\\*\\*Fat\\*\\*:\\s*(.+)/);\n const restaurantMatch = section.match(/\\*\\*Restaurant\\*\\*:\\s*(.+)/);\n const addressMatch = section.match(/\\*\\*Address\\*\\*:\\s*(.+)/);\n const whyMatch = section.match(/\\*\\*Why this dish\\*\\*:\\s*(.+)/);\n\n if (\n mealMatch &&\n dishMatch &&\n calMatch &&\n proteinMatch &&\n carbsMatch &&\n fatMatch &&\n restaurantMatch &&\n addressMatch &&\n whyMatch\n ) {\n newSlots.push({\n id: Date.now().toString() + '-' + idx,\n name: mealMatch[1].trim(),\n time: '',\n locationType:\n restaurantMatch[1].trim().toLowerCase() === 'home' ? 'home' : 'restaurant',\n menuItem: {\n foodName: dishMatch[1].trim(),\n calories: parseInt(calMatch[1].trim(), 10),\n protein: parseInt(proteinMatch[1].trim(), 10) || 0,\n carbs: parseInt(carbsMatch[1].trim(), 10) || 0,\n fat: parseInt(fatMatch[1].trim(), 10) || 0,\n restaurantName:\n restaurantMatch[1].trim() === 'home'\n ? ''\n : restaurantMatch[1].trim(),\n restaurantAddress: addressMatch[1].trim()\n },\n reason: whyMatch[1].trim(),\n notify: false,\n alternatives: []\n });\n }\n });\n\n console.log('Parsed Meal Plan Slots:', newSlots);\n return newSlots;\n } catch (err: any) {\n console.error('Error generating meal plan:', err);\n throw err;\n }\n };\n\n //////////////////////////////////////////////////////////////////////\n // Handle Entire Plan Generation\n //////////////////////////////////////////////////////////////////////\n const handleGeneratePlan = async () => {\n if (remainingCalories <= 0) {\n Alert.alert('No calories remaining', 'You have used up your calories for today.');\n return;\n }\n setIsGenerating(true);\n setError(null);\n try {\n const newSlots = await generateMealPlan(planFeedback);\n if (!newSlots || newSlots.length === 0) {\n setError('No meal plan returned. Try again or modify your feedback.');\n } else {\n setSlots(newSlots);\n // Mark the plan as edited (unsaved)\n setIsPlanSaved(false);\n }\n } catch (err: any) {\n setError(`Meal plan generation failed. ${err.message || ''}`);\n } finally {\n setIsGenerating(false);\n }\n };\n\n //////////////////////////////////////////////////////////////////////\n // Single Meal Swap Handler\n //////////////////////////////////////////////////////////////////////\n const handleSwapMeal = async (slotId: string, swapReason: string) => {\n const slot = slots.find((s) => s.id === slotId);\n if (!slot) return;\n setSwappingMeal(slotId);\n\n let availableDishes = '';\n if (slot.locationType === 'restaurant') {\n try {\n const nearbyItems = await fetchNearbyMenuItems();\n const restaurantGroups = groupItemsByRestaurant(nearbyItems);\n if (Object.keys(restaurantGroups).length === 0) {\n availableDishes = 'No restaurant data found.';\n } else {\n let block = '';\n for (const key in restaurantGroups) {\n const [rName, rAddr] = key.split('__');\n block += `\\nRestaurant: ${rName}, ${rAddr}\\n`;\n const dishes = restaurantGroups[key];\n for (const dish of dishes) {\n block += ` - ${dish.foodName} (${dish.calories} cal, P:${dish.protein || 0}g C:${dish.carbs || 0}g F:${dish.fat || 0}g)\\n`;\n }\n }\n availableDishes = block.trim();\n }\n } catch (err) {\n console.error('Error fetching restaurant items for swap:', err);\n }\n } else {\n availableDishes = 'User will cook at home. Provide realistic macros.';\n }\n\n const systemMsg = `\nYou are a meal swapping assistant.\nOnly return the swapped meal in the exact format provided.\nInclude macros (protein, carbs, fat).\nUse only the provided restaurant data if applicable.\nIgnore meal timing constraints.\n `;\n const userMsg = `\nI want to swap one meal.\n\n**Meal to swap**: ${slot.name}\n**Reason**: ${swapReason || 'None'}\n**Calories Remaining**: ${remainingCalories}\n**Dietary Preferences**: ${dietaryPreferences.join(', ') || 'None'}\n**Meal Setting**: ${slot.locationType}\n\nHere are available dishes for this meal:\n${availableDishes}\n\nOnly return this exact format:\n---\n**Meal**: ${slot.name}\n**Dish**: <dish name>\n**Calories**: <kcal>\n**Protein**: <g>\n**Carbs**: <g>\n**Fat**: <g>\n**Restaurant**: <restaurant name or 'home'>\n**Address**: <restaurant address or 'N/A'>\n**Why this dish**: <reason>\n---\n `;\n const messages = [\n { role: 'system', content: systemMsg },\n { role: 'user', content: userMsg }\n ];\n\n try {\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${DEEPSEEK_API_KEY}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages,\n temperature: 0.7\n })\n });\n\n if (!response.ok) {\n const text = await response.text();\n console.error('DeepSeek Swap Meal API response not ok:', text);\n throw new Error(`DeepSeek swap meal error: ${response.status} - ${text}`);\n }\n\n const completionData = await response.json();\n console.log('DeepSeek Swap Meal response:', completionData);\n const rawResponse = completionData.choices?.[0]?.message?.content || '';\n console.log('Raw Swap Meal response:\\n', rawResponse);\n\n const section = rawResponse.split('---').find((s: string) => s.trim() !== '');\n if (section) {\n const dishMatch = section.match(/\\*\\*Dish\\*\\*:\\s*(.+)/);\n const calMatch = section.match(/\\*\\*Calories\\*\\*:\\s*(.+)/);\n const proteinMatch = section.match(/\\*\\*Protein\\*\\*:\\s*(.+)/);\n const carbsMatch = section.match(/\\*\\*Carbs\\*\\*:\\s*(.+)/);\n const fatMatch = section.match(/\\*\\*Fat\\*\\*:\\s*(.+)/);\n const restaurantMatch = section.match(/\\*\\*Restaurant\\*\\*:\\s*(.+)/);\n const addressMatch = section.match(/\\*\\*Address\\*\\*:\\s*(.+)/);\n const whyMatch = section.match(/\\*\\*Why this dish\\*\\*:\\s*(.+)/);\n\n if (\n dishMatch && calMatch &&\n proteinMatch && carbsMatch && fatMatch &&\n restaurantMatch && addressMatch && whyMatch\n ) {\n const newSlot = {\n ...slot,\n menuItem: {\n foodName: dishMatch[1].trim(),\n calories: parseInt(calMatch[1].trim(), 10),\n protein: parseInt(proteinMatch[1].trim(), 10) || 0,\n carbs: parseInt(carbsMatch[1].trim(), 10) || 0,\n fat: parseInt(fatMatch[1].trim(), 10) || 0,\n restaurantName: restaurantMatch[1].trim() === 'home' ? '' : restaurantMatch[1].trim(),\n restaurantAddress: addressMatch[1].trim()\n },\n reason: whyMatch[1].trim()\n };\n setSlots((curr) => curr.map((s) => (s.id === slotId ? newSlot : s)));\n // Mark plan as edited\n setIsPlanSaved(false);\n }\n }\n } catch (err) {\n console.error('Error swapping meal with DeepSeek:', err);\n Alert.alert('Error', 'Failed to swap meal');\n } finally {\n setSwappingMeal(null);\n }\n };\n\n //////////////////////////////////////////////////////////////////////\n // Save Plan to Firestore\n //////////////////////////////////////////////////////////////////////\n const handleSavePlan = async () => {\n if (!auth.currentUser) {\n Alert.alert('Login Required', 'Please log in first');\n return;\n }\n const today = new Date().toISOString().split('T')[0];\n try {\n await setDoc(doc(db, `users/${auth.currentUser.uid}/plans`, today), {\n slots: slots,\n createdAt: serverTimestamp()\n });\n setIsPlanSaved(true);\n Alert.alert('Success', 'Meal plan saved for today!', [\n { text: 'OK', onPress: () => router.push('/(app)/dashboard') }\n ]);\n } catch (err) {\n console.error('PlanMyDayScreen: error saving plan', err);\n Alert.alert('Error', 'Failed to save plan');\n }\n };\n\n //////////////////////////////////////////////////////////////////////\n // Rendering UI Sections\n //////////////////////////////////////////////////////////////////////\n const renderMealSettings = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Meal Settings</Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Choose Home or Restaurant for each meal\n </Text>\n </View>\n {['breakfast', 'lunch', 'snack', 'dinner'].map((meal) => (\n <View key={meal} style={styles.mealSettingRow}>\n <Text style={[styles.mealSettingLabel, isDark && styles.textLight]}>\n {meal === 'snack' ? 'Snack' : meal.charAt(0).toUpperCase() + meal.slice(1)}\n </Text>\n <View style={styles.mealSettingOptions}>\n <TouchableOpacity\n style={[\n styles.mealSettingOption,\n mealSettings[meal as keyof typeof mealSettings] === 'home' &&\n styles.mealSettingOptionActive\n ]}\n onPress={() =>\n setMealSettings((prev) => ({\n ...prev,\n [meal]: 'home'\n }))\n }\n >\n <Text style={styles.mealSettingOptionText}>Home</Text>\n </TouchableOpacity>\n <TouchableOpacity\n style={[\n styles.mealSettingOption,\n mealSettings[meal as keyof typeof mealSettings] === 'restaurant' &&\n styles.mealSettingOptionActive\n ]}\n onPress={() =>\n setMealSettings((prev) => ({\n ...prev,\n [meal]: 'restaurant'\n }))\n }\n >\n <Text style={styles.mealSettingOptionText}>Restaurant</Text>\n </TouchableOpacity>\n </View>\n </View>\n ))}\n </View>\n );\n\n const renderDietaryPreferences = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>\n Dietary Preferences\n </Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Select your dietary preferences to customize your meal plan\n </Text>\n </View>\n <View style={styles.preferencesContainer}>\n {availablePreferences.map((pref) => (\n <TouchableOpacity\n key={pref.id}\n style={[\n styles.preferenceOption,\n dietaryPreferences.includes(pref.id) && styles.activePreference,\n isDark && styles.preferenceOptionDark,\n dietaryPreferences.includes(pref.id) && isDark && styles.activePreferenceDark\n ]}\n onPress={() =>\n setDietaryPreferences((curr) =>\n curr.includes(pref.id)\n ? curr.filter((p) => p !== pref.id)\n : [...curr, pref.id]\n )\n }\n >\n {dietaryPreferences.includes(pref.id) && (\n <View style={styles.checkmarkContainer}>\n <Check size={14} color=\"#fff\" />\n </View>\n )}\n <Text\n style={[\n styles.preferenceText,\n dietaryPreferences.includes(pref.id) && styles.activePreferenceText,\n isDark && styles.preferenceTextDark,\n dietaryPreferences.includes(pref.id) && isDark && styles.activePreferenceTextDark\n ]}\n >\n {pref.label}\n </Text>\n </TouchableOpacity>\n ))}\n </View>\n <TouchableOpacity\n style={styles.updateButton}\n onPress={async () => {\n if (!auth.currentUser) return;\n try {\n await updateDoc(doc(db, 'users', auth.currentUser.uid), { dietaryPreferences });\n Alert.alert('Saved', 'Dietary preferences updated.');\n } catch (err) {\n console.error('Error saving dietary preferences', err);\n Alert.alert('Error', 'Failed to save preferences');\n }\n }}\n >\n <Heart size={16} color=\"#fff\" />\n <Text style={styles.updateButtonText}>Update Preferences</Text>\n </TouchableOpacity>\n </View>\n );\n\n const renderCalorieBudget = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>\n Today's Calorie Budget\n </Text>\n </View>\n <View style={[styles.calorieBudgetInfo, isDark && styles.calorieBudgetInfoDark]}>\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, isDark && styles.budgetValueDark]}>\n {dailyCalorieTarget}\n </Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>\n Daily Target\n </Text>\n </View>\n <View style={styles.divider} />\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, { color: '#ff6b6b' }]}>{todayCalories}</Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>Consumed</Text>\n </View>\n <View style={styles.divider} />\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, { color: '#2ecc71' }]}>{remainingCalories}</Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>Remaining</Text>\n </View>\n </View>\n </View>\n );\n\n // Updated: Render meal slots.\n const renderMealSlots = () => (\n <View style={styles.slotsContainer}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Your Meal Plan</Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Customize your meals for any time\n </Text>\n </View>\n {slots.length === 0 ? (\n <View style={[styles.emptySlots, isDark && styles.emptySlotsDark]}>\n <Calendar size={32} color={isDark ? '#aaa' : '#ccc'} />\n <Text style={[styles.emptySlotsText, isDark && styles.textLight]}>\n No meals set yet\n </Text>\n <Text style={[styles.emptySlotsSubtext, isDark && styles.emptySlotsSubtextDark]}>\n Generate a plan to see your meals!\n </Text>\n {!isGenerating && (\n <TouchableOpacity\n style={[styles.generateButton, styles.generateButtonLarge, { marginTop: 16 }]}\n onPress={handleGeneratePlan}\n >\n <LayoutGrid size={20} color=\"#fff\" />\n <Text style={styles.generateButtonText}>Generate Plan</Text>\n </TouchableOpacity>\n )}\n </View>\n ) : (\n slots.map((slot) => (\n <View key={slot.id} style={[styles.mealSlotContainer, isDark && styles.sectionDark]}>\n {slot.menuItem ? (\n <>\n <MealPlanCard\n slot={slot}\n onTimeChange={(slotId: string, time: string) => {\n setSlots((curr) =>\n curr.map((s) => (s.id === slotId ? { ...s, time } : s))\n );\n setIsPlanSaved(false);\n }}\n onToggleLocation={(slotId: string) => {\n setSlots((curr) =>\n curr.map((s) =>\n s.id === slotId\n ? {\n ...s,\n locationType: s.locationType === 'home' ? 'restaurant' : 'home',\n menuItem: null,\n reason: ''\n }\n : s\n )\n );\n setIsPlanSaved(false);\n }}\n onSwap={(slotId: string) => {\n handleSwapMeal(slotId, swapReasons[slot.id] || '');\n }}\n onToggleNotify={(slotId: string, notify: boolean) => {\n setSlots((curr) => curr.map((s) => (s.id === slotId ? { ...s, notify } : s)));\n }}\n />\n {swappingMeal === slot.id ? (\n <View style={styles.swappingContainer}>\n <ActivityIndicator size=\"small\" color=\"#2ecc71\" />\n <Text style={[styles.swappingText, isDark && styles.textLight]}>\n Regenerating {slot.name}...\n </Text>\n </View>\n ) : (\n <>\n <View style={styles.reasonContainer}>\n <Text style={[styles.reasonLabel, isDark && styles.textLight]}>Reason:</Text>\n <Text style={[styles.reasonText, isDark && styles.textLight]}>{slot.reason}</Text>\n </View>\n <View style={styles.swapContainer}>\n <TextInput\n style={[styles.swapInput, isDark && styles.swapInputDark]}\n placeholder={`Reason to re-gen ${slot.name}...`}\n placeholderTextColor={isDark ? '#aaa' : '#666'}\n value={swapReasons[slot.id] || ''}\n onChangeText={(txt) =>\n setSwapReasons((prev) => ({ ...prev, [slot.id]: txt }))\n }\n />\n <TouchableOpacity\n style={styles.swapButton}\n onPress={() => handleSwapMeal(slot.id, swapReasons[slot.id] || '')}\n >\n <LayoutGrid size={18} color=\"#fff\" />\n <Text style={styles.swapButtonText}>Re-Gen Meal</Text>\n </TouchableOpacity>\n </View>\n </>\n )}\n </>\n ) : (\n <View style={styles.completedContainer}>\n <Check size={16} color=\"#2ecc71\" />\n <Text style={[styles.completedText, isDark && styles.textLight]}>Meal Completed</Text>\n </View>\n )}\n </View>\n ))\n )}\n\n {slots.length > 0 && !isGenerating && (\n <View style={styles.actionsContainer}>\n <TouchableOpacity\n style={[styles.generateButton, styles.generateButtonLarge, { marginRight: 8 }]}\n onPress={handleGeneratePlan}\n >\n <LayoutGrid size={20} color=\"#fff\" />\n <Text style={styles.generateButtonText}>Regenerate Plan</Text>\n </TouchableOpacity>\n <TouchableOpacity\n style={[styles.saveButton, isPlanSaved && styles.savedButton]}\n onPress={handleSavePlan}\n >\n <FileCheck size={20} color=\"#fff\" />\n <Text style={styles.saveButtonText}>\n {isPlanSaved ? 'Plan Saved' : 'Save Plan'}\n </Text>\n </TouchableOpacity>\n </View>\n )}\n\n {isGenerating && (\n <View style={styles.generatingContainer}>\n <ActivityIndicator size=\"large\" color=\"#2ecc71\" />\n <Text style={[styles.generatingText, isDark && styles.textLight]}>\n Generating your meal plan...\n </Text>\n </View>\n )}\n {error && (\n <View style={styles.errorContainer}>\n <AlertCircle size={20} color=\"#ff6b6b\" />\n <Text style={styles.errorText}>{error}</Text>\n </View>\n )}\n </View>\n );\n\n const renderPlanFeedback = () => {\n if (slots.length === 0) return null;\n return (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Plan Feedback</Text>\n <TextInput\n style={[styles.feedbackInput, isDark && styles.feedbackInputDark]}\n placeholder=\"Enter any feedback or instructions for your meal plan...\"\n placeholderTextColor={isDark ? '#aaa' : '#666'}\n value={planFeedback}\n onChangeText={setPlanFeedback}\n multiline\n />\n </View>\n );\n };\n\n //////////////////////////////////////////////////////////////////////\n // Main Render\n //////////////////////////////////////////////////////////////////////\n return (\n <KeyboardAvoidingView\n style={[styles.container, isDark && styles.containerDark]}\n behavior={Platform.OS === 'ios' ? 'padding' : 'height'}\n >\n <View style={[styles.header, isDark && styles.headerDark]}>\n <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>\n <ArrowLeft size={24} color=\"#2ecc71\" />\n </TouchableOpacity>\n <Text style={[styles.headerTitle, isDark && styles.textLight]}>Plan My Day</Text>\n <Utensils size={24} color=\"#2ecc71\" />\n </View>\n\n {loading ? (\n <View style={[styles.loadingContainer, isDark && styles.loadingContainerDark]}>\n <ActivityIndicator size=\"large\" color=\"#2ecc71\" />\n <Text style={[styles.loadingText, isDark && styles.textLight]}>\n Loading your meal planning options...\n </Text>\n </View>\n ) : (\n <ScrollView\n style={styles.scrollView}\n contentContainerStyle={styles.scrollContent}\n showsVerticalScrollIndicator={false}\n >\n {slots.length === 0 && renderMealSettings()}\n {renderDietaryPreferences()}\n {renderCalorieBudget()}\n {renderMealSlots()}\n {renderPlanFeedback()}\n <View style={styles.bottomSpacer} />\n </ScrollView>\n )}\n </KeyboardAvoidingView>\n );\n};\n\n//////////////////////////////////////////////////////////////////////////\n// STYLES\n//////////////////////////////////////////////////////////////////////////\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#f9f9f9' },\n containerDark: { backgroundColor: '#121212' },\n\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: 16,\n paddingTop: Platform.OS === 'ios' ? 50 : 30,\n paddingBottom: 16,\n backgroundColor: 'white',\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.1,\n shadowRadius: 4,\n elevation: 2,\n },\n headerDark: { backgroundColor: '#1e1e1e', shadowColor: '#000' },\n backButton: { padding: 4 },\n headerTitle: { fontSize: 20, fontWeight: 'bold', color: '#2ecc71' },\n textLight: { color: '#f2f2f2' },\n\n scrollView: { flex: 1 },\n scrollContent: { padding: 16, paddingBottom: 90 },\n\n loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n loadingContainerDark: { backgroundColor: '#121212' },\n loadingText: { marginTop: 16, color: '#666', fontSize: 16 },\n\n section: {\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 16,\n marginBottom: 16,\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.05,\n shadowRadius: 3,\n elevation: 2,\n },\n sectionDark: { backgroundColor: '#1e1e1e', shadowColor: '#000' },\n sectionHeader: { marginBottom: 16 },\n sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#333', marginBottom: 4 },\n sectionSubtitle: { fontSize: 14, color: '#666' },\n subtitleDark: { color: '#aaa' },\n\n mealSettingRow: {\n flexDirection: 'row',\n alignItems: 'center',\n marginBottom: 8,\n justifyContent: 'space-between'\n },\n mealSettingLabel: { fontSize: 16, fontWeight: '600', color: '#333' },\n mealSettingOptions: { flexDirection: 'row' },\n mealSettingOption: {\n backgroundColor: '#f0f0f0',\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderRadius: 8,\n marginRight: 8,\n },\n mealSettingOptionActive: { backgroundColor: '#2ecc71' },\n mealSettingOptionText: { fontSize: 14, color: '#333' },\n\n preferencesContainer: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 16 },\n preferenceOption: {\n flexDirection: 'row',\n alignItems: 'center',\n backgroundColor: 'white',\n borderRadius: 20,\n paddingHorizontal: 12,\n paddingVertical: 6,\n marginRight: 8,\n marginBottom: 8,\n borderWidth: 1,\n borderColor: '#eee'\n },\n preferenceOptionDark: { backgroundColor: '#252525', borderColor: '#333' },\n activePreference: { backgroundColor: '#2ecc71', borderColor: '#2ecc71' },\n activePreferenceDark: { backgroundColor: '#2ecc71', borderColor: '#2ecc71' },\n checkmarkContainer: { marginRight: 4 },\n preferenceText: { fontSize: 14, color: '#666' },\n preferenceTextDark: { color: '#aaa' },\n activePreferenceText: { color: 'white', fontWeight: 'bold' },\n activePreferenceTextDark: { color: 'white', fontWeight: 'bold' },\n\n updateButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#2ecc71',\n borderRadius: 8,\n paddingVertical: 12\n },\n updateButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n\n calorieBudgetInfo: { flexDirection: 'row', borderRadius: 8, padding: 16 },\n calorieBudgetInfoDark: { backgroundColor: '#252525' },\n budgetItem: { flex: 1, alignItems: 'center' },\n budgetValue: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 4 },\n budgetValueDark: { color: '#f2f2f2' },\n budgetLabel: { fontSize: 12, color: '#666' },\n budgetLabelDark: { color: '#aaa' },\n divider: { width: 1, backgroundColor: '#eee', marginHorizontal: 8 },\n\n slotsContainer: { marginBottom: 16 },\n emptySlots: {\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 24,\n alignItems: 'center',\n justifyContent: 'center',\n borderWidth: 1,\n borderColor: '#f0f0f0',\n borderStyle: 'dashed'\n },\n emptySlotsDark: { backgroundColor: '#1e1e1e', borderColor: '#333' },\n emptySlotsText: {\n fontSize: 16,\n fontWeight: 'bold',\n color: '#666',\n marginTop: 12,\n marginBottom: 8\n },\n emptySlotsSubtext: { fontSize: 14, color: '#888', textAlign: 'center' },\n emptySlotsSubtextDark: { color: '#777' },\n\n mealSlotContainer: {\n marginBottom: 16,\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 12\n },\n\n // Style for completed (logged) meal view.\n completedContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n paddingVertical: 10,\n justifyContent: 'center'\n },\n completedText: { fontSize: 16, fontWeight: 'bold', color: '#2ecc71', marginLeft: 6 },\n\n reasonContainer: { marginTop: 8 },\n reasonLabel: { fontSize: 14, fontWeight: 'bold' },\n reasonText: { fontSize: 14, marginTop: 4 },\n\n swapContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },\n swapInput: {\n flex: 1,\n borderColor: '#ccc',\n borderWidth: 1,\n borderRadius: 8,\n paddingHorizontal: 8,\n paddingVertical: 6\n },\n swapInputDark: { borderColor: '#555', color: '#f2f2f2' },\n swapButton: {\n backgroundColor: '#3498db',\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderRadius: 8,\n marginLeft: 8,\n flexDirection: 'row',\n alignItems: 'center'\n },\n swapButtonText: { color: 'white', fontWeight: 'bold', marginLeft: 6 },\n\n swappingContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },\n swappingText: { marginLeft: 8, fontSize: 14, color: '#666' },\n\n actionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 16 },\n generateButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#3498db',\n borderRadius: 8,\n paddingVertical: 12,\n paddingHorizontal: 10\n },\n generateButtonLarge: { paddingVertical: 14 },\n generateButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n\n saveButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#2ecc71',\n borderRadius: 8,\n paddingVertical: 12,\n paddingHorizontal: 10\n },\n savedButton: { backgroundColor: '#27ae60' },\n saveButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n\n generatingContainer: { marginTop: 16, alignItems: 'center', justifyContent: 'center' },\n generatingText: { marginTop: 8, fontSize: 16, color: '#333' },\n\n errorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n marginTop: 16,\n borderRadius: 8,\n padding: 8,\n backgroundColor: '#ffe6e6'\n },\n errorText: { marginLeft: 8, color: '#ff6b6b' },\n\n feedbackInput: {\n minHeight: 60,\n borderColor: '#ccc',\n borderWidth: 1,\n borderRadius: 8,\n paddingHorizontal: 12,\n paddingVertical: 8,\n marginTop: 8,\n textAlignVertical: 'top',\n backgroundColor: '#fff'\n },\n feedbackInputDark: {\n backgroundColor: '#252525',\n borderColor: '#555',\n color: '#f2f2f2'\n },\n\n bottomSpacer: { height: 80 }\n});\n\nexport default PlanMyDayScreen;\n"}, "0fcebed5bb3b464b77586215778b50353e1c6277": {"t": 1785765400.664445, "n": 39223, "h": "6777b37eebb7a7b3", "c": "import React, { useState, useEffect, useContext } from 'react';\nimport {\n View,\n Text,\n StyleSheet,\n TouchableOpacity,\n ScrollView,\n ActivityIndicator,\n Alert,\n Platform,\n TextInput\n} from 'react-native';\nimport { router } from 'expo-router';\nimport { auth, db } from '../../firebaseConfig';\nimport {\n doc,\n getDoc,\n collection,\n addDoc,\n serverTimestamp,\n updateDoc,\n setDoc,\n getDocs\n} from 'firebase/firestore';\nimport {\n ArrowLeft,\n LayoutGrid,\n Calendar,\n Check,\n FileCheck,\n Heart,\n CircleAlert as AlertCircle,\n Utensils\n} from 'lucide-react-native';\nimport * as Notifications from 'expo-notifications';\nimport { ThemeContext } from '../../context/ThemeContext';\nimport MealPlanCard from '../../components/MealPlanCard';\nimport { Restaurant, MealSlot, MenuItem } from '../../types/mealPlanner';\n\n// ---------------------------------------------------------------------\n// Constants & DeepSeek Configuration (Using HTTP requests)\n// ---------------------------------------------------------------------\nconst DEEPSEEK_BASE_URL = 'https://api.deepseek.com';\nconst DEEPSEEK_API_KEY = 'sk-95593a35636148368ec4a6d868bf1bb8';\n\n// If you want notifications, keep this; otherwise remove.\nNotifications.setNotificationHandler({\n handleNotification: async () => ({\n shouldShowAlert: true,\n shouldPlaySound: true,\n shouldSetBadge: false,\n }),\n});\n\nconst PlanMyDayScreen: React.FC = () => {\n const { theme } = useContext(ThemeContext);\n const isDark = theme === 'dark';\n\n // -----------------------\n // State variables\n // -----------------------\n const [dietaryPreferences, setDietaryPreferences] = useState<string[]>([]);\n const [availablePreferences] = useState<{ id: string; label: string }[]>([\n { id: 'vegetarian', label: 'Vegetarian' },\n { id: 'vegan', label: 'Vegan' },\n { id: 'glutenFree', label: 'Gluten Free' },\n { id: 'dairyFree', label: 'Dairy Free' },\n { id: 'ketogenic', label: 'Ketogenic' },\n { id: 'paleo', label: 'Paleo' },\n ]);\n const [dailyCalorieTarget, setDailyCalorieTarget] = useState<number>(2000);\n const [todayCalories, setTodayCalories] = useState<number>(0);\n const remainingCalories = Math.max(0, dailyCalorieTarget - todayCalories);\n\n // We have 4 guaranteed meals: breakfast, lunch, “snack” (after dinner), dinner\n // Let user pick “home” or “restaurant” for each before generating the plan\n const [mealSettings, setMealSettings] = useState<{\n breakfast: 'home' | 'restaurant';\n lunch: 'home' | 'restaurant';\n snack: 'home' | 'restaurant';\n dinner: 'home' | 'restaurant';\n }>({\n breakfast: 'home',\n lunch: 'home',\n snack: 'home', // “snack (after dinner)”\n dinner: 'home'\n });\n\n // Final slots after plan generation\n const [slots, setSlots] = useState<MealSlot[]>([]);\n const [loading, setLoading] = useState<boolean>(true);\n const [isGenerating, setIsGenerating] = useState<boolean>(false);\n const [isPlanSaved, setIsPlanSaved] = useState<boolean>(false);\n const [error, setError] = useState<string | null>(null);\n\n // Plan feedback—only relevant if a plan is generated\n const [planFeedback, setPlanFeedback] = useState<string>('');\n\n // Reason for swapping each meal\n const [swapReasons, setSwapReasons] = useState<{ [key: string]: string }>({});\n\n // For meal-level re-generation (swapping) loading indicator\n const [swappingMeal, setSwappingMeal] = useState<string | null>(null);\n\n // ---------------------------------------------------------------------\n // Initialization: load user data and saved plan if it exists.\n // ---------------------------------------------------------------------\n useEffect(() => {\n const init = async () => {\n if (!auth.currentUser) {\n setLoading(false);\n setError('You must be logged in to use PlanMyDay');\n return;\n }\n try {\n // 1. Load user doc and preferences.\n const userRef = doc(db, 'users', auth.currentUser.uid);\n const userSnap = await getDoc(userRef);\n if (userSnap.exists()) {\n const userData = userSnap.data();\n if (userData.dietaryPreferences) {\n setDietaryPreferences(userData.dietaryPreferences);\n }\n if (typeof userData.dailyCalorieTarget === 'number') {\n setDailyCalorieTarget(userData.dailyCalorieTarget);\n }\n if (typeof userData.todayCalories === 'number') {\n setTodayCalories(userData.todayCalories);\n }\n }\n\n // 2. Check if a plan exists for today\n const today = new Date().toISOString().split('T')[0];\n const planRef = doc(db, `users/${auth.currentUser.uid}/plans`, today);\n const planSnap = await getDoc(planRef);\n if (planSnap.exists()) {\n const savedPlan = planSnap.data();\n setSlots(savedPlan.slots || []);\n setIsPlanSaved(true);\n }\n } catch (err) {\n console.error('PlanMyDayScreen init error:', err);\n setError('Initialization failed. Please try again.');\n } finally {\n setLoading(false);\n }\n };\n init();\n }, []);\n\n // ---------------------------------------------------------------------\n // Distance calculation & location-based filtering\n // ---------------------------------------------------------------------\n const toRad = (deg: number) => deg * (Math.PI / 180);\n const getDistance = (lat1: number, lon1: number, lat2: number, lon2: number): number => {\n const R = 6371; // Earth radius in km\n const dLat = toRad(lat2 - lat1);\n const dLon = toRad(lon2 - lon1);\n const a =\n Math.sin(dLat / 2) ** 2 +\n Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;\n const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n return R * c;\n };\n\n // For demonstration, a default location near NTU, you can customize\n const LATITUDE = 1.355049655134308;\n const LONGITUDE = 103.68518139204353;\n\n // ---------------------------------------------------------------------\n // Fetch nearby menu items from Firestore\n // ---------------------------------------------------------------------\n const fetchNearbyMenuItems = async (): Promise<any[]> => {\n try {\n const snapshot = await getDocs(collection(db, 'restaurants'));\n const nearbyItems: any[] = [];\n snapshot.forEach((docSnap) => {\n const data = docSnap.data();\n if (data.location) {\n const dist = getDistance(\n data.location.lat,\n data.location.lng,\n LATITUDE,\n LONGITUDE\n );\n if (dist < 2.0 && data.items) {\n data.items.forEach((item: any) => {\n nearbyItems.push({\n ...item,\n restaurantName: data.name,\n restaurantAddress: data.address || ''\n });\n });\n }\n }\n });\n return nearbyItems;\n } catch (error) {\n console.error('fetchNearbyMenuItems error:', error);\n return [];\n }\n };\n\n // ---------------------------------------------------------------------\n // Generate Meal Plan using DeepSeek\n // ---------------------------------------------------------------------\n const generateMealPlan = async (feedback: string = ''): Promise<MealSlot[]> => {\n // We have 4 guaranteed meals: breakfast, lunch, snack, dinner\n // Use mealSettings to determine locationType\n // Build the textual info about each meal\n\n // For convenience, we’ll store them in an array\n const guaranteedMeals = ['breakfast', 'lunch', 'snack', 'dinner'];\n\n // Gather nearby restaurant data\n const nearbyMenuItems = await fetchNearbyMenuItems();\n\n // Build available dishes text for each meal\n const availableByMeal: { [key: string]: string } = {};\n guaranteedMeals.forEach((meal) => {\n const setting = mealSettings[meal as keyof typeof mealSettings];\n if (setting === 'restaurant') {\n // Show items from Firestore\n const items = nearbyMenuItems.map(\n (i) =>\n `${i.foodName} (${i.calories} kcal) from ${i.restaurantName} [Address: ${i.restaurantAddress}]`\n );\n availableByMeal[meal] = items.length ? items.join('\\n') : 'No dishes found';\n } else {\n availableByMeal[meal] = 'User will cook at home. You can invent any realistic dish.';\n }\n });\n\n const dietLabels = dietaryPreferences.length\n ? dietaryPreferences.join(', ')\n : 'None';\n\n const systemPrompt = `\nYou are a meal recommendation assistant.\nOnly return answers in the format specified by the user.\nNever guess the calories for restaurant items; use the data provided.\nIgnore any meal timing constraints.\n `;\n\n let userPrompt = `\nGenerate a meal plan for today with these constraints:\n\n**Calories Remaining**: ${remainingCalories} kcal\n**Dietary Preferences**: ${dietLabels}\n**User Feedback**: ${feedback || 'None'}\n\nWe have four guaranteed meals: \n1) Breakfast\n2) Lunch\n3) Snack (after dinner)\n4) Dinner\n\nIf you need to add an extra snack to fit leftover calories, feel free to do so.\n\nFor each meal, use this format exactly (no time range needed):\n\n---\n**Meal**: <meal name>\n**Dish**: <dish name>\n**Calories**: <kcal>\n**Restaurant**: <restaurant name or 'home'>\n**Address**: <restaurant address or 'N/A'>\n**Why this dish**: <short reason>\n---\n\nAvailable dishes per meal:\n `;\n guaranteedMeals.forEach((meal) => {\n userPrompt += `\\n🍽️ ${meal.toUpperCase()} (${mealSettings[meal as keyof typeof mealSettings]}):\\n${availableByMeal[meal]}\\n`;\n });\n\n const messages = [\n { role: 'system', content: systemPrompt },\n { role: 'user', content: userPrompt }\n ];\n\n try {\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${DEEPSEEK_API_KEY}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages,\n temperature: 0.7\n })\n });\n\n const completionData = await response.json();\n const rawResponse = completionData.choices?.[0]?.message?.content || '';\n const sections = rawResponse.split('---').filter((sec: string) => sec.trim() !== '');\n const newSlots: MealSlot[] = [];\n\n sections.forEach((section, idx) => {\n const mealMatch = section.match(/\\*\\*Meal\\*\\*:\\s*(.+)/);\n const dishMatch = section.match(/\\*\\*Dish\\*\\*:\\s*(.+)/);\n const caloriesMatch = section.match(/\\*\\*Calories\\*\\*:\\s*(.+)/);\n const restaurantMatch = section.match(/\\*\\*Restaurant\\*\\*:\\s*(.+)/);\n const addressMatch = section.match(/\\*\\*Address\\*\\*:\\s*(.+)/);\n const whyMatch = section.match(/\\*\\*Why this dish\\*\\*:\\s*(.+)/);\n\n if (\n mealMatch &&\n dishMatch &&\n caloriesMatch &&\n restaurantMatch &&\n addressMatch &&\n whyMatch\n ) {\n newSlots.push({\n id: Date.now().toString() + '-' + idx,\n name: mealMatch[1].trim(),\n time: '',\n locationType: restaurantMatch[1].trim().toLowerCase() === 'home' ? 'home' : 'restaurant',\n menuItem: {\n foodName: dishMatch[1].trim(),\n calories: parseInt(caloriesMatch[1].trim(), 10),\n restaurantName: restaurantMatch[1].trim(),\n restaurantAddress: addressMatch[1].trim()\n },\n reason: whyMatch[1].trim(),\n notify: false,\n alternatives: []\n });\n }\n });\n return newSlots;\n } catch (err) {\n console.error('Error generating meal plan:', err);\n return [];\n }\n };\n\n // ---------------------------------------------------------------------\n // Handle Plan Generation\n // ---------------------------------------------------------------------\n const handleGeneratePlan = async () => {\n if (remainingCalories <= 0) {\n Alert.alert('No calories remaining', 'You have used up your calories for today.');\n return;\n }\n setIsGenerating(true);\n setError(null);\n try {\n const newSlots = await generateMealPlan(planFeedback);\n setSlots(newSlots);\n setIsPlanSaved(false);\n } catch (err) {\n setError('Meal plan generation failed.');\n console.error(err);\n } finally {\n setIsGenerating(false);\n }\n };\n\n // ---------------------------------------------------------------------\n // Meal Swap (re-gen for a single slot)\n // ---------------------------------------------------------------------\n const handleSwapMeal = async (slotId: string, swapReason: string) => {\n const slot = slots.find((s) => s.id === slotId);\n if (!slot) return;\n\n setSwappingMeal(slotId); // show loading indicator for this slot\n\n let availableDishes = '';\n if (slot.locationType === 'restaurant') {\n const nearbyItems = await fetchNearbyMenuItems();\n availableDishes = nearbyItems\n .map(\n (i: any) =>\n `${i.foodName} (${i.calories} kcal) from ${i.restaurantName} [Address: ${i.restaurantAddress}]`\n )\n .join('\\n');\n } else {\n availableDishes = 'User will cook at home. Be creative!';\n }\n\n const messages = [\n {\n role: 'system',\n content: `You are a meal swapping assistant. Only return the swapped meal in the specified format.\nUse only the provided calories from the data.\nIgnore time constraints entirely.`\n },\n {\n role: 'user',\n content: `\nI want to swap just one meal.\n\n**Meal to swap**: ${slot.name}\n**Reason**: ${swapReason || 'None'}\n**Calories Remaining**: ${remainingCalories}\n**Dietary Preferences**: ${dietaryPreferences.join(', ') || 'None'}\n**Meal Setting**: ${slot.locationType}\n\nHere are available dishes for this meal:\n${availableDishes}\n\nOnly return this format:\n---\n**Meal**: ${slot.name}\n**Dish**: <dish name>\n**Calories**: <kcal>\n**Restaurant**: <restaurant name or 'home'>\n**Address**: <restaurant address or 'N/A'>\n**Why this dish**: <reason>\n---\n`\n }\n ];\n\n try {\n const response = await fetch(`${DEEPSEEK_BASE_URL}/v1/chat/completions`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${DEEPSEEK_API_KEY}`\n },\n body: JSON.stringify({\n model: 'deepseek-chat',\n messages,\n temperature: 0.7\n })\n });\n const completionData = await response.json();\n const rawResponse = completionData.choices?.[0]?.message?.content || '';\n const section = rawResponse.split('---').find((s: string) => s.trim() !== '');\n if (section) {\n const mealMatch = section.match(/\\*\\*Meal\\*\\*:\\s*(.+)/);\n const dishMatch = section.match(/\\*\\*Dish\\*\\*:\\s*(.+)/);\n const caloriesMatch = section.match(/\\*\\*Calories\\*\\*:\\s*(.+)/);\n const restaurantMatch = section.match(/\\*\\*Restaurant\\*\\*:\\s*(.+)/);\n const addressMatch = section.match(/\\*\\*Address\\*\\*:\\s*(.+)/);\n const whyMatch = section.match(/\\*\\*Why this dish\\*\\*:\\s*(.+)/);\n if (\n mealMatch &&\n dishMatch &&\n caloriesMatch &&\n restaurantMatch &&\n addressMatch &&\n whyMatch\n ) {\n const newSlot = {\n ...slot,\n menuItem: {\n foodName: dishMatch[1].trim(),\n calories: parseInt(caloriesMatch[1].trim(), 10),\n restaurantName: restaurantMatch[1].trim(),\n restaurantAddress: addressMatch[1].trim()\n },\n reason: whyMatch[1].trim()\n };\n setSlots((curr) => curr.map((s) => (s.id === slotId ? newSlot : s)));\n }\n }\n } catch (err) {\n console.error('Error swapping meal:', err);\n Alert.alert('Error', 'Failed to swap meal');\n } finally {\n setSwappingMeal(null);\n }\n };\n\n // ---------------------------------------------------------------------\n // Save the current plan to Firestore\n // ---------------------------------------------------------------------\n const handleSavePlan = async () => {\n if (!auth.currentUser) {\n Alert.alert('Login Required', 'Please log in first');\n return;\n }\n const today = new Date().toISOString().split('T')[0];\n try {\n await setDoc(doc(db, `users/${auth.currentUser.uid}/plans`, today), {\n slots: slots,\n createdAt: serverTimestamp(),\n });\n setIsPlanSaved(true);\n Alert.alert('Success', 'Meal plan saved for today!', [\n { text: 'OK', onPress: () => router.push('/(app)/dashboard') }\n ]);\n } catch (err) {\n console.error('PlanMyDayScreen: error saving plan', err);\n Alert.alert('Error', 'Failed to save plan');\n }\n };\n\n // ---------------------------------------------------------------------\n // Quick Log Implementation\n // ---------------------------------------------------------------------\n const handleQuickLog = async (slotId: string) => {\n const slot = slots.find((s) => s.id === slotId);\n if (!slot || !slot.menuItem) return;\n\n if (!auth.currentUser) {\n Alert.alert('Login Required', 'Please log in to quick log a meal.');\n return;\n }\n\n try {\n await addDoc(collection(db, 'mealLogs'), {\n userId: auth.currentUser.uid,\n date: new Date(),\n createdAt: serverTimestamp(),\n mealName: slot.name,\n mealLocation: slot.locationType,\n dish: slot.menuItem.foodName,\n calories: slot.menuItem.calories,\n restaurantName: slot.menuItem.restaurantName || 'home',\n restaurantAddress: slot.menuItem.restaurantAddress || 'N/A',\n });\n Alert.alert('Success', 'Meal has been quick-logged!');\n } catch (err) {\n console.error('handleQuickLog error:', err);\n Alert.alert('Error', 'Failed to log meal');\n }\n };\n\n // ---------------------------------------------------------------------\n // Rendering UI Sections\n // ---------------------------------------------------------------------\n\n // 1) Meal Settings UI: user picks home/restaurant for each meal\n const renderMealSettings = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Meal Settings</Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Choose Home or Restaurant for each meal\n </Text>\n </View>\n {['breakfast', 'lunch', 'snack', 'dinner'].map((meal) => (\n <View key={meal} style={styles.mealSettingRow}>\n <Text style={[styles.mealSettingLabel, isDark && styles.textLight]}>\n {meal === 'snack' ? 'Snack (after dinner)' : meal.charAt(0).toUpperCase() + meal.slice(1)}\n </Text>\n <View style={styles.mealSettingOptions}>\n <TouchableOpacity\n style={[\n styles.mealSettingOption,\n mealSettings[meal as keyof typeof mealSettings] === 'home' && styles.mealSettingOptionActive\n ]}\n onPress={() =>\n setMealSettings((prev) => ({\n ...prev,\n [meal]: 'home'\n }))\n }\n >\n <Text style={styles.mealSettingOptionText}>Home</Text>\n </TouchableOpacity>\n\n <TouchableOpacity\n style={[\n styles.mealSettingOption,\n mealSettings[meal as keyof typeof mealSettings] === 'restaurant' && styles.mealSettingOptionActive\n ]}\n onPress={() =>\n setMealSettings((prev) => ({\n ...prev,\n [meal]: 'restaurant'\n }))\n }\n >\n <Text style={styles.mealSettingOptionText}>Restaurant</Text>\n </TouchableOpacity>\n </View>\n </View>\n ))}\n </View>\n );\n\n // 2) Dietary Preferences\n const renderDietaryPreferences = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>\n Dietary Preferences\n </Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Select your dietary preferences to customize your meal plan\n </Text>\n </View>\n <View style={styles.preferencesContainer}>\n {availablePreferences.map((pref) => (\n <TouchableOpacity\n key={pref.id}\n style={[\n styles.preferenceOption,\n dietaryPreferences.includes(pref.id) && styles.activePreference,\n isDark && styles.preferenceOptionDark,\n dietaryPreferences.includes(pref.id) && isDark && styles.activePreferenceDark\n ]}\n onPress={() =>\n setDietaryPreferences((curr) =>\n curr.includes(pref.id)\n ? curr.filter((p) => p !== pref.id)\n : [...curr, pref.id]\n )\n }\n >\n {dietaryPreferences.includes(pref.id) && (\n <View style={styles.checkmarkContainer}>\n <Check size={14} color=\"#fff\" />\n </View>\n )}\n <Text\n style={[\n styles.preferenceText,\n dietaryPreferences.includes(pref.id) && styles.activePreferenceText,\n isDark && styles.preferenceTextDark,\n dietaryPreferences.includes(pref.id) && isDark && styles.activePreferenceTextDark\n ]}\n >\n {pref.label}\n </Text>\n </TouchableOpacity>\n ))}\n </View>\n <TouchableOpacity\n style={styles.updateButton}\n onPress={async () => {\n if (!auth.currentUser) return;\n try {\n await updateDoc(doc(db, 'users', auth.currentUser.uid), { dietaryPreferences });\n Alert.alert('Saved', 'Dietary preferences updated.');\n } catch (err) {\n console.error('Error saving dietary preferences', err);\n Alert.alert('Error', 'Failed to save dietary preferences');\n }\n }}\n >\n <Heart size={16} color=\"#fff\" />\n <Text style={styles.updateButtonText}>Update Preferences</Text>\n </TouchableOpacity>\n </View>\n );\n\n // 3) Calorie Budget\n const renderCalorieBudget = () => (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Today's Calorie Budget</Text>\n </View>\n <View style={[styles.calorieBudgetInfo, isDark && styles.calorieBudgetInfoDark]}>\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, isDark && styles.budgetValueDark]}>\n {dailyCalorieTarget}\n </Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>\n Daily Target\n </Text>\n </View>\n <View style={styles.divider} />\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, { color: '#ff6b6b' }]}>{todayCalories}</Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>Consumed</Text>\n </View>\n <View style={styles.divider} />\n <View style={styles.budgetItem}>\n <Text style={[styles.budgetValue, { color: '#2ecc71' }]}>{remainingCalories}</Text>\n <Text style={[styles.budgetLabel, isDark && styles.budgetLabelDark]}>Remaining</Text>\n </View>\n </View>\n </View>\n );\n\n // 4) Meal Slots\n const renderMealSlots = () => (\n <View style={styles.slotsContainer}>\n <View style={styles.sectionHeader}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Your Meal Plan</Text>\n <Text style={[styles.sectionSubtitle, isDark && styles.subtitleDark]}>\n Customize your meals for any time\n </Text>\n </View>\n {slots.length === 0 ? (\n // No plan yet => show large button\n <View style={[styles.emptySlots, isDark && styles.emptySlotsDark]}>\n <Calendar size={32} color={isDark ? '#aaa' : '#ccc'} />\n <Text style={[styles.emptySlotsText, isDark && styles.textLight]}>\n No meals set yet\n </Text>\n <Text style={[styles.emptySlotsSubtext, isDark && styles.emptySlotsSubtextDark]}>\n Generate a plan to see your meals!\n </Text>\n {!isGenerating && (\n <TouchableOpacity\n style={[styles.generateButton, styles.generateButtonLarge, { marginTop: 16 }]}\n onPress={handleGeneratePlan}\n >\n <LayoutGrid size={20} color=\"#fff\" />\n <Text style={styles.generateButtonText}>Generate Plan</Text>\n </TouchableOpacity>\n )}\n </View>\n ) : (\n // Show each slot\n slots.map((slot) => (\n <View key={slot.id} style={styles.mealSlotContainer}>\n {/* \n We pass handleQuickLog so that the Quick Log button works.\n We also pass an \"onSwap\" or \"swap\" function, but we have a custom handleSwapMeal \n so we can show a per-slot spinner.\n */}\n <MealPlanCard\n slot={slot}\n onTimeChange={(time: string) => {\n setSlots((curr) => curr.map((s) => (s.id === slot.id ? { ...s, time } : s)));\n }}\n onToggleLocation={(id: string) => {\n setSlots((curr) =>\n curr.map((s) =>\n s.id === id\n ? {\n ...s,\n locationType: s.locationType === 'home' ? 'restaurant' : 'home',\n menuItem: null,\n reason: ''\n }\n : s\n )\n );\n }}\n onSwap={(id: string) => {\n // We'll reuse the swapReasons\n handleSwapMeal(id, swapReasons[id] || '');\n }}\n onToggleNotify={(id: string, notify: boolean) => {\n setSlots((curr) => curr.map((s) => (s.id === id ? { ...s, notify } : s)));\n }}\n onQuickLog={(id: string) => handleQuickLog(id)}\n />\n {/* If we are regenerating this particular slot, show a spinner instead of swap UI */}\n {swappingMeal === slot.id ? (\n <View style={styles.swappingContainer}>\n <ActivityIndicator size=\"small\" color=\"#2ecc71\" />\n <Text style={[styles.swappingText, isDark && styles.textLight]}>\n Regenerating {slot.name}...\n </Text>\n </View>\n ) : (\n <>\n <View style={styles.reasonContainer}>\n <Text style={[styles.reasonLabel, isDark && styles.textLight]}>Reason:</Text>\n <Text style={[styles.reasonText, isDark && styles.textLight]}>\n {slot.reason}\n </Text>\n </View>\n <View style={styles.swapContainer}>\n <TextInput\n style={[styles.swapInput, isDark && styles.swapInputDark]}\n placeholder={`Reason to re-gen ${slot.name}...`}\n placeholderTextColor={isDark ? '#aaa' : '#666'}\n value={swapReasons[slot.id] || ''}\n onChangeText={(text) =>\n setSwapReasons((prev) => ({ ...prev, [slot.id]: text }))\n }\n />\n <TouchableOpacity\n style={styles.swapButton}\n onPress={() => handleSwapMeal(slot.id, swapReasons[slot.id] || '')}\n >\n <LayoutGrid size={18} color=\"#fff\" />\n <Text style={styles.swapButtonText}>Re-Gen Meal</Text>\n </TouchableOpacity>\n </View>\n </>\n )}\n </View>\n ))\n )}\n\n {/* If we have some slots, show the “Regenerate Plan” & “Save Plan” actions */}\n {slots.length > 0 && !isGenerating && (\n <View style={styles.actionsContainer}>\n <TouchableOpacity\n style={[styles.generateButton, styles.generateButtonLarge, { marginRight: 8 }]}\n onPress={handleGeneratePlan}\n >\n <LayoutGrid size={20} color=\"#fff\" />\n <Text style={styles.generateButtonText}>Regenerate Plan</Text>\n </TouchableOpacity>\n <TouchableOpacity\n style={[styles.saveButton, isPlanSaved && styles.savedButton]}\n onPress={handleSavePlan}\n disabled={isPlanSaved}\n >\n <FileCheck size={20} color=\"#fff\" />\n <Text style={styles.saveButtonText}>\n {isPlanSaved ? 'Plan Saved' : 'Save Plan'}\n </Text>\n </TouchableOpacity>\n </View>\n )}\n {isGenerating && (\n <View style={styles.generatingContainer}>\n <ActivityIndicator size=\"large\" color=\"#2ecc71\" />\n <Text style={[styles.generatingText, isDark && styles.textLight]}>\n Generating your meal plan...\n </Text>\n </View>\n )}\n {error && (\n <View style={styles.errorContainer}>\n <AlertCircle size={20} color=\"#ff6b6b\" />\n <Text style={styles.errorText}>{error}</Text>\n </View>\n )}\n </View>\n );\n\n // 5) Conditional Plan Feedback at the bottom\n const renderPlanFeedback = () => {\n // Only show if there's a plan\n if (slots.length === 0) return null;\n\n return (\n <View style={[styles.section, isDark && styles.sectionDark]}>\n <Text style={[styles.sectionTitle, isDark && styles.textLight]}>Plan Feedback</Text>\n <TextInput\n style={[styles.feedbackInput, isDark && styles.feedbackInputDark]}\n placeholder=\"Enter any feedback or instructions for your meal plan...\"\n placeholderTextColor={isDark ? '#aaa' : '#666'}\n value={planFeedback}\n onChangeText={setPlanFeedback}\n />\n </View>\n );\n };\n\n // ---------------------------------------------------------------------\n // Main render\n // ---------------------------------------------------------------------\n return (\n <View style={[styles.container, isDark && styles.containerDark]}>\n {/* Header Bar */}\n <View style={[styles.header, isDark && styles.headerDark]}>\n <TouchableOpacity style={styles.backButton} onPress={() => router.back()}>\n <ArrowLeft size={24} color=\"#2ecc71\" />\n </TouchableOpacity>\n <Text style={[styles.headerTitle, isDark && styles.textLight]}>Plan My Day</Text>\n <Utensils size={24} color=\"#2ecc71\" />\n </View>\n\n {loading ? (\n <View style={[styles.loadingContainer, isDark && styles.loadingContainerDark]}>\n <ActivityIndicator size=\"large\" color=\"#2ecc71\" />\n <Text style={[styles.loadingText, isDark && styles.textLight]}>\n Loading your meal planning options...\n </Text>\n </View>\n ) : (\n <ScrollView\n style={styles.scrollView}\n contentContainerStyle={styles.scrollContent}\n showsVerticalScrollIndicator={false}\n >\n {/* 1) Pre-generation meal location settings */}\n {slots.length === 0 && renderMealSettings()}\n {/* 2) Dietary preferences */}\n {renderDietaryPreferences()}\n {/* 3) Calorie budget */}\n {renderCalorieBudget()}\n {/* 4) Meal slots (or empty plan) */}\n {renderMealSlots()}\n {/* 5) Feedback at the bottom (only if we have a plan) */}\n {renderPlanFeedback()}\n <View style={styles.bottomSpacer} />\n </ScrollView>\n )}\n </View>\n );\n};\n\n//////////////////////////////////////////////////////////////////////////\n// STYLES\n//////////////////////////////////////////////////////////////////////////\nconst styles = StyleSheet.create({\n container: { flex: 1, backgroundColor: '#f9f9f9' },\n containerDark: { backgroundColor: '#121212' },\n\n header: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'space-between',\n paddingHorizontal: 16,\n paddingTop: Platform.OS === 'ios' ? 50 : 30,\n paddingBottom: 16,\n backgroundColor: 'white',\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.1,\n shadowRadius: 4,\n elevation: 2,\n },\n headerDark: { backgroundColor: '#1e1e1e', shadowColor: '#000' },\n backButton: { padding: 4 },\n headerTitle: { fontSize: 20, fontWeight: 'bold', color: '#2ecc71' },\n textLight: { color: '#f2f2f2' },\n\n scrollView: { flex: 1 },\n scrollContent: { padding: 16, paddingBottom: 90 },\n\n loadingContainer: { flex: 1, justifyContent: 'center', alignItems: 'center' },\n loadingContainerDark: { backgroundColor: '#121212' },\n loadingText: { marginTop: 16, color: '#666', fontSize: 16 },\n\n // Section containers\n section: {\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 16,\n marginBottom: 16,\n shadowColor: '#000',\n shadowOffset: { width: 0, height: 2 },\n shadowOpacity: 0.05,\n shadowRadius: 3,\n elevation: 2,\n },\n sectionDark: { backgroundColor: '#1e1e1e', shadowColor: '#000' },\n sectionHeader: { marginBottom: 16 },\n sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#333', marginBottom: 4 },\n sectionSubtitle: { fontSize: 14, color: '#666' },\n subtitleDark: { color: '#aaa' },\n\n // Meal Settings UI\n mealSettingRow: {\n flexDirection: 'row',\n alignItems: 'center',\n marginBottom: 8,\n justifyContent: 'space-between'\n },\n mealSettingLabel: { fontSize: 16, fontWeight: '600', color: '#333' },\n mealSettingOptions: { flexDirection: 'row' },\n mealSettingOption: {\n backgroundColor: '#f0f0f0',\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderRadius: 8,\n marginRight: 8,\n },\n mealSettingOptionActive: {\n backgroundColor: '#2ecc71'\n },\n mealSettingOptionText: {\n fontSize: 14,\n color: '#333',\n },\n\n // Preferences\n preferencesContainer: { flexDirection: 'row', flexWrap: 'wrap', marginBottom: 16 },\n preferenceOption: {\n flexDirection: 'row',\n alignItems: 'center',\n backgroundColor: 'white',\n borderRadius: 20,\n paddingHorizontal: 12,\n paddingVertical: 6,\n marginRight: 8,\n marginBottom: 8,\n borderWidth: 1,\n borderColor: '#eee',\n },\n preferenceOptionDark: { backgroundColor: '#252525', borderColor: '#333' },\n activePreference: { backgroundColor: '#2ecc71', borderColor: '#2ecc71' },\n activePreferenceDark: { backgroundColor: '#2ecc71', borderColor: '#2ecc71' },\n checkmarkContainer: { marginRight: 4 },\n preferenceText: { fontSize: 14, color: '#666' },\n preferenceTextDark: { color: '#aaa' },\n activePreferenceText: { color: 'white', fontWeight: 'bold' },\n activePreferenceTextDark: { color: 'white', fontWeight: 'bold' },\n updateButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#2ecc71',\n borderRadius: 8,\n paddingVertical: 12,\n },\n updateButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n\n // Calorie Budget\n calorieBudgetInfo: { flexDirection: 'row', borderRadius: 8, padding: 16 },\n calorieBudgetInfoDark: { backgroundColor: '#252525' },\n budgetItem: { flex: 1, alignItems: 'center' },\n budgetValue: { fontSize: 24, fontWeight: 'bold', color: '#333', marginBottom: 4 },\n budgetValueDark: { color: '#f2f2f2' },\n budgetLabel: { fontSize: 12, color: '#666' },\n budgetLabelDark: { color: '#aaa' },\n divider: { width: 1, backgroundColor: '#eee', marginHorizontal: 8 },\n\n // Slots\n slotsContainer: { marginBottom: 16 },\n emptySlots: {\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 24,\n alignItems: 'center',\n justifyContent: 'center',\n borderWidth: 1,\n borderColor: '#f0f0f0',\n borderStyle: 'dashed',\n },\n emptySlotsDark: { backgroundColor: '#1e1e1e', borderColor: '#333' },\n emptySlotsText: {\n fontSize: 16,\n fontWeight: 'bold',\n color: '#666',\n marginTop: 12,\n marginBottom: 8,\n },\n emptySlotsSubtext: { fontSize: 14, color: '#888', textAlign: 'center' },\n emptySlotsSubtextDark: { color: '#777' },\n\n // Action buttons\n actionsContainer: { flexDirection: 'row', justifyContent: 'space-between', marginTop: 16 },\n generateButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#3498db',\n borderRadius: 8,\n paddingVertical: 12,\n paddingHorizontal: 10,\n },\n generateButtonLarge: {\n paddingVertical: 14, // bigger vertical padding\n },\n generateButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n saveButton: {\n flexDirection: 'row',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: '#2ecc71',\n borderRadius: 8,\n paddingVertical: 12,\n paddingHorizontal: 10,\n },\n savedButton: { backgroundColor: '#27ae60' },\n saveButtonText: { color: 'white', fontWeight: 'bold', fontSize: 16, marginLeft: 8 },\n\n // Generation states\n generatingContainer: { alignItems: 'center', justifyContent: 'center', paddingVertical: 24 },\n generatingText: { marginTop: 12, fontSize: 16, color: '#666' },\n errorContainer: {\n flexDirection: 'row',\n alignItems: 'center',\n backgroundColor: '#fff0f0',\n borderRadius: 8,\n padding: 12,\n marginTop: 12,\n },\n errorText: { color: '#ff6b6b', marginLeft: 8, fontSize: 14 },\n\n // Plan feedback near bottom\n feedbackInput: {\n borderColor: '#ccc',\n borderWidth: 1,\n borderRadius: 8,\n paddingHorizontal: 12,\n paddingVertical: 8,\n marginTop: 8,\n color: '#333',\n },\n feedbackInputDark: { borderColor: '#555', color: '#f2f2f2' },\n\n // Slot container\n mealSlotContainer: {\n marginBottom: 16,\n backgroundColor: 'white',\n borderRadius: 12,\n padding: 12,\n },\n\n // Reason\n reasonContainer: { marginTop: 8 },\n reasonLabel: { fontSize: 14, fontWeight: 'bold' },\n reasonText: { fontSize: 14, marginTop: 4 },\n\n // Swap\n swapContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },\n swapInput: {\n flex: 1,\n borderColor: '#ccc',\n borderWidth: 1,\n borderRadius: 8,\n paddingHorizontal: 8,\n paddingVertical: 6,\n },\n swapInputDark: { borderColor: '#555', color: '#f2f2f2' },\n swapButton: {\n backgroundColor: '#3498db',\n paddingVertical: 8,\n paddingHorizontal: 12,\n borderRadius: 8,\n marginLeft: 8,\n flexDirection: 'row',\n alignItems: 'center',\n },\n swapButtonText: { color: 'white', fontWeight: 'bold', marginLeft: 6 },\n\n // Swapping spinner\n swappingContainer: { flexDirection: 'row', alignItems: 'center', marginTop: 8 },\n swappingText: { marginLeft: 8, fontSize: 14, color: '#666' },\n\n bottomSpacer: { height: 80 },\n});\n\nexport default PlanMyDayScreen;"}}}}