import 'dart:convert'; import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/global.dart'; import 'package:dating_touchme_app/controller/overlay_controller.dart'; import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/im/im_manager.dart'; import 'package:dating_touchme_app/model/discover/task_data.dart'; import 'package:dating_touchme_app/model/discover/task_template_data.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/mine/chat_static_data.dart'; import 'package:dating_touchme_app/model/rtc/link_mic_card_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/network/network_service.dart'; import 'package:dating_touchme_app/pages/mine/edit_info_page.dart'; import 'package:dating_touchme_app/pages/setting/match_league_page.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; import 'package:dating_touchme_app/rtc/rtm_manager.dart'; import 'package:dating_touchme_app/service/live_chat_message_service.dart'; import 'package:easy_refresh/easy_refresh.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:permission_handler/permission_handler.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; import '../../model/discover/audience_list_data.dart'; import '../../model/live/live_chat_message.dart'; import '../../pages/discover/live_end_page.dart'; import '../../pages/mine/real_name_page.dart'; import '../../pages/setting/match_spread_page.dart'; import '../../widget/live/live_recharge_popup.dart'; import '../message/call_controller.dart'; import 'discover_controller.dart'; import 'svga_player_manager.dart'; // 当前角色 enum CurrentRole { broadcaster, //主持 maleAudience, //男嘉宾 femaleAudience, //女嘉宾 audience, //观众 normalUser, //普通用户 } /// 直播房间相关控制器 class RoomController extends GetxController with WidgetsBindingObserver { RoomController({NetworkService? networkService}) : _networkService = networkService ?? Get.find(); final NetworkService _networkService; CurrentRole currentRole = CurrentRole.normalUser; var isLive = false.obs; var matchmakerFlag = false.obs; /// 当前频道信息 final Rxn rtcChannel = Rxn(); final Rxn rtcChannelDetail = Rxn(); /// 聊天消息列表 final RxList chatMessages = [].obs; /// 礼物产品列表 final RxList giftProducts = [].obs; /// 连麦卡片信息 final Rxn linkMicCard = Rxn(); /// 玫瑰数量 final RxInt roseCount = 0.obs; var isDialogShowing = false.obs; /// 消息服务实例 final LiveChatMessageService _messageService = LiveChatMessageService.instance; // matchmakerFlag @override void onInit() { super.onInit(); WidgetsBinding.instance.addObserver(this); matchmakerFlag.value = GlobalData().userData!.matchmakerFlag!; // 注册消息监听 _registerMessageListener(); // 加载礼物产品列表 loadGiftProducts(); listRefreshController = EasyRefreshController( controlFinishRefresh: true, controlFinishLoad: true, ); } @override void didChangeAppLifecycleState(AppLifecycleState state) { if (state == AppLifecycleState.resumed) { // print('_handleAppResumed'); } } @override void onClose() { super.onClose(); WidgetsBinding.instance.removeObserver(this); // 移除消息监听 _messageService.unregisterMessageListener(); // 清空RTM消息列表 chatMessages.clear(); } final durationMins = 0.obs; getDurationMins() async { try { final channelId = RTCManager.instance.currentChannelId; final response = await _networkService.rtcApi.userGetRtcChannelAbstract(channelId: channelId ?? ""); if (response.data.isSuccess && response.data.data != null) { durationMins.value = response.data.data?.durationMins ?? 0; } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e){ print('钱包数据获取失败: $e'); SmartDialog.showToast('钱包数据获取失败'); rethrow; } } final todayLiveIncomeStr = "0.00".obs; getLiveIncome() async { try { final response = await _networkService.rtcApi.userGetTodayLiveIncome(); if (response.data.isSuccess && response.data.data != null) { todayLiveIncomeStr.value = response.data.data?.todayLiveIncomeStr ?? "0.00"; } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e){ print('钱包数据获取失败: $e'); SmartDialog.showToast('钱包数据获取失败'); rethrow; } } final consumption = ChatStaticData(todayLiveDurationMins: 0).obs; getLiveData() async { try { final channelId = RTCManager.instance.currentChannelId; final response = await _networkService.userApi.getChatStaticsInfo(); if (response.data.isSuccess && response.data.data != null) { consumption.value = response.data.data!; } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e){ print('钱包数据获取失败: $e'); SmartDialog.showToast('钱包数据获取失败'); rethrow; } } final tab = 0.obs; changeTab(int i) async { tab.value = i; if(tab.value == 0){ page.value = 1; audienceList.clear(); await getAudienceList(); } else if(tab.value == 1) { page.value = 1; audienceList.clear(); await getOnMicList(); } else if(tab.value == 2) { page.value = 1; audienceList.clear(); await getFriendList(); } update(); listRefreshController.finishRefresh(IndicatorResult.success); listRefreshController.finishLoad(IndicatorResult.none); } final page = 1.obs; final audienceList = [].obs; late final EasyRefreshController listRefreshController; getAudienceList() async { try{ final channelId = RTCManager.instance.currentChannelId; final response = await _networkService.rtcApi.userPageRtcChannelAudience( pageNum: page.value, pageSize: 10, channelId: channelId ?? "" ); if (response.data.isSuccess && response.data.data != null) { final data = response.data.data?.records ?? []; audienceList.addAll(data.toList()); if((data.length ?? 0) == 10){ listRefreshController.finishLoad(IndicatorResult.success); } else { listRefreshController.finishLoad(IndicatorResult.noMore); } update(); } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e) { print('提现记录获取失败: $e'); SmartDialog.showToast('提现记录获取失败'); rethrow; } } getOnMicList() async { try{ final channelId = RTCManager.instance.currentChannelId; final response = await _networkService.rtcApi.userPageMicJoinRtcChannelUser( pageNum: page.value, pageSize: 10, channelId: channelId ?? "" ); if (response.data.isSuccess && response.data.data != null) { final data = response.data.data?.records ?? []; audienceList.addAll(data.toList()); if((data.length ?? 0) == 10){ listRefreshController.finishLoad(IndicatorResult.success); } else { listRefreshController.finishLoad(IndicatorResult.noMore); } update(); } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e) { print('提现记录获取失败: $e'); SmartDialog.showToast('提现记录获取失败'); rethrow; } } getFriendList() async { try{ final response = await _networkService.userApi.userPageFriendRelation( pageNum: page.value, pageSize: 10, ); if (response.data.isSuccess && response.data.data != null) { final data = response.data.data?.records ?? []; final List b = data.map((e){ return Records( id: e.id, userManagementId: null, userId: e.userId, miId: e.miId, nickName: e.nickName, profilePhoto: e.profilePhoto, genderCode: e.genderCode, birthYear: e.birthYear, birthDate: e.birthDate, age: e.age, provinceCode: e.provinceCode, provinceName: e.provinceName, cityCode: e.cityCode, cityName: e.cityName, ); }).toList(); audienceList.addAll(b); if((data.length ?? 0) == 10){ listRefreshController.finishLoad(IndicatorResult.success); } else { listRefreshController.finishLoad(IndicatorResult.noMore); } update(); } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e) { print('好友列表获取失败: $e'); SmartDialog.showToast('好友列表获取失败'); rethrow; } } final taskData = TaskData().obs; getTaskData(int type) async { try{ final response = await _networkService.userApi.userGetUserTaskComplete( taskType: type, ); if (response.data.isSuccess) { if(response.data.data == null){ throw Exception(response.data.message ?? '获取数据失败'); } final data = response.data.data ?? TaskData(); taskData.value = data; } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e) { print('任务列表获取失败: $e'); // SmartDialog.showToast('任务列表获取失败'); rethrow; } } final taskTemplateData = TaskTemplateData().obs; getTaskTemplateData() async { try{ final response = await _networkService.rtcApi.userGetTaskTemplateDetail( id: taskData.value.taskTemplateId ?? "", ); if (response.data.isSuccess) { final data = response.data.data ?? TaskTemplateData(); taskTemplateData.value = data; } else { // 响应失败,抛出异常 throw Exception(response.data.message ?? '获取数据失败'); } } catch(e) { print('任务列表获取失败: $e'); // SmartDialog.showToast('任务列表获取失败'); rethrow; } } void setDialogDismiss(bool flag){ isDialogShowing.value = flag; } /// 注册消息监听 void _registerMessageListener() { _messageService.registerMessageListener( onMessageReceived: (message) { _addMessage(message); }, onMessageError: (error) { print('❌ 消息处理错误: $error'); }, ); } /// 添加消息到列表(带去重和数量限制) void _addMessage(LiveChatMessage message) { // 去重:检查是否已存在相同的消息(基于 userId + content + timestamp) final exists = chatMessages.any( (m) => m.userId == message.userId && m.content == message.content && (m.timestamp - message.timestamp).abs() < 1000, ); // 1秒内的相同消息视为重复 if (exists) { print('⚠️ 消息已存在,跳过添加'); return; } chatMessages.add(message); print('✅ 消息已添加到列表,当前消息数: ${chatMessages.length}'); // 限制消息数量,最多保留100条 if (chatMessages.length > 300) { chatMessages.removeAt(0); print('📝 消息列表已满,移除最旧的消息'); } } /// 调用接口创建 RTC 频道 Future createRtcChannel() async { if (isLive.value) { return; } // 检查是否正在通话中(包括呼叫中) try { // 先尝试使用 Get.find 获取已注册的实例,如果不存在再使用 instance CallController callController; if (Get.isRegistered()) { callController = Get.find(); } else { callController = CallController.instance; } print('⚠️ [RoomController] 检查通话状态: currentCall=${callController.currentCall.value}, hashCode=${callController.hashCode}'); if (callController.currentCall.value != null) { SmartDialog.showToast('请先结束通话'); print('⚠️ [RoomController] 当前正在通话中,无法开始直播'); return; } } catch (e) { // 如果获取失败,说明没有正在进行的通话,继续执行 print('⚠️ [RoomController] 获取CallController失败(可能没有通话): $e'); } final granted = await _ensureRtcPermissions(); if (!granted) return; try { final response = await _networkService.rtcApi.createRtcChannel(); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; currentRole = CurrentRole.broadcaster; isLive.value = true; await _joinRtcChannel( base.data!.token, base.data!.channelId, base.data!.uid, ClientRoleType.clientRoleBroadcaster, ); } else { final message = base.message.isNotEmpty ? base.message : '创建频道失败'; SmartDialog.showToast(message); } } catch (e) { debugPrint('创建频道异常:$e'); } } Future joinChannel(String channelName) async { // 检查是否正在通话中(包括呼叫中) try { // 直接使用 CallController.instance,因为它使用单例模式会自动注册 final callController = CallController.instance; if (callController.currentCall.value != null) { SmartDialog.showToast('请先结束通话'); print('⚠️ [RoomController] 当前正在通话中,无法加入直播间'); return; } } catch (e) { // 忽略错误,继续处理加入频道逻辑 print('⚠️ [RoomController] 检查通话状态失败: $e'); } final response = await _networkService.rtcApi.getSwRtcToken(channelName); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannel.value = base.data; // 加入他人直播间时,当前用户默认作为普通观众,不在麦上 currentRole = CurrentRole.normalUser; isLive.value = false; await _joinRtcChannel( base.data!.token, channelName, base.data!.uid, ClientRoleType.clientRoleAudience, ); } } Future _joinRtcChannel( String token, String channelName, int uid, ClientRoleType roleType, ) async { try { await _fetchRtcChannelDetail(channelName); await RTCManager.instance.joinChannel( token: token, channelId: channelName, uid: uid, role: roleType, ); } catch (e) { SmartDialog.showToast('加入频道失败:$e'); } } Future joinChat(CurrentRole role) async { // 根据角色检查权限:男/女嘉宾需要摄像头和麦克风权限,普通观众只需要麦克风权限 bool granted = false; if (role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience) { // 男/女嘉宾上麦:需要摄像头和麦克风权限 granted = await _ensureRtcPermissions(); } else { // 普通观众上麦:只需要麦克风权限 granted = await _ensureMicrophonePermission(); } if (!granted) { print('❌ [RoomController] 权限检查失败,无法上麦'); return; } final data = { 'channelId': RTCManager.instance.currentChannelId, 'seatNumber': role == CurrentRole.maleAudience ? 1 : 2, 'isMicrophoneOn': role != CurrentRole.normalUser ? true : false, 'isVideoOn': role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience ? true : false, }; final response = await _networkService.rtcApi.connectRtcChannel(data); if (!response.data.isSuccess) { // 刷新 DiscoverController.loadRtcChannelPage if (Get.isRegistered()) { final discoverController = Get.find(); discoverController.loadRtcChannelPage(); } SmartDialog.showToast(response.data.message); return; } if (!response.data.data['success']) { return; } currentRole = role; if (role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience) { await RTCManager.instance.publishVideo(role); } else { await RTCManager.instance.publishAudio(); } // 上麦成功后刷新连麦卡片数据 await getUserPropLinkMicCard(); RtcSeatUserInfo userInfo = RtcSeatUserInfo( uid: rtcChannel.value?.uid, miId: GlobalData().userData?.id ?? '', userId: GlobalData().userData?.id ?? '', nickName: GlobalData().userData?.nickName ?? '', profilePhoto: GlobalData().userData?.profilePhoto ?? '', seatNumber: role == CurrentRole.maleAudience ? 1 : 2, isFriend: false, isMicrophoneOn: true, isVideoOn: role == CurrentRole.maleAudience || role == CurrentRole.femaleAudience ? true : false, genderCode: GlobalData().userData?.genderCode ?? 0, ); final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: role == CurrentRole.maleAudience ? userInfo : rtcChannelDetail.value?.maleInfo, femaleInfo: role == CurrentRole.femaleAudience ? userInfo : rtcChannelDetail.value?.femaleInfo, ); rtcChannelDetail.value = newDetail; isLive.value = true; } Future leaveChat() async { final data = {'channelId': RTCManager.instance.currentChannelId}; final response = await _networkService.rtcApi.disconnectRtcChannel(data); if (response.data.isSuccess) { isLive.value = false; await RTCManager.instance.unpublish(currentRole); if (currentRole == CurrentRole.maleAudience) { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: null, femaleInfo: rtcChannelDetail.value!.femaleInfo, ); rtcChannelDetail.value = newDetail; } else if (currentRole == CurrentRole.femaleAudience) { final newDetail = RtcChannelDetail( channelId: rtcChannelDetail.value!.channelId, anchorInfo: rtcChannelDetail.value!.anchorInfo, maleInfo: rtcChannelDetail.value!.maleInfo, femaleInfo: null, ); rtcChannelDetail.value = newDetail; } currentRole = CurrentRole.normalUser; } } /// 获取 RTC 频道详情(公有方法,供外部调用) Future fetchRtcChannelDetail(String channelName) async { try { final response = await _networkService.rtcApi.getRtcChannelDetail( channelName, ); final base = response.data; if (base.isSuccess && base.data != null) { rtcChannelDetail.value = base.data; } } catch (e) { print('获取 RTC 频道详情失败:$e'); } } Future _fetchRtcChannelDetail(String channelName) async { await fetchRtcChannelDetail(channelName); } /// 发送公屏消息 Future sendChatMessage(String content) async { final channelName = RTCManager.instance.currentChannelId; final result = await _messageService.sendMessage( content: content, channelName: channelName, ); // 如果发送成功,立即添加到本地列表(优化体验,避免等待 RTM 回调) if (result.success && result.message != null) { _addMessage(result.message!); } } Future _ensureRtcPermissions() async { final statuses = await [Permission.camera, Permission.microphone].request(); final allGranted = statuses.values.every((status) => status.isGranted); if (allGranted) { return true; } final permanentlyDenied = statuses.values.any( (status) => status.isPermanentlyDenied, ); if (permanentlyDenied) { SmartDialog.showToast('请在系统设置中开启摄像头和麦克风权限'); await openAppSettings(); } else { SmartDialog.showToast('请允许摄像头和麦克风权限以进入房间'); } return false; } /// 检查麦克风权限(用于普通观众上麦) Future _ensureMicrophonePermission() async { final micStatus = await Permission.microphone.request(); if (micStatus.isGranted) { return true; } if (micStatus.isPermanentlyDenied) { SmartDialog.showToast('请在系统设置中开启麦克风权限'); await openAppSettings(); } else { SmartDialog.showToast('请允许麦克风权限以进入房间'); } return false; } Future leaveChannel() async { if (currentRole == CurrentRole.broadcaster) { try { // 先调用销毁 RTC 频道 API final destroyResponse = await _networkService.rtcApi.destroyRtcChannel(); if (destroyResponse.data.isSuccess) { // 然后发送结束直播消息 final channelId = RTCManager.instance.currentChannelId; if (channelId != null && channelId.isNotEmpty) { await RTMManager.instance.publishChannelMessage( channelName: channelId, message: json.encode({'type': 'end_live'}), ); } } } catch (e) { print('❌ 销毁 RTC 频道异常: $e'); } } else if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) { try { // 嘉宾离开时调用断开 RTC 频道连接接口 final channelId = RTCManager.instance.currentChannelId; if (channelId != null && channelId.isNotEmpty) { final data = {'channelId': channelId}; final response = await _networkService.rtcApi.disconnectRtcChannel(data); if (response.data.isSuccess) { print('✅ [RoomController] 嘉宾已断开 RTC 频道连接,channelId: $channelId'); } else { print('⚠️ [RoomController] 断开 RTC 频道连接失败: ${response.data.message}'); } } } catch (e) { print('❌ [RoomController] 断开 RTC 频道连接异常: $e'); } } isLive.value = false; if (currentRole == CurrentRole.maleAudience || currentRole == CurrentRole.femaleAudience) { await RTCManager.instance.unpublish(currentRole); } currentRole = CurrentRole.normalUser; await RTCManager.instance.leaveChannel(); // 清空RTM消息列表 chatMessages.clear(); } /// 加载礼物产品列表 Future loadGiftProducts() async { try { final response = await _networkService.rtcApi.listGiftProduct(); final base = response.data; if (base.isSuccess && base.data != null) { giftProducts.assignAll(base.data!); print('✅ 礼物产品列表加载成功,共 ${giftProducts.length} 个'); } else { print('❌ 加载礼物产品列表失败: ${base.message}'); } } catch (e) { print('❌ 加载礼物产品列表异常: $e'); } } /// 赠送礼物 Future sendGift({ required GiftProductModel gift, required int targetUserId, required int type, }) async { try { // 先调用消费接口 final channelId = RTCManager.instance.currentChannelId; if (channelId == null || channelId.isEmpty) { SmartDialog.showToast('频道ID不存在'); return; } // 准备请求参数 final requestData = { 'channelId': int.tryParse(channelId) ?? 0, 'type': type, // 1.送礼 2.添加好友 'toUId': targetUserId, 'productSpecId': int.tryParse(gift.productSpecId) ?? 0, 'quantity': 1, }; // 调用消费接口 final response = await _networkService.rtcApi.costChannelGift( requestData, ); if (!response.data.isSuccess) { SmartDialog.showToast( response.data.message.isNotEmpty ? response.data.message : '发送礼物失败', ); return; } if (response.data.isSuccess && !response.data.data['success']) { final code = response.data.data['code']; if (code == 'E0002') { // 玫瑰不足,显示 toast 并弹出充值弹框 SmartDialog.showToast('玫瑰不足请充值'); Get.log('❌ 送礼失败: ${response.data.data}'); // 使用 addPostFrameCallback 确保在下一帧显示弹框,避免与 toast 冲突 WidgetsBinding.instance.addPostFrameCallback((_) { setDialogDismiss(true); SmartDialog.show( alignment: Alignment.bottomCenter, onDismiss: (){ setDialogDismiss(false); }, maskColor: Colors.black.withOpacity(0.5), builder: (_) => const LiveRechargePopup(), ); }); } else { SmartDialog.showToast('发送礼物失败'); } return; } // 消费成功后刷新玫瑰数量 await getVirtualAccount(); // 消费成功后再添加到本地播放队列 final svgaManager = SvgaPlayerManager.instance; svgaManager.addToQueue( SvgaAnimationItem( svgaFile: gift.svgaFile, targetUserId: targetUserId, senderUserId: rtcChannel.value?.uid, giftProductId: gift.productId, ), ); print('✅ 礼物已添加到播放队列: ${gift.productTitle}'); // 在公屏显示赠送礼物消息 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': senderNickName, 'senderAvatar': GlobalData().userData?.profilePhoto ?? '', }; await RTMManager.instance.publishChannelMessage( channelName: channelId, message: json.encode(messageData), ); print('✅ 礼物消息已发送: ${gift.productTitle}'); // 创建公屏消息 final giftMessage = LiveChatMessage( userId: GlobalData().userId ?? GlobalData().userData?.id ?? '', userName: senderNickName, avatar: GlobalData().userData?.profilePhoto, content: '向$targetNickName赠送了【${gift.productTitle}】', timestamp: DateTime.now().millisecondsSinceEpoch, ); // 添加到消息列表 _addMessage(giftMessage); } catch (e) { print('❌ 发送礼物失败: $e'); SmartDialog.showToast('发送礼物失败'); } } /// 接收RTC消息 Future receiveRTCMessage(Map message) async { if (message['type'] == 'gift') { // 处理礼物消息 try { 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 = '礼物'; if (giftProductId != null && giftProductId.isNotEmpty) { try { final gift = giftProducts.firstWhere( (g) => g.productId == giftProductId, ); giftTitle = gift.productTitle; } catch (e) { // 如果找不到对应的礼物,使用默认名称 print('⚠️ 未找到礼物ID: $giftProductId'); } } // 获取目标用户昵称(优先使用消息中的昵称,如果不存在则从频道详情中查找) 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 : '用户'; } } } } } // 在公屏显示赠送礼物消息 final giftMessage = LiveChatMessage( userId: senderUserId?.toString() ?? '', userName: senderNickName, avatar: senderAvatar?.isNotEmpty == true ? senderAvatar : null, content: '向$targetNickName赠送了【$giftTitle】', timestamp: DateTime.now().millisecondsSinceEpoch, ); _addMessage(giftMessage); if (svgaFile.isNotEmpty) { // 添加到播放队列 final svgaManager = SvgaPlayerManager.instance; svgaManager.addToQueue( SvgaAnimationItem( svgaFile: svgaFile, targetUserId: targetUserId, senderUserId: senderUserId is int ? senderUserId : (int.tryParse(senderUserId?.toString() ?? '') ?? 0), giftProductId: giftProductId, ), ); print('✅ 收到礼物消息,已添加到播放队列: $senderNickName 赠送了礼物'); } } catch (e) { print('❌ 处理礼物消息失败: $e'); } } else if (message['type'] == 'kick_user') { // 处理踢人消息 try { final kickingUId = message['kickingUId']; final operatorName = message['operatorName']?.toString() ?? '主持人'; print('✅ 收到踢人消息: 被踢用户ID $kickingUId'); // 判断当前用户是否是被踢的用户 if (rtcChannel.value?.uid == kickingUId) { // 被踢用户:关闭小窗口 if (Get.isRegistered()) { try { final overlayController = Get.find(); overlayController.hide(); // 隐藏直播房间小窗 } catch (e) { print('关闭小窗口失败: $e'); } } // 被踢用户:离开房间 await leaveChannel(); // 跳转到结束直播页面,并传入被踢出标识 Get.off( () => LiveEndPage(isKickedOut: true, operatorName: operatorName), ); } else { // 其他用户:刷新房间详情 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await fetchRtcChannelDetail(channelName); } print('✅ 其他用户收到踢人消息,已刷新房间详情'); } } catch (e) { print('❌ 处理踢人消息失败: $e'); } } else if(message['type'] == 'invite_to_mic'){ // 处理踢人消息+ try { final userId = message['userId']; final operatorName = message['operatorName']?.toString() ?? '主持人'; print('✅ 收到邀请消息: 被邀请用户ID $userId'); // 判断当前用户是否是被踢的用户 if (userId.contains(GlobalData().userId ?? '')) { if(rtcChannelDetail.value?.maleInfo == null && GlobalData().userData?.genderCode == 0 && currentRole != CurrentRole.broadcaster || rtcChannelDetail.value?.femaleInfo == null && GlobalData().userData?.genderCode == 1 && currentRole != CurrentRole.broadcaster || isLive.value && currentRole != CurrentRole.broadcaster && !isLive.value){ setDialogDismiss(true); SmartDialog.show( alignment: Alignment.center, maskColor: Colors.black.withOpacity(0.5), onDismiss: () { setDialogDismiss(false); }, builder: (context) { // return LiveRoomGuestListDialog( // initialTab: isMaleSeat ? 1 : 0, // 0: 女嘉宾, 1: 男嘉宾 // ); return ClipRRect( borderRadius: BorderRadius.all(Radius.circular(16.w)), child: Material( child: Stack( children: [ Container( width: 311.w, height: 210.w, color: Colors.white, padding: EdgeInsets.only( top: 53.w, left: 23.w, right: 23.w, bottom: 20.w ), child: Column( children: [ Text( "主持人邀请您视频连麦", style: TextStyle( fontSize: 21.w, color: const Color.fromRGBO(117, 98, 249, 1), fontWeight: FontWeight.w500 ), ), SizedBox(height: 15.w,), if(GlobalData().userData?.genderCode == 0) Text( "有相亲卡的用户免费", style: TextStyle( fontSize: 12.w, color: const Color.fromRGBO(87, 87, 87, 1), fontWeight: FontWeight.w500 ), ), SizedBox(height: 28.w,), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( width: 128.w, height: 40.w, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(12.w)), color: const Color.fromRGBO(237, 237, 237, 1) ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( Assets.imagesHangUpIcon, width: 26.w, height: 26.w, ), SizedBox(width: 5.w,), Text( "拒绝", style: TextStyle( fontSize: 15.w ), ) ], ), ).onTap((){ // 隐藏键盘 FocusScope.of(context).unfocus(); // 隐藏 overlay SmartDialog.dismiss(); setDialogDismiss(false); }), Container( width: 128.w, height: 40.w, decoration: BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(12.w)), color: const Color.fromRGBO(117, 98, 249, 1) ), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Image.asset( Assets.imagesAnswerIcon, width: 18.w, height: 13.w, ), SizedBox(width: 5.w,), Text( "接受", style: TextStyle( fontSize: 15.w, color: Colors.white ), ) ], ), ).onTap(() async { // 隐藏键盘 FocusScope.of(context).unfocus(); // 隐藏 overlay SmartDialog.dismiss(); setDialogDismiss(false); // 检查是否需要弹出充值弹框 if (!isLive.value) { final userData = GlobalData().userData; final isMale = userData?.genderCode == 0; if (isMale) { final cardNum = linkMicCard.value?.num ?? 0; // 如果显示"上麦20玫瑰"且玫瑰数量小于20,弹出充值弹框 if (cardNum == 0 && roseCount.value < 20) { setDialogDismiss(true); SmartDialog.show( alignment: Alignment.bottomCenter, maskColor: Colors.black.withOpacity(0.5), onDismiss: (){ setDialogDismiss(false); }, builder: (_) => const LiveRechargePopup(), ); return; } } } if(isLive.value){ await leaveChat(); }else{ await joinChat(GlobalData().userData?.genderCode == 0 ? CurrentRole.maleAudience : CurrentRole.femaleAudience); } }), ], ) ], ), ), Positioned( top: 0, left: 0, child: Container( padding: EdgeInsets.symmetric(vertical: 5.w, horizontal: 15.w), decoration: BoxDecoration( borderRadius: BorderRadius.only( topLeft: Radius.circular(16.w), bottomRight: Radius.circular(16.w), ), color: const Color.fromRGBO(117, 98, 249, 1) ), child: Text( GlobalData().userData?.genderCode == 0 ? "20玫瑰" : "免费", style: TextStyle( fontSize: 14.w, color: Colors.white, fontWeight: FontWeight.w500 ), ), ), ) ], ), ), ); }, ); } } else { // 其他用户:刷新房间详情 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await fetchRtcChannelDetail(channelName); } print('✅ 其他用户收到邀请消息,已刷新房间详情'); } } catch (e) { print('❌ 处理邀请消息失败: $e'); } } else if(message['type'] == 'kick_more_user'){ // 处理踢人消息+ try { final userId = message['userId'].map((e) => int.parse(e)).toList(); final operatorName = message['operatorName']?.toString() ?? '主持人'; print('✅ 收到踢人消息: 被踢人用户ID $userId'); // 判断当前用户是否是被踢的用户 if (userId.contains(rtcChannel.value?.uid ?? '')) { // 被踢用户:关闭小窗口 if (Get.isRegistered()) { try { final overlayController = Get.find(); overlayController.hide(); // 隐藏直播房间小窗 } catch (e) { print('关闭小窗口失败: $e'); } } // 被踢用户:离开房间 await leaveChannel(); // 跳转到结束直播页面,并传入被踢出标识 Get.off( () => LiveEndPage(isKickedOut: true, operatorName: operatorName), ); } else { // 其他用户:刷新房间详情 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await fetchRtcChannelDetail(channelName); } print('✅ 其他用户收到踢人消息,已刷新房间详情'); } } catch (e) { print('❌ 处理踢人消息失败: $e'); } } else if (message['type'] == 'end_mic') { // 处理踢人消息 try { final kickingUId = message['kickingUId']; final operatorName = message['operatorName']?.toString() ?? '主持人'; print('✅ 收到下麦消息: 被下麦用户ID $kickingUId'); // 判断当前用户是否是被踢的用户 if (rtcChannel.value?.uid == kickingUId) { await leaveChat(); } else { // 其他用户:刷新房间详情 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await fetchRtcChannelDetail(channelName); } print('✅ 其他用户收到下麦消息,已刷新房间详情'); } } catch (e) { print('❌ 处理下麦消息失败: $e'); } } else if (message['type'] == 'close_mic') { // 处理踢人消息 try { final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await fetchRtcChannelDetail(channelName); } print('✅ 其他用户收到下麦消息,已刷新房间详情'); } catch (e) { print('❌ 处理下麦消息失败: $e'); } } } void registerMatch(int type) async { if (GlobalData().userData!.profilePhoto == null || GlobalData().userData!.profilePhoto!.isEmpty) { SmartDialog.showToast('请先上传头像'); await Get.to(() => EditInfoPage()); return; } if (GlobalData().userData!.hasUploadProfilePhoto != true) { showGeneralDialog( context: Get.context!, pageBuilder: (BuildContext buildContext, Animation animation, Animation secondaryAnimation) { return TDConfirmDialog( title: '温馨提示', content: '当前头像正在审核中,请稍候,如果长时间没有审核结果,请联系客服。', ); }, ); return; } if(GlobalData().userData!.identityCard == null || GlobalData().userData!.identityCard!.isEmpty){ SmartDialog.showToast('请先进行实名认证'); await Get.to(() => RealNamePage(type: 1)); return; } if(type == 1){ Get.to(() => MatchLeaguePage()); } else { await createRtcChannel(); } } int calculateAge(String birthdayStr) { final birthday = DateTime.parse(birthdayStr); // 自动识别 1996-1-20 final today = DateTime.now(); int age = today.year - birthday.year; // 如果今年生日还没过,年龄要减 1 if (today.month < birthday.month || (today.month == birthday.month && today.day < birthday.day)) { age--; } return age; } changeMic(bool mic) async { try { final channelId = RTCManager.instance.currentChannelId; var res = await _networkService.rtcApi.enableRtcChannelUserAudio( { "channelId": channelId, "isMicrophoneOn": mic, "isVideoOn": true } ); if(res.data.isSuccess){ final messageData = { 'type': 'close_mic', 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode(messageData), ); print("关闭麦克风成功"); } } } catch(e){ print("关闭麦克风失败"); } } inviteMic(List> selectUserId) async { try { final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { final idIsNull = selectUserId .where((e) => e["uid"] == 0) .toList(); final idNotNull = selectUserId .where((e) => e["uid"] != 0) .toList(); final userIds = idNotNull.map((e) => e["userId"]).toList(); final messageData = { 'type': 'invite_to_mic', 'userId': userIds, 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode(messageData), ); final others = idIsNull.map((e) => e["userId"]).toList(); final channelId = RTCManager.instance.currentChannelId; if (channelId == null || channelId.isEmpty) { SmartDialog.showToast('频道ID不存在'); return; } // 获取房间详情 final channelDetail = rtcChannelDetail.value; final anchorName = channelDetail?.anchorInfo?.nickName ?? '主持人'; final anchorAvatar = channelDetail?.anchorInfo?.profilePhoto ?? ''; // 确保头像和封面URL不为空且格式正确 final cleanedAvatar = anchorAvatar.trim().replaceAll('`', ''); final cleanedCover = cleanedAvatar; // 封面使用主持人头像 // 构建消息参数,包含房间信息(不需要 type 字段,因为 event 已经是 'live_room_invite') final messageParams = { 'channelId': channelId, 'anchorAvatar': cleanedAvatar, 'anchorName': anchorName, 'coverImage': cleanedCover, // 封面使用主持人头像 'genderCode': GlobalData().userData?.genderCode.toString() ?? "", "age": calculateAge( (GlobalData().userData?.birthDate != null && GlobalData().userData!.birthDate!.isNotEmpty) ? (GlobalData().userData?.birthDate ?? "") : GlobalData().userData?.birthYear != null && GlobalData().userData!.birthYear!.isNotEmpty ? "${GlobalData().userData?.birthYear}-01-01" : "").toString(), "provinceName": GlobalData().userData?.provinceName ?? "", }; print('📤 [LiveRoomGuestListDialog] 发送房间邀请消息: anchorAvatar=$cleanedAvatar, coverImage=$cleanedCover'); others.forEach((e) async { // 发送自定义消息 final result = await IMManager.instance.sendCustomMessage( e, 'live_room_invite', messageParams, ); if (result != null) { SmartDialog.showToast('邀请已发送'); SmartDialog.dismiss(); } else { SmartDialog.showToast('邀请发送失败,id$e'); } }); print('✅ 邀请消息已发送: 邀请用户ID $selectUserId'); // 刷新频道详情 await fetchRtcChannelDetail(channelName); } } catch (e){ print('❌ 邀请用户异常: $e'); SmartDialog.showToast('邀请用户失败'); } } kickUser(List selectUserId) async { try { final futures = selectUserId.map((e){ final requestData = {'channelId': rtcChannelDetail.value!.channelId, 'kickingUId': e}; return _networkService.rtcApi.kickingRtcChannelUser( requestData, ); }); Future.wait(futures).then((e) async { final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { final messageData = { 'type': 'kick_more_user', 'userId': selectUserId, 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode(messageData), ); print('✅ 邀请消息已发送: 邀请用户ID $selectUserId'); // 刷新频道详情 await fetchRtcChannelDetail(channelName); } }); } catch (e){ print('❌ 邀请用户异常: $e'); SmartDialog.showToast('邀请用户失败'); } } /// 踢出 RTC 频道用户 Future kickingRtcChannelUser({ required String channelId, required int kickingUId, }) async { try { final requestData = {'channelId': channelId, 'kickingUId': kickingUId}; final response = await _networkService.rtcApi.kickingRtcChannelUser( requestData, ); if (response.data.isSuccess) { SmartDialog.showToast('已踢出用户'); // 发送 RTM 消息通知所有用户 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { final messageData = { 'type': 'kick_user', 'kickingUId': kickingUId, 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode(messageData), ); print('✅ 踢人消息已发送: 踢出用户ID $kickingUId'); // 刷新频道详情 await fetchRtcChannelDetail(channelName); } } else { final message = response.data.message.isNotEmpty ? response.data.message : '踢出用户失败'; SmartDialog.showToast(message); } } catch (e) { print('❌ 踢出用户异常: $e'); SmartDialog.showToast('踢出用户失败'); } } /// 踢出 RTC 频道用户 Future endMic({ required String channelId, required int kickingUId, }) async { try { // 发送 RTM 消息通知所有用户 final channelName = RTCManager.instance.currentChannelId; if (channelName != null && channelName.isNotEmpty) { final messageData = { 'type': 'end_mic', 'kickingUId': kickingUId, 'operatorId': GlobalData().userData?.id ?? '', 'operatorName': GlobalData().userData?.nickName ?? '', }; await RTMManager.instance.publishChannelMessage( channelName: channelName, message: json.encode(messageData), ); print('✅ 下麦消息已发送: 下麦用户ID $kickingUId'); // 刷新频道详情 await fetchRtcChannelDetail(channelName); } } catch (e) { print('❌ 下麦用户异常: $e'); SmartDialog.showToast('下麦用户失败'); } } /// 获取用户道具连麦卡片 Future getUserPropLinkMicCard() async { try { final response = await _networkService.rtcApi.getUserPropLinkMicCard(); final base = response.data; if (base.isSuccess && base.data != null) { linkMicCard.value = base.data; print( '✅ 获取连麦卡片成功: type=${base.data!.type}, num=${base.data!.num}, unitSellingPrice=${base.data!.unitSellingPrice}', ); } else { linkMicCard.value = null; print('❌ 获取连麦卡片失败: ${base.message}'); } } catch (e) { linkMicCard.value = null; print('❌ 获取连麦卡片异常: $e'); } } /// 获取虚拟账户(玫瑰数量) Future getVirtualAccount() async { try { final response = await _networkService.userApi.getVirtualAccount({}); final base = response.data; if (base.isSuccess && base.data != null) { roseCount.value = base.data!.balance ?? 0; print('✅ 获取玫瑰数量成功: ${roseCount.value}'); } else { roseCount.value = 0; print('❌ 获取玫瑰数量失败: ${base.message}'); } } catch (e) { roseCount.value = 0; print('❌ 获取玫瑰数量异常: $e'); } } }