import { useCamera } from '@/add-log/camera/useCamera'; import { useFoodIdentification } from '@/add-log/food-detection/useFoodIdentification'; import { useFoodLogEditor } from '@/add-log/food-log-editor/useFoodLogEditor'; import { useAlert } from '@/aesthetics/Alert'; import { useHaptics } from '@/aesthetics/useHaptics'; import { useStrings } from '@/common/hooks/useStrings'; import { startSpan } from '@/remote/monitor'; import type { StagedFoodLog, Suggestion } from '@/types/foodlog'; import * as Crypto from 'expo-crypto'; import * as ImagePicker from 'expo-image-picker'; import { useMediaLibraryPermissions } from 'expo-image-picker'; import { useFocusEffect, useRouter } from 'expo-router'; import { useCallback, useRef, useState } from 'react'; import { Linking } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { useLogInput } from './hooks/useLogInput'; import { useLogSuggestions } from './hooks/useLogSuggestions'; import { useStagedLogs } from './hooks/useStagedLogs'; export const useLogScreen = () => { const strings = useStrings(); const { impact } = useHaptics(); const { alert, showAlert } = useAlert(); const router = useRouter(); const insets = useSafeAreaInsets(); const [isScreenFocused, setIsScreenFocused] = useState(true); const mountedRef = useRef(true); useFocusEffect( useCallback(() => { mountedRef.current = true; setIsScreenFocused(true); return () => { mountedRef.current = false; setIsScreenFocused(false); }; }, []) ); const [status, requestPermission] = useMediaLibraryPermissions(); const [stagedFoodLogs, setStagedFoodLogs] = useState([]); const { suggestions, addSuggestion, deleteSuggestion } = useLogSuggestions(); const { identifyFromImage, fetchIngredientsForLog, handleFoodCandidateSwitch, cancelStream } = useFoodIdentification({ setStagedFoodLogs, mountedRef }); const { addLog, attachPhoto, deleteLog, logAll } = useStagedLogs({ stagedFoodLogs, setStagedFoodLogs, mountedRef, identifyFromImage, fetchIngredientsForLog, cancelStream, }); const handleRetry = useCallback( (logId: string) => { const log = stagedFoodLogs.find(l => l.id === logId); if (!log?.photoUri) return; setStagedFoodLogs(prev => prev.map(l => (l.id === logId ? { ...l, detectionPhase: 'capturing' as const } : l)) ); identifyFromImage(logId, log.photoUri); }, [stagedFoodLogs, setStagedFoodLogs, identifyFromImage] ); const { text: textInput, setText: setTextInput, submit: handleTextSubmit, } = useLogInput({ addLog }); const { cameraRef, hasPermission, requestCameraPermission, device, format, cameraMode, setCameraMode, cameraOn, cameraActive, setCameraReady, isCropMode, showCropOverlay, zoom, cameraGesture, cropGestures, cropBoxWidth, cropBoxHeight, cropAreaCenterY, takePhoto, focusPoint, } = useCamera({ insetsTop: insets.top, isScreenFocused, addLog: (foodName, photoUri, logId) => addLog(foodName, photoUri, logId), attachPhoto, mountedRef, showAlert, }); const { editingLog, setEditingLog, editFoodName, setEditFoodName, editIngredients, newIngredient, setNewIngredient, handleToggleIngredient, handleEditStagedLog, handleSaveEdit, handleAddIngredient, handleRemoveIngredient, } = useFoodLogEditor({ setStagedFoodLogs }); const handlePickImage = useCallback(async () => { if (!status?.granted) { const permission = await requestPermission(); if (!mountedRef.current) return; if (!permission.granted) { alert( strings.addLog.log.cameraRollAccessTitle, strings.addLog.log.cameraRollAccessMessage, [ { text: strings.common.cancel, style: 'cancel' }, { text: strings.addLog.log.openSettings, onPress: () => Linking.openSettings() }, ] ); return; } } const logId = Crypto.randomUUID(); const result = await startSpan( { name: 'camera_roll_pick', op: 'ui.action', attributes: { log_id: logId } }, () => ImagePicker.launchImageLibraryAsync({ mediaTypes: ['images'], quality: 0.8, }) ); if (!mountedRef.current) return; if (!result.canceled && result.assets[0]) { addLog('', result.assets[0].uri, logId); } }, [addLog, status, requestPermission, strings]); const handleSuggestionPress = useCallback( (suggestion: Suggestion) => { impact(); addLog(suggestion.label, undefined, suggestion.ingredients); }, [addLog, impact] ); return { camera: { ref: cameraRef, hasPermission, requestPermission: requestCameraPermission, device, format, on: cameraOn, active: cameraActive, mode: cameraMode, setMode: setCameraMode, zoom, setReady: setCameraReady, gesture: cameraGesture, isCropMode, showCropOverlay, cropGestures, cropBoxWidth, cropBoxHeight, cropAreaCenterY, takePhoto, focusPoint, }, logs: { staged: stagedFoodLogs, edit: handleEditStagedLog, delete: deleteLog, toggleIngredient: handleToggleIngredient, switchCandidate: handleFoodCandidateSwitch, retry: handleRetry, logAll, }, editor: { log: editingLog, setLog: setEditingLog, foodName: editFoodName, setFoodName: setEditFoodName, ingredients: editIngredients, newIngredient, setNewIngredient, addIngredient: handleAddIngredient, removeIngredient: handleRemoveIngredient, save: handleSaveEdit, }, suggestions: { items: suggestions, press: handleSuggestionPress, delete: deleteSuggestion, add: addSuggestion, }, input: { text: textInput, setText: setTextInput, submit: handleTextSubmit, pickImage: handlePickImage, }, layout: { insets, router }, haptics: { impact }, }; };