Browse Source

feat(call): 实现通话功能并优化消息处理

- 添加音频播放器支持来电铃声循环播放
- 实现通话消息状态更新和消息修改功能
- 优化会话列表加载和刷新防抖机制
- 支持自定义通话消息格式替换旧文本格式
- 添加通话音频播放和停止控制
- 优化直播间小窗口显示和隐藏逻辑
- 实现通话邀请弹框和消息解析功能
- 添加资产文件支持音频资源
- 优化消息通知对话框支持emoji显示
- 移除旧格式消息处理逻辑简化代码结构
master
Jolie 3 months ago
parent
commit
b6915f264d
14 changed files with 439 additions and 329 deletions
  1. BIN
      assets/audio/call.mp3
  2. 158
      lib/controller/message/call_manager.dart
  3. 93
      lib/controller/message/conversation_controller.dart
  4. 1
      lib/generated/assets.dart
  5. 342
      lib/im/im_manager.dart
  6. 21
      lib/pages/discover/live_room_page.dart
  7. 52
      lib/pages/message/conversation_tab.dart
  8. 27
      lib/widget/live/draggable_overlay_widget.dart
  9. 15
      lib/widget/message/call_item.dart
  10. 13
      lib/widget/message/gift_item.dart
  11. 18
      lib/widget/message/message_item.dart
  12. 12
      lib/widget/message/message_notification_dialog.dart
  13. 15
      lib/widget/message/text_item.dart
  14. 1
      pubspec.yaml

BIN
assets/audio/call.mp3

158
lib/controller/message/call_manager.dart

@ -1,9 +1,10 @@
import 'dart:async';
import 'dart:convert';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
import '../../generated/assets.dart';
import '../../im/im_manager.dart';
import 'chat_controller.dart';
@ -37,9 +38,25 @@ class CallManager extends GetxController {
Timer? _callTimer;
int _callDurationSeconds = 0;
final RxInt callDurationSeconds = RxInt(0);
//
final AudioPlayer _callAudioPlayer = AudioPlayer();
bool _isPlayingCallAudio = false;
CallManager() {
print('📞 [CallManager] 通话管理器已初始化');
//
_callAudioPlayer.onPlayerComplete.listen((_) async {
if (_isPlayingCallAudio) {
//
try {
await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', '')));
} catch (e) {
print('❌ [CallManager] 循环播放来电铃声失败: $e');
_isPlayingCallAudio = false;
}
}
});
}
///
@ -78,6 +95,11 @@ class CallManager extends GetxController {
chatController: chatController,
);
//
if (callType == CallType.video) {
startCallAudio();
}
// TODO: SDK
// await RTCManager.instance.startCall(targetUserId, callType);
@ -119,6 +141,9 @@ class CallManager extends GetxController {
);
currentCall.value = session;
//
stopCallAudio();
//
_startCallTimer();
@ -151,6 +176,9 @@ class CallManager extends GetxController {
try {
print('📞 [CallManager] 拒绝通话');
//
stopCallAudio();
//
await _updateCallMessageStatus(
message: message,
@ -191,6 +219,9 @@ class CallManager extends GetxController {
);
}
//
stopCallAudio();
//
_stopCallTimer();
@ -219,6 +250,9 @@ class CallManager extends GetxController {
try {
print('📞 [CallManager] 结束通话,时长: ${callDuration}');
//
stopCallAudio();
//
_stopCallTimer();
@ -305,7 +339,7 @@ class CallManager extends GetxController {
}
}
///
/// 使modifyMessage修改现有消息
Future<bool> _updateCallMessageStatus({
required EMMessage message,
required String callStatus,
@ -320,26 +354,80 @@ class CallManager extends GetxController {
}
final callType = callInfo['callType'] as String? ?? 'voice';
final targetUserId = message.from ?? message.to ?? '';
final messageId = message.msgId;
//
return await _sendCallMessage(
targetUserId: targetUserId,
callType: callType,
callStatus: callStatus,
callDuration: callDuration,
chatController: chatController,
);
if (messageId.isEmpty) {
print('❌ [CallManager] 消息ID为空,无法修改消息');
return false;
}
// 使modifyMessage修改
if (message.body.type == MessageType.CUSTOM) {
//
final callParams = <String, String>{
'callType': callType,
'callStatus': callStatus,
};
if (callDuration != null) {
callParams['callDuration'] = callDuration.toString();
}
//
final customBody = EMCustomMessageBody(
event: 'call',
params: callParams,
);
// 使modifyMessage修改消息
final success = await IMManager.instance.modifyMessage(
messageId: messageId,
msgBody: customBody,
attributes: null, //
);
if (success) {
print('✅ [CallManager] 消息修改成功: messageId=$messageId, callStatus=$callStatus');
// chatController
if (chatController != null) {
//
try {
final index = chatController.messages.indexWhere((msg) => msg.msgId == messageId);
if (index != -1) {
final updatedMessage = chatController.messages[index];
if (updatedMessage.body.type == MessageType.CUSTOM) {
final customBody = updatedMessage.body as EMCustomMessageBody;
// Map并更新
final updatedParams = Map<String, String>.from(customBody.params ?? {});
updatedParams['callType'] = callType;
updatedParams['callStatus'] = callStatus;
if (callDuration != null) {
updatedParams['callDuration'] = callDuration.toString();
}
// EMCustomMessageBody的params可能是只读的
// UI更新onMessageContentChanged回调时处理
chatController.update();
}
}
} catch (e) {
print('⚠️ [CallManager] 更新本地消息列表失败: $e');
}
}
}
return success;
}
//
return false;
} catch (e) {
print('❌ [CallManager] 更新通话消息状态失败: $e');
return false;
}
}
///
///
Map<String, dynamic>? _parseCallInfo(EMMessage message) {
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'call' && customBody.params != null) {
@ -353,15 +441,6 @@ class CallManager extends GetxController {
};
}
}
//
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
if (content != null && content.startsWith('[CALL:]')) {
final jsonStr = content.substring(7); // '[CALL:]'
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
}
} catch (e) {
print('解析通话信息失败: $e');
}
@ -374,10 +453,45 @@ class CallManager extends GetxController {
///
int get currentCallDuration => callDurationSeconds.value;
///
///
Future<void> startCallAudio() async {
if (_isPlayingCallAudio) {
return; //
}
try {
_isPlayingCallAudio = true;
print('🔊 [CallManager] 开始播放来电铃声');
await _callAudioPlayer.play(AssetSource(Assets.audioCall.replaceFirst('assets/', '')));
} catch (e) {
print('❌ [CallManager] 播放来电铃声失败: $e');
_isPlayingCallAudio = false;
}
}
///
///
Future<void> stopCallAudio() async {
if (!_isPlayingCallAudio) {
return; //
}
try {
_isPlayingCallAudio = false;
print('🔇 [CallManager] 停止播放来电铃声');
await _callAudioPlayer.stop();
} catch (e) {
print('❌ [CallManager] 停止播放来电铃声失败: $e');
}
}
@override
void onClose() {
stopCallAudio();
_stopCallTimer();
currentCall.value = null;
_callAudioPlayer.dispose();
super.onClose();
}
}

93
lib/controller/message/conversation_controller.dart

@ -1,4 +1,4 @@
import 'dart:convert';
import 'dart:async';
import 'package:get/get.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
import '../../im/im_manager.dart';
@ -62,6 +62,9 @@ class ConversationController extends GetxController {
//
final totalUnreadCount = 0.obs;
//
Timer? _refreshDebounceTimer;
/// ChatController
void cacheUserInfo(String userId, ExtendedUserInfo userInfo) {
_userInfoCache[userId] = userInfo;
@ -118,8 +121,8 @@ class ConversationController extends GetxController {
}
///
Future<void> loadConversations() async {
if (isLoading.value) return;
Future<void> loadConversations({bool showLoading = true}) async {
if (isLoading.value && showLoading) return;
// IM
if (!IMManager.instance.isLoggedIn) {
@ -132,7 +135,10 @@ class ConversationController extends GetxController {
}
try {
isLoading.value = true;
//
if (showLoading) {
isLoading.value = true;
}
errorMessage.value = '';
// IMManager获取会话列表
@ -160,7 +166,9 @@ class ConversationController extends GetxController {
}
errorMessage.value = '加载会话列表失败,请稍后重试';
} finally {
isLoading.value = false;
if (showLoading) {
isLoading.value = false;
}
}
}
@ -297,8 +305,28 @@ class ConversationController extends GetxController {
}
}
///
Future<void> refreshConversations() async {
@override
void onClose() {
_refreshDebounceTimer?.cancel();
super.onClose();
}
///
Future<void> refreshConversations({bool force = false}) async {
// 使
if (!force && isLoading.value) {
//
_refreshDebounceTimer?.cancel();
// 300ms后刷新
_refreshDebounceTimer = Timer(const Duration(milliseconds: 300), () {
refreshConversations(force: true);
});
return;
}
//
_refreshDebounceTimer?.cancel();
// IM未登录
if (!IMManager.instance.isLoggedIn) {
if (Get.isLogEnable) {
@ -361,8 +389,8 @@ class ConversationController extends GetxController {
return;
}
} else {
//
await loadConversations();
//
await loadConversations(showLoading: false);
}
}
@ -390,33 +418,7 @@ class ConversationController extends GetxController {
if(message.body.type == MessageType.TXT){
final body = message.body as EMTextMessageBody;
final content = body.content;
// CALL消息
if (content != null && content.startsWith('[CALL:]')) {
try {
final jsonStr = content.substring(7); // '[CALL:]'
final callInfo = jsonDecode(jsonStr) as Map<String, dynamic>;
final callType = callInfo['callType'] as String?;
if (callType == 'video') {
return '[视频通话]';
} else if (callType == 'voice') {
return '[语音通话]';
}
} catch (e) {
//
if (Get.isLogEnable) {
Get.log('⚠️ [ConversationController] 解析CALL消息失败: $e');
}
}
}
// GIFT消息
if (content != null && content.startsWith('[GIFT:]')) {
return '[礼物]';
}
return content ?? '';
return body.content;
}else if(message.body.type == MessageType.IMAGE){
return '[图片]';
}else if(message.body.type == MessageType.VOICE){
@ -432,6 +434,25 @@ class ConversationController extends GetxController {
//
if(body.event == 'live_room_invite'){
return '[分享房间]';
} else if (body.event == 'gift') {
return '[礼物]';
} else if (body.event == 'call') {
//
try {
if (body.params != null) {
final callType = body.params!['callType'] ?? 'voice';
if (callType == 'video') {
return '[视频通话]';
} else if (callType == 'voice') {
return '[语音通话]';
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [ConversationController] 解析通话消息类型失败: $e');
}
}
return '[通话消息]';
}
return '[自定义消息]';
}

1
lib/generated/assets.dart

@ -2,6 +2,7 @@
class Assets {
Assets._();
static const String audioCall = 'assets/audio/call.mp3';
static const String emojiEmoji01 = 'assets/images/emoji/emoji_01.png';
static const String emojiEmoji02 = 'assets/images/emoji/emoji_02.png';
static const String emojiEmoji03 = 'assets/images/emoji/emoji_03.png';

342
lib/im/im_manager.dart

@ -1,6 +1,5 @@
import 'dart:io';
import 'dart:async';
import 'dart:convert';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -1415,158 +1414,174 @@ class IMManager {
}
// CALL消息-
if (message.body.type == MessageType.TXT) {
try {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
if (content != null && content.startsWith('[CALL:]')) {
//
//
Map<String, dynamic>? callInfo;
String? callType;
String? callStatus;
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'call' && customBody.params != null) {
final params = customBody.params!;
callType = params['callType'] ?? 'voice';
callStatus = params['callStatus'] ?? 'missed';
callInfo = {
'callType': callType,
'callStatus': callStatus,
};
}
}
//
if (callInfo != null && callType != null && callStatus != null) {
// missed calling
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) {
//
Map<String, dynamic>? attributes;
try {
final jsonStr = content.substring(7); // '[CALL:]'
final callInfo = jsonDecode(jsonStr) as Map<String, dynamic>;
final callType = callInfo['callType'] as String?;
final callStatus = callInfo['callStatus'] as String?;
// missed calling
if (callType == 'video' && (callStatus == 'missed' || callStatus == 'calling')) {
//
Map<String, dynamic>? attributes;
try {
attributes = message.attributes;
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e');
}
}
attributes = message.attributes;
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e');
}
}
String? nickName;
String? avatarUrl;
String? nickName;
String? avatarUrl;
if (attributes != null) {
nickName = attributes['sender_nickName'] as String?;
avatarUrl = attributes['sender_avatarUrl'] as String?;
}
if (attributes != null) {
nickName = attributes['sender_nickName'] as String?;
avatarUrl = attributes['sender_avatarUrl'] as String?;
// ConversationController
if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) {
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
final cachedUserInfo = conversationController.getCachedUserInfo(fromId);
if (cachedUserInfo != null) {
nickName = nickName ?? cachedUserInfo.nickName;
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e');
}
}
}
// ConversationController
if ((nickName == null || nickName.isEmpty) || (avatarUrl == null || avatarUrl.isEmpty)) {
try {
if (Get.isRegistered<ConversationController>()) {
final conversationController = Get.find<ConversationController>();
final cachedUserInfo = conversationController.getCachedUserInfo(fromId);
if (cachedUserInfo != null) {
nickName = nickName ?? cachedUserInfo.nickName;
avatarUrl = avatarUrl ?? cachedUserInfo.avatarUrl;
final finalNickName = nickName ?? fromId;
final finalAvatarUrl = avatarUrl ?? '';
//
final callManager = CallManager.instance;
callManager.startCallAudio();
//
SmartDialog.show(
builder: (context) {
return VideoCallInviteDialog(
avatarUrl: finalAvatarUrl,
nickName: finalNickName,
onTap: () async {
//
SmartDialog.dismiss();
//
callManager.stopCallAudio();
//
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
));
},
onAccept: () async {
//
SmartDialog.dismiss();
// acceptCall
callManager.stopCallAudio();
//
ChatController? chatController;
try {
final tag = 'chat_$fromId';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 获取ChatController失败: $e');
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 从ConversationController获取用户信息失败: $e');
final accepted = await callManager.acceptCall(
message: message,
chatController: chatController,
);
if (accepted) {
//
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
));
}
}
}
final finalNickName = nickName ?? fromId;
final finalAvatarUrl = avatarUrl ?? '';
//
SmartDialog.show(
builder: (context) {
return VideoCallInviteDialog(
avatarUrl: finalAvatarUrl,
nickName: finalNickName,
onTap: () async {
//
SmartDialog.dismiss();
//
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
));
},
onAccept: () async {
//
SmartDialog.dismiss();
//
final callManager = CallManager.instance;
ChatController? chatController;
try {
final tag = 'chat_$fromId';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 获取ChatController失败: $e');
}
}
final accepted = await callManager.acceptCall(
message: message,
chatController: chatController,
);
if (accepted) {
//
Get.to(() => VideoCallPage(
targetUserId: fromId,
isInitiator: false,
));
}
},
onReject: () async {
//
SmartDialog.dismiss();
//
final callManager = CallManager.instance;
ChatController? chatController;
try {
final tag = 'chat_$fromId';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 获取ChatController失败: $e');
}
}
await callManager.rejectCall(
message: message,
chatController: chatController,
);
},
},
onReject: () async {
//
SmartDialog.dismiss();
// rejectCall
callManager.stopCallAudio();
// rejected
ChatController? chatController;
try {
final tag = 'chat_$fromId';
if (Get.isRegistered<ChatController>(tag: tag)) {
chatController = Get.find<ChatController>(tag: tag);
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 获取ChatController失败: $e');
}
}
// 使 modifyMessage
await callManager.rejectCall(
message: message,
chatController: chatController,
);
},
alignment: Alignment.topCenter,
animationType: SmartAnimationType.centerFade_otherSlide,
animationTime: Duration(milliseconds: 300),
maskColor: Colors.transparent,
maskWidget: null,
clickMaskDismiss: false,
);
if (Get.isLogEnable) {
Get.log('📞 [IMManager] 显示视频通话邀请弹框: $fromId');
}
}
// CALL
return;
} catch (e) {
//
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e');
}
},
alignment: Alignment.topCenter,
animationType: SmartAnimationType.centerFade_otherSlide,
animationTime: Duration(milliseconds: 300),
maskColor: Colors.transparent,
maskWidget: null,
clickMaskDismiss: false,
keepSingle: true, //
);
if (Get.isLogEnable) {
Get.log('📞 [IMManager] 显示视频通话邀请弹框: $fromId');
}
}
} catch (e) {
//
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 解析消息内容失败: $e');
}
// CALL
return;
}
} catch (e) {
//
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e');
}
}
@ -1721,33 +1736,7 @@ class IMManager {
try {
if (message.body.type == MessageType.TXT) {
final body = message.body as EMTextMessageBody;
final content = body.content;
// CALL消息
if (content != null && content.startsWith('[CALL:]')) {
try {
final jsonStr = content.substring(7); // '[CALL:]'
final callInfo = jsonDecode(jsonStr) as Map<String, dynamic>;
final callType = callInfo['callType'] as String?;
if (callType == 'video') {
return '[视频通话]';
} else if (callType == 'voice') {
return '[语音通话]';
}
} catch (e) {
//
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 解析CALL消息失败: $e');
}
}
}
// GIFT消息
if (content != null && content.startsWith('[GIFT:]')) {
return '[礼物]';
}
return content ?? '';
return body.content;
} else if (message.body.type == MessageType.IMAGE) {
return '[图片]';
} else if (message.body.type == MessageType.VOICE) {
@ -1762,6 +1751,25 @@ class IMManager {
final body = message.body as EMCustomMessageBody;
if (body.event == 'live_room_invite') {
return '[分享房间]';
} else if (body.event == 'gift') {
return '[礼物]';
} else if (body.event == 'call') {
//
try {
if (body.params != null) {
final callType = body.params!['callType'] ?? 'voice';
if (callType == 'video') {
return '[视频通话]';
} else if (callType == 'voice') {
return '[语音通话]';
}
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 解析通话消息类型失败: $e');
}
}
return '[通话消息]';
}
return '[自定义消息]';
}

21
lib/pages/discover/live_room_page.dart

@ -48,7 +48,9 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
_overlayController = Get.find<OverlayController>();
// build build setState
WidgetsBinding.instance.addPostFrameCallback((_) {
_overlayController.hide();
if (_overlayController.showOverlay.value) {
_overlayController.hide();
}
});
//
WakelockPlus.enable();
@ -133,6 +135,13 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override
Widget build(BuildContext context) {
// build 使 addPostFrameCallback build setState
WidgetsBinding.instance.addPostFrameCallback((_) {
if (_overlayController.showOverlay.value) {
_overlayController.hide();
}
});
return PopScope(
onPopInvokedWithResult: (bool didPop, Object? result) async {
SmartDialog.dismiss();
@ -194,10 +203,14 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
popularityText: popularityText,
avatarAsset: avatarAsset,
onCloseTap: () {
SmartDialog.dismiss();
// 退RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController = Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
Get.back();
Future.delayed(Duration(seconds: 1), (){
_overlayController.show();
});
},
);
}),

52
lib/pages/message/conversation_tab.dart

@ -48,40 +48,30 @@ class _ConversationTabState extends State<ConversationTab>
);
}
//
// 使 Obx FutureBuilder
// 使 Obx conversations filterType FutureBuilder
return Obx(() {
return FutureBuilder<List<EMConversation>>(
future: controller.getFilteredConversations(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
final filteredConversations = controller.conversations;
final filteredConversations = snapshot.data ?? [];
if (filteredConversations.isEmpty) {
return Center(
child: Text(
controller.filterType.value == FilterType.none
? '暂无会话'
: '暂无符合条件的会话',
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
);
}
if (filteredConversations.isEmpty) {
return Center(
child: Text(
controller.filterType.value == FilterType.none
? '暂无会话'
: '暂无符合条件的会话',
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
);
}
return ListView.builder(
padding: const EdgeInsets.only(top: 8),
itemCount: filteredConversations.length,
itemBuilder: (context, index) {
final conversation = filteredConversations[index];
return _buildConversationItem(conversation);
},
);
return ListView.builder(
padding: const EdgeInsets.only(top: 8),
itemCount: filteredConversations.length,
itemBuilder: (context, index) {
final conversation = filteredConversations[index];
return _buildConversationItem(conversation);
},
);
});

27
lib/widget/live/draggable_overlay_widget.dart

@ -1,5 +1,6 @@
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:dating_touchme_app/controller/discover/room_controller.dart';
import 'package:dating_touchme_app/controller/overlay_controller.dart';
import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:dating_touchme_app/pages/discover/live_room_page.dart';
import 'package:flutter/material.dart';
@ -31,6 +32,7 @@ class _DraggableOverlayWidgetState extends State<DraggableOverlayWidget> {
bool _isDragging = false;
final RTCManager _rtcManager = RTCManager.instance;
final RoomController _roomController = Get.find<RoomController>();
final OverlayController _overlayController = Get.find<OverlayController>();
@override
void initState() {
@ -187,13 +189,26 @@ class _DraggableOverlayWidgetState extends State<DraggableOverlayWidget> {
],
),
),
).onTap(() {
//
widget.onClose?.call();
// 使 Future.microtask
Future.microtask(() {
).onTap(() async {
// OverlayController
_overlayController.hide();
//
// 使300ms
int waitCount = 0;
const maxWait = 6; // 650ms300ms
while (waitCount < maxWait && _overlayController.showOverlay.value) {
await Future.delayed(const Duration(milliseconds: 50));
waitCount++;
}
//
if (!_overlayController.showOverlay.value) {
Get.to(() => const LiveRoomPage(id: 0));
});
} else {
//
_overlayController.hide();
await Future.delayed(const Duration(milliseconds: 100));
Get.to(() => const LiveRoomPage(id: 0));
}
});
}),
);

15
lib/widget/message/call_item.dart

@ -1,4 +1,3 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:im_flutter_sdk/im_flutter_sdk.dart';
@ -39,10 +38,9 @@ class CallItem extends StatelessWidget {
super.key,
});
///
///
Map<String, dynamic>? _parseCallInfo() {
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'call' && customBody.params != null) {
@ -57,17 +55,6 @@ class CallItem extends StatelessWidget {
};
}
}
// [CALL:]
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
// [CALL:]
if (content.startsWith('[CALL:]')) {
final jsonStr = content.substring(7); // '[CALL:]'
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
}
} catch (e) {
print('解析通话信息失败: $e');
}

13
lib/widget/message/gift_item.dart

@ -1,4 +1,3 @@
import 'dart:convert';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
@ -25,10 +24,9 @@ class GiftItem extends StatelessWidget {
super.key,
});
/// params
/// params
Map<String, dynamic>? _parseGiftInfo() {
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
if (customBody.event == 'gift' && customBody.params != null) {
@ -43,15 +41,6 @@ class GiftItem extends StatelessWidget {
};
}
}
// [GIFT:]
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
if (content.startsWith('[GIFT:]')) {
final jsonStr = content.substring(7); // '[GIFT:]'
return jsonDecode(jsonStr) as Map<String, dynamic>;
}
}
} catch (e) {
print('解析礼物信息失败: $e');
}

18
lib/widget/message/message_item.dart

@ -26,40 +26,26 @@ class MessageItem extends StatelessWidget {
super.key,
});
//
//
bool _isCallMessage() {
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
return customBody.event == 'call';
}
// [CALL:]
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
final content = textBody.content;
// [CALL:]
return content.startsWith('[CALL:]');
}
} catch (e) {
//
}
return false;
}
//
//
bool _isGiftMessage() {
try {
//
if (message.body.type == MessageType.CUSTOM) {
final customBody = message.body as EMCustomMessageBody;
return customBody.event == 'gift';
}
// [GIFT:]
if (message.body.type == MessageType.TXT) {
final textBody = message.body as EMTextMessageBody;
return textBody.content.startsWith('[GIFT:]');
}
} catch (e) {
//
}

12
lib/widget/message/message_notification_dialog.dart

@ -2,6 +2,7 @@ import 'package:cached_network_image/cached_network_image.dart';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'emoji_text_widget.dart';
///
class MessageNotificationDialog extends StatelessWidget {
@ -85,15 +86,14 @@ class MessageNotificationDialog extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
//
Text(
messageContent,
style: TextStyle(
// 使EmojiTextWidget支持emoji显示
EmojiTextWidget(
text: messageContent,
textStyle: TextStyle(
fontSize: 13.sp,
color: Color(0xFF666666),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
emojiSize: 20.w,
),
],
),

15
lib/widget/message/text_item.dart

@ -27,23 +27,8 @@ class TextItem extends StatelessWidget {
super.key,
});
///
bool _isLegacySpecialMessage() {
final content = textBody.content;
//
return content.startsWith('[GIFT:]') ||
content.startsWith('[ROOM:]') ||
content.startsWith('[CALL:]');
}
@override
Widget build(BuildContext context) {
// JSON
if (_isLegacySpecialMessage()) {
//
return SizedBox.shrink();
}
//
final revenueInfo = _getRevenueInfo();

1
pubspec.yaml

@ -109,6 +109,7 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/audio/
- assets/images/emoji/
# - images/a_dot_ham.jpeg

Loading…
Cancel
Save