import React, { useState, useEffect, useRef } from 'react'; import { View, Text, TextInput, TouchableOpacity, FlatList, KeyboardAvoidingView, Platform, ActivityIndicator, SafeAreaView, StatusBar, Image, Dimensions } from 'react-native'; import { FontAwesome5, Ionicons } from '@expo/vector-icons'; import io, { Socket } from 'socket.io-client'; import { useRouter } from 'expo-router'; import * as Haptics from 'expo-haptics'; import Animated, { FadeInRight, FadeInLeft, FadeIn } from 'react-native-reanimated'; // Define types type Message = { id: string; text: string; senderId: string; senderType: string; timestamp: Date; isRead: boolean; }; const PatientChat: React.FC = () => { const router = useRouter(); // State const [message, setMessage] = useState(''); const [messages, setMessages] = useState([]); const [isConnecting, setIsConnecting] = useState(true); const [isTyping, setIsTyping] = useState(false); const [isConnected, setIsConnected] = useState(false); const [inputHeight, setInputHeight] = useState(40); // Refs const socket = useRef(null); const flatListRef = useRef(null); // Generate a random user ID for anonymous patient const patientId = useRef(`user_${Math.random().toString(36).substring(2, 9)}`); // Fixed doctor ID const doctorId = useRef('main_doctor'); const chatId = useRef(`${patientId.current}-${doctorId.current}`); // Setup socket connection useEffect(() => { // Add haptic feedback when component mounts if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } // Connect to your socket server socket.current = io('http://localhost:3000', { transports: ['websocket'], query: { userId: patientId.current, userType: 'patient', } }); // Handle connection socket.current.on('connect', () => { console.log('Connected to chat server'); setIsConnecting(false); // Join chat with doctor joinDoctorChat(); // Add welcome message setMessages([ { id: 'system-welcome', text: 'Connected to healthcare chat. Start a conversation with a doctor.', senderId: 'system', senderType: 'system', timestamp: new Date(), isRead: true, } ]); }); // Handle connection error socket.current.on('connect_error', (error) => { console.log('Connection error:', error); setMessages([ { id: 'system-error', text: 'Failed to connect to the chat server. Please try again later.', senderId: 'system', senderType: 'system', timestamp: new Date(), isRead: true, } ]); setIsConnecting(false); }); // Handle previous messages socket.current.on('previousMessages', ({ chatId, messages: messageHistory }: { chatId: string; messages: Message[] }) => { if (messageHistory && messageHistory.length > 0) { setMessages(prevMessages => [ ...prevMessages, ...messageHistory.map(msg => ({ ...msg, timestamp: new Date(msg.timestamp) })) ]); setIsConnected(true); // Add haptic feedback when messages arrive if (Platform.OS !== 'web') { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } } }); // Handle incoming messages socket.current.on('message', ({ chatId, message: data }) => { // Convert timestamp to Date const newMessage = { ...data, timestamp: new Date(data.timestamp), }; // Add message to chat setMessages(prevMessages => [...prevMessages, newMessage]); setIsTyping(false); setIsConnected(true); // Add haptic feedback when message arrives if (data.senderId !== patientId.current && Platform.OS !== 'web') { Haptics.notificationAsync(Haptics.NotificationFeedbackType.Success); } // Mark message as read if (data.senderId !== patientId.current) { socket.current?.emit('markAsRead', { chatId: chatId.current, messageIds: [data.id], }); } }); // Handle typing indicator socket.current.on('typing', ({ chatId, userId, isTyping: typing }) => { if (userId !== patientId.current) { setIsTyping(typing); } }); // Clean up on unmount return () => { if (socket.current) { socket.current.disconnect(); } }; }, []); // Function to handle input height changes const updateInputHeight = (height: number) => { const newHeight = Math.min(Math.max(40, height), 100); setInputHeight(newHeight); }; // Join chat with a doctor const joinDoctorChat = () => { socket.current?.emit('joinChat', { patientId: patientId.current, doctorId: doctorId.current, }); }; // Send a message const sendMessage = () => { if (!message.trim()) return; // Add haptic feedback on send if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Medium); } // Send to server socket.current?.emit('message', { text: message, chatId: chatId.current, timestamp: new Date(), }); // Clear input setMessage(''); setInputHeight(40); }; // Handle typing const handleTyping = (text: string) => { setMessage(text); // Send typing status to server socket.current?.emit('typing', { chatId: chatId.current, isTyping: text.length > 0, }); }; // Typing indicator component const TypingIndicator = () => { const [dots, setDots] = useState(1); useEffect(() => { const interval = setInterval(() => { setDots(prev => (prev % 3) + 1); }, 500); return () => clearInterval(interval); }, []); return ( Doctor is typing{'.'.repeat(dots)} ); }; // User Message Component const UserMessage = ({ message }: { message: Message }) => ( {message.text} {message.timestamp instanceof Date ? message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} {message.isRead && } ); // Doctor Message Component const DoctorMessage = ({ message }: { message: Message }) => ( {message.text} {message.timestamp instanceof Date ? message.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : new Date(message.timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })} ); // System Message Component const SystemMessage = ({ message }: { message: Message }) => ( {message.text} ); // Render message based on sender const renderMessage = ({ item }: { item: Message }) => { if (item.senderId === 'system') { return ; } else if (item.senderId === patientId.current) { return ; } else { return ; } }; return ( {isConnecting ? ( Connecting to doctor... ) : ( {/* Chat header */} { if (Platform.OS !== 'web') { Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); } router.back(); }} style={{ padding: 8 }} > Professional Chat {isConnected ? 'Doctor is online' : 'Connecting to doctor...'} {/* Messages list */} item.id} contentContainerStyle={{ paddingVertical: 12, flexGrow: 1 }} onContentSizeChange={() => flatListRef.current?.scrollToEnd({ animated: true })} onLayout={() => flatListRef.current?.scrollToEnd({ animated: true })} style={{ flex: 1 }} /> {/* Typing indicator */} {isTyping && } {/* Message input */} updateInputHeight(e.nativeEvent.contentSize.height) } /> )} ); }; export default PatientChat;