import { useAuth } from '@/assets/contexts/useAuth'; import { getCachedContacts } from '@/assets/services/offlineStorage'; import { colors } from '@/assets/styles/colors'; import { fonts } from '@/assets/styles/fonts'; import { FontAwesome } from '@expo/vector-icons'; import { useEffect, useState } from 'react'; import { Alert, Linking, Platform, StyleSheet, Text, useWindowDimensions } from 'react-native'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { runOnJS, useAnimatedStyle, useSharedValue, withSpring, withTiming, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; const BTN_SIZE = 64; const MARGIN = 16; export function EmergencyButton() { const { isAuthenticated } = useAuth(); const insets = useSafeAreaInsets(); const { width: screenWidth, height: screenHeight } = useWindowDimensions(); const [emergencyPhone, setEmergencyPhone] = useState(null); const tabBarHeight = (Platform.OS === 'ios' ? 54 : 64) + insets.bottom; const maxX = screenWidth - BTN_SIZE - MARGIN; const minY = insets.top + MARGIN; const maxY = screenHeight - tabBarHeight - BTN_SIZE - MARGIN; const x = useSharedValue(screenWidth - BTN_SIZE - MARGIN); const y = useSharedValue(screenHeight - tabBarHeight - BTN_SIZE - MARGIN); const startX = useSharedValue(0); const startY = useSharedValue(0); const scale = useSharedValue(1); const isDragActive = useSharedValue(false); useEffect(() => { if (!isAuthenticated) return; getCachedContacts().then(({ contactData }) => { if (contactData?.emergencyPhone) { setEmergencyPhone(contactData.emergencyPhone); } }); }, [isAuthenticated]); const handlePress = () => { if (!emergencyPhone) return; const url = `tel:${emergencyPhone.replace(/\s/g, '')}`; Alert.alert( 'Linha de Emergência 24h', `Ligar para ${emergencyPhone}?`, [ { text: 'Cancelar', style: 'cancel' }, { text: 'Ligar', style: 'default', onPress: () => Linking.openURL(url).catch(() => Alert.alert('Erro', 'Não foi possível abrir a aplicação de chamadas.'), ), }, ], ); }; // Toque simples → abre o Alert const tap = Gesture.Tap() .maxDuration(500) .onEnd(() => { runOnJS(handlePress)(); }); // Segurar 800ms → ativa modo arrasto com feedback de escala const longPress = Gesture.LongPress() .minDuration(800) .onStart(() => { isDragActive.value = true; scale.value = withSpring(1.2, { damping: 12, stiffness: 200 }); startX.value = x.value; startY.value = y.value; }); // Pan → só move quando o modo arrasto está ativo const pan = Gesture.Pan() .onUpdate((e) => { if (!isDragActive.value) return; x.value = Math.max(MARGIN, Math.min(maxX, startX.value + e.translationX)); y.value = Math.max(minY, Math.min(maxY, startY.value + e.translationY)); }) .onEnd(() => { if (!isDragActive.value) return; isDragActive.value = false; scale.value = withSpring(1, { damping: 15, stiffness: 250 }); // snap para a borda mais próxima sem bounce const snapRight = x.value + BTN_SIZE / 2 > screenWidth / 2; x.value = withTiming(snapRight ? maxX : MARGIN, { duration: 250 }); }); const composed = Gesture.Race( tap, Gesture.Simultaneous(longPress, pan), ); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: x.value }, { translateY: y.value }, { scale: scale.value }, ], })); if (!isAuthenticated || !emergencyPhone) return null; return ( SOS ); } const styles = StyleSheet.create({ btn: { position: 'absolute', top: 0, left: 0, width: BTN_SIZE, height: BTN_SIZE, borderRadius: BTN_SIZE / 2, backgroundColor: colors.vermelho, alignItems: 'center', justifyContent: 'center', gap: 4, shadowColor: '#000', shadowOpacity: 0.3, shadowRadius: 8, shadowOffset: { width: 0, height: 4 }, elevation: 10, zIndex: 999, }, label: { color: colors.branco, fontFamily: fonts.bold, fontSize: 11, letterSpacing: 0.5, }, });