feat(auth): 添加登录验证到食物记录相关功能

- 在食物拍照、语音记录和营养成分分析功能中添加登录验证
- 使用 ensureLoggedIn 方法确保用户已登录后再调用服务端接口
- 使用 pushIfAuthedElseLogin 方法处理需要登录的页面导航
- 添加新的营养图标资源
- 在路由常量中添加 FOOD_CAMERA 路由定义
- 更新 Memory Bank 任务文档,记录登录验证和路由常量管理的实现模式
This commit is contained in:
richarjiang
2025-10-16 17:45:52 +08:00
parent 339c748a0f
commit b75a8991ac
7 changed files with 241 additions and 11 deletions

View File

@@ -1,5 +1,6 @@
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import { Ionicons } from '@expo/vector-icons';
import { CameraType, CameraView, useCameraPermissions } from 'expo-camera';
@@ -24,6 +25,7 @@ export default function FoodCameraScreen() {
const router = useRouter();
const params = useLocalSearchParams<{ mealType?: string }>();
const cameraRef = useRef<CameraView>(null);
const { ensureLoggedIn } = useAuthGuard();
const [currentMealType, setCurrentMealType] = useState<MealType>(
(params.mealType as MealType) || 'dinner'
@@ -103,9 +105,12 @@ export default function FoodCameraScreen() {
});
if (photo) {
// 跳转到食物识别页面
// 先验证登录状态,再跳转到食物识别页面
console.log('照片拍摄成功:', photo.uri);
router.replace(`/food/food-recognition?imageUri=${encodeURIComponent(photo.uri)}&mealType=${currentMealType}`);
const isLoggedIn = await ensureLoggedIn();
if (isLoggedIn) {
router.replace(`/food/food-recognition?imageUri=${encodeURIComponent(photo.uri)}&mealType=${currentMealType}`);
}
}
} catch (error) {
console.error('拍照失败:', error);
@@ -127,7 +132,11 @@ export default function FoodCameraScreen() {
if (!result.canceled && result.assets[0]) {
const imageUri = result.assets[0].uri;
console.log('从相册选择的照片:', imageUri);
router.push(`/food/food-recognition?imageUri=${encodeURIComponent(imageUri)}&mealType=${currentMealType}`);
// 先验证登录状态,再跳转到食物识别页面
const isLoggedIn = await ensureLoggedIn();
if (isLoggedIn) {
router.push(`/food/food-recognition?imageUri=${encodeURIComponent(imageUri)}&mealType=${currentMealType}`);
}
}
} catch (error) {
console.error('选择照片失败:', error);

View File

@@ -1,5 +1,6 @@
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useCosUpload } from '@/hooks/useCosUpload';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import {
@@ -30,6 +31,7 @@ import ImageViewing from 'react-native-image-viewing';
export default function NutritionLabelAnalysisScreen() {
const safeAreaTop = useSafeAreaTop();
const router = useRouter();
const { pushIfAuthedElseLogin, ensureLoggedIn } = useAuthGuard();
const { upload, uploading: uploadingToCos, progress: uploadProgress } = useCosUpload({
prefix: 'nutrition-labels'
});
@@ -186,7 +188,7 @@ export default function NutritionLabelAnalysisScreen() {
right={
isLiquidGlassAvailable() ? (
<TouchableOpacity
onPress={() => router.push('/food/nutrition-analysis-history')}
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
activeOpacity={0.7}
>
<GlassView
@@ -200,7 +202,7 @@ export default function NutritionLabelAnalysisScreen() {
</TouchableOpacity>
) : (
<TouchableOpacity
onPress={() => router.push('/food/nutrition-analysis-history')}
onPress={() => pushIfAuthedElseLogin('/food/nutrition-analysis-history')}
style={[styles.historyButton, styles.fallbackBackground]}
activeOpacity={0.7}
>
@@ -241,7 +243,13 @@ export default function NutritionLabelAnalysisScreen() {
{!isAnalyzing && !isUploading && !newAnalysisResult && (
<TouchableOpacity
style={styles.analyzeButton}
onPress={() => startNewAnalysis(imageUri)}
onPress={async () => {
// 先验证登录状态
const isLoggedIn = await ensureLoggedIn();
if (isLoggedIn) {
startNewAnalysis(imageUri);
}
}}
activeOpacity={0.8}
>
<Ionicons name="search-outline" size={20} color="#FFF" />

View File

@@ -1,6 +1,7 @@
import { HeaderBar } from '@/components/ui/HeaderBar';
import { Colors } from '@/constants/Colors';
import { useAppDispatch } from '@/hooks/redux';
import { useAuthGuard } from '@/hooks/useAuthGuard';
import { useColorScheme } from '@/hooks/useColorScheme';
import { useSafeAreaTop } from '@/hooks/useSafeAreaWithPadding';
import { analyzeFoodFromText } from '@/services/foodRecognition';
@@ -28,6 +29,7 @@ export default function VoiceRecordScreen() {
const colorTokens = Colors[theme];
const { mealType = 'dinner' } = useLocalSearchParams<{ mealType?: string }>();
const dispatch = useAppDispatch();
const { ensureLoggedIn } = useAuthGuard();
// 状态管理
const [recordState, setRecordState] = useState<VoiceRecordState>('idle');
@@ -222,6 +224,12 @@ export default function VoiceRecordScreen() {
// 开始录音
const startRecording = async () => {
// 先验证登录状态
const isLoggedIn = await ensureLoggedIn();
if (!isLoggedIn) {
return;
}
try {
// 重置状态
setRecognizedText('');
@@ -292,6 +300,12 @@ export default function VoiceRecordScreen() {
return;
}
// 先验证登录状态
const isLoggedIn = await ensureLoggedIn();
if (!isLoggedIn) {
return;
}
try {
triggerHapticFeedback('impactMedium');
setRecordState('analyzing');