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:
parent
764c149e2e
commit
dbf6a8a74a
@ -35,6 +35,8 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
|
|||||||
// Animation values
|
// Animation values
|
||||||
const scale = useRef(new Animated.Value(1)).current;
|
const scale = useRef(new Animated.Value(1)).current;
|
||||||
const opacity = 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
|
// Hide FAB when call is active
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -68,6 +70,51 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
|
|||||||
}
|
}
|
||||||
}, [isCallActive, scale, opacity]);
|
}, [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
|
// Press animation with haptic feedback
|
||||||
const handlePressIn = () => {
|
const handlePressIn = () => {
|
||||||
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium);
|
||||||
@ -103,6 +150,18 @@ export function VoiceFAB({ onPress, style, disabled = false, isListening = false
|
|||||||
style,
|
style,
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
|
{/* Pulse ring when listening */}
|
||||||
|
{isListening && (
|
||||||
|
<Animated.View
|
||||||
|
style={[
|
||||||
|
styles.pulseRing,
|
||||||
|
{
|
||||||
|
transform: [{ scale: pulseScale }],
|
||||||
|
opacity: pulseOpacity,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
style={[
|
style={[
|
||||||
styles.fab,
|
styles.fab,
|
||||||
@ -133,6 +192,13 @@ const styles = StyleSheet.create({
|
|||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
zIndex: 100,
|
zIndex: 100,
|
||||||
},
|
},
|
||||||
|
pulseRing: {
|
||||||
|
position: 'absolute',
|
||||||
|
width: FAB_SIZE,
|
||||||
|
height: FAB_SIZE,
|
||||||
|
borderRadius: BorderRadius.full,
|
||||||
|
backgroundColor: AppColors.error,
|
||||||
|
},
|
||||||
fab: {
|
fab: {
|
||||||
width: FAB_SIZE,
|
width: FAB_SIZE,
|
||||||
height: FAB_SIZE,
|
height: FAB_SIZE,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user