11 changed files with 563 additions and 93 deletions
Unified 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