import React, { useState, useEffect, useRef, useCallback } from 'react' import { View, Text, TextInput, TouchableOpacity, FlatList, StyleSheet, KeyboardAvoidingView, Platform, Alert, } from 'react-native' import * as SecureStore from 'expo-secure-store' import { COLORS, WS_BASE } from '../../constants/Config' import { getSRChat, sendSRChat } from '../../services/api' export default function SRChatRoomScreen() { const [srId, setSrId] = useState('') const [joined, setJoined] = useState(false) const [msgs, setMsgs] = useState([]) const [input, setInput] = useState('') const [sending, setSending] = useState(false) const wsRef = useRef(null) const flatRef = useRef(null) const join = useCallback(async () => { const id = parseInt(srId, 10) if (!id) { Alert.alert('오류', 'SR 번호를 입력해주세요.'); return } try { const r = await getSRChat(id) setMsgs(r.data?.items ?? r.data ?? []) setJoined(true) // WebSocket 연결 const token = await SecureStore.getItemAsync('grd_token') const ws = new WebSocket(`${WS_BASE}/ws/sr-chat/${id}?token=${token ?? ''}`) ws.onmessage = e => { try { const msg = JSON.parse(e.data) if (msg.content) setMsgs(prev => [...prev, msg]) } catch {} } ws.onerror = () => {} wsRef.current = ws } catch { Alert.alert('오류', 'SR 채팅방을 열 수 없습니다.') } }, [srId]) useEffect(() => () => { wsRef.current?.close() }, []) const send = async () => { if (!input.trim() || sending) return setSending(true) try { await sendSRChat(parseInt(srId, 10), input.trim()) setMsgs(prev => [...prev, { id: Date.now(), content: input.trim(), sender: 'me', created_at: new Date().toISOString(), msg_type: 'text' }]) setInput('') setTimeout(() => flatRef.current?.scrollToEnd(), 100) } catch {} finally { setSending(false) } } if (!joined) { return ( SR 채팅방 SR 번호를 입력하면 해당 SR의 채팅방에 연결됩니다. 채팅방 참여 ) } return ( SR #{srId} 채팅 { setJoined(false); wsRef.current?.close() }}> 나가기 String(i)} contentContainerStyle={{ padding: 12 }} renderItem={({ item }) => { const isMe = item.sender === 'me' || item.sender_name === 'me' return ( {!isMe && {item.sender_name ?? item.sender}} {item.content} {item.created_at?.slice(11, 16)} ) }} /> 전송 ) } const s = StyleSheet.create({ joinContainer: { flex: 1, backgroundColor: COLORS.bg, padding: 24, justifyContent: 'center' }, joinTitle: { fontSize: 22, fontWeight: '800', color: COLORS.text, marginBottom: 8 }, joinDesc: { fontSize: 14, color: COLORS.muted, marginBottom: 24, lineHeight: 22 }, joinInput: { backgroundColor: '#fff', borderRadius: 10, borderWidth: 1, borderColor: COLORS.border, padding: 14, fontSize: 15, marginBottom: 12 }, joinBtn: { backgroundColor: COLORS.accent, borderRadius: 10, padding: 14, alignItems: 'center' }, joinBtnText: { color: '#fff', fontWeight: '800', fontSize: 15 }, container: { flex: 1, backgroundColor: COLORS.bg }, header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', backgroundColor: COLORS.gnbBg, padding: 14 }, headerTitle: { fontSize: 15, fontWeight: '700', color: '#fff' }, leave: { color: COLORS.accent, fontSize: 14 }, msgWrap: { marginBottom: 10, maxWidth: '80%' }, msgWrapRight: { alignSelf: 'flex-end' }, sender: { fontSize: 11, color: COLORS.muted, marginBottom: 2 }, bubble: { backgroundColor: '#fff', borderRadius: 12, padding: 10, elevation: 1 }, bubbleRight: { backgroundColor: COLORS.accent }, msgText: { fontSize: 14, color: COLORS.text }, msgTextRight: { color: '#fff' }, msgTime: { fontSize: 10, color: COLORS.muted, marginTop: 2 }, inputRow: { flexDirection: 'row', padding: 10, backgroundColor: '#fff', borderTopWidth: 1, borderTopColor: COLORS.border, gap: 8 }, textInput: { flex: 1, backgroundColor: COLORS.bg, borderRadius: 20, paddingHorizontal: 14, paddingVertical: 8, fontSize: 14, maxHeight: 100 }, sendBtn: { backgroundColor: COLORS.accent, borderRadius: 20, paddingHorizontal: 16, justifyContent: 'center' }, sendBtnText: { color: '#fff', fontWeight: '700' }, })