From b13360f3753554d45680b5a43506810deed3626e Mon Sep 17 00:00:00 2001 From: Gabino Ocotl Date: Thu, 10 Jul 2025 00:28:32 -0500 Subject: [PATCH] fixed the bug where it would not upload on inital render to the results page --- apps/mobile/src/context/AppContext.tsx | 81 +++++++++++---------- apps/mobile/src/hooks/useAudioRecording.ts | 23 ++++-- apps/mobile/src/screens/RecordingScreen.tsx | 61 ++++++++++------ 3 files changed, 96 insertions(+), 69 deletions(-) diff --git a/apps/mobile/src/context/AppContext.tsx b/apps/mobile/src/context/AppContext.tsx index d323231..79373da 100644 --- a/apps/mobile/src/context/AppContext.tsx +++ b/apps/mobile/src/context/AppContext.tsx @@ -272,7 +272,7 @@ interface AppContextType { dispatch: React.Dispatch; actions: { checkApiHealth: () => Promise; - uploadRecording: () => Promise; + uploadRecording: (recordingUri?: string) => Promise; uploadRecordingWithMode: (mode?: 'fast' | 'optimized') => Promise; resetApp: () => void; setCoachingMode: (mode: 'fast' | 'optimized') => void; @@ -339,52 +339,57 @@ export function AppProvider({ children }: { children: ReactNode }) { } }, []); - const uploadRecording = useCallback(async () => { - if (!state.recordingData.uri) { - dispatch({ type: 'SET_ERROR', payload: 'No recording found to upload' }); - return; - } + const uploadRecording = useCallback( + async (recordingUri?: string) => { + const uriToUse = recordingUri || state.recordingData.uri; - dispatch({ type: 'START_UPLOAD' }); + if (!uriToUse) { + dispatch({ + type: 'SET_ERROR', + payload: 'No recording found to upload', + }); + return; + } - try { - const result = await apiService.submitCheckin(state.recordingData.uri); + dispatch({ type: 'START_UPLOAD' }); - if (result.success && result.data) { - // Extract processing time from success message - const processingTimeMatch = result.message?.match(/(\d+)ms/); - const processingTime = processingTimeMatch - ? parseInt(processingTimeMatch[1]) - : undefined; + try { + const result = await apiService.submitCheckin(uriToUse); - dispatch({ - type: 'UPLOAD_SUCCESS', - payload: { - data: result.data, - processingTime, - }, - }); + if (result.success && result.data) { + // Extract processing time from success message + const processingTimeMatch = result.message?.match(/(\d+)ms/); + const processingTime = processingTimeMatch + ? parseInt(processingTimeMatch[1]) + : undefined; - // Navigate to results after successful upload - dispatch({ type: 'NAVIGATE_TO', payload: 'results' }); - } else { - // Handle API errors with detailed error information - const errorMessage = result.error || 'Upload failed'; - const details = result.code ? ` (${result.code})` : ''; + dispatch({ + type: 'UPLOAD_SUCCESS', + payload: { + data: result.data, + processingTime, + }, + }); + } else { + // Handle API errors with detailed error information + const errorMessage = result.error || 'Upload failed'; + const details = result.code ? ` (${result.code})` : ''; + dispatch({ + type: 'UPLOAD_ERROR', + payload: `${errorMessage}${details}`, + }); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Upload recording error:', error); dispatch({ type: 'UPLOAD_ERROR', - payload: `${errorMessage}${details}`, + payload: 'Network error. Please check your connection and try again.', }); } - } catch (error) { - // eslint-disable-next-line no-console - console.error('Upload recording error:', error); - dispatch({ - type: 'UPLOAD_ERROR', - payload: 'Network error. Please check your connection and try again.', - }); - } - }, [state.recordingData.uri]); + }, + [state.recordingData.uri] + ); const uploadRecordingWithMode = useCallback( async (mode?: 'fast' | 'optimized') => { diff --git a/apps/mobile/src/hooks/useAudioRecording.ts b/apps/mobile/src/hooks/useAudioRecording.ts index fed6e77..e7a2293 100644 --- a/apps/mobile/src/hooks/useAudioRecording.ts +++ b/apps/mobile/src/hooks/useAudioRecording.ts @@ -75,7 +75,9 @@ export function useAudioRecording(): AudioRecordingHook { // Check permissions first if (!hasPermissions) { const granted = await requestPermissions(); - if (!granted) return false; + if (!granted) { + return false; + } } // Prepare and start recording @@ -101,23 +103,28 @@ export function useAudioRecording(): AudioRecordingHook { try { setError(null); - if (!recorderState.isRecording) { - return null; - } - - // Stop timer + // Stop timer first if (timerRef.current) { clearInterval(timerRef.current); timerRef.current = null; } - // Stop recording - await audioRecorder.stop(); + // Stop recording even if state shows not recording (defensive programming) + try { + await audioRecorder.stop(); + } catch (stopError) { + // Continue anyway, this might be expected if already stopped + } // Get recording URI const uri = audioRecorder.uri; setRecordingUri(uri); + if (!uri) { + setError('Recording file not found after stopping'); + return null; + } + return uri; } catch (err) { setError('Failed to stop recording'); diff --git a/apps/mobile/src/screens/RecordingScreen.tsx b/apps/mobile/src/screens/RecordingScreen.tsx index 173cea6..97a91f9 100644 --- a/apps/mobile/src/screens/RecordingScreen.tsx +++ b/apps/mobile/src/screens/RecordingScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect, useCallback } from 'react'; +import React, { useState, useEffect, useCallback, useRef } from 'react'; import { View, Text, @@ -24,6 +24,7 @@ export function RecordingScreen() { const audioRecording = useAudioRecording(); const [timeRemaining, setTimeRemaining] = useState(MAX_RECORDING_DURATION); const [pulseAnimation] = useState(new Animated.Value(1)); + const stopRecordingRef = useRef<(() => Promise) | null>(null); useEffect(() => { // Request permissions when component mounts @@ -46,26 +47,46 @@ export function RecordingScreen() { }, }); - // Navigate to results immediately for testing without backend + // Navigate to results immediately to show loading/processing state dispatch({ type: 'NAVIGATE_TO', payload: 'results' }); - // Still try to upload in background (will fail gracefully without backend) - setTimeout(async () => { - try { - await actions.uploadRecording(); - } catch (error) { - // Silently fail - we're already on results screen - // Upload will be retried when backend is available - } - }, 500); + // Start upload process in background - this will update the results screen with data or errors + try { + await actions.uploadRecording(recordingUri); + } catch (uploadError) { + // Error will be handled by the uploadRecording function and shown in results screen + } } else { - Alert.alert('Recording Error', 'Failed to save recording.'); + Alert.alert( + 'Recording Error', + 'Failed to save recording. No recording file was created.' + ); + // Navigate to results to show error state + dispatch({ + type: 'SET_ERROR', + payload: 'Recording failed - no audio file was created', + }); + dispatch({ type: 'NAVIGATE_TO', payload: 'results' }); } } catch (error) { - Alert.alert('Recording Error', 'Failed to stop recording.'); + Alert.alert( + 'Recording Error', + `Failed to stop recording: ${error instanceof Error ? error.message : 'Unknown error'}` + ); + // Set error state and navigate to results + dispatch({ + type: 'SET_ERROR', + payload: `Recording failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + }); + dispatch({ type: 'NAVIGATE_TO', payload: 'results' }); } }, [audioRecording, dispatch, actions]); + // Store the stopRecording function in a ref to avoid dependency cycles + useEffect(() => { + stopRecordingRef.current = stopRecording; + }, [stopRecording]); + useEffect(() => { // Pulse animation for recording button const pulseAnimationLoop = Animated.loop( @@ -101,15 +122,11 @@ export function RecordingScreen() { setTimeRemaining(Math.max(0, remaining)); // Auto-stop at max duration - if (remaining <= 0) { - stopRecording(); + if (remaining <= 0 && stopRecordingRef.current) { + stopRecordingRef.current(); } } - }, [ - audioRecording.recordingDuration, - audioRecording.isRecording, - stopRecording, - ]); + }, [audioRecording.recordingDuration, audioRecording.isRecording]); useEffect(() => { // Handle audio recording errors @@ -310,9 +327,7 @@ export function RecordingScreen() { {/* Tips - positioned at bottom */} {!audioRecording.isRecording && ( - - 💡 Tips for a good recording: - + Tips for a good recording: • Find a quiet space{'\n'}• Speak clearly and naturally{'\n'}• Share whatever feels comfortable{'\n'}• There's no right or