Browse Source

更新IM逻辑

master
Jolie 3 months ago
parent
commit
9b13ca0e00
4 changed files with 441 additions and 14 deletions
  1. 31
      lib/controller/message/conversation_controller.dart
  2. 220
      lib/im/im_manager.dart
  3. 97
      lib/pages/main/main_page.dart
  4. 107
      lib/widget/message/message_notification_dialog.dart

31
lib/controller/message/conversation_controller.dart

@ -58,6 +58,9 @@ class ConversationController extends GetxController {
//
final filterType = FilterType.none.obs;
//
final totalUnreadCount = 0.obs;
/// ChatController
void cacheUserInfo(String userId, ExtendedUserInfo userInfo) {
_userInfoCache[userId] = userInfo;
@ -141,6 +144,9 @@ class ConversationController extends GetxController {
//
conversations.value = convList;
//
await _updateTotalUnreadCount();
// 使GetX日志系统
if (Get.isLogEnable) {
@ -418,6 +424,31 @@ class ConversationController extends GetxController {
}
}
///
Future<void> _updateTotalUnreadCount() async {
try {
int total = 0;
for (var conversation in conversations) {
final unreadCount = await getUnreadCount(conversation);
total += unreadCount;
}
totalUnreadCount.value = total;
if (Get.isLogEnable) {
Get.log('✅ [ConversationController] 总未读数已更新: $total');
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [ConversationController] 更新总未读数失败: $e');
}
totalUnreadCount.value = 0;
}
}
///
Future<void> refreshTotalUnreadCount() async {
await _updateTotalUnreadCount();
}
///
String formatMessageTime(int timestamp) {
DateTime messageTime = DateTime.fromMillisecondsSinceEpoch(timestamp);

220
lib/im/im_manager.dart

@ -1,6 +1,7 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
@ -12,7 +13,24 @@ import '../controller/message/conversation_controller.dart';
import '../controller/message/chat_controller.dart';
import '../controller/global.dart';
import '../pages/mine/login_page.dart';
import '../pages/message/chat_page.dart';
import '../network/user_api.dart';
import '../widget/message/message_notification_dialog.dart';
//
class _NotificationMessage {
final String fromId;
final String nickName;
final String avatarUrl;
final String messageContent;
_NotificationMessage({
required this.fromId,
required this.nickName,
required this.avatarUrl,
required this.messageContent,
});
}
// IM管理器实现使SDK类型和方法
class IMManager {
@ -40,6 +58,12 @@ class IMManager {
// Presence key userId
final Map<String, Function(bool)> _presenceCallbacks = {};
//
final List<_NotificationMessage> _notificationQueue = [];
//
bool _isShowingNotification = false;
IMManager._internal() {
print('IMManager instance created');
}
@ -186,6 +210,8 @@ class IMManager {
for (var message in messages) {
if (message.direction == MessageDirection.RECEIVE) {
_parseUserInfoFromMessageExt(message);
//
_checkAndShowNotificationDialog(message);
}
}
//
@ -1353,6 +1379,200 @@ class IMManager {
}
}
///
void _checkAndShowNotificationDialog(EMMessage message) {
try {
// ID
final fromId = message.from;
if (fromId == null || fromId.isEmpty) {
return;
}
//
// _activeChatControllers ID
if (_activeChatControllers.containsKey(fromId)) {
//
return;
}
//
String messageContent = _getMessageContent(message);
if (messageContent.isEmpty) {
return;
}
//
Map<String, dynamic>? attributes;
try {
attributes = message.attributes;
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 无法访问消息扩展字段: $e');
}
return;
}
String? nickName;
String? avatarUrl;
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');
}
}
}
// 使
final finalNickName = nickName ?? fromId;
final finalAvatarUrl = avatarUrl ?? '';
//
_notificationQueue.add(_NotificationMessage(
fromId: fromId,
nickName: finalNickName,
avatarUrl: finalAvatarUrl,
messageContent: messageContent,
));
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 消息已加入通知队列: fromId=$fromId, nickName=$finalNickName, 队列长度=${_notificationQueue.length}');
}
//
if (!_isShowingNotification) {
_showNextNotification();
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 处理消息通知失败: $e');
}
}
}
///
void _showNextNotification() {
//
if (_notificationQueue.isEmpty || _isShowingNotification) {
return;
}
//
final notification = _notificationQueue.removeAt(0);
_isShowingNotification = true;
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 显示消息通知弹框: fromId=${notification.fromId}, nickName=${notification.nickName}, 剩余队列长度=${_notificationQueue.length}');
}
//
SmartDialog.show(
builder: (context) {
return MessageNotificationDialog(
avatarUrl: notification.avatarUrl,
nickName: notification.nickName,
messageContent: notification.messageContent,
onTap: () {
//
SmartDialog.dismiss();
_onNotificationDismissed();
//
Get.to(() => ChatPage(
userId: notification.fromId,
userData: null, // userData ChatPage
));
},
);
},
alignment: Alignment.topCenter,
animationType: SmartAnimationType.centerFade_otherSlide,
animationTime: Duration(milliseconds: 300),
usePenetrate: false,
clickMaskDismiss: true,
backDismiss: true,
keepSingle: true, //
maskColor: Colors.transparent, //
);
// 3
Future.delayed(Duration(seconds: 3), () {
if (_isShowingNotification) {
SmartDialog.dismiss();
//
Future.delayed(Duration(milliseconds: 300), () {
_onNotificationDismissed();
});
}
});
}
///
void _onNotificationDismissed() {
if (!_isShowingNotification) {
return;
}
_isShowingNotification = false;
if (Get.isLogEnable) {
Get.log('✅ [IMManager] 消息通知弹框已关闭,剩余队列长度=${_notificationQueue.length}');
}
//
Future.delayed(Duration(milliseconds: 300), () {
if (!_isShowingNotification) {
_showNextNotification();
}
});
}
///
String _getMessageContent(EMMessage message) {
try {
if (message.body.type == MessageType.TXT) {
final body = message.body as EMTextMessageBody;
return body.content ?? '';
} else if (message.body.type == MessageType.IMAGE) {
return '[图片]';
} else if (message.body.type == MessageType.VOICE) {
return '[语音]';
} else if (message.body.type == MessageType.VIDEO) {
return '[视频]';
} else if (message.body.type == MessageType.FILE) {
return '[文件]';
} else if (message.body.type == MessageType.LOCATION) {
return '[位置]';
} else if (message.body.type == MessageType.CUSTOM) {
final body = message.body as EMCustomMessageBody;
if (body.event == 'live_room_invite') {
return '[分享房间]';
}
return '[自定义消息]';
}
} catch (e) {
if (Get.isLogEnable) {
Get.log('⚠️ [IMManager] 获取消息内容失败: $e');
}
}
return '';
}
///
void _refreshConversationList() {
try {

97
lib/pages/main/main_page.dart

@ -3,12 +3,12 @@ import 'package:dating_touchme_app/pages/message/message_page.dart';
import 'package:dating_touchme_app/pages/mine/mine_page.dart';
import 'package:dating_touchme_app/rtc/rtm_manager.dart';
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_svga/flutter_svga.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:get_storage/get_storage.dart';
import 'package:dating_touchme_app/controller/mine/user_controller.dart';
import 'package:dating_touchme_app/controller/message/conversation_controller.dart';
import 'package:tdesign_flutter/tdesign_flutter.dart';
import '../../extension/router_service.dart';
@ -85,22 +85,38 @@ class _MainPageState extends State<MainPage> {
minePage,
],
),
bottomNavigationBar: TDBottomTabBar(
currentIndex: currentIndex,
TDBottomTabBarBasicType.iconText,
componentType: TDBottomTabBarComponentType.normal,
useVerticalDivider: false,
navigationTabs: [
tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0),
tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1),
tabItem('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 2),
tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 3),
]
)
bottomNavigationBar: _buildBottomNavigationBar(),
),
);
}
///
Widget _buildBottomNavigationBar() {
// ConversationController Obx
if (!Get.isRegistered<ConversationController>()) {
Get.put(ConversationController(), permanent: false);
}
return Obx(() {
// Obx 访 observable使 GetX
final controller = Get.find<ConversationController>();
final unreadCount = controller.totalUnreadCount.value;
return TDBottomTabBar(
currentIndex: currentIndex,
TDBottomTabBarBasicType.iconText,
componentType: TDBottomTabBarComponentType.normal,
useVerticalDivider: false,
navigationTabs: [
tabItem('首页', Assets.imagesHomePre, Assets.imagesHomeNol, 0),
tabItem('找对象', Assets.imagesDiscoverPre, Assets.imagesDiscoverNol, 1),
tabItemWithBadge('消息', Assets.imagesMessagePre, Assets.imagesMessageNol, 2, unreadCount),
tabItem('我的', Assets.imagesMinePre, Assets.imagesMineNol, 3),
]
);
});
}
/// item
TDBottomTabBarTabConfig tabItem(String title, String selectedIcon, String unselectedIcon, int index) {
return TDBottomTabBarTabConfig(
@ -115,4 +131,57 @@ class _MainPageState extends State<MainPage> {
},
);
}
/// item
TDBottomTabBarTabConfig tabItemWithBadge(String title, String selectedIcon, String unselectedIcon, int index, int unreadCount) {
//
Widget buildIconWithBadge(String iconPath, bool isSelected) {
return Stack(
clipBehavior: Clip.none,
children: [
Image.asset(iconPath, width: 25, height: 25, fit: BoxFit.cover),
if (unreadCount > 0)
Positioned(
right: -6.w,
top: -6.w,
child: Container(
padding: EdgeInsets.symmetric(horizontal: unreadCount > 99 ? 4.w : 5.w, vertical: 2.w),
decoration: BoxDecoration(
color: Color(0xFFFF3B30),
borderRadius: BorderRadius.circular(10.w),
border: Border.all(color: Colors.white, width: 1.w),
),
constraints: BoxConstraints(
minWidth: 16.w,
minHeight: 16.w,
),
child: Center(
child: Text(
unreadCount > 99 ? '99+' : unreadCount.toString(),
style: TextStyle(
color: Colors.white,
fontSize: 10.sp,
fontWeight: FontWeight.w500,
),
textAlign: TextAlign.center,
),
),
),
),
],
);
}
return TDBottomTabBarTabConfig(
tabText: title,
selectedIcon: buildIconWithBadge(selectedIcon, true),
unselectedIcon: buildIconWithBadge(unselectedIcon, false),
selectTabTextStyle: TextStyle(color: Color(0xFFED4AC3)),
unselectTabTextStyle: TextStyle(color: Color(0xFF999999)),
onTap: () {
currentIndex = index;
pageController.jumpToPage(index);
},
);
}
}

107
lib/widget/message/message_notification_dialog.dart

@ -0,0 +1,107 @@
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';
///
class MessageNotificationDialog extends StatelessWidget {
final String avatarUrl;
final String nickName;
final String messageContent;
final VoidCallback? onTap;
const MessageNotificationDialog({
super.key,
required this.avatarUrl,
required this.nickName,
required this.messageContent,
this.onTap,
});
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: Container(
margin: EdgeInsets.symmetric(horizontal: 16.w),
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 12.h),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12.w),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
offset: Offset(0, 2.h),
),
],
),
child: Row(
children: [
//
ClipOval(
child: avatarUrl.isNotEmpty
? CachedNetworkImage(
imageUrl: avatarUrl,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
placeholder: (context, url) => Image.asset(
Assets.imagesUserAvatar,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
),
errorWidget: (context, url, error) => Image.asset(
Assets.imagesUserAvatar,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
),
)
: Image.asset(
Assets.imagesUserAvatar,
width: 48.w,
height: 48.w,
fit: BoxFit.cover,
),
),
SizedBox(width: 12.w),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
//
Text(
nickName,
style: TextStyle(
fontSize: 15.sp,
fontWeight: FontWeight.w600,
color: Color(0xFF333333),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
SizedBox(height: 4.h),
//
Text(
messageContent,
style: TextStyle(
fontSize: 13.sp,
color: Color(0xFF666666),
),
maxLines: 2,
overflow: TextOverflow.ellipsis,
),
],
),
),
],
),
),
);
}
}
Loading…
Cancel
Save