Add red pulsing animation to VoiceFAB when listening

- Add pulse ring that expands and fades out while in listening mode
- Animation uses native driver for smooth 60fps performance
- Ring starts at FAB size and scales to 1.8x with opacity fade

🤖 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:31:19 -08:00
parent 764c149e2e
commit dbf6a8a74a

View File

@ -35,6 +35,8 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
// Animation values
const scale = useRef(new Animated.Value(1)).current;
const opacity = useRef(new Animated.Value(1)).current;
const pulseScale = useRef(new Animated.Value(1)).current;
const pulseOpacity = useRef(new Animated.Value(0)).current;
// Hide FAB when call is active
useEffect(() => {
@ -68,6 +70,51 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
}
}, [isCallActive, scale, opacity]);
// Pulse animation when listening
useEffect(() => {
if (isListening && !isCallActive) {
// Start pulsing animation
const pulseAnimation = Animated.loop(
Animated.sequence([
Animated.parallel([
Animated.timing(pulseScale, {
toValue: 1.8,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(pulseOpacity, {
toValue: 0,
duration: 1000,
useNativeDriver: true,
}),
]),
Animated.parallel([
Animated.timing(pulseScale, {
toValue: 1,
duration: 0,
useNativeDriver: true,
}),
Animated.timing(pulseOpacity, {
toValue: 0.6,
duration: 0,
useNativeDriver: true,
}),
]),
])
);
pulseAnimation.start();
return () => {
pulseAnimation.stop();
pulseScale.setValue(1);
pulseOpacity.setValue(0);
};
} else {
pulseScale.setValue(1);
pulseOpacity.setValue(0);
}
}, [isListening, isCallActive, pulseScale, pulseOpacity]);
// Press animation with haptic feedback
const handlePressIn = () => {
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
@ -103,6 +150,18 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
style,
]}
>
{/* Pulse ring when listening */}
{isListening && (
<Animated.View
style={[
styles.pulseRing,
{
transform: [{ scale: pulseScale }],
opacity: pulseOpacity,
},
]}
/>
)}
<TouchableOpacity
style={[
styles.fab,
@ -133,6 +192,13 @@ const styles = StyleSheet.create({
alignItems: 'center',
zIndex: 100,
},
pulseRing: {
position: 'absolute',
width: FAB_SIZE,
height: FAB_SIZE,
borderRadius: BorderRadius.full,
backgroundColor: AppColors.error,
},
fab: {
width: FAB_SIZE,
height: FAB_SIZE,