allscore_app/lib/dialogs/chat_dialog.dart

392 lines
13 KiB
Dart

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<dynamic, dynamic>;
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<ChatDialog> createState() => _ChatDialogState();
}
class _ChatDialogState extends State<ChatDialog> {
ChatType _selectedChatType = ChatType.all;
final TextEditingController _messageController = TextEditingController();
final ScrollController _scrollController = ScrollController();
List<ChatMessage> _messages = [];
late DatabaseReference _chatRef;
StreamSubscription<DatabaseEvent>? _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);
// orderByKey()를 사용하여 메시지 ID(timestamp) 기준으로 정렬
_chatSubscription = _chatRef
.orderByKey()
.onValue
.listen((event) {
if (!event.snapshot.exists) {
setState(() => _messages = []);
return;
}
try {
final messagesMap = event.snapshot.value as Map<dynamic, dynamic>;
final List<ChatMessage> messages = [];
messagesMap.forEach((key, value) {
if (value is Map) {
messages.add(ChatMessage(
message: value['message'] ?? '',
senderNickname: value['sender_nickname'] ?? '',
timestamp: value['timestamp'] ?? 0,
));
}
});
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<void> _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(
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[200],
borderRadius: BorderRadius.circular(25),
),
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.3),
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),
),
),
),
),
],
),
),
const Divider(),
],
// 채팅 내용이 표시될 영역
Expanded(
child: Container(
padding: const EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(12),
),
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: Row(
mainAxisAlignment: isMyMessage
? MainAxisAlignment.end
: MainAxisAlignment.start,
children: [
if (!isMyMessage) ...[
Text(
message.senderNickname,
style: const TextStyle(
fontSize: 12,
color: Colors.grey,
),
),
const SizedBox(width: 8),
],
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
decoration: BoxDecoration(
color: isMyMessage ? Colors.blue[100] : Colors.white,
borderRadius: BorderRadius.circular(16),
border: Border.all(
color: Colors.grey[300]!,
),
),
child: Text(message.message),
),
],
),
);
},
),
),
),
// 메시지 입력 영역
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),
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),
mini: true,
);
}
}