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:
parent
1c23ca41b8
commit
764c149e2e
@ -1,6 +1,6 @@
|
|||||||
import { Tabs } from 'expo-router';
|
import { Tabs } from 'expo-router';
|
||||||
import React, { useCallback } from 'react';
|
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 { Feather } from '@expo/vector-icons';
|
||||||
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
||||||
|
|
||||||
@ -9,6 +9,7 @@ import { VoiceFAB } from '@/components/VoiceFAB';
|
|||||||
import { AppColors } from '@/constants/theme';
|
import { AppColors } from '@/constants/theme';
|
||||||
import { useColorScheme } from '@/hooks/use-color-scheme';
|
import { useColorScheme } from '@/hooks/use-color-scheme';
|
||||||
import { useVoiceCall } from '@/contexts/VoiceCallContext';
|
import { useVoiceCall } from '@/contexts/VoiceCallContext';
|
||||||
|
import { useVoice } from '@/contexts/VoiceContext';
|
||||||
|
|
||||||
export default function TabLayout() {
|
export default function TabLayout() {
|
||||||
const colorScheme = useColorScheme();
|
const colorScheme = useColorScheme();
|
||||||
@ -17,15 +18,17 @@ export default function TabLayout() {
|
|||||||
// VoiceFAB uses VoiceCallContext internally to hide when call is active
|
// VoiceFAB uses VoiceCallContext internally to hide when call is active
|
||||||
useVoiceCall(); // Ensure context is available
|
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(() => {
|
const handleVoiceFABPress = useCallback(() => {
|
||||||
// TODO: Integrate with voice call service when ready
|
if (isListening) {
|
||||||
Alert.alert(
|
stopSession();
|
||||||
'Voice Call',
|
} else {
|
||||||
'Voice call with Julia AI will be available soon.',
|
startSession();
|
||||||
[{ text: 'OK' }]
|
}
|
||||||
);
|
}, [isListening, startSession, stopSession]);
|
||||||
}, []);
|
|
||||||
|
|
||||||
// Calculate tab bar height based on safe area
|
// Calculate tab bar height based on safe area
|
||||||
// On iOS with home indicator, insets.bottom is ~34px
|
// On iOS with home indicator, insets.bottom is ~34px
|
||||||
@ -116,8 +119,8 @@ export default function TabLayout() {
|
|||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
{/* Voice FAB - shown when no call is active */}
|
{/* Voice FAB - toggle listening mode */}
|
||||||
<VoiceFAB onPress={handleVoiceFABPress} />
|
<VoiceFAB onPress={handleVoiceFABPress} isListening={isListening} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
/**
|
/**
|
||||||
* Voice Floating Action Button Component
|
* Voice Floating Action Button Component
|
||||||
*
|
*
|
||||||
* A floating action button for initiating voice calls with Julia AI.
|
* A floating action button for toggling voice listening mode.
|
||||||
* Shows on screens where voice chat is available.
|
* Tap to start/stop listening.
|
||||||
* Hidden when a call is already active.
|
* Hidden when a call is already active.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@ -23,11 +23,12 @@ interface VoiceFABProps {
|
|||||||
onPress: () => void;
|
onPress: () => void;
|
||||||
style?: ViewStyle;
|
style?: ViewStyle;
|
||||||
disabled?: boolean;
|
disabled?: boolean;
|
||||||
|
isListening?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const FAB_SIZE = 56;
|
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 { isCallActive } = useVoiceCall();
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
|
||||||
@ -103,7 +104,11 @@ export function VoiceFAB({ onPress, style, disabled = false }: VoiceFABProps) {
|
|||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[styles.fab, disabled && styles.fabDisabled]}
|
style={[
|
||||||
|
styles.fab,
|
||||||
|
isListening && styles.fabListening,
|
||||||
|
disabled && styles.fabDisabled,
|
||||||
|
]}
|
||||||
onPress={onPress}
|
onPress={onPress}
|
||||||
onPressIn={handlePressIn}
|
onPressIn={handlePressIn}
|
||||||
onPressOut={handlePressOut}
|
onPressOut={handlePressOut}
|
||||||
@ -111,7 +116,7 @@ export function VoiceFAB({ onPress, style, disabled = false }: VoiceFABProps) {
|
|||||||
activeOpacity={0.9}
|
activeOpacity={0.9}
|
||||||
>
|
>
|
||||||
<Ionicons
|
<Ionicons
|
||||||
name="mic"
|
name={isListening ? 'mic' : 'mic-outline'}
|
||||||
size={28}
|
size={28}
|
||||||
color={disabled ? AppColors.textMuted : AppColors.white}
|
color={disabled ? AppColors.textMuted : AppColors.white}
|
||||||
/>
|
/>
|
||||||
@ -141,6 +146,9 @@ const styles = StyleSheet.create({
|
|||||||
shadowRadius: 8,
|
shadowRadius: 8,
|
||||||
elevation: 8,
|
elevation: 8,
|
||||||
},
|
},
|
||||||
|
fabListening: {
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
|
},
|
||||||
fabDisabled: {
|
fabDisabled: {
|
||||||
backgroundColor: AppColors.surface,
|
backgroundColor: AppColors.surface,
|
||||||
shadowOpacity: 0.1,
|
shadowOpacity: 0.1,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user