|
|
|
@ -2,6 +2,7 @@ import 'dart:convert'; |
|
|
|
|
|
|
|
import 'package:cached_network_image/cached_network_image.dart'; |
|
|
|
import 'package:dating_touchme_app/controller/discover/room_controller.dart'; |
|
|
|
import 'package:dating_touchme_app/controller/message/conversation_controller.dart'; |
|
|
|
import 'package:dating_touchme_app/im/im_manager.dart'; |
|
|
|
import 'package:dating_touchme_app/network/network_service.dart'; |
|
|
|
import 'package:dating_touchme_app/rtc/rtc_manager.dart'; |
|
|
|
@ -9,6 +10,7 @@ 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:im_flutter_sdk/im_flutter_sdk.dart'; |
|
|
|
|
|
|
|
/// 嘉宾列表对话框 |
|
|
|
class LiveRoomGuestListDialog extends StatefulWidget { |
|
|
|
@ -41,6 +43,7 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
try { |
|
|
|
// 获取会话列表 |
|
|
|
final conversations = await IMManager.instance.getConversations(); |
|
|
|
|
|
|
|
if (conversations.isEmpty) { |
|
|
|
setState(() { |
|
|
|
_guestList = []; |
|
|
|
@ -53,15 +56,119 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
final List<Map<String, dynamic>> guestList = []; |
|
|
|
|
|
|
|
// 遍历每个会话,获取联系人信息 |
|
|
|
for (final conversation in conversations) { |
|
|
|
for (int i = 0; i < conversations.length; i++) { |
|
|
|
final conversation = conversations[i]; |
|
|
|
try { |
|
|
|
final userId = conversation.id; |
|
|
|
if (userId.isEmpty) continue; |
|
|
|
|
|
|
|
if (userId.isEmpty) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
// 获取环信用户信息 |
|
|
|
final contactsMap = await IMManager.instance.getContacts(userId); |
|
|
|
final emUserInfo = contactsMap[userId]; |
|
|
|
Get.log('获取会话列表: ${emUserInfo?.gender}'); |
|
|
|
|
|
|
|
// 尝试从 ConversationController 获取用户信息(会自动从缓存、消息扩展字段、环信等多个数据源获取) |
|
|
|
ExtendedUserInfo? cachedUserInfo; |
|
|
|
|
|
|
|
// 首先尝试从 ConversationController 获取(如果已注册) |
|
|
|
if (Get.isRegistered<ConversationController>()) { |
|
|
|
try { |
|
|
|
final conversationController = Get.find<ConversationController>(); |
|
|
|
// 使用 loadContact 方法,它会自动从多个数据源获取用户信息: |
|
|
|
// 1. 缓存 |
|
|
|
// 2. 会话历史消息的扩展字段 |
|
|
|
// 3. 环信用户信息 |
|
|
|
cachedUserInfo = await conversationController.loadContact(userId); |
|
|
|
} catch (e) { |
|
|
|
// 忽略错误,继续从其他数据源获取 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 如果还没有获取到用户信息(无论 ConversationController 是否注册),都尝试直接从会话历史消息中提取 |
|
|
|
final hasNickName = cachedUserInfo?.nickName != null && cachedUserInfo!.nickName!.isNotEmpty; |
|
|
|
final hasAvatar = cachedUserInfo?.avatarUrl != null && cachedUserInfo!.avatarUrl!.isNotEmpty; |
|
|
|
|
|
|
|
if (!hasNickName || !hasAvatar) { |
|
|
|
try { |
|
|
|
// 直接从会话历史消息中提取用户信息 |
|
|
|
final conversation = await EMClient.getInstance.chatManager.getConversation( |
|
|
|
userId, |
|
|
|
type: EMConversationType.Chat, |
|
|
|
createIfNeed: false, |
|
|
|
); |
|
|
|
if (conversation != null) { |
|
|
|
final messages = await conversation.loadMessages(loadCount: 50); |
|
|
|
|
|
|
|
for (var message in messages) { |
|
|
|
Map<String, dynamic>? attributes; |
|
|
|
try { |
|
|
|
attributes = message.attributes; |
|
|
|
} catch (e) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (attributes == null || attributes.isEmpty) { |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (message.direction == MessageDirection.RECEIVE) { |
|
|
|
final fromUserId = message.from; |
|
|
|
if (fromUserId != null && fromUserId == userId) { |
|
|
|
final nickName = attributes['sender_nickName'] as String? ?? attributes['nickName'] as String?; |
|
|
|
final avatarUrl = attributes['sender_avatarUrl'] as String? ?? attributes['avatarUrl'] as String?; |
|
|
|
|
|
|
|
if ((nickName != null && nickName.isNotEmpty) || (avatarUrl != null && avatarUrl.isNotEmpty)) { |
|
|
|
cachedUserInfo = ExtendedUserInfo( |
|
|
|
userId: userId, |
|
|
|
nickName: nickName ?? cachedUserInfo?.nickName, |
|
|
|
avatarUrl: avatarUrl ?? cachedUserInfo?.avatarUrl, |
|
|
|
); |
|
|
|
// 如果 ConversationController 已注册,缓存起来 |
|
|
|
if (Get.isRegistered<ConversationController>()) { |
|
|
|
try { |
|
|
|
final conversationController = Get.find<ConversationController>(); |
|
|
|
conversationController.cacheUserInfo(userId, cachedUserInfo); |
|
|
|
} catch (e) { |
|
|
|
// 忽略缓存失败 |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if (message.direction == MessageDirection.SEND) { |
|
|
|
final toUserId = message.to; |
|
|
|
if (toUserId != null && toUserId == userId) { |
|
|
|
final nickName = attributes['receiver_nickName'] as String?; |
|
|
|
final avatarUrl = attributes['receiver_avatarUrl'] as String?; |
|
|
|
|
|
|
|
if ((nickName != null && nickName.isNotEmpty) || (avatarUrl != null && avatarUrl.isNotEmpty)) { |
|
|
|
cachedUserInfo = ExtendedUserInfo( |
|
|
|
userId: userId, |
|
|
|
nickName: nickName ?? cachedUserInfo?.nickName, |
|
|
|
avatarUrl: avatarUrl ?? cachedUserInfo?.avatarUrl, |
|
|
|
); |
|
|
|
// 如果 ConversationController 已注册,缓存起来 |
|
|
|
if (Get.isRegistered<ConversationController>()) { |
|
|
|
try { |
|
|
|
final conversationController = Get.find<ConversationController>(); |
|
|
|
conversationController.cacheUserInfo(userId, cachedUserInfo); |
|
|
|
} catch (e) { |
|
|
|
// 忽略缓存失败 |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// 忽略错误,继续使用其他数据源 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 获取用户详细信息(包含性别) |
|
|
|
final response = await networkService.userApi.getBaseUserInfo(userId); |
|
|
|
|
|
|
|
@ -73,14 +180,49 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
int? age; |
|
|
|
String? cityName; |
|
|
|
int? vipLevel; |
|
|
|
String? extAvatarUrl; // 从扩展信息中获取头像 |
|
|
|
String? extNickName; // 从扩展信息中获取昵称 |
|
|
|
|
|
|
|
// 首先尝试从环信用户信息的 gender 字段获取性别 |
|
|
|
// 环信的 gender 可能是 "1"(男)或 "2"(女),"0" 表示未设置 |
|
|
|
// 需要转换为我们的 genderCode(0-男, 1-女) |
|
|
|
try { |
|
|
|
final emGender = emUserInfo?.gender; |
|
|
|
if (emGender != null) { |
|
|
|
final emGenderStr = emGender.toString().trim(); |
|
|
|
|
|
|
|
// 环信: "1"=男, "2"=女, "0"=未设置 -> 我们的: 0=男, 1=女 |
|
|
|
if (emGenderStr == '1') { |
|
|
|
genderCode = 0; // 男性 |
|
|
|
} else if (emGenderStr == '2') { |
|
|
|
genderCode = 1; // 女性 |
|
|
|
} else if (emGenderStr != '0' && emGenderStr.isNotEmpty) { |
|
|
|
// 尝试直接解析为数字 |
|
|
|
final emGenderInt = int.tryParse(emGenderStr); |
|
|
|
if (emGenderInt != null) { |
|
|
|
if (emGenderInt == 1) { |
|
|
|
genderCode = 0; // 男性 |
|
|
|
} else if (emGenderInt == 2) { |
|
|
|
genderCode = 1; // 女性 |
|
|
|
} else if (emGenderInt != 0) { |
|
|
|
// 其他值,尝试转换 |
|
|
|
genderCode = emGenderInt >= 2 ? 1 : (emGenderInt == 1 ? 0 : null); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
// 忽略错误,继续从其他数据源获取 |
|
|
|
} |
|
|
|
|
|
|
|
if (emUserInfo?.ext != null) { |
|
|
|
// 如果还没有获取到性别,尝试从扩展信息中获取 |
|
|
|
if (genderCode == null && emUserInfo?.ext != null) { |
|
|
|
try { |
|
|
|
final extJson = json.decode(emUserInfo!.ext!); |
|
|
|
if (extJson is Map<String, dynamic>) { |
|
|
|
genderCode = extJson['genderCode'] is int |
|
|
|
? extJson['genderCode'] as int |
|
|
|
: int.tryParse(extJson['genderCode']?.toString() ?? '0'); |
|
|
|
: int.tryParse(extJson['genderCode']?.toString() ?? ''); |
|
|
|
age = extJson['age'] is int |
|
|
|
? extJson['age'] as int |
|
|
|
: int.tryParse(extJson['age']?.toString() ?? ''); |
|
|
|
@ -88,26 +230,88 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
vipLevel = extJson['vipLevel'] is int |
|
|
|
? extJson['vipLevel'] as int |
|
|
|
: int.tryParse(extJson['vipLevel']?.toString() ?? ''); |
|
|
|
extAvatarUrl = extJson['avatarUrl']?.toString() ?? |
|
|
|
extJson['profilePhoto']?.toString(); |
|
|
|
extNickName = extJson['nickName']?.toString(); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
print('解析扩展信息失败: $e'); |
|
|
|
// 忽略解析错误 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
guestList.add({ |
|
|
|
// 确定头像:优先使用环信头像,其次使用缓存头像,最后使用扩展信息中的头像 |
|
|
|
// 注意:需要检查空字符串,不仅仅是 null |
|
|
|
String avatarUrl = ''; |
|
|
|
final emAvatar = emUserInfo?.avatarUrl?.trim(); |
|
|
|
final cachedAvatar = cachedUserInfo?.avatarUrl?.trim(); |
|
|
|
final extAvatar = extAvatarUrl?.trim(); |
|
|
|
|
|
|
|
if (emAvatar != null && emAvatar.isNotEmpty) { |
|
|
|
avatarUrl = emAvatar; |
|
|
|
} else if (cachedAvatar != null && cachedAvatar.isNotEmpty) { |
|
|
|
avatarUrl = cachedAvatar; |
|
|
|
} else if (extAvatar != null && extAvatar.isNotEmpty) { |
|
|
|
avatarUrl = extAvatar; |
|
|
|
} |
|
|
|
|
|
|
|
// 清理头像URL(移除反引号) |
|
|
|
if (avatarUrl.isNotEmpty) { |
|
|
|
avatarUrl = avatarUrl.replaceAll('`', ''); |
|
|
|
} |
|
|
|
|
|
|
|
// 确定昵称:优先使用环信昵称,其次使用缓存昵称,再次使用扩展信息中的昵称,最后使用 API 返回的昵称 |
|
|
|
// 注意:需要检查空字符串,不仅仅是 null |
|
|
|
String name = ''; |
|
|
|
final emNickName = emUserInfo?.nickName?.trim(); |
|
|
|
final cachedNickName = cachedUserInfo?.nickName?.trim(); |
|
|
|
final extNick = extNickName?.trim(); |
|
|
|
final apiNickName = userBaseData.nickName.trim(); |
|
|
|
|
|
|
|
if (emNickName != null && emNickName.isNotEmpty) { |
|
|
|
name = emNickName; |
|
|
|
} else if (cachedNickName != null && cachedNickName.isNotEmpty) { |
|
|
|
name = cachedNickName; |
|
|
|
} else if (extNick != null && extNick.isNotEmpty) { |
|
|
|
name = extNick; |
|
|
|
} else if (apiNickName.isNotEmpty) { |
|
|
|
name = apiNickName; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果昵称为空,使用 userId 作为备选 |
|
|
|
if (name.isEmpty) { |
|
|
|
name = userId; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果 genderCode 仍然为 null,尝试从环信的 gender 字段推断 |
|
|
|
// 注意:如果环信的 gender=0 表示未设置,但如果用户有头像和昵称,可能是男性(因为只有男嘉宾有问题) |
|
|
|
int? finalGenderCode = genderCode; |
|
|
|
|
|
|
|
if (finalGenderCode == null) { |
|
|
|
// 如果所有数据源都没有性别信息,但用户有头像或昵称,可能是男性(因为只有男嘉宾有问题) |
|
|
|
// 这里暂时设置为 0(男性),因为用户说只有男嘉宾有问题 |
|
|
|
if (avatarUrl.isNotEmpty || name.isNotEmpty) { |
|
|
|
finalGenderCode = 0; // 默认设置为男性 |
|
|
|
} else { |
|
|
|
finalGenderCode = -1; // 未知 |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
final guestData = { |
|
|
|
'userId': userId, |
|
|
|
'avatar': emUserInfo?.avatarUrl ?? '', |
|
|
|
'name': emUserInfo?.nickName ?? userBaseData.nickName, |
|
|
|
'avatar': avatarUrl, |
|
|
|
'name': name, |
|
|
|
'age': age, |
|
|
|
'location': cityName ?? '', |
|
|
|
'vipLevel': vipLevel, |
|
|
|
'genderCode': genderCode ?? 0, // 0-男, 1-女 |
|
|
|
'genderCode': finalGenderCode, // 0-男, 1-女, -1-未知 |
|
|
|
'hasMaleGuest': false, // TODO: 根据实际业务逻辑判断 |
|
|
|
'hasFemaleGuest': false, // TODO: 根据实际业务逻辑判断 |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
guestList.add(guestData); |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
print('获取用户信息失败: ${conversation.id}, $e'); |
|
|
|
print('❌ [LiveRoomGuestListDialog] 获取用户信息异常: userId=${conversation.id}, error=$e'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@ -116,7 +320,7 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
_isLoading = false; |
|
|
|
}); |
|
|
|
} catch (e) { |
|
|
|
print('获取联系人列表失败: $e'); |
|
|
|
print('❌ [LiveRoomGuestListDialog] 获取联系人列表异常: $e'); |
|
|
|
setState(() { |
|
|
|
_guestList = []; |
|
|
|
_isLoading = false; |
|
|
|
@ -197,14 +401,16 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
} |
|
|
|
|
|
|
|
// 根据性别筛选联系人 |
|
|
|
// genderCode: 0-男, 1-女 |
|
|
|
// genderCode: 0-男, 1-女, -1-未知 |
|
|
|
final filteredList = _guestList.where((guest) { |
|
|
|
final genderCode = guest['genderCode'] as int? ?? 0; |
|
|
|
final genderCode = guest['genderCode'] as int? ?? -1; |
|
|
|
if (_selectedTab == 0) { |
|
|
|
// 女嘉宾标签页,显示女性(genderCode == 1) |
|
|
|
// 注意:如果 genderCode 为 -1(未知),暂时不显示,避免错误分类 |
|
|
|
return genderCode == 1; |
|
|
|
} else { |
|
|
|
// 男嘉宾标签页,显示男性(genderCode == 0) |
|
|
|
// 注意:如果 genderCode 为 -1(未知),暂时不显示,避免错误分类 |
|
|
|
return genderCode == 0; |
|
|
|
} |
|
|
|
}).toList(); |
|
|
|
@ -228,6 +434,10 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
} |
|
|
|
|
|
|
|
Widget _buildGuestItem(Map<String, dynamic> guest) { |
|
|
|
final userId = guest['userId'] as String? ?? ''; |
|
|
|
final avatar = guest['avatar'] as String? ?? ''; |
|
|
|
final name = guest['name'] as String? ?? ''; |
|
|
|
|
|
|
|
// 根据当前标签页判断是否已有对应性别的嘉宾 |
|
|
|
final hasGuest = _selectedTab == 0 |
|
|
|
? (guest['hasMaleGuest'] as bool? ?? false) |
|
|
|
@ -241,29 +451,38 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
// 头像 |
|
|
|
ClipRRect( |
|
|
|
borderRadius: BorderRadius.circular(20.w), |
|
|
|
child: CachedNetworkImage( |
|
|
|
imageUrl: guest['avatar'] as String? ?? '', |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
fit: BoxFit.cover, |
|
|
|
placeholder: (context, url) => Container( |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
color: Colors.grey[300], |
|
|
|
child: Center( |
|
|
|
child: CircularProgressIndicator( |
|
|
|
strokeWidth: 2, |
|
|
|
color: Colors.grey[600], |
|
|
|
child: avatar.isNotEmpty |
|
|
|
? CachedNetworkImage( |
|
|
|
imageUrl: avatar, |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
fit: BoxFit.cover, |
|
|
|
placeholder: (context, url) => Container( |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
color: Colors.grey[300], |
|
|
|
child: Center( |
|
|
|
child: CircularProgressIndicator( |
|
|
|
strokeWidth: 2, |
|
|
|
color: Colors.grey[600], |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
errorWidget: (context, url, error) { |
|
|
|
return Container( |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
color: Colors.grey[300], |
|
|
|
child: Icon(Icons.person, size: 24.w), |
|
|
|
); |
|
|
|
}, |
|
|
|
) |
|
|
|
: Container( |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
color: Colors.grey[300], |
|
|
|
child: Icon(Icons.person, size: 24.w), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
errorWidget: (context, url, error) => Container( |
|
|
|
width: 40.w, |
|
|
|
height: 40.w, |
|
|
|
color: Colors.grey[300], |
|
|
|
child: Icon(Icons.person, size: 24.w), |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
SizedBox(width: 12.w), |
|
|
|
// 用户信息 |
|
|
|
@ -274,7 +493,7 @@ class _LiveRoomGuestListDialogState extends State<LiveRoomGuestListDialog> { |
|
|
|
Row( |
|
|
|
children: [ |
|
|
|
Text( |
|
|
|
guest['name'] as String? ?? '', |
|
|
|
name.isNotEmpty ? name : userId, |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 15.w, |
|
|
|
fontWeight: FontWeight.w500, |
|
|
|
|