import 'package:flutter/material.dart'; import 'package:firebase_database/firebase_database.dart'; import 'dart:async'; enum ChatType { all, // 전체 채팅 team // 팀 채팅 } class ChatMessage { final String message; final String senderNickname; final int timestamp; ChatMessage({ required this.message, required this.senderNickname, required this.timestamp, }); factory ChatMessage.fromSnapshot(DataSnapshot snapshot) { final data = snapshot.value as Map; return ChatMessage( message: data['message'] ?? '', senderNickname: data['sender_nickname'] ?? data['sender_name'] ?? '', timestamp: data['timestamp'] ?? 0, ); } } class ChatDialog extends StatefulWidget { const ChatDialog({ Key? key, required this.roomType, required this.roomStatus, required this.roomSeq, required this.teamName, required this.myNickname, }) : super(key: key); final String roomType; final String roomStatus; final String roomSeq; final String teamName; // 사용자의 팀 이름 final String myNickname; // 사용자 닉네임 @override State createState() => _ChatDialogState(); } class _ChatDialogState extends State { ChatType _selectedChatType = ChatType.all; final TextEditingController _messageController = TextEditingController(); final ScrollController _scrollController = ScrollController(); List _messages = []; late DatabaseReference _chatRef; StreamSubscription? _chatSubscription; bool get _isTeamChatEnabled => widget.roomType.toUpperCase() == 'TEAM'; @override void initState() { super.initState(); _initChatRef(); } void _initChatRef() { _subscribeToChatMessages(); } void _subscribeToChatMessages() { _chatSubscription?.cancel(); final roomKey = 'korea-${widget.roomSeq}'; final path = _selectedChatType == ChatType.all ? 'rooms/$roomKey/chats/all' : 'rooms/$roomKey/chats/team/${widget.teamName}'; _chatRef = FirebaseDatabase.instance.ref(path); // orderByChild('timestamp')를 사용하여 timestamp 기준으로 정렬 _chatSubscription = _chatRef .orderByChild('timestamp') .onValue .listen((event) { if (!event.snapshot.exists) { setState(() => _messages = []); return; } try { final messagesMap = event.snapshot.value as Map; final List messages = []; messagesMap.forEach((key, value) { if (value is Map) { messages.add(ChatMessage( message: value['message'] ?? '', senderNickname: value['sender_nickname'] ?? '', timestamp: value['timestamp'] ?? 0, )); } }); // timestamp 기준으로 정렬 messages.sort((a, b) => a.timestamp.compareTo(b.timestamp)); setState(() => _messages = messages); _scrollToBottom(); } catch (e) { setState(() => _messages = []); } }); } void _scrollToBottom() { if (_scrollController.hasClients) { _scrollController.animateTo( _scrollController.position.maxScrollExtent, duration: const Duration(milliseconds: 300), curve: Curves.easeOut, ); } } Future _sendMessage() async { if (_messageController.text.trim().isEmpty) return; try { final timestamp = DateTime.now().millisecondsSinceEpoch; final messageId = 'id$timestamp'; // timestamp를 이용한 메시지 ID 생성 final message = _messageController.text.trim(); final messageData = { 'message': message, 'sender_nickname': widget.myNickname, 'timestamp': timestamp, }; // messageId를 사용하여 직접 참조 생성 final newMessageRef = _chatRef.child(messageId); await newMessageRef.set(messageData); _messageController.clear(); _scrollToBottom(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('message error: ${e.toString()}')), ); } } } @override void dispose() { _chatSubscription?.cancel(); _messageController.dispose(); _scrollController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return Dialog( backgroundColor: Colors.white, child: Container( width: MediaQuery.of(context).size.width * 0.8, height: MediaQuery.of(context).size.height * 0.7, padding: const EdgeInsets.all(16.0), child: Column( children: [ // 모달 헤더 Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ const Text( 'Chat', style: TextStyle( fontSize: 20, fontWeight: FontWeight.bold, ), ), IconButton( icon: const Icon(Icons.close), onPressed: () => Navigator.pop(context), ), ], ), // 채팅 타입 선택 탭 - TEAM 타입일 때만 표시 if (_isTeamChatEnabled) ...[ Container( margin: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(25), border: Border.all( color: Colors.grey[300]!, ), ), child: Row( children: [ Expanded( child: GestureDetector( onTap: () { setState(() => _selectedChatType = ChatType.all); _subscribeToChatMessages(); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( color: _selectedChatType == ChatType.all ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(25), boxShadow: _selectedChatType == ChatType.all ? [ BoxShadow( color: Colors.grey.withOpacity(0.1), blurRadius: 4, offset: const Offset(0, 2), ) ] : null, ), child: const Text( 'Global Chat', textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold), ), ), ), ), Expanded( child: GestureDetector( onTap: () { setState(() => _selectedChatType = ChatType.team); _subscribeToChatMessages(); }, child: Container( padding: const EdgeInsets.symmetric(vertical: 8.0), decoration: BoxDecoration( color: _selectedChatType == ChatType.team ? Colors.white : Colors.transparent, borderRadius: BorderRadius.circular(25), boxShadow: _selectedChatType == ChatType.team ? [ BoxShadow( color: Colors.grey.withOpacity(0.3), blurRadius: 4, offset: const Offset(0, 2), ) ] : null, ), child: const Text( 'Team Chat', textAlign: TextAlign.center, style: TextStyle(fontWeight: FontWeight.bold), ), ), ), ), ], ), ), Divider(color: Colors.grey[300]), ], // 채팅 내용이 표시될 영역 Expanded( child: Container( padding: const EdgeInsets.all(8.0), decoration: BoxDecoration( color: Colors.grey[100], borderRadius: BorderRadius.circular(12), border: Border.all(color: Colors.grey[200]!), ), child: ListView.builder( controller: _scrollController, itemCount: _messages.length, itemBuilder: (context, index) { final message = _messages[index]; final isMyMessage = message.senderNickname == widget.myNickname; return Padding( padding: const EdgeInsets.symmetric(vertical: 4.0), child: Column( crossAxisAlignment: isMyMessage ? CrossAxisAlignment.end : CrossAxisAlignment.start, children: [ if (!isMyMessage) Padding( padding: const EdgeInsets.only(left: 12.0, bottom: 4.0), child: Text( message.senderNickname, style: const TextStyle( fontSize: 12, color: Colors.grey, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, maxLines: 1, ), ), ConstrainedBox( constraints: BoxConstraints( maxWidth: MediaQuery.of(context).size.width * 0.7, ), child: Container( padding: const EdgeInsets.symmetric( horizontal: 12, vertical: 8, ), decoration: BoxDecoration( color: isMyMessage ? Colors.grey[800] : Colors.white, borderRadius: BorderRadius.circular(16), border: Border.all( color: Colors.grey[300]!, ), ), child: Text( message.message, style: TextStyle( fontSize: 14, height: 1.3, color: isMyMessage ? Colors.white : Colors.black, ), softWrap: true, ), ), ), ], ), ); }, ), ), ), // 메시지 입력 영역 Container( padding: const EdgeInsets.symmetric(vertical: 8.0), child: Row( children: [ Expanded( child: TextField( controller: _messageController, decoration: InputDecoration( hintText: 'Enter a message', border: OutlineInputBorder( borderRadius: BorderRadius.circular(20), ), contentPadding: const EdgeInsets.symmetric( horizontal: 16, vertical: 8, ), ), onSubmitted: (_) => _sendMessage(), ), ), const SizedBox(width: 8), IconButton( icon: const Icon( Icons.send, color: Colors.black, // 아이콘 색상을 검은색으로 ), onPressed: _sendMessage, ), ], ), ), ], ), ), ); } } class ChatButton extends StatelessWidget { const ChatButton({ Key? key, required this.roomType, required this.roomStatus, required this.roomSeq, required this.teamName, required this.myNickname, }) : super(key: key); final String roomType; final String roomStatus; final String roomSeq; final String teamName; final String myNickname; void _openChatDialog(BuildContext context) { showDialog( context: context, barrierDismissible: false, builder: (context) => ChatDialog( roomType: roomType, roomStatus: roomStatus, roomSeq: roomSeq, teamName: teamName, myNickname: myNickname, ), ); } @override Widget build(BuildContext context) { return FloatingActionButton( onPressed: () => _openChatDialog(context), child: const Icon( Icons.chat, color: Colors.white, // 아이콘 색상을 흰색으로 ), backgroundColor: Colors.black, // 버튼 배경색을 검은색으로 mini: true, ); } }