import { Pressable, View, Text, Animated } from "react-native"; import { useEffect, useRef } from "react"; import { StyleSheet } from "react-native-unistyles"; interface VoiceButtonProps { state: "idle" | "recording" | "processing" | "playing"; onPress: () => void; disabled?: boolean; } const styles = StyleSheet.create((theme) => ({ container: { alignItems: "center", gap: theme.spacing[4], }, pressable: { opacity: 1, }, pressableDisabled: { opacity: 0.5, }, button: { width: 80, height: 80, borderRadius: theme.borderRadius.full, alignItems: "center", justifyContent: "center", ...theme.shadow.md, }, buttonIdle: { backgroundColor: theme.colors.surface2, }, buttonRecording: { backgroundColor: theme.colors.destructive, }, buttonProcessing: { backgroundColor: theme.colors.primary, }, buttonPlaying: { backgroundColor: theme.colors.palette.green[500], }, label: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, }, // Recording icon recordingIcon: { width: 24, height: 24, backgroundColor: theme.colors.foreground, borderRadius: theme.borderRadius.md, }, // Processing icon processingIconContainer: { width: 24, height: 24, }, processingDot: { width: 6, height: 6, backgroundColor: theme.colors.primaryForeground, borderRadius: theme.borderRadius.full, position: "absolute", }, processingDotTop: { top: 0, left: 12, }, processingDotRight: { top: 12, right: 0, }, processingDotBottom: { bottom: 0, left: 12, }, // Playing icon playingIconContainer: { flexDirection: "row", alignItems: "center", gap: theme.spacing[1], }, playingBar: { backgroundColor: theme.colors.foreground, borderRadius: theme.borderRadius.sm, }, playingBar1: { width: 4, height: 16, }, playingBar2: { width: 4, height: 24, }, playingBar3: { width: 4, height: 12, }, // Idle icon (microphone) micContainer: { width: 24, height: 32, position: "relative", }, micCapsule: { position: "absolute", bottom: 0, left: 4, width: 16, height: 24, backgroundColor: theme.colors.foreground, borderTopLeftRadius: 999, borderTopRightRadius: 999, }, micBase: { position: "absolute", bottom: 0, left: 0, width: 24, height: 6, backgroundColor: theme.colors.foreground, borderRadius: theme.borderRadius.full, }, })); export function VoiceButton({ state, onPress, disabled = false }: VoiceButtonProps) { const pulseAnim = useRef(new Animated.Value(1)).current; useEffect(() => { if (state === "recording") { Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.2, duration: 800, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: 800, useNativeDriver: true, }), ]), ).start(); } else { pulseAnim.setValue(1); } }, [state, pulseAnim]); const getButtonStyle = () => { switch (state) { case "recording": return styles.buttonRecording; case "processing": return styles.buttonProcessing; case "playing": return styles.buttonPlaying; default: return styles.buttonIdle; } }; const getIcon = () => { switch (state) { case "recording": return ; case "processing": return ( ); case "playing": return ( ); default: return ( ); } }; const getLabel = () => { switch (state) { case "recording": return "Recording..."; case "processing": return "Processing..."; case "playing": return "Playing..."; default: return "Tap to speak"; } }; return ( {getIcon()} {getLabel()} ); }