feat: add food camera and recognition features

- Implemented FoodCameraScreen for capturing food images with meal type selection.
- Created FoodRecognitionScreen for processing and recognizing food images.
- Added Redux slice for managing food recognition state and results.
- Integrated image upload functionality to cloud storage.
- Enhanced UI components for better user experience during food recognition.
- Updated FloatingFoodOverlay to navigate to the new camera screen.
- Added food recognition service for API interaction.
- Improved styling and layout for various components.
This commit is contained in:
richarjiang
2025-09-04 10:18:42 +08:00
parent 0b75087855
commit 6cb0435b30
9 changed files with 1798 additions and 17 deletions

View File

@@ -0,0 +1,111 @@
import { type FoodRecognitionResponse } from '@/services/foodRecognition';
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
// 食物识别状态类型定义
export interface FoodRecognitionState {
// 按ID存储的识别结果
recognitionResults: Record<string, FoodRecognitionResponse>;
// 当前正在处理的识别ID
currentRecognitionId: string | null;
// 加载状态
loading: boolean;
// 错误信息
error: string | null;
}
// 初始状态
const initialState: FoodRecognitionState = {
recognitionResults: {},
currentRecognitionId: null,
loading: false,
error: null,
};
const foodRecognitionSlice = createSlice({
name: 'foodRecognition',
initialState,
reducers: {
// 设置加载状态
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload;
if (action.payload) {
state.error = null;
}
},
// 设置错误信息
setError: (state, action: PayloadAction<string | null>) => {
state.error = action.payload;
state.loading = false;
},
// 保存识别结果
saveRecognitionResult: (state, action: PayloadAction<{ id: string; result: FoodRecognitionResponse }>) => {
const { id, result } = action.payload;
state.recognitionResults[id] = result;
state.currentRecognitionId = id;
state.loading = false;
state.error = null;
},
// 设置当前识别ID
setCurrentRecognitionId: (state, action: PayloadAction<string>) => {
state.currentRecognitionId = action.payload;
},
// 清除指定的识别结果
clearRecognitionResult: (state, action: PayloadAction<string>) => {
const id = action.payload;
delete state.recognitionResults[id];
if (state.currentRecognitionId === id) {
state.currentRecognitionId = null;
}
},
// 清除所有识别结果
clearAllRecognitionResults: (state) => {
state.recognitionResults = {};
state.currentRecognitionId = null;
state.error = null;
},
// 清除错误
clearError: (state) => {
state.error = null;
},
},
});
// Action creators
export const {
setLoading,
setError,
saveRecognitionResult,
setCurrentRecognitionId,
clearRecognitionResult,
clearAllRecognitionResults,
clearError,
} = foodRecognitionSlice.actions;
// Selectors
export const selectFoodRecognitionResult = (id: string) => (state: { foodRecognition: FoodRecognitionState }) =>
state.foodRecognition.recognitionResults[id] || null;
export const selectCurrentFoodRecognitionResult = (state: { foodRecognition: FoodRecognitionState }) => {
const currentId = state.foodRecognition.currentRecognitionId;
return currentId ? state.foodRecognition.recognitionResults[currentId] || null : null;
};
export const selectFoodRecognitionLoading = (state: { foodRecognition: FoodRecognitionState }) =>
state.foodRecognition.loading;
export const selectFoodRecognitionError = (state: { foodRecognition: FoodRecognitionState }) =>
state.foodRecognition.error;
export const selectCurrentRecognitionId = (state: { foodRecognition: FoodRecognitionState }) =>
state.foodRecognition.currentRecognitionId;
export default foodRecognitionSlice.reducer;