Files
digital-pilates/app/food-camera.tsx

428 lines
11 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { Ionicons } from '@expo/vector-icons';
import { CameraType, CameraView, useCameraPermissions } from 'expo-camera';
import * as ImagePicker from 'expo-image-picker';
import { useLocalSearchParams, useRouter } from 'expo-router';
import React, { useRef, useState } from 'react';
import {
Alert,
Dimensions,
StatusBar,
StyleSheet,
Text,
TouchableOpacity,
View,
} from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
type MealType = 'breakfast' | 'lunch' | 'dinner' | 'snack';
const { width: screenWidth, height: screenHeight } = Dimensions.get('window');
export default function FoodCameraScreen() {
const router = useRouter();
const params = useLocalSearchParams<{ mealType?: string }>();
const cameraRef = useRef<CameraView>(null);
const [currentMealType, setCurrentMealType] = useState<MealType>(
(params.mealType as MealType) || 'dinner'
);
const [facing, setFacing] = useState<CameraType>('back');
const [permission, requestPermission] = useCameraPermissions();
// 餐次选择选项
const mealOptions = [
{ key: 'breakfast' as const, label: '早餐', icon: '☀️' },
{ key: 'lunch' as const, label: '午餐', icon: '🌤️' },
{ key: 'dinner' as const, label: '晚餐', icon: '🌙' },
{ key: 'snack' as const, label: '加餐', icon: '🍎' },
];
if (!permission) {
// 权限仍在加载中
return (
<SafeAreaView style={styles.container}>
<HeaderBar
title="食物拍摄"
onBack={() => router.back()}
transparent={true}
/>
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>...</Text>
</View>
</SafeAreaView>
);
}
if (!permission.granted) {
// 没有相机权限
return (
<SafeAreaView style={styles.container}>
<HeaderBar
title="食物拍摄"
onBack={() => router.back()}
transparent={true}
/>
<View style={styles.permissionContainer}>
<Ionicons name="camera-outline" size={64} color="#999" />
<Text style={styles.permissionTitle}></Text>
<Text style={styles.permissionText}>
访
</Text>
<TouchableOpacity
style={styles.permissionButton}
onPress={requestPermission}
>
<Text style={styles.permissionButtonText}>访</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
// 切换相机前后摄像头
function toggleCameraFacing() {
setFacing(current => (current === 'back' ? 'front' : 'back'));
}
// 拍摄照片
const takePicture = async () => {
if (cameraRef.current) {
try {
const photo = await cameraRef.current.takePictureAsync({
quality: 0.8,
base64: false,
});
if (photo) {
// TODO: 处理拍摄的照片,可以传递到下一个页面进行食物识别
console.log('照片拍摄成功:', photo.uri);
Alert.alert('拍摄成功', '照片已保存,后续会添加食物识别功能');
// router.push(`/food-recognition?imageUri=${photo.uri}&mealType=${currentMealType}`);
}
} catch (error) {
console.error('拍照失败:', error);
Alert.alert('拍照失败', '请重试');
}
}
};
// 从相册选择照片
const pickImageFromGallery = async () => {
try {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
allowsEditing: true,
aspect: [4, 3],
quality: 0.8,
});
if (!result.canceled && result.assets[0]) {
const imageUri = result.assets[0].uri;
console.log('从相册选择的照片:', imageUri);
Alert.alert('选择成功', '照片已选择,后续会添加食物识别功能');
// router.push(`/food-recognition?imageUri=${imageUri}&mealType=${currentMealType}`);
}
} catch (error) {
console.error('选择照片失败:', error);
Alert.alert('选择失败', '请重试');
}
};
// AR功能暂时显示提示
const handleARPress = () => {
Alert.alert('AR功能', 'AR食物识别功能即将推出');
};
// 餐次选择
const handleMealTypeChange = (mealType: MealType) => {
setCurrentMealType(mealType);
};
return (
<View style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor="transparent" translucent />
{/* 头部导航 */}
<HeaderBar
title=""
onBack={() => router.back()}
transparent={true}
backColor={'#333'}
/>
{/* 主要内容区域 */}
<View style={styles.contentContainer}>
{/* 取景框容器 */}
<View style={styles.cameraFrameContainer}>
<Text style={styles.hintText}></Text>
{/* 相机取景框 */}
<View style={styles.cameraFrame}>
<CameraView
ref={cameraRef}
style={styles.cameraView}
facing={facing}
>
{/* 取景框装饰 */}
<View style={styles.viewfinderOverlay}>
<View style={[styles.corner, styles.topLeft]} />
<View style={[styles.corner, styles.topRight]} />
<View style={[styles.corner, styles.bottomLeft]} />
<View style={[styles.corner, styles.bottomRight]} />
</View>
</CameraView>
</View>
</View>
{/* 餐次选择器 */}
<View style={styles.mealTypeContainer}>
{mealOptions.map((option) => (
<TouchableOpacity
key={option.key}
style={[
styles.mealTypeButton,
currentMealType === option.key && styles.mealTypeButtonActive
]}
onPress={() => handleMealTypeChange(option.key)}
>
<Text style={styles.mealTypeIcon}>{option.icon}</Text>
<Text style={[
styles.mealTypeText,
currentMealType === option.key && styles.mealTypeTextActive
]}>
{option.label}
</Text>
</TouchableOpacity>
))}
</View>
{/* 底部控制栏 */}
<View style={styles.bottomContainer}>
<View style={styles.controlsContainer}>
{/* 拍照按钮 */}
<TouchableOpacity style={styles.captureButton} onPress={takePicture}>
<View style={styles.captureButtonInner} />
</TouchableOpacity>
</View>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
contentContainer: {
flex: 1,
paddingTop: 100,
},
cameraFrameContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 20,
},
cameraFrame: {
width: 300,
height: 300,
borderRadius: 20,
overflow: 'hidden',
borderWidth: 3,
borderColor: '#FFF',
backgroundColor: '#000',
},
cameraView: {
flex: 1,
},
viewfinderOverlay: {
flex: 1,
position: 'relative',
},
camera: {
flex: 1,
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
loadingText: {
color: '#FFF',
fontSize: 16,
},
permissionContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
paddingHorizontal: 40,
},
permissionTitle: {
color: '#FFF',
fontSize: 20,
fontWeight: '600',
marginTop: 20,
marginBottom: 10,
},
permissionText: {
color: '#CCC',
fontSize: 16,
textAlign: 'center',
marginBottom: 30,
lineHeight: 22,
},
permissionButton: {
backgroundColor: Colors.light.primary,
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 24,
},
permissionButtonText: {
color: '#FFF',
fontSize: 16,
fontWeight: '600',
},
header: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
zIndex: 10,
},
hintText: {
color: '#FFF',
fontSize: 16,
fontWeight: '500',
marginBottom: 20,
textAlign: 'center',
},
corner: {
position: 'absolute',
width: 30,
height: 30,
borderColor: '#FFF',
borderWidth: 3,
},
topLeft: {
top: 0,
left: 0,
borderRightWidth: 0,
borderBottomWidth: 0,
},
topRight: {
top: 0,
right: 0,
borderLeftWidth: 0,
borderBottomWidth: 0,
},
bottomLeft: {
bottom: 0,
left: 0,
borderRightWidth: 0,
borderTopWidth: 0,
},
bottomRight: {
bottom: 0,
right: 0,
borderLeftWidth: 0,
borderTopWidth: 0,
},
mealTypeContainer: {
flexDirection: 'row',
justifyContent: 'center',
paddingHorizontal: 20,
marginVertical: 20,
},
mealTypeButton: {
alignItems: 'center',
paddingHorizontal: 16,
paddingVertical: 8,
marginHorizontal: 8,
borderRadius: 20,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
minWidth: 70,
},
mealTypeButtonActive: {
backgroundColor: 'rgba(255, 255, 255, 0.9)',
},
mealTypeIcon: {
fontSize: 20,
marginBottom: 2,
},
mealTypeText: {
color: '#FFF',
fontSize: 12,
fontWeight: '500',
},
mealTypeTextActive: {
color: '#333',
},
bottomContainer: {
paddingBottom: 40,
},
controlsContainer: {
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 20,
paddingHorizontal: 40,
},
controlButton: {
alignItems: 'center',
},
controlButtonText: {
color: '#FFF',
fontSize: 12,
marginTop: 8,
fontWeight: '500',
},
albumButton: {
width: 50,
height: 50,
borderRadius: 10,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: '#FFF',
},
captureButton: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: '#FFF',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 4,
borderColor: 'rgba(255, 255, 255, 0.3)',
},
captureButtonInner: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#FFF',
borderWidth: 2,
borderColor: '#333',
},
arButton: {
width: 50,
height: 50,
borderRadius: 25,
backgroundColor: 'rgba(255, 255, 255, 0.2)',
justifyContent: 'center',
alignItems: 'center',
borderWidth: 2,
borderColor: '#FFF',
},
arButtonText: {
color: '#FFF',
fontSize: 14,
fontWeight: 'bold',
},
});