Add toggle listening mode on VoiceFAB tap

- FAB now toggles between idle and listening states
- Green background (idle) → Red background (listening)
- Icon switches between mic-outline and mic
- Connects to VoiceContext for state management

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sergei 2026-01-27 16:30:20 -08:00
parent 1c23ca41b8
commit 764c149e2e
2 changed files with 27 additions and 16 deletions

View File

@ -1,6 +1,6 @@
import { Tabs } from 'expo-router';
import React, { useCallback } from 'react';
import { Platform, View, Alert } from 'react-native';
import { Platform, View } from 'react-native';
import { Feather } from '@expo/vector-icons';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
@ -9,6 +9,7 @@ import { VoiceFAB } from '@/components/VoiceFAB';
import { AppColors } from '@/constants/theme';
import { useColorScheme } from '@/hooks/use-color-scheme';
import { useVoiceCall } from '@/contexts/VoiceCallContext';
import { useVoice } from '@/contexts/VoiceContext';
export default function TabLayout() {
const colorScheme = useColorScheme();
@ -17,15 +18,17 @@ export default function TabLayout() {
// VoiceFAB uses VoiceCallContext internally to hide when call is active
useVoiceCall(); // Ensure context is available
// Handle voice FAB press - initiate voice call
// Voice context for listening mode toggle
const { isListening, startSession, stopSession } = useVoice();
// Handle voice FAB press - toggle listening mode
const handleVoiceFABPress = useCallback(() => {
// TODO: Integrate with voice call service when ready
Alert.alert(
'Voice Call',
'Voice call with Julia AI will be available soon.',
[{ text: 'OK' }]
);
}, []);
if (isListening) {
stopSession();
} else {
startSession();
}
}, [isListening, startSession, stopSession]);
// Calculate tab bar height based on safe area
// On iOS with home indicator, insets.bottom is ~34px
@ -116,8 +119,8 @@ export default function TabLayout() {
/>
</Tabs>
{/* Voice FAB - shown when no call is active */}
<VoiceFAB onPress={handleVoiceFABPress} />
{/* Voice FAB - toggle listening mode */}
<VoiceFAB onPress={handleVoiceFABPress} isListening={isListening} />
</View>
);
}

View File

@ -1,8 +1,8 @@
/**
* Voice Floating Action Button Component
*
* A floating action button for initiating voice calls with Julia AI.
* Shows on screens where voice chat is available.
* A floating action button for toggling voice listening mode.
* Tap to start/stop listening.
* Hidden when a call is already active.
*/
@ -23,11 +23,12 @@ interface VoiceFABProps {
onPress: () => void;
style?: ViewStyle;
disabled?: boolean;
isListening?: boolean;
}
const FAB_SIZE = 56;
export function VoiceFAB({ onPress, style, disabled = false }: VoiceFABProps) {
export function VoiceFAB({ onPress, style, disabled = false, isListening = false }: VoiceFABProps) {
const { isCallActive } = useVoiceCall();
const insets = useSafeAreaInsets();
@ -103,7 +104,11 @@ export function VoiceFAB({ onPress, style, disabled = false }: VoiceFABProps) {
]}
>
<TouchableOpacity
style={[styles.fab, disabled && styles.fabDisabled]}
style={[
styles.fab,
isListening && styles.fabListening,
disabled && styles.fabDisabled,
]}
onPress={onPress}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
@ -111,7 +116,7 @@ export function VoiceFAB({ onPress, style, disabled = false }: VoiceFABProps) {
activeOpacity={0.9}
>
<Ionicons
name="mic"
name={isListening ? 'mic' : 'mic-outline'}
size={28}
color={disabled ? AppColors.textMuted : AppColors.white}
/>
@ -141,6 +146,9 @@ const styles = StyleSheet.create({
shadowRadius: 8,
elevation: 8,
},
fabListening: {
backgroundColor: AppColors.error,
},
fabDisabled: {
backgroundColor: AppColors.surface,
shadowOpacity: 0.1,