11 changed files with 563 additions and 93 deletions
Split View
Diff Options
-
85lib/controller/discover/room_controller.dart
-
37lib/model/live/live_chat_message.dart
-
37lib/pages/discover/live_room_page.dart
-
11lib/pages/home/nearby_tab.dart
-
11lib/pages/home/recommend_tab.dart
-
47lib/rtc/rtm_manager.dart
-
250lib/service/live_chat_message_service.dart
-
32lib/widget/live/live_room_action_bar.dart
-
40lib/widget/live/live_room_chat_item.dart
-
79lib/widget/live/live_room_notice_chat_panel.dart
-
27lib/widget/live/live_room_user_header.dart
@ -0,0 +1,37 @@ |
|||
/// 直播间聊天消息模型 |
|||
class LiveChatMessage { |
|||
final String userId; |
|||
final String userName; |
|||
final String? avatar; |
|||
final String content; |
|||
final int timestamp; |
|||
|
|||
LiveChatMessage({ |
|||
required this.userId, |
|||
required this.userName, |
|||
this.avatar, |
|||
required this.content, |
|||
required this.timestamp, |
|||
}); |
|||
|
|||
factory LiveChatMessage.fromJson(Map<String, dynamic> json) { |
|||
return LiveChatMessage( |
|||
userId: json['userId'] ?? json['uid'] ?? '', |
|||
userName: json['userName'] ?? json['nickName'] ?? '用户', |
|||
avatar: json['avatar'] ?? json['profilePhoto'], |
|||
content: json['content'] ?? json['message'] ?? '', |
|||
timestamp: json['timestamp'] ?? DateTime.now().millisecondsSinceEpoch, |
|||
); |
|||
} |
|||
|
|||
Map<String, dynamic> toJson() { |
|||
return { |
|||
'userId': userId, |
|||
'userName': userName, |
|||
'avatar': avatar, |
|||
'content': content, |
|||
'timestamp': timestamp, |
|||
}; |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,250 @@ |
|||
import 'dart:convert'; |
|||
import 'dart:typed_data'; |
|||
import 'package:agora_rtm/agora_rtm.dart'; |
|||
import 'package:dating_touchme_app/controller/global.dart'; |
|||
import 'package:dating_touchme_app/model/live/live_chat_message.dart'; |
|||
import 'package:dating_touchme_app/rtc/rtc_manager.dart'; |
|||
import 'package:dating_touchme_app/rtc/rtm_manager.dart'; |
|||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; |
|||
import 'package:get_storage/get_storage.dart'; |
|||
|
|||
/// 直播间聊天消息服务 |
|||
/// 负责消息的发送、接收、解析和管理 |
|||
class LiveChatMessageService { |
|||
// 单例模式 |
|||
static final LiveChatMessageService _instance = LiveChatMessageService._internal(); |
|||
factory LiveChatMessageService() => _instance; |
|||
static LiveChatMessageService get instance => _instance; |
|||
|
|||
final GetStorage _storage = GetStorage(); |
|||
bool _isListenerRegistered = false; |
|||
|
|||
// 消息监听回调 |
|||
Function(LiveChatMessage message)? onMessageReceived; |
|||
Function(String error)? onMessageError; |
|||
|
|||
LiveChatMessageService._internal(); |
|||
|
|||
/// 注册消息监听器 |
|||
void registerMessageListener({ |
|||
Function(LiveChatMessage message)? onMessageReceived, |
|||
Function(String error)? onMessageError, |
|||
}) { |
|||
if (_isListenerRegistered) { |
|||
print('⚠️ 消息监听器已注册,跳过重复注册'); |
|||
return; |
|||
} |
|||
|
|||
this.onMessageReceived = onMessageReceived; |
|||
this.onMessageError = onMessageError; |
|||
|
|||
RTMManager.instance.onMessageEvent = (MessageEvent event) { |
|||
_handleIncomingMessage(event); |
|||
}; |
|||
|
|||
_isListenerRegistered = true; |
|||
print('✅ 消息监听器注册完成'); |
|||
} |
|||
|
|||
/// 移除消息监听器 |
|||
void unregisterMessageListener() { |
|||
RTMManager.instance.onMessageEvent = null; |
|||
_isListenerRegistered = false; |
|||
onMessageReceived = null; |
|||
onMessageError = null; |
|||
print('✅ 消息监听器已移除'); |
|||
} |
|||
|
|||
/// 处理接收到的消息 |
|||
void _handleIncomingMessage(MessageEvent event) { |
|||
try { |
|||
// 解析消息内容 |
|||
final messageText = _parseMessageContent(event.message); |
|||
final messageData = json.decode(messageText) as Map<String, dynamic>; |
|||
|
|||
// 只处理聊天消息类型 |
|||
if (messageData['type'] == 'chat_message') { |
|||
final chatMessage = LiveChatMessage.fromJson(messageData); |
|||
onMessageReceived?.call(chatMessage); |
|||
} |
|||
} catch (e, stackTrace) { |
|||
final error = '解析RTM消息失败: $e'; |
|||
print('❌ $error'); |
|||
print('❌ 堆栈信息: $stackTrace'); |
|||
onMessageError?.call(error); |
|||
} |
|||
} |
|||
|
|||
/// 解析消息内容(支持 String 和 Uint8List) |
|||
String _parseMessageContent(dynamic message) { |
|||
if (message is String) { |
|||
return message; |
|||
} else if (message is Uint8List) { |
|||
return utf8.decode(message); |
|||
} else { |
|||
return message.toString(); |
|||
} |
|||
} |
|||
|
|||
/// 构建消息数据 |
|||
Map<String, dynamic> _buildMessageData(String content) { |
|||
final userId = _storage.read('userId') ?? GlobalData().userId ?? ''; |
|||
final userData = GlobalData().userData; |
|||
final userName = userData?.nickName ?? '用户'; |
|||
final avatar = userData?.profilePhoto; |
|||
|
|||
return { |
|||
'type': 'chat_message', |
|||
'userId': userId, |
|||
'userName': userName, |
|||
'avatar': avatar, |
|||
'content': content.trim(), |
|||
'timestamp': DateTime.now().millisecondsSinceEpoch, |
|||
}; |
|||
} |
|||
|
|||
/// 验证消息内容 |
|||
String? _validateMessage(String content) { |
|||
final trimmed = content.trim(); |
|||
if (trimmed.isEmpty) { |
|||
return '消息内容不能为空'; |
|||
} |
|||
if (trimmed.length > 500) { |
|||
return '消息内容不能超过500个字符'; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/// 检查发送前置条件 |
|||
String? _checkSendPreconditions(String channelName) { |
|||
if (!RTMManager.instance.isInitialized) { |
|||
return 'RTM 未初始化,无法发送消息'; |
|||
} |
|||
if (!RTMManager.instance.isLoggedIn) { |
|||
return 'RTM 未登录,无法发送消息'; |
|||
} |
|||
if (channelName.isEmpty) { |
|||
return '未加入频道,无法发送消息'; |
|||
} |
|||
return null; |
|||
} |
|||
|
|||
/// 发送聊天消息 |
|||
/// |
|||
/// [content] 消息内容 |
|||
/// [channelName] 频道名称,如果为空则从 RTCManager 获取 |
|||
/// [showToast] 是否显示 Toast 提示,默认为 true |
|||
/// |
|||
/// 返回发送结果,true 表示成功,false 表示失败 |
|||
Future<MessageSendResult> sendMessage({ |
|||
required String content, |
|||
String? channelName, |
|||
bool showToast = true, |
|||
}) async { |
|||
// 验证消息内容 |
|||
final validationError = _validateMessage(content); |
|||
if (validationError != null) { |
|||
if (showToast) { |
|||
SmartDialog.showToast(validationError); |
|||
} |
|||
return MessageSendResult.failure(validationError); |
|||
} |
|||
|
|||
// 获取频道名称 |
|||
final targetChannelName = channelName ?? |
|||
RTCManager.instance.currentChannelId ?? ''; |
|||
|
|||
// 检查前置条件 |
|||
final preconditionError = _checkSendPreconditions(targetChannelName); |
|||
if (preconditionError != null) { |
|||
print('❌ $preconditionError'); |
|||
if (showToast) { |
|||
SmartDialog.showToast(preconditionError); |
|||
} |
|||
return MessageSendResult.failure(preconditionError); |
|||
} |
|||
|
|||
try { |
|||
// 构建消息数据 |
|||
final messageData = _buildMessageData(content); |
|||
final messageJson = json.encode(messageData); |
|||
|
|||
print('📤 发送消息到频道: $targetChannelName'); |
|||
print('📤 消息内容: $messageJson'); |
|||
|
|||
// 通过 RTM 发送消息 |
|||
final success = await RTMManager.instance.publishChannelMessage( |
|||
channelName: targetChannelName, |
|||
message: messageJson, |
|||
); |
|||
|
|||
if (success) { |
|||
print('✅ 消息发送成功'); |
|||
// 创建消息对象(用于立即显示) |
|||
final chatMessage = LiveChatMessage( |
|||
userId: messageData['userId'] as String, |
|||
userName: messageData['userName'] as String, |
|||
avatar: messageData['avatar'] as String?, |
|||
content: messageData['content'] as String, |
|||
timestamp: messageData['timestamp'] as int, |
|||
); |
|||
return MessageSendResult.success(chatMessage); |
|||
} else { |
|||
final error = '消息发送失败,请重试'; |
|||
print('❌ $error'); |
|||
if (showToast) { |
|||
SmartDialog.showToast(error); |
|||
} |
|||
return MessageSendResult.failure(error); |
|||
} |
|||
} catch (e, stackTrace) { |
|||
final error = '发送消息异常: $e'; |
|||
print('❌ $error'); |
|||
print('❌ 堆栈信息: $stackTrace'); |
|||
if (showToast) { |
|||
SmartDialog.showToast(error); |
|||
} |
|||
return MessageSendResult.failure(error); |
|||
} |
|||
} |
|||
|
|||
/// 创建消息对象(用于本地显示) |
|||
LiveChatMessage createLocalMessage(String content) { |
|||
final messageData = _buildMessageData(content); |
|||
return LiveChatMessage( |
|||
userId: messageData['userId'] as String, |
|||
userName: messageData['userName'] as String, |
|||
avatar: messageData['avatar'] as String?, |
|||
content: messageData['content'] as String, |
|||
timestamp: messageData['timestamp'] as int, |
|||
); |
|||
} |
|||
} |
|||
|
|||
/// 消息发送结果 |
|||
class MessageSendResult { |
|||
final bool success; |
|||
final LiveChatMessage? message; |
|||
final String? error; |
|||
|
|||
MessageSendResult._({ |
|||
required this.success, |
|||
this.message, |
|||
this.error, |
|||
}); |
|||
|
|||
factory MessageSendResult.success(LiveChatMessage message) { |
|||
return MessageSendResult._( |
|||
success: true, |
|||
message: message, |
|||
); |
|||
} |
|||
|
|||
factory MessageSendResult.failure(String error) { |
|||
return MessageSendResult._( |
|||
success: false, |
|||
error: error, |
|||
); |
|||
} |
|||
} |
|||
|
|||
Write
Preview
Loading…
Cancel
Save