From dbf6a8a74a4dd4c8e379e37bc24b4b23ca1f20a2 Mon Sep 17 00:00:00 2001 From: Sergei Date: Tue, 27 Jan 2026 16:31:19 -0800 Subject: [PATCH] Add red pulsing animation to VoiceFAB when listening MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- components/VoiceFAB.tsx | 66 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/components/VoiceFAB.tsx b/components/VoiceFAB.tsx index 8baebe5..476ea7c 100644 --- a/components/VoiceFAB.tsx +++ b/components/VoiceFAB.tsx @@ -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 && ( + + )}