"use client" import type React from "react" import { useState, useEffect, useRef } from "react" import { StyleSheet, TouchableOpacity, View, Text, Vibration, ActivityIndicator, Dimensions, Animated, Linking, } from "react-native" import { Ionicons } from "@expo/vector-icons" import * as Location from "expo-location" import AsyncStorage from "@react-native-async-storage/async-storage" import { PanGestureHandler, State } from "react-native-gesture-handler" import { LinearGradient } from "expo-linear-gradient" import { Modalize } from "react-native-modalize" import { COLORS, SHADOWS } from "../src/constants/theme" import { StatusBar } from "expo-status-bar" const STORAGE_KEY = "panic_button_position" const { width, height } = Dimensions.get("window") const BUTTON_SIZE = 56 interface EnhancedPanicButtonProps { onPanic?: (location?: { latitude: number; longitude: number } | null) => void } const EnhancedPanicButton: React.FC = ({ onPanic }) => { const [loading, setLoading] = useState(false) const [ready, setReady] = useState(false) const [position, setPosition] = useState({ x: width - BUTTON_SIZE - 24, y: height - BUTTON_SIZE - 120 }) const [currentModal, setCurrentModal] = useState<'main' | 'permission' | 'error' | null>(null) // Refs para os modais const mainModalRef = useRef(null) const permissionModalRef = useRef(null) const errorModalRef = useRef(null) // Animated values const translateX = useRef(new Animated.Value(position.x)).current const translateY = useRef(new Animated.Value(position.y)).current const lastOffset = useRef({ x: position.x, y: position.y }) const pulseAnim = useRef(new Animated.Value(1)).current const shakeAnim = useRef(new Animated.Value(0)).current const glowAnim = useRef(new Animated.Value(0)).current // Animação de pulso e brilho do botão useEffect(() => { const pulse = Animated.loop( Animated.sequence([ Animated.timing(pulseAnim, { toValue: 1.12, duration: 1200, useNativeDriver: true, }), Animated.timing(pulseAnim, { toValue: 1, duration: 1200, useNativeDriver: true, }), ]), ) const glow = Animated.loop( Animated.sequence([ Animated.timing(glowAnim, { toValue: 1, duration: 2000, useNativeDriver: true, }), Animated.timing(glowAnim, { toValue: 0, duration: 2000, useNativeDriver: true, }), ]), ) pulse.start() glow.start() return () => { pulse.stop() glow.stop() } }, []) // Carregar posição salva useEffect(() => { AsyncStorage.getItem(STORAGE_KEY).then((data) => { if (data) { const pos = JSON.parse(data) setPosition(pos) translateX.setValue(pos.x) translateY.setValue(pos.y) lastOffset.current = pos } setReady(true) }) }, []) // Salvar posição ao mover const savePosition = (x: number, y: number) => { const newPos = { x: Math.max(0, Math.min(x, width - BUTTON_SIZE)), y: Math.max(0, Math.min(y, height - BUTTON_SIZE)), } setPosition(newPos) AsyncStorage.setItem(STORAGE_KEY, JSON.stringify(newPos)) lastOffset.current = newPos } const clamp = (value: number, min: number, max: number) => Math.max(min, Math.min(value, max)) const onHandlerStateChange = (event: any) => { if (event.nativeEvent.oldState === State.ACTIVE) { let newX = lastOffset.current.x + event.nativeEvent.translationX let newY = lastOffset.current.y + event.nativeEvent.translationY newX = clamp(newX, 0, width - BUTTON_SIZE) newY = clamp(newY, 0, height - BUTTON_SIZE) savePosition(newX, newY) translateX.setValue(newX) translateY.setValue(newY) translateX.setOffset(0) translateY.setOffset(0) } } const handlePress = () => { Vibration.vibrate([50, 100, 50]) setCurrentModal('main') mainModalRef.current?.open() } const shakeAnimation = () => { Animated.sequence([ Animated.timing(shakeAnim, { toValue: 10, duration: 80, useNativeDriver: true }), Animated.timing(shakeAnim, { toValue: -10, duration: 80, useNativeDriver: true }), Animated.timing(shakeAnim, { toValue: 10, duration: 80, useNativeDriver: true }), Animated.timing(shakeAnim, { toValue: -10, duration: 80, useNativeDriver: true }), Animated.timing(shakeAnim, { toValue: 0, duration: 80, useNativeDriver: true }), ]).start() } const confirmPanic = async () => { setLoading(true) try { const { status } = await Location.requestForegroundPermissionsAsync() if (status !== "granted") { setLoading(false) mainModalRef.current?.close() setTimeout(() => { setCurrentModal('permission') permissionModalRef.current?.open() }, 300) shakeAnimation() return } const location = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.High, timeout: 10000, }) setLoading(false) mainModalRef.current?.close() Vibration.vibrate([100, 200, 100, 200, 100]) if (onPanic) { onPanic({ latitude: location.coords.latitude, longitude: location.coords.longitude, }) } } catch (e) { setLoading(false) mainModalRef.current?.close() setTimeout(() => { setCurrentModal('error') errorModalRef.current?.open() }, 300) shakeAnimation() } } const handlePermissionDenied = () => { permissionModalRef.current?.close() if (onPanic) onPanic(null) } const handleLocationError = () => { errorModalRef.current?.close() if (onPanic) onPanic(null) } const openSettings = () => { Linking.openSettings() permissionModalRef.current?.close() } const retryLocation = () => { errorModalRef.current?.close() setTimeout(() => { setCurrentModal('main') mainModalRef.current?.open() }, 300) } if (!ready) return null const renderMainModal = () => ( {/* Header com gradiente */} Botão de Pânico Situação de emergência detectada {/* Corpo do modal */} Use apenas em emergências reais O que acontecerá: Localização Enviada Sua posição GPS será compartilhada Equipe Notificada Emergência será comunicada imediatamente Resposta Rápida Ação imediata será iniciada {loading ? ( Obtendo sua localização... Aguarde alguns segundos ) : ( mainModalRef.current?.close()} > Cancelar Acionar Pânico )} ) const renderPermissionModal = () => ( Permissão Necessária Acesso à localização requerido Por que precisamos? Para enviar sua localização exata à equipe de emergência e garantir que o socorro chegue rapidamente ao local correto. Continuar sem GPS Abrir Configurações ) const renderErrorModal = () => ( Erro de Localização Não foi possível obter GPS O que fazer? • Verifique se o GPS está ativado{'\n'} • Certifique-se de estar em local aberto{'\n'} • O pânico será acionado mesmo sem localização Acionar sem GPS Tentar Novamente ) return ( <> { const { translationX, translationY } = event.nativeEvent let newX = lastOffset.current.x + translationX let newY = lastOffset.current.y + translationY newX = clamp(newX, 0, width - BUTTON_SIZE) newY = clamp(newY, 0, height - BUTTON_SIZE) translateX.setValue(newX) translateY.setValue(newY) }} onHandlerStateChange={onHandlerStateChange} > {/* Efeito de brilho */} {/* Modais sem Portal */} setCurrentModal(null)} > {renderMainModal()} setCurrentModal(null)} > {renderPermissionModal()} setCurrentModal(null)} > {renderErrorModal()} ) } const styles = StyleSheet.create({ fab: { position: "absolute", width: BUTTON_SIZE, height: BUTTON_SIZE, borderRadius: BUTTON_SIZE / 2, zIndex: 999, }, glowEffect: { position: "absolute", width: BUTTON_SIZE + 16, height: BUTTON_SIZE + 16, borderRadius: (BUTTON_SIZE + 16) / 2, backgroundColor: "#EF4444", top: -8, left: -8, zIndex: -1, }, fabButton: { width: "100%", height: "100%", borderRadius: BUTTON_SIZE / 2, overflow: "hidden", ...SHADOWS.large, }, fabGradient: { width: "100%", height: "100%", alignItems: "center", justifyContent: "center", borderRadius: BUTTON_SIZE / 2, }, fabIconContainer: { width: 32, height: 32, borderRadius: 16, backgroundColor: "rgba(255, 255, 255, 0.2)", alignItems: "center", justifyContent: "center", }, // Estilos do Modalize modalStyle: { backgroundColor: "transparent", }, overlayStyle: { backgroundColor: "rgba(0, 0, 0, 0.6)", }, childrenStyle: { backgroundColor: "transparent", }, modalHandle: { backgroundColor: "rgba(255, 255, 255, 0.3)", width: 40, height: 4, }, // Conteúdo dos modais modalContent: { backgroundColor: COLORS.card, borderTopLeftRadius: 24, borderTopRightRadius: 24, overflow: "hidden", marginBottom: 0, }, modalHeader: { paddingTop: 32, paddingBottom: 24, paddingHorizontal: 24, }, headerContent: { alignItems: "center", }, iconContainer: { marginBottom: 16, }, iconRing: { width: 72, height: 72, borderRadius: 36, backgroundColor: "rgba(255, 255, 255, 0.2)", alignItems: "center", justifyContent: "center", borderWidth: 2, borderColor: "rgba(255, 255, 255, 0.3)", }, modalTitle: { fontSize: 24, fontWeight: "bold", color: "white", marginBottom: 8, textAlign: "center", }, modalSubtitle: { fontSize: 16, color: "rgba(255, 255, 255, 0.9)", textAlign: "center", lineHeight: 22, }, modalBody: { padding: 24, }, // Seção de aviso warningSection: { marginBottom: 24, }, warningCard: { flexDirection: "row", alignItems: "center", padding: 16, borderRadius: 16, borderWidth: 1, borderColor: "#F3E8FF", }, warningIconContainer: { width: 32, height: 32, borderRadius: 16, backgroundColor: "rgba(217, 119, 6, 0.1)", alignItems: "center", justifyContent: "center", marginRight: 12, }, warningText: { fontSize: 14, color: "#92400E", fontWeight: "600", flex: 1, }, // Seção de recursos featuresSection: { marginBottom: 24, }, featuresTitle: { fontSize: 16, fontWeight: "bold", color: COLORS.text, marginBottom: 16, }, featuresList: { gap: 12, }, featureItem: { flexDirection: "row", alignItems: "center", }, featureIcon: { width: 32, height: 32, borderRadius: 16, alignItems: "center", justifyContent: "center", marginRight: 12, }, featureContent: { flex: 1, }, featureTitle: { fontSize: 14, fontWeight: "600", color: COLORS.text, marginBottom: 2, }, featureDescription: { fontSize: 12, color: COLORS.textLight, lineHeight: 16, }, // Seção de loading loadingSection: { marginBottom: 8, }, loadingContainer: { alignItems: "center", paddingVertical: 32, paddingHorizontal: 24, borderRadius: 16, borderWidth: 1, borderColor: "#FEE2E2", }, loadingText: { fontSize: 16, color: COLORS.text, marginTop: 16, fontWeight: "600", }, loadingSubtext: { fontSize: 14, color: COLORS.textLight, marginTop: 4, }, // Seções específicas dos modais permissionSection: { marginBottom: 24, }, permissionCard: { alignItems: "center", padding: 24, borderRadius: 16, borderWidth: 1, borderColor: "#FEF3C7", }, permissionTitle: { fontSize: 16, fontWeight: "bold", color: "#92400E", marginTop: 12, marginBottom: 8, }, permissionText: { fontSize: 14, color: "#92400E", textAlign: "center", lineHeight: 20, }, errorSection: { marginBottom: 24, }, errorCard: { alignItems: "center", padding: 24, borderRadius: 16, borderWidth: 1, borderColor: "#FEE2E2", }, errorTitle: { fontSize: 16, fontWeight: "bold", color: "#DC2626", marginTop: 12, marginBottom: 8, }, errorText: { fontSize: 14, color: "#DC2626", textAlign: "center", lineHeight: 20, }, // Ações actionsSection: { flexDirection: "row", gap: 12, marginBottom: 20, }, cancelButton: { flex: 1, backgroundColor: COLORS.background, borderRadius: 16, paddingVertical: 16, alignItems: "center", borderWidth: 1.5, borderColor: COLORS.border, }, cancelButtonText: { fontSize: 16, color: COLORS.textLight, fontWeight: "600", }, confirmButton: { flex: 1, borderRadius: 16, overflow: "hidden", ...SHADOWS.medium, }, settingsButton: { flex: 1, borderRadius: 16, overflow: "hidden", ...SHADOWS.medium, }, retryButton: { flex: 1, borderRadius: 16, overflow: "hidden", ...SHADOWS.medium, }, confirmButtonGradient: { flexDirection: "row", alignItems: "center", justifyContent: "center", paddingVertical: 16, gap: 8, }, confirmButtonText: { fontSize: 16, color: "white", fontWeight: "bold", }, }) export default EnhancedPanicButton