Browse Source

feat(live): 添加用户ID支持并重构聊天消息功能

- 在 LiveChatMessage 模型中添加 uid 字段支持
- 更新消息序列化逻辑以包含用户ID信息
- 重构用户资料对话框为独立组件并优化显示逻辑
- 改进聊天消息滚动和自动定位到底部的机制
- 优化礼物赠送消息的发送和显示逻辑
- 移除过时的用户信息查找代码并提升性能
- 调整直播间页面布局结构和组件组织方式
master
Jolie 3 months ago
parent
commit
a7c406124e
7 changed files with 336 additions and 294 deletions
  1. 118
      lib/controller/discover/room_controller.dart
  2. 13
      lib/model/live/live_chat_message.dart
  3. 69
      lib/pages/discover/live_room_page.dart
  4. 4
      lib/service/live_chat_message_service.dart
  5. 208
      lib/widget/live/live_room_chat_item.dart
  6. 23
      lib/widget/live/live_room_notice_chat_panel.dart
  7. 195
      lib/widget/live/live_room_user_profile_dialog.dart

118
lib/controller/discover/room_controller.dart

@ -429,14 +429,50 @@ class RoomController extends GetxController with WidgetsBindingObserver {
);
print('✅ 礼物已添加到播放队列: ${gift.productTitle}');
// RTM
//
final senderNickName = GlobalData().userData?.nickName ?? '用户';
String targetNickName = '用户';
//
final channelDetail = rtcChannelDetail.value;
if (channelDetail != null) {
//
final anchorInfo = channelDetail.anchorInfo;
if (anchorInfo != null && anchorInfo.uid == targetUserId) {
targetNickName = anchorInfo.nickName.isNotEmpty
? anchorInfo.nickName
: '用户';
}
//
else {
final maleInfo = channelDetail.maleInfo;
if (maleInfo != null && maleInfo.uid == targetUserId) {
targetNickName = maleInfo.nickName.isNotEmpty
? maleInfo.nickName
: '用户';
}
//
else {
final femaleInfo = channelDetail.femaleInfo;
if (femaleInfo != null && femaleInfo.uid == targetUserId) {
targetNickName = femaleInfo.nickName.isNotEmpty
? femaleInfo.nickName
: '用户';
}
}
}
}
// RTM
final messageData = {
'type': 'gift',
'svgaFile': gift.svgaFile,
'giftProductId': gift.productId,
'targetUserId': targetUserId,
'targetNickName': targetNickName, //
'senderUserId': rtcChannel.value?.uid,
'senderNickName': GlobalData().userData?.nickName ?? '',
'senderNickName': senderNickName,
'senderAvatar': GlobalData().userData?.profilePhoto ?? '',
};
await RTMManager.instance.publishChannelMessage(
@ -445,27 +481,6 @@ class RoomController extends GetxController with WidgetsBindingObserver {
);
print('✅ 礼物消息已发送: ${gift.productTitle}');
//
final senderNickName = GlobalData().userData?.nickName ?? '用户';
String targetNickName = '用户';
//
final channelDetail = rtcChannelDetail.value;
if (channelDetail != null) {
//
if (channelDetail.anchorInfo?.uid == targetUserId) {
targetNickName = channelDetail.anchorInfo?.nickName ?? '用户';
}
//
else if (channelDetail.maleInfo?.uid == targetUserId) {
targetNickName = channelDetail.maleInfo?.nickName ?? '用户';
}
//
else if (channelDetail.femaleInfo?.uid == targetUserId) {
targetNickName = channelDetail.femaleInfo?.nickName ?? '用户';
}
}
//
final giftMessage = LiveChatMessage(
userId: GlobalData().userId ?? GlobalData().userData?.id ?? '',
@ -491,8 +506,10 @@ class RoomController extends GetxController with WidgetsBindingObserver {
final svgaFile = message['svgaFile']?.toString() ?? '';
final giftProductId = message['giftProductId']?.toString();
final targetUserId = message['targetUserId'];
final targetNickNameFromMessage = message['targetNickName']?.toString();
final senderUserId = message['senderUserId'];
final senderNickName = message['senderNickName']?.toString() ?? '用户';
final senderAvatar = message['senderAvatar']?.toString();
//
String giftTitle = '礼物';
@ -508,21 +525,44 @@ class RoomController extends GetxController with WidgetsBindingObserver {
}
}
//
String targetNickName = '用户';
final channelDetail = rtcChannelDetail.value;
if (channelDetail != null) {
//
if (channelDetail.anchorInfo?.uid == targetUserId) {
targetNickName = channelDetail.anchorInfo?.nickName ?? '用户';
}
//
else if (channelDetail.maleInfo?.userId == targetUserId) {
targetNickName = channelDetail.maleInfo?.nickName ?? '用户';
}
//
else if (channelDetail.femaleInfo?.userId == targetUserId) {
targetNickName = channelDetail.femaleInfo?.nickName ?? '用户';
// 使
String targetNickName = targetNickNameFromMessage ?? '用户';
if (targetNickName == '用户' || targetNickName.isEmpty) {
final channelDetail = rtcChannelDetail.value;
if (channelDetail != null) {
final targetUserIdStr = targetUserId?.toString() ?? '';
final targetUid = targetUserId is int
? targetUserId
: (int.tryParse(targetUserIdStr) ?? 0);
// uid userId
final anchorInfo = channelDetail.anchorInfo;
if (anchorInfo != null &&
(anchorInfo.uid == targetUid || anchorInfo.userId == targetUserIdStr)) {
targetNickName = anchorInfo.nickName.isNotEmpty
? anchorInfo.nickName
: '用户';
}
// uid userId
else {
final maleInfo = channelDetail.maleInfo;
if (maleInfo != null &&
(maleInfo.uid == targetUid || maleInfo.userId == targetUserIdStr)) {
targetNickName = maleInfo.nickName.isNotEmpty
? maleInfo.nickName
: '用户';
}
// uid userId
else {
final femaleInfo = channelDetail.femaleInfo;
if (femaleInfo != null &&
(femaleInfo.uid == targetUid || femaleInfo.userId == targetUserIdStr)) {
targetNickName = femaleInfo.nickName.isNotEmpty
? femaleInfo.nickName
: '用户';
}
}
}
}
}
@ -530,7 +570,7 @@ class RoomController extends GetxController with WidgetsBindingObserver {
final giftMessage = LiveChatMessage(
userId: senderUserId?.toString() ?? '',
userName: senderNickName,
avatar: null, //
avatar: senderAvatar?.isNotEmpty == true ? senderAvatar : null,
content: '$targetNickName赠送了$giftTitle',
timestamp: DateTime.now().millisecondsSinceEpoch,
);

13
lib/model/live/live_chat_message.dart

@ -1,6 +1,7 @@
///
class LiveChatMessage {
final String userId;
final int? uid;
final String userName;
final String? avatar;
final String content;
@ -10,17 +11,19 @@ class LiveChatMessage {
required this.userId,
required this.userName,
this.avatar,
this.uid,
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,
userId: json['userId'],
userName: json['userName'],
avatar: json['avatar'],
uid: json['uid'],
content: json['content'],
timestamp: json['timestamp'],
);
}

69
lib/pages/discover/live_room_page.dart

@ -274,48 +274,37 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
padding: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 10.w),
Obx(() {
final detail =
_roomController.rtcChannelDetail.value;
final anchorInfo = detail?.anchorInfo;
SizedBox(height: 10.w),
Obx(() {
final detail = _roomController.rtcChannelDetail.value;
final anchorInfo = detail?.anchorInfo;
final userName = anchorInfo!.nickName;
final avatarAsset = anchorInfo.profilePhoto;
const popularityText = '0'; // TODO: 使
final userName = anchorInfo!.nickName;
final avatarAsset = anchorInfo.profilePhoto;
const popularityText = '0'; // TODO: 使
return LiveRoomUserHeader(
userName: userName,
popularityText: popularityText,
avatarAsset: avatarAsset,
onCloseTap: () {
SmartDialog.dismiss();
// 退RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController =
Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
Get.back();
},
);
}),
SizedBox(height: 7.w),
LiveRoomAnchorShowcase(),
SizedBox(height: 5.w),
const LiveRoomActiveSpeaker(),
SizedBox(height: 9.w),
const LiveRoomNoticeChatPanel(),
SizedBox(height: 10.w),
],
),
),
),
return LiveRoomUserHeader(
userName: userName,
popularityText: popularityText,
avatarAsset: avatarAsset,
onCloseTap: () {
SmartDialog.dismiss();
// 退RTM消息
if (Get.isRegistered<RoomController>()) {
final roomController = Get.find<RoomController>();
roomController.chatMessages.clear();
}
_overlayController.show();
Get.back();
},
);
}),
SizedBox(height: 7.w),
LiveRoomAnchorShowcase(),
SizedBox(height: 5.w),
const LiveRoomActiveSpeaker(),
SizedBox(height: 9.w),
Expanded(child: const LiveRoomNoticeChatPanel()),
// / LiveRoomActionBar
if (MediaQuery.of(context).viewInsets.bottom == 0)
SafeArea(

4
lib/service/live_chat_message_service.dart

@ -132,11 +132,13 @@ class LiveChatMessageService {
final userData = GlobalData().userData;
final userName = userData?.nickName ?? '用户';
final avatar = userData?.profilePhoto;
RoomController controller = Get.find<RoomController>();
final uid = controller.rtcChannel.value?.uid;
return {
'type': 'chat_message',
'userId': userId,
'userName': userName,
'uid': uid,
'avatar': avatar,
'content': content.trim(),
'timestamp': DateTime.now().millisecondsSinceEpoch,

208
lib/widget/live/live_room_chat_item.dart

@ -3,14 +3,13 @@ import 'package:dating_touchme_app/extension/ex_widget.dart';
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:dating_touchme_app/model/live/live_chat_message.dart';
import 'package:dating_touchme_app/widget/live/live_gift_popup.dart';
import 'package:dating_touchme_app/widget/live/live_room_user_profile_dialog.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../pages/message/chat_page.dart';
class LiveRoomChatItem extends StatelessWidget {
const LiveRoomChatItem({super.key, required this.message});
@ -20,22 +19,7 @@ class LiveRoomChatItem extends StatelessWidget {
final roomController = Get.find<RoomController>();
// userId
final channelDetail = roomController.rtcChannelDetail.value;
int? targetUid;
//
if (channelDetail?.anchorInfo?.userId == message.userId) {
targetUid = channelDetail?.anchorInfo?.uid;
} else if (channelDetail?.maleInfo?.userId == message.userId) {
targetUid = channelDetail?.maleInfo?.uid;
} else if (channelDetail?.femaleInfo?.userId == message.userId) {
targetUid = channelDetail?.femaleInfo?.uid;
}
if (targetUid == null) {
SmartDialog.showToast('无法获取用户信息');
return;
}
int? targetUid = message.uid;
// ValueNotifier
final activeGift = ValueNotifier<int?>(null);
@ -66,188 +50,6 @@ class LiveRoomChatItem extends StatelessWidget {
);
}
void _showUserProfileDialog(BuildContext context) {
SmartDialog.show(
alignment: Alignment.bottomCenter,
builder: (context) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.w),
topRight: Radius.circular(20.w),
),
),
height: 200.w,
margin: EdgeInsets.only(top: 40.w),
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.w),
//
Row(
children: [
SizedBox(width: 110.w),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.userName,
style: TextStyle(
fontSize: 18.w,
fontWeight: FontWeight.w600,
color: const Color.fromRGBO(51, 51, 51, 1),
),
),
SizedBox(height: 8.w),
//
Wrap(
spacing: 6.w,
runSpacing: 6.w,
children: [
_buildTag(
'在线',
const Color.fromRGBO(198, 246, 213, 1),
),
],
),
],
),
),
GestureDetector(
onTap: () => SmartDialog.dismiss(),
child: Icon(
Icons.close,
size: 24.w,
color: const Color.fromRGBO(153, 153, 153, 1),
),
),
],
),
SizedBox(height: 25.w),
//
GestureDetector(
onTap: () {
SmartDialog.dismiss();
_showGiftPopup(context, 1);
},
child: Container(
width: double.infinity,
height: 44.w,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color.fromRGBO(117, 98, 249, 1),
Color.fromRGBO(152, 124, 255, 1),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(22.w),
),
child: Center(
child: Text(
'送礼物',
style: TextStyle(
fontSize: 16.w,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
),
SizedBox(height: 15.w),
//
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildActionLink('私聊', () {
SmartDialog.dismiss();
Get.to(() => ChatPage(
userId: message.userId,
));
}),
Container(
width: 1.w,
height: 12.w,
color: const Color.fromRGBO(229, 229, 229, 1),
margin: EdgeInsets.symmetric(horizontal: 15.w),
),
_buildActionLink('送礼物加好友', () {
SmartDialog.dismiss();
_showGiftPopup(context, 1);
}),
],
),
],
),
),
Container(
margin: EdgeInsets.only(left: 30.w),
child: ClipOval(
child: message.avatar != null && message.avatar!.isNotEmpty
? Image.network(
message.avatar!,
width: 80.w,
height: 80.w,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
Assets.imagesUserAvatar,
width: 60.w,
height: 60.w,
);
},
)
: Image.asset(
Assets.imagesUserAvatar,
width: 60.w,
height: 60.w,
),
),
),
],
);
},
);
}
Widget _buildTag(String text, Color bgColor) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(10.w),
),
child: Text(
text,
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, 1),
),
),
);
}
Widget _buildActionLink(String text, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Text(
text,
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(153, 153, 153, 1),
),
),
);
}
@override
Widget build(BuildContext context) {
return Container(
@ -275,7 +77,11 @@ class LiveRoomChatItem extends StatelessWidget {
height: 25.w,
),
).onTap(() {
_showUserProfileDialog(context);
showUserProfileDialog(
context,
message,
() => _showGiftPopup(context, 1),
);
})
: Image.asset(
Assets.imagesUserAvatar,

23
lib/widget/live/live_room_notice_chat_panel.dart

@ -78,7 +78,6 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
@override
Widget build(BuildContext context) {
return Container(
height: 230.w,
padding: EdgeInsets.only(left: 13.w, right: 9.w),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
@ -86,13 +85,21 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
Expanded(
child: Obx(() {
//
final messages = controller.chatMessages;
// 使 WidgetsBinding
WidgetsBinding.instance.addPostFrameCallback((_) {
if (scrollController.hasClients) {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOut,
);
if (scrollController.hasClients && messages.isNotEmpty) {
//
Future.microtask(() {
if (scrollController.hasClients) {
scrollController.animateTo(
scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 200),
curve: Curves.easeOut,
);
}
});
}
});
@ -110,7 +117,7 @@ class _LiveRoomNoticeChatPanelState extends State<LiveRoomNoticeChatPanel> {
),
SizedBox(height: 15.w),
//
...controller.chatMessages.map(
...messages.map(
(message) => LiveRoomChatItem(message: message),
),
],

195
lib/widget/live/live_room_user_profile_dialog.dart

@ -0,0 +1,195 @@
import 'package:dating_touchme_app/generated/assets.dart';
import 'package:dating_touchme_app/model/live/live_chat_message.dart';
import 'package:dating_touchme_app/pages/message/chat_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
///
void showUserProfileDialog(
BuildContext context,
LiveChatMessage message,
VoidCallback onShowGiftPopup,
) {
SmartDialog.show(
alignment: Alignment.bottomCenter,
builder: (context) {
return Stack(
children: [
Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20.w),
topRight: Radius.circular(20.w),
),
),
height: 200.w,
margin: EdgeInsets.only(top: 40.w),
padding: EdgeInsets.symmetric(horizontal: 15.w),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10.w),
//
Row(
children: [
SizedBox(width: 110.w),
//
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
message.userName,
style: TextStyle(
fontSize: 18.w,
fontWeight: FontWeight.w600,
color: const Color.fromRGBO(51, 51, 51, 1),
),
),
SizedBox(height: 8.w),
//
Wrap(
spacing: 6.w,
runSpacing: 6.w,
children: [
_buildTag(
'在线',
const Color.fromRGBO(198, 246, 213, 1),
),
],
),
],
),
),
GestureDetector(
onTap: () => SmartDialog.dismiss(),
child: Icon(
Icons.close,
size: 24.w,
color: const Color.fromRGBO(153, 153, 153, 1),
),
),
],
),
SizedBox(height: 25.w),
//
GestureDetector(
onTap: () {
SmartDialog.dismiss();
onShowGiftPopup();
},
child: Container(
width: double.infinity,
height: 44.w,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [
Color.fromRGBO(117, 98, 249, 1),
Color.fromRGBO(152, 124, 255, 1),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(22.w),
),
child: Center(
child: Text(
'送礼物',
style: TextStyle(
fontSize: 16.w,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
),
),
),
SizedBox(height: 15.w),
//
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildActionLink('私聊', () {
SmartDialog.dismiss();
Get.to(() => ChatPage(
userId: message.userId,
));
}),
Container(
width: 1.w,
height: 12.w,
color: const Color.fromRGBO(229, 229, 229, 1),
margin: EdgeInsets.symmetric(horizontal: 15.w),
),
_buildActionLink('送礼物加好友', () {
SmartDialog.dismiss();
onShowGiftPopup();
}),
],
),
],
),
),
Container(
margin: EdgeInsets.only(left: 30.w),
child: ClipOval(
child: message.avatar != null && message.avatar!.isNotEmpty
? Image.network(
message.avatar!,
width: 80.w,
height: 80.w,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Image.asset(
Assets.imagesUserAvatar,
width: 60.w,
height: 60.w,
);
},
)
: Image.asset(
Assets.imagesUserAvatar,
width: 60.w,
height: 60.w,
),
),
),
],
);
},
);
}
Widget _buildTag(String text, Color bgColor) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 3.w),
decoration: BoxDecoration(
color: bgColor,
borderRadius: BorderRadius.circular(10.w),
),
child: Text(
text,
style: TextStyle(
fontSize: 11.w,
color: const Color.fromRGBO(51, 51, 51, 1),
),
),
);
}
Widget _buildActionLink(String text, VoidCallback onTap) {
return GestureDetector(
onTap: onTap,
child: Text(
text,
style: TextStyle(
fontSize: 12.w,
color: const Color.fromRGBO(153, 153, 153, 1),
),
),
);
}
Loading…
Cancel
Save