From 540e152f14df224f7c2fdf0b041d2c72658924d8 Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Wed, 26 Nov 2025 21:42:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(live):=20=E5=AE=9E=E7=8E=B0=E7=A4=BC?= =?UTF-8?q?=E7=89=A9=E5=BC=B9=E7=AA=97=E7=94=A8=E6=88=B7=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E4=B8=8E=E7=A4=BC=E7=89=A9=E5=B1=95=E7=A4=BA=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 将 LiveGiftPopup 从 StatelessWidget 改为 StatefulWidget 以支持状态管理 - 新增用户选择逻辑,支持单个用户选中与全选/取消全选功能 - 使用 Obx 监听 RoomController 中的 RTC 频道详情动态构建用户列表 - 过滤掉当前用户自身,最多显示三个可送礼用户 - 用户头像使用 CachedNetworkImage 加载,支持加载占位与错误处理 - 礼物区域支持分页展示,每页最多 8 个礼物(2 行 4 列) - 支持 Map 和 GiftProductModel 两种数据结构的礼物列表渲染 - LiveRoomGiftItem 组件适配网络图片加载与文本截断显示 - 动态获取并显示礼物名称与价格(单位:支) - 优化空礼物列表提示与 UI 布局细节 --- lib/controller/discover/room_controller.dart | 70 +++++++- lib/widget/live/live_gift_popup.dart | 164 +++++++++---------- 2 files changed, 149 insertions(+), 85 deletions(-) diff --git a/lib/controller/discover/room_controller.dart b/lib/controller/discover/room_controller.dart index 7b57bbb..3819ea6 100644 --- a/lib/controller/discover/room_controller.dart +++ b/lib/controller/discover/room_controller.dart @@ -2,6 +2,7 @@ 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/live/svga_player_manager.dart'; import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_data.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; @@ -331,9 +332,76 @@ class RoomController extends GetxController { } } + /// 赠送礼物 + Future sendGift({ + required GiftProductModel gift, + String? targetUserId, + }) async { + try { + // 添加到本地播放队列 + final svgaManager = SvgaPlayerManager.instance; + svgaManager.addToQueue( + SvgaAnimationItem( + svgaFile: gift.svgaFile, + targetUserId: targetUserId, + senderUserId: GlobalData().userId ?? GlobalData().userData?.id, + giftProductId: gift.productId, + ), + ); + print('✅ 礼物已添加到播放队列: ${gift.productTitle}'); + + // 发送 RTM 消息通知其他用户 + final channelId = RTCManager.instance.currentChannelId; + if (channelId != null && channelId.isNotEmpty) { + final messageData = { + 'type': 'gift', + 'svgaFile': gift.svgaFile, + 'giftProductId': gift.productId, + 'targetUserId': targetUserId, + 'senderUserId': GlobalData().userId ?? GlobalData().userData?.id, + 'senderNickName': GlobalData().userData?.nickName ?? '', + }; + + await RTMManager.instance.publishChannelMessage( + channelName: channelId, + message: json.encode(messageData), + ); + print('✅ 礼物消息已发送: ${gift.productTitle}'); + } + } catch (e) { + print('❌ 发送礼物失败: $e'); + SmartDialog.showToast('发送礼物失败'); + } + } + /// 接收RTC消息 Future receiveRTCMessage(Map message) async { - if (message['type'] == 'join_chat') { + if (message['type'] == 'gift') { + // 处理礼物消息 + try { + final svgaFile = message['svgaFile']?.toString() ?? ''; + final giftProductId = message['giftProductId']?.toString(); + final targetUserId = message['targetUserId']?.toString(); + final senderUserId = message['senderUserId']?.toString(); + final senderNickName = message['senderNickName']?.toString() ?? ''; + + if (svgaFile.isNotEmpty) { + // 添加到播放队列 + final svgaManager = SvgaPlayerManager.instance; + svgaManager.addToQueue( + SvgaAnimationItem( + svgaFile: svgaFile, + targetUserId: targetUserId, + senderUserId: senderUserId, + giftProductId: giftProductId, + ), + ); + print('✅ 收到礼物消息,已添加到播放队列: $senderNickName 赠送了礼物'); + } + } catch (e) { + print('❌ 处理礼物消息失败: $e'); + } + } else if (message['type'] == 'join_chat') { final response = await _networkService.rtcApi .getDatingRtcChannelUserDetail( rtcChannel.value!.channelId, diff --git a/lib/widget/live/live_gift_popup.dart b/lib/widget/live/live_gift_popup.dart index 0da1d71..73d23d8 100644 --- a/lib/widget/live/live_gift_popup.dart +++ b/lib/widget/live/live_gift_popup.dart @@ -2,10 +2,12 @@ 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/global.dart'; import 'package:dating_touchme_app/generated/assets.dart'; +import 'package:dating_touchme_app/model/live/gift_product_model.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/widget/live/live_room_gift_item.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_swiper_null_safety/flutter_swiper_null_safety.dart'; import 'package:get/get.dart'; import 'package:tdesign_flutter/tdesign_flutter.dart'; @@ -29,34 +31,71 @@ class LiveGiftPopup extends StatefulWidget { } class _LiveGiftPopupState extends State { - // 选中的用户ID集合 - final Set _selectedUserIds = {}; + // 选中的用户ID(单选) + String? _selectedUserId; - // 切换用户选中状态 + // 切换用户选中状态(单选模式) void _toggleUserSelection(String userId) { setState(() { - if (_selectedUserIds.contains(userId)) { - _selectedUserIds.remove(userId); + if (_selectedUserId == userId) { + // 如果点击的是已选中的用户,则取消选中 + _selectedUserId = null; } else { - _selectedUserIds.add(userId); + // 否则选中该用户(自动取消之前的选中) + _selectedUserId = userId; } }); } - // 全选/取消全选 - void _toggleSelectAll(List users) { - setState(() { - if (_selectedUserIds.length == users.length) { - // 如果已全选,则取消全选 - _selectedUserIds.clear(); - } else { - // 否则全选 - _selectedUserIds.clear(); - for (var user in users) { - _selectedUserIds.add(user.userId); - } - } - }); + // 处理赠送礼物 + Future _handleSendGift() async { + // 检查是否选中了礼物 + final activeIndex = widget.activeGift.value; + if (activeIndex == null || + activeIndex < 0 || + activeIndex >= widget.giftList.length) { + SmartDialog.showToast('请先选择礼物'); + return; + } + + // 检查是否选中了接收用户 + if (_selectedUserId == null || _selectedUserId!.isEmpty) { + SmartDialog.showToast('请先选择接收礼物的用户'); + return; + } + + // 获取选中的礼物 + final giftItem = widget.giftList[activeIndex]; + GiftProductModel? gift; + + if (giftItem is GiftProductModel) { + gift = giftItem; + } else if (giftItem is Map) { + // 如果是 Map,需要从 RoomController 的 giftProducts 中查找 + SmartDialog.showToast('礼物数据格式错误'); + return; + } else { + SmartDialog.showToast('礼物数据格式错误'); + return; + } + + // 获取 RoomController + final roomController = Get.isRegistered() + ? Get.find() + : null; + + if (roomController == null) { + SmartDialog.showToast('房间控制器未初始化'); + return; + } + + // 发送礼物 + await roomController.sendGift(gift: gift, targetUserId: _selectedUserId); + + // 关闭弹窗 + SmartDialog.dismiss(); + + SmartDialog.showToast('礼物已送出'); } @override @@ -141,7 +180,7 @@ class _LiveGiftPopupState extends State { ...displayUsers.asMap().entries.map((entry) { final index = entry.key; final user = entry.value; - final isSelected = _selectedUserIds.contains(user.userId); + final isSelected = _selectedUserId == user.userId; return GestureDetector( onTap: () => _toggleUserSelection(user.userId), child: Padding( @@ -228,27 +267,6 @@ class _LiveGiftPopupState extends State { }), ], ), - // 如果列表为空,不显示全选按钮 - if (displayUsers.isNotEmpty) - GestureDetector( - onTap: () => _toggleSelectAll(displayUsers), - child: Container( - width: 63.w, - height: 30.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(30.w)), - color: const Color.fromRGBO(117, 98, 249, 1), - ), - child: Center( - child: Text( - _selectedUserIds.length == displayUsers.length - ? "取消全选" - : "全选", - style: TextStyle(fontSize: 13.w, color: Colors.white), - ), - ), - ), - ), ], ), ); @@ -371,49 +389,27 @@ class _LiveGiftPopupState extends State { builder: (context, num, _) { return Row( children: [ - _buildAdjustButton( - label: "-", - enabled: num > 1, - onTap: () { - if (widget.giftNum.value <= 1) return; - widget.giftNum.value -= 1; - }, - ), - SizedBox( - width: 23.w, - child: Center( - child: Text( - "$num", - style: TextStyle(fontSize: 13.w, color: Colors.white), + GestureDetector( + onTap: () => _handleSendGift(), + child: Container( + width: 63.w, + height: 30.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(30.w)), + gradient: const LinearGradient( + begin: Alignment.centerLeft, + end: Alignment.centerRight, + colors: [ + Color.fromRGBO(61, 138, 224, 1), + Color.fromRGBO(131, 89, 255, 1), + ], + ), ), - ), - ), - _buildAdjustButton( - label: "+", - enabled: true, - onTap: () { - widget.giftNum.value += 1; - }, - ), - SizedBox(width: 9.w), - Container( - width: 63.w, - height: 30.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(30.w)), - gradient: const LinearGradient( - begin: Alignment.centerLeft, - end: Alignment.centerRight, - colors: [ - Color.fromRGBO(61, 138, 224, 1), - Color.fromRGBO(131, 89, 255, 1), - ], - ), - ), - child: Center( - child: Text( - "赠送", - style: TextStyle(fontSize: 13.w, color: Colors.white), + child: Center( + child: Text( + "赠送", + style: TextStyle(fontSize: 13.w, color: Colors.white), + ), ), ), ),