diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 6e690e6..7ccf30a 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,6 +1,6 @@ import { Tabs } from 'expo-router'; import React, { useCallback, useEffect, useRef } from 'react'; -import { Platform, View } from 'react-native'; +import { Platform, View, AppState, AppStateStatus } from 'react-native'; import { Feather } from '@expo/vector-icons'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; @@ -143,6 +143,55 @@ export default function TabLayout() { } }, [status, sttIsListening, startListening]); + // ============================================================================ + // TAB NAVIGATION PERSISTENCE + // Ensure voice session continues when user switches between tabs. + // The session state is in VoiceContext (root level), but STT may stop due to: + // 1. Native audio session changes + // 2. Tab unmount/remount (though tabs layout doesn't unmount) + // 3. AppState changes (background/foreground) + // ============================================================================ + + // Monitor and recover STT state during tab navigation + // If session is active but STT stopped unexpectedly, restart it + useEffect(() => { + // Check every 500ms if STT needs to be restarted + const intervalId = setInterval(() => { + // Only act if session should be active (isListening from VoiceContext) + // but STT is not actually listening, and we're not in speaking/processing mode + if ( + sessionActiveRef.current && + !sttIsListening && + status !== 'speaking' && + status !== 'processing' + ) { + console.log('[TabLayout] STT watchdog: restarting STT (session active but STT stopped)'); + startListening(); + } + }, 500); + + return () => clearInterval(intervalId); + }, [sttIsListening, status, startListening]); + + // Handle app state changes (background/foreground) + // When app comes back to foreground, restart STT if session was active + useEffect(() => { + const handleAppStateChange = (nextAppState: AppStateStatus) => { + if (nextAppState === 'active' && sessionActiveRef.current) { + // App came to foreground, give it a moment then check STT + setTimeout(() => { + if (sessionActiveRef.current && !sttIsListening && status !== 'speaking' && status !== 'processing') { + console.log('[TabLayout] App foregrounded - restarting STT'); + startListening(); + } + }, 300); + } + }; + + const subscription = AppState.addEventListener('change', handleAppStateChange); + return () => subscription.remove(); + }, [sttIsListening, status, startListening]); + // Handle voice FAB press - toggle listening mode const handleVoiceFABPress = useCallback(() => { if (isListening) {