feat: 支持食物库接口
This commit is contained in:
@@ -1,9 +1,13 @@
|
||||
import type { FoodItem } from '@/components/model/food/FoodDetailModal';
|
||||
import { FoodDetailModal } from '@/components/model/food/FoodDetailModal';
|
||||
import { useFoodLibrary, useFoodSearch } from '@/hooks/useFoodLibrary';
|
||||
import type { FoodItem, MealType, SelectedFoodItem } from '@/types/food';
|
||||
import { Ionicons } from '@expo/vector-icons';
|
||||
import { Image } from 'expo-image';
|
||||
import { useLocalSearchParams, useRouter } from 'expo-router';
|
||||
import React, { useState } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import {
|
||||
ActivityIndicator,
|
||||
Modal,
|
||||
SafeAreaView,
|
||||
ScrollView,
|
||||
StatusBar,
|
||||
@@ -11,89 +15,10 @@ import {
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
View
|
||||
} from 'react-native';
|
||||
|
||||
// 食物分类类型
|
||||
export interface FoodCategory {
|
||||
id: string;
|
||||
name: string;
|
||||
foods: FoodItem[];
|
||||
}
|
||||
|
||||
// 模拟食物数据
|
||||
const FOOD_DATA: FoodCategory[] = [
|
||||
{
|
||||
id: 'common',
|
||||
name: '常见',
|
||||
foods: [
|
||||
{ id: '1', name: '无糖美式咖啡', emoji: '☕', calories: 1, unit: '100克' },
|
||||
{ id: '2', name: '荷包蛋(油煎)', emoji: '🍳', calories: 195, unit: '100克' },
|
||||
{ id: '3', name: '鸡蛋', emoji: '🥚', calories: 139, unit: '100克' },
|
||||
{ id: '4', name: '香蕉', emoji: '🍌', calories: 93, unit: '100克' },
|
||||
{ id: '5', name: '猕猴桃', emoji: '🥝', calories: 61, unit: '100克' },
|
||||
{ id: '6', name: '苹果', emoji: '🍎', calories: 53, unit: '100克' },
|
||||
{ id: '7', name: '草莓', emoji: '🍓', calories: 32, unit: '100克' },
|
||||
{ id: '8', name: '蛋烧麦', emoji: '🥟', calories: 157, unit: '100克' },
|
||||
{ id: '9', name: '米饭', emoji: '🍚', calories: 116, unit: '100克' },
|
||||
{ id: '10', name: '鲜玉米', emoji: '🌽', calories: 112, unit: '100克' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'custom',
|
||||
name: '自定义',
|
||||
foods: []
|
||||
},
|
||||
{
|
||||
id: 'favorite',
|
||||
name: '收藏',
|
||||
foods: []
|
||||
},
|
||||
{
|
||||
id: 'fruits',
|
||||
name: '水果蔬菜',
|
||||
foods: [
|
||||
{ id: '11', name: '苹果', emoji: '🍎', calories: 53, unit: '100克' },
|
||||
{ id: '12', name: '香蕉', emoji: '🍌', calories: 93, unit: '100克' },
|
||||
{ id: '13', name: '草莓', emoji: '🍓', calories: 32, unit: '100克' },
|
||||
{ id: '14', name: '猕猴桃', emoji: '🥝', calories: 61, unit: '100克' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'meat',
|
||||
name: '肉蛋奶',
|
||||
foods: [
|
||||
{ id: '15', name: '鸡蛋', emoji: '🥚', calories: 139, unit: '100克' },
|
||||
{ id: '16', name: '荷包蛋(油煎)', emoji: '🍳', calories: 195, unit: '100克' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'beans',
|
||||
name: '豆类坚果',
|
||||
foods: []
|
||||
},
|
||||
{
|
||||
id: 'drinks',
|
||||
name: '零食饮料',
|
||||
foods: [
|
||||
{ id: '17', name: '无糖美式咖啡', emoji: '☕', calories: 1, unit: '100克' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'staple',
|
||||
name: '主食',
|
||||
foods: [
|
||||
{ id: '18', name: '米饭', emoji: '🍚', calories: 116, unit: '100克' },
|
||||
{ id: '19', name: '鲜玉米', emoji: '🌽', calories: 112, unit: '100克' },
|
||||
{ id: '20', name: '蛋烧麦', emoji: '🥟', calories: 157, unit: '100克' },
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 'vegetables',
|
||||
name: '菜肴',
|
||||
foods: []
|
||||
}
|
||||
];
|
||||
// 餐次映射保持不变
|
||||
|
||||
// 餐次映射
|
||||
const MEAL_TYPE_MAP = {
|
||||
@@ -106,20 +31,53 @@ const MEAL_TYPE_MAP = {
|
||||
export default function FoodLibraryScreen() {
|
||||
const router = useRouter();
|
||||
const params = useLocalSearchParams<{ mealType?: string }>();
|
||||
const mealType = (params.mealType as 'breakfast' | 'lunch' | 'dinner' | 'snack') || 'breakfast';
|
||||
const mealType = (params.mealType as MealType) || 'breakfast';
|
||||
|
||||
// Redux hooks
|
||||
const { categories, loading, error, clearErrors } = useFoodLibrary();
|
||||
const { searchResults, searchLoading, search, clearResults } = useFoodSearch();
|
||||
|
||||
// 本地状态
|
||||
const [selectedCategoryId, setSelectedCategoryId] = useState('common');
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [selectedFood, setSelectedFood] = useState<FoodItem | null>(null);
|
||||
const [showFoodDetail, setShowFoodDetail] = useState(false);
|
||||
const [selectedFoodItems, setSelectedFoodItems] = useState<SelectedFoodItem[]>([]);
|
||||
const [showMealSelector, setShowMealSelector] = useState(false);
|
||||
const [currentMealType, setCurrentMealType] = useState<MealType>(mealType);
|
||||
|
||||
// 获取当前选中的分类
|
||||
const selectedCategory = FOOD_DATA.find(cat => cat.id === selectedCategoryId);
|
||||
const selectedCategory = categories.find(cat => cat.id === selectedCategoryId);
|
||||
|
||||
// 过滤食物列表
|
||||
const filteredFoods = selectedCategory?.foods.filter(food =>
|
||||
food.name.toLowerCase().includes(searchText.toLowerCase())
|
||||
) || [];
|
||||
|
||||
// 过滤食物列表 - 优先显示搜索结果
|
||||
const filteredFoods = useMemo(() => {
|
||||
if (searchText.trim() && searchResults.length > 0) {
|
||||
return searchResults;
|
||||
}
|
||||
|
||||
if (selectedCategory) {
|
||||
console.log('selectedCategory', selectedCategory);
|
||||
|
||||
return selectedCategory.foods
|
||||
}
|
||||
|
||||
return [];
|
||||
}, [searchText, searchResults, selectedCategory]);
|
||||
|
||||
|
||||
// 处理搜索
|
||||
useEffect(() => {
|
||||
const timeoutId = setTimeout(() => {
|
||||
if (searchText.trim()) {
|
||||
search(searchText);
|
||||
} else {
|
||||
clearResults();
|
||||
}
|
||||
}, 300); // 防抖
|
||||
|
||||
return () => clearTimeout(timeoutId);
|
||||
}, [searchText, search, clearResults]);
|
||||
|
||||
// 处理食物选择 - 显示详情弹窗
|
||||
const handleSelectFood = (food: FoodItem) => {
|
||||
@@ -136,18 +94,53 @@ export default function FoodLibraryScreen() {
|
||||
|
||||
// 处理食物保存
|
||||
const handleSaveFood = (food: FoodItem, amount: number, unit: string) => {
|
||||
// 这里可以处理保存逻辑,比如添加到营养记录
|
||||
console.log('保存食物:', food, amount, unit);
|
||||
// 计算实际热量
|
||||
const actualCalories = Math.round((food.calories * amount) / 100);
|
||||
|
||||
// 创建新的选择项目
|
||||
const newSelectedItem: SelectedFoodItem = {
|
||||
id: `${food.id}_${Date.now()}`, // 使用时间戳确保唯一性
|
||||
food,
|
||||
amount,
|
||||
unit,
|
||||
calories: actualCalories
|
||||
};
|
||||
|
||||
// 添加到已选择列表
|
||||
setSelectedFoodItems(prev => [...prev, newSelectedItem]);
|
||||
|
||||
console.log('保存食物:', food, amount, unit, '热量:', actualCalories);
|
||||
setShowFoodDetail(false);
|
||||
router.back(); // 返回上一页
|
||||
};
|
||||
|
||||
// 移除已选择的食物
|
||||
const handleRemoveSelectedFood = (itemId: string) => {
|
||||
setSelectedFoodItems(prev => prev.filter(item => item.id !== itemId));
|
||||
};
|
||||
|
||||
// 计算总热量
|
||||
const totalCalories = selectedFoodItems.reduce((sum, item) => sum + item.calories, 0);
|
||||
|
||||
// 关闭详情弹窗
|
||||
const handleCloseFoodDetail = () => {
|
||||
setShowFoodDetail(false);
|
||||
setSelectedFood(null);
|
||||
};
|
||||
|
||||
// 处理餐次选择
|
||||
const handleMealTypeSelect = (selectedMealType: MealType) => {
|
||||
setCurrentMealType(selectedMealType);
|
||||
setShowMealSelector(false);
|
||||
};
|
||||
|
||||
// 餐次选择选项
|
||||
const mealOptions = [
|
||||
{ key: 'breakfast' as const, label: '早餐', color: '#FF6B35' },
|
||||
{ key: 'lunch' as const, label: '午餐', color: '#4CAF50' },
|
||||
{ key: 'dinner' as const, label: '晚餐', color: '#2196F3' },
|
||||
{ key: 'snack' as const, label: '加餐', color: '#FF9800' },
|
||||
];
|
||||
|
||||
return (
|
||||
<SafeAreaView style={styles.container}>
|
||||
<StatusBar barStyle="dark-content" backgroundColor="#F8F9FA" />
|
||||
@@ -158,20 +151,8 @@ export default function FoodLibraryScreen() {
|
||||
<Ionicons name="chevron-back" size={24} color="#333" />
|
||||
</TouchableOpacity>
|
||||
<Text style={styles.headerTitle}>食物库</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.customButton}
|
||||
onPress={() => {
|
||||
const testFood: FoodItem = {
|
||||
id: 'test',
|
||||
name: '测试食物',
|
||||
emoji: '🍎',
|
||||
calories: 100,
|
||||
unit: '100克'
|
||||
};
|
||||
handleSelectFood(testFood);
|
||||
}}
|
||||
>
|
||||
<Text style={styles.customButtonText}>测试弹窗</Text>
|
||||
<TouchableOpacity style={styles.customButton}>
|
||||
<Text style={styles.customButtonText}>自定义</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
@@ -189,76 +170,223 @@ export default function FoodLibraryScreen() {
|
||||
|
||||
{/* 主要内容区域 - 卡片样式 */}
|
||||
<View style={styles.mainContentCard}>
|
||||
<View style={styles.mainContent}>
|
||||
{/* 左侧分类导航 */}
|
||||
<View style={styles.categoryContainer}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{FOOD_DATA.map((category) => (
|
||||
<TouchableOpacity
|
||||
key={category.id}
|
||||
style={[
|
||||
styles.categoryItem,
|
||||
selectedCategoryId === category.id && styles.categoryItemActive
|
||||
]}
|
||||
onPress={() => setSelectedCategoryId(category.id)}
|
||||
>
|
||||
<Text style={[
|
||||
styles.categoryText,
|
||||
selectedCategoryId === category.id && styles.categoryTextActive
|
||||
]}>
|
||||
{category.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</ScrollView>
|
||||
{loading && categories.length === 0 ? (
|
||||
<View style={styles.loadingContainer}>
|
||||
<ActivityIndicator size="large" color="#4CAF50" />
|
||||
<Text style={styles.loadingText}>加载食物库中...</Text>
|
||||
</View>
|
||||
|
||||
{/* 右侧食物列表 */}
|
||||
<View style={styles.foodContainer}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{filteredFoods.map((food) => (
|
||||
<View key={food.id} style={styles.foodItem}>
|
||||
<View style={styles.foodInfo}>
|
||||
<Text style={styles.foodEmoji}>{food.emoji}</Text>
|
||||
<View style={styles.foodDetails}>
|
||||
<Text style={styles.foodName}>{food.name}</Text>
|
||||
<Text style={styles.foodCalories}>
|
||||
{food.calories}千卡/{food.unit}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
) : error ? (
|
||||
<View style={styles.errorContainer}>
|
||||
<Text style={styles.errorText}>{error}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.retryButton}
|
||||
onPress={() => {
|
||||
clearErrors();
|
||||
// 这里可以重新加载数据
|
||||
}}
|
||||
>
|
||||
<Text style={styles.retryButtonText}>重试</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
) : (
|
||||
<View style={styles.mainContent}>
|
||||
{/* 左侧分类导航 */}
|
||||
<View style={styles.categoryContainer}>
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{categories.map((category) => (
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
onPress={() => handleSelectFood(food)}
|
||||
key={category.id}
|
||||
style={[
|
||||
styles.categoryItem,
|
||||
selectedCategoryId === category.id && styles.categoryItemActive
|
||||
]}
|
||||
onPress={() => {
|
||||
setSelectedCategoryId(category.id);
|
||||
// 切换分类时清除搜索
|
||||
if (searchText) {
|
||||
setSearchText('');
|
||||
clearResults();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Ionicons name="add" size={20} color="#666" />
|
||||
<Text style={[
|
||||
styles.categoryText,
|
||||
selectedCategoryId === category.id && styles.categoryTextActive
|
||||
]}>
|
||||
{category.name}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
|
||||
{filteredFoods.length === 0 && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyText}>暂无食物数据</Text>
|
||||
{/* 右侧食物列表 */}
|
||||
<View style={styles.foodContainer}>
|
||||
{searchLoading ? (
|
||||
<View style={styles.searchLoadingContainer}>
|
||||
<ActivityIndicator size="small" color="#4CAF50" />
|
||||
<Text style={styles.searchLoadingText}>搜索中...</Text>
|
||||
</View>
|
||||
) : (
|
||||
<ScrollView showsVerticalScrollIndicator={false}>
|
||||
{filteredFoods.map((food) => (
|
||||
<View key={food.id} style={styles.foodItem}>
|
||||
<View style={styles.foodInfo}>
|
||||
{food.imageUrl ? <Image
|
||||
style={styles.foodImage}
|
||||
source={{ uri: food.imageUrl }}
|
||||
cachePolicy={'memory-disk'}
|
||||
/> : <Text style={styles.foodEmoji}>{food.emoji || '🍽️'}</Text>}
|
||||
<View style={styles.foodDetails}>
|
||||
<Text style={styles.foodName}>{food.name}</Text>
|
||||
<Text style={styles.foodCalories}>
|
||||
{food.calories}千卡/{food.unit}
|
||||
</Text>
|
||||
</View>
|
||||
</View>
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
onPress={() => handleSelectFood(food)}
|
||||
>
|
||||
<Ionicons name="add" size={20} color="#666" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
))}
|
||||
|
||||
{filteredFoods.length === 0 && !searchLoading && (
|
||||
<View style={styles.emptyContainer}>
|
||||
<Text style={styles.emptyText}>
|
||||
{searchText ? '未找到相关食物' : '暂无食物数据'}
|
||||
</Text>
|
||||
{searchText && (
|
||||
<Text style={styles.emptySubText}>
|
||||
尝试使用其他关键词搜索
|
||||
</Text>
|
||||
)}
|
||||
</View>
|
||||
)}
|
||||
</ScrollView>
|
||||
)}
|
||||
</ScrollView>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
|
||||
{/* 已选择食物列表 */}
|
||||
{selectedFoodItems.length > 0 && (
|
||||
<View style={styles.selectedFoodsContainer}>
|
||||
<View style={styles.selectedFoodsHeader}>
|
||||
<Text style={styles.selectedFoodsTitle}>已选择食物 ({selectedFoodItems.length})</Text>
|
||||
<Text style={styles.totalCalories}>总热量: {totalCalories}千卡</Text>
|
||||
</View>
|
||||
<ScrollView
|
||||
horizontal
|
||||
showsHorizontalScrollIndicator={false}
|
||||
style={styles.selectedFoodsList}
|
||||
contentContainerStyle={styles.selectedFoodsContent}
|
||||
>
|
||||
{selectedFoodItems.map((item) => (
|
||||
<View key={item.id} style={styles.selectedFoodItem}>
|
||||
<TouchableOpacity
|
||||
style={styles.removeButton}
|
||||
onPress={() => handleRemoveSelectedFood(item.id)}
|
||||
>
|
||||
<Ionicons name="close-circle" size={16} color="#FF4444" />
|
||||
</TouchableOpacity>
|
||||
|
||||
{item.food.imageUrl ? <Image
|
||||
style={styles.selectedFoodImage}
|
||||
source={{ uri: item.food.imageUrl }}
|
||||
/> : <Text style={styles.selectedFoodEmoji}>{item.food.emoji}</Text>}
|
||||
<Text style={styles.selectedFoodName} numberOfLines={1}>{item.food.name}</Text>
|
||||
<Text style={styles.selectedFoodAmount}>{item.amount}{item.unit}</Text>
|
||||
<Text style={styles.selectedFoodCalories}>{item.calories}千卡</Text>
|
||||
</View>
|
||||
))}
|
||||
</ScrollView>
|
||||
</View>
|
||||
)}
|
||||
|
||||
{/* 底部餐次选择和记录按钮 */}
|
||||
<View style={styles.bottomContainer}>
|
||||
<View style={styles.mealSelector}>
|
||||
<View style={styles.mealIndicator} />
|
||||
<Text style={styles.mealText}>{MEAL_TYPE_MAP[mealType]}</Text>
|
||||
<TouchableOpacity
|
||||
style={styles.mealSelector}
|
||||
onPress={() => setShowMealSelector(true)}
|
||||
>
|
||||
<View style={[
|
||||
styles.mealIndicator,
|
||||
{ backgroundColor: mealOptions.find(option => option.key === currentMealType)?.color || '#FF6B35' }
|
||||
]} />
|
||||
<Text style={styles.mealText}>{MEAL_TYPE_MAP[currentMealType]}</Text>
|
||||
<Ionicons name="chevron-down" size={16} color="#333" />
|
||||
</View>
|
||||
</TouchableOpacity>
|
||||
|
||||
<TouchableOpacity style={styles.recordButton}>
|
||||
<Text style={styles.recordButtonText}>记录</Text>
|
||||
<TouchableOpacity
|
||||
style={[
|
||||
styles.recordButton,
|
||||
selectedFoodItems.length === 0 && styles.recordButtonDisabled
|
||||
]}
|
||||
disabled={selectedFoodItems.length === 0}
|
||||
onPress={() => {
|
||||
// 这里可以处理记录逻辑
|
||||
console.log('记录食物:', selectedFoodItems);
|
||||
// 记录成功后可以清空选择列表或返回上一页
|
||||
router.back();
|
||||
}}
|
||||
>
|
||||
<Text style={[
|
||||
styles.recordButtonText,
|
||||
selectedFoodItems.length === 0 && styles.recordButtonTextDisabled
|
||||
]}>记录</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
||||
{/* 餐次选择弹窗 */}
|
||||
<Modal
|
||||
visible={showMealSelector}
|
||||
transparent={true}
|
||||
animationType="fade"
|
||||
onRequestClose={() => setShowMealSelector(false)}
|
||||
>
|
||||
<View style={styles.mealSelectorOverlay}>
|
||||
<TouchableOpacity
|
||||
style={styles.mealSelectorBackdrop}
|
||||
onPress={() => setShowMealSelector(false)}
|
||||
/>
|
||||
<View style={styles.mealSelectorModal}>
|
||||
<View style={styles.mealSelectorHeader}>
|
||||
<Text style={styles.mealSelectorTitle}>选择餐次</Text>
|
||||
<TouchableOpacity onPress={() => setShowMealSelector(false)}>
|
||||
<Ionicons name="close" size={24} color="#666" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.mealOptionsContainer}>
|
||||
{mealOptions.map((option) => (
|
||||
<TouchableOpacity
|
||||
key={option.key}
|
||||
style={[
|
||||
styles.mealOption,
|
||||
currentMealType === option.key && styles.mealOptionActive
|
||||
]}
|
||||
onPress={() => handleMealTypeSelect(option.key)}
|
||||
>
|
||||
<View style={[styles.mealOptionIndicator, { backgroundColor: option.color }]} />
|
||||
<Text style={[
|
||||
styles.mealOptionText,
|
||||
currentMealType === option.key && styles.mealOptionTextActive
|
||||
]}>
|
||||
{option.label}
|
||||
</Text>
|
||||
{currentMealType === option.key && (
|
||||
<Ionicons name="checkmark" size={20} color="#4CAF50" />
|
||||
)}
|
||||
</TouchableOpacity>
|
||||
))}
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Modal>
|
||||
|
||||
{/* 食物详情弹窗 */}
|
||||
<FoodDetailModal
|
||||
visible={showFoodDetail}
|
||||
@@ -386,18 +514,24 @@ const styles = StyleSheet.create({
|
||||
alignItems: 'center',
|
||||
flex: 1,
|
||||
},
|
||||
foodImage: {
|
||||
width: 32,
|
||||
height: 32,
|
||||
},
|
||||
foodEmoji: {
|
||||
fontSize: 32,
|
||||
marginRight: 12,
|
||||
},
|
||||
foodDetails: {
|
||||
flex: 1,
|
||||
marginLeft: 12
|
||||
},
|
||||
foodName: {
|
||||
fontSize: 16,
|
||||
color: '#333',
|
||||
fontWeight: '500',
|
||||
marginBottom: 2,
|
||||
|
||||
},
|
||||
foodCalories: {
|
||||
fontSize: 14,
|
||||
@@ -421,6 +555,61 @@ const styles = StyleSheet.create({
|
||||
fontSize: 16,
|
||||
color: '#999',
|
||||
},
|
||||
emptySubText: {
|
||||
fontSize: 14,
|
||||
color: '#CCC',
|
||||
marginTop: 8,
|
||||
textAlign: 'center',
|
||||
},
|
||||
// 加载状态样式
|
||||
loadingContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 60,
|
||||
},
|
||||
loadingText: {
|
||||
fontSize: 16,
|
||||
color: '#666',
|
||||
marginTop: 12,
|
||||
},
|
||||
// 错误状态样式
|
||||
errorContainer: {
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 60,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
errorText: {
|
||||
fontSize: 16,
|
||||
color: '#FF4444',
|
||||
textAlign: 'center',
|
||||
marginBottom: 16,
|
||||
},
|
||||
retryButton: {
|
||||
backgroundColor: '#4CAF50',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 10,
|
||||
borderRadius: 20,
|
||||
},
|
||||
retryButtonText: {
|
||||
fontSize: 14,
|
||||
color: '#FFF',
|
||||
fontWeight: '500',
|
||||
},
|
||||
// 搜索加载状态样式
|
||||
searchLoadingContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 20,
|
||||
},
|
||||
searchLoadingText: {
|
||||
fontSize: 14,
|
||||
color: '#666',
|
||||
marginLeft: 8,
|
||||
},
|
||||
bottomContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
@@ -462,4 +651,147 @@ const styles = StyleSheet.create({
|
||||
color: '#FFF',
|
||||
fontWeight: '500',
|
||||
},
|
||||
recordButtonDisabled: {
|
||||
backgroundColor: '#CCC',
|
||||
},
|
||||
recordButtonTextDisabled: {
|
||||
color: '#999',
|
||||
},
|
||||
// 已选择食物列表样式
|
||||
selectedFoodsContainer: {
|
||||
backgroundColor: '#FFF',
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: '#E5E5E5',
|
||||
paddingVertical: 12,
|
||||
maxHeight: 140, // 限制最大高度
|
||||
},
|
||||
selectedFoodsHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 16,
|
||||
marginBottom: 8,
|
||||
},
|
||||
selectedFoodsTitle: {
|
||||
fontSize: 14,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
totalCalories: {
|
||||
fontSize: 14,
|
||||
color: '#4CAF50',
|
||||
fontWeight: '500',
|
||||
},
|
||||
selectedFoodsList: {
|
||||
paddingHorizontal: 16,
|
||||
},
|
||||
selectedFoodsContent: {
|
||||
paddingRight: 16,
|
||||
},
|
||||
selectedFoodItem: {
|
||||
backgroundColor: '#F8F9FA',
|
||||
borderRadius: 12,
|
||||
padding: 8,
|
||||
marginRight: 8,
|
||||
minWidth: 80,
|
||||
alignItems: 'center',
|
||||
position: 'relative',
|
||||
},
|
||||
removeButton: {
|
||||
position: 'absolute',
|
||||
top: 4,
|
||||
right: 4,
|
||||
backgroundColor: '#FFF',
|
||||
borderRadius: 8,
|
||||
zIndex: 1,
|
||||
},
|
||||
selectedFoodImage: {
|
||||
width: 20,
|
||||
height: 20,
|
||||
marginBottom: 4,
|
||||
},
|
||||
selectedFoodEmoji: {
|
||||
fontSize: 20,
|
||||
marginBottom: 4,
|
||||
},
|
||||
selectedFoodName: {
|
||||
fontSize: 12,
|
||||
color: '#333',
|
||||
fontWeight: '500',
|
||||
textAlign: 'center',
|
||||
marginBottom: 2,
|
||||
maxWidth: 64,
|
||||
},
|
||||
selectedFoodAmount: {
|
||||
fontSize: 11,
|
||||
color: '#666',
|
||||
marginBottom: 2,
|
||||
},
|
||||
selectedFoodCalories: {
|
||||
fontSize: 11,
|
||||
color: '#4CAF50',
|
||||
fontWeight: '500',
|
||||
},
|
||||
// 餐次选择弹窗样式
|
||||
mealSelectorOverlay: {
|
||||
flex: 1,
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
justifyContent: 'flex-end',
|
||||
},
|
||||
mealSelectorBackdrop: {
|
||||
flex: 1,
|
||||
},
|
||||
mealSelectorModal: {
|
||||
backgroundColor: '#FFF',
|
||||
borderTopLeftRadius: 20,
|
||||
borderTopRightRadius: 20,
|
||||
paddingBottom: 34, // 为底部安全区域留出空间
|
||||
},
|
||||
mealSelectorHeader: {
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: '#E5E5E5',
|
||||
},
|
||||
mealSelectorTitle: {
|
||||
fontSize: 18,
|
||||
fontWeight: '600',
|
||||
color: '#333',
|
||||
},
|
||||
mealOptionsContainer: {
|
||||
paddingHorizontal: 20,
|
||||
paddingVertical: 16,
|
||||
},
|
||||
mealOption: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
paddingVertical: 16,
|
||||
paddingHorizontal: 16,
|
||||
borderRadius: 12,
|
||||
marginBottom: 8,
|
||||
backgroundColor: '#F8F9FA',
|
||||
},
|
||||
mealOptionActive: {
|
||||
backgroundColor: '#E8F5E8',
|
||||
borderWidth: 1,
|
||||
borderColor: '#4CAF50',
|
||||
},
|
||||
mealOptionIndicator: {
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: 6,
|
||||
marginRight: 12,
|
||||
},
|
||||
mealOptionText: {
|
||||
flex: 1,
|
||||
fontSize: 16,
|
||||
color: '#333',
|
||||
fontWeight: '500',
|
||||
},
|
||||
mealOptionTextActive: {
|
||||
color: '#4CAF50',
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user