1 line
82 KiB
JSON
1 line
82 KiB
JSON
{"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;"}}}} |