From cd9dddda34b73d5ae22cdebced0580fee5474046 Mon Sep 17 00:00:00 2001 From: Sergei Date: Sun, 18 Jan 2026 22:00:26 -0800 Subject: [PATCH] Add Chat tab with Julia AI + voice call improvements - Enable Chat tab (replace Debug) - text chat with Julia AI - Add voice call button in chat header and input area - Add speaker/earpiece toggle in voice-call screen - setAudioOutput() function for switching audio output --- app/(tabs)/_layout.tsx | 14 +++++++------- app/voice-call.tsx | 27 +++++++++++++++++++++++---- utils/audioSession.ts | 42 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 72 insertions(+), 11 deletions(-) diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 0622606..09b0c19 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -46,11 +46,14 @@ export default function TabLayout() { href: null, }} /> - {/* Chat hidden for now - testing via debug */} + {/* Chat with Julia AI */} ( + + ), }} /> {/* Voice tab hidden - using Debug for testing */} @@ -69,14 +72,11 @@ export default function TabLayout() { ), }} /> - {/* Debug tab for testing */} + {/* Debug tab hidden */} ( - - ), + href: null, }} /> {/* Hide explore tab */} diff --git a/app/voice-call.tsx b/app/voice-call.tsx index 3e7416f..cebd98f 100644 --- a/app/voice-call.tsx +++ b/app/voice-call.tsx @@ -35,6 +35,7 @@ import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; import { VOICE_NAME } from '@/services/livekitService'; import { useVoiceTranscript } from '@/contexts/VoiceTranscriptContext'; import { useLiveKitRoom, ConnectionState } from '@/hooks/useLiveKitRoom'; +import { setAudioOutput } from '@/utils/audioSession'; const { width: SCREEN_WIDTH } = Dimensions.get('window'); @@ -47,6 +48,9 @@ export default function VoiceCallScreen() { const [logsMinimized, setLogsMinimized] = React.useState(false); const logsScrollRef = useRef(null); + // Speaker/earpiece toggle state + const [isSpeakerOn, setIsSpeakerOn] = React.useState(true); + // LiveKit hook - ALL logic is here const { state, @@ -159,6 +163,13 @@ export default function VoiceCallScreen() { router.back(); }; + // Toggle speaker/earpiece + const handleToggleSpeaker = async () => { + const newSpeakerState = !isSpeakerOn; + setIsSpeakerOn(newSpeakerState); + await setAudioOutput(newSpeakerState); + }; + // Copy logs to clipboard const copyLogs = async () => { const logsText = logs.map(l => `[${l.timestamp}] ${l.message}`).join('\n'); @@ -376,10 +387,18 @@ export default function VoiceCallScreen() { - {/* Speaker button (placeholder for future) */} - - - Speaker + {/* Speaker/Earpiece toggle */} + + + {isSpeakerOn ? 'Speaker' : 'Earpiece'} diff --git a/utils/audioSession.ts b/utils/audioSession.ts index a36db3e..7400001 100644 --- a/utils/audioSession.ts +++ b/utils/audioSession.ts @@ -145,3 +145,45 @@ export async function reconfigureAudioForPlayback(): Promise { // Don't throw - this is a best-effort operation } } + +/** + * Switch audio output between speaker and earpiece + * + * @param useSpeaker - true for speaker, false for earpiece + */ +export async function setAudioOutput(useSpeaker: boolean): Promise { + if (Platform.OS !== 'ios') { + console.log('[AudioSession] setAudioOutput - skipping on non-iOS'); + return; + } + + console.log(`[AudioSession] Setting audio output to ${useSpeaker ? 'SPEAKER' : 'EARPIECE'}...`); + + try { + const AudioSession = await getAudioSession(); + if (!AudioSession) { + console.error('[AudioSession] Failed to get AudioSession module'); + return; + } + + // Configure audio output + await AudioSession.configureAudio({ + ios: { + defaultOutput: useSpeaker ? 'speaker' : 'earpiece', + }, + }); + + // Also update the full configuration to ensure it takes effect + await AudioSession.setAppleAudioConfiguration({ + audioCategory: 'playAndRecord', + audioCategoryOptions: useSpeaker + ? ['allowBluetooth', 'allowBluetoothA2DP', 'defaultToSpeaker', 'mixWithOthers'] + : ['allowBluetooth', 'allowBluetoothA2DP', 'mixWithOthers'], + audioMode: 'voiceChat', + }); + + console.log(`[AudioSession] Audio output set to ${useSpeaker ? 'SPEAKER' : 'EARPIECE'}`); + } catch (error) { + console.error('[AudioSession] setAudioOutput error:', error); + } +}