From a6896411547877951e61baa3bb0dd7488d13d3ff Mon Sep 17 00:00:00 2001 From: Jolie <412895109@qq.com> Date: Thu, 27 Nov 2025 23:55:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(live):=20=E6=9B=B4=E6=96=B0=E7=9B=B4?= =?UTF-8?q?=E6=92=AD=E9=97=B4UI=E5=B9=B6=E5=A2=9E=E5=BC=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E4=BA=A4=E4=BA=92=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增房间用户添加图标资源 - 优化主播展示区域,支持点击添加嘉宾功能 - 改进活跃说话人组件,使用网络图片并添加加载状态 - 实现用户上麦状态判断逻辑 - 添加嘉宾列表弹窗功能 - 优化视频占位图显示逻辑 - 调整UI布局和样式细节 --- assets/images/room_user_add.png | Bin 0 -> 689 bytes lib/generated/assets.dart | 1 + lib/widget/live/live_room_active_speaker.dart | 148 ++++++++--- .../live/live_room_anchor_showcase.dart | 234 +++++++++++------- 4 files changed, 252 insertions(+), 131 deletions(-) create mode 100644 assets/images/room_user_add.png diff --git a/assets/images/room_user_add.png b/assets/images/room_user_add.png new file mode 100644 index 0000000000000000000000000000000000000000..913aa75ff851692cd6225d7208e7608ed4316dc4 GIT binary patch literal 689 zcmeAS@N?(olHy`uVBq!ia0vp^CqS5k4M?tyST_$yF%}28J29*~C-V}>VJUX<4B-HR z8jh3>1_q{RPZ!6KiaBp@Z_H#d6mSSUDPwW##NX}Zwic7LZ^(15eRxkhK|;b#>a4D=I(liUAGxOzht#L8ueN#IcE2>vzGUFrxl(T=5BIO;9wc1 b#3A`hNBCE=m3MOhlO2PntDnm{r-UW|tJRj9 literal 0 HcmV?d00001 diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart index 28561ce..5ebfadd 100644 --- a/lib/generated/assets.dart +++ b/lib/generated/assets.dart @@ -152,6 +152,7 @@ class Assets { static const String imagesRocket1 = 'assets/images/rocket1.svga'; static const String imagesRocket2 = 'assets/images/rocket2.svga'; static const String imagesRocket3 = 'assets/images/rocket3.svga'; + static const String imagesRoomUserAdd = 'assets/images/room_user_add.png'; static const String imagesRoomVideo = 'assets/images/room_video.png'; static const String imagesRose = 'assets/images/rose.png'; static const String imagesRoseBanner = 'assets/images/rose_banner.png'; diff --git a/lib/widget/live/live_room_active_speaker.dart b/lib/widget/live/live_room_active_speaker.dart index bf55b82..2dfe78f 100644 --- a/lib/widget/live/live_room_active_speaker.dart +++ b/lib/widget/live/live_room_active_speaker.dart @@ -1,54 +1,126 @@ +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:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; class LiveRoomActiveSpeaker extends StatelessWidget { const LiveRoomActiveSpeaker({super.key}); @override Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Stack( - clipBehavior: Clip.none, - children: [ - Container( - width: 34.w, - height: 34.w, - margin: EdgeInsets.only(left: 13.w), - child: ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(34.w)), - child: Image.asset( - Assets.imagesUserAvatar, - width: 34.w, - height: 34.w, + // 获取 RoomController(在 Obx 外部获取,避免重复查找) + final roomController = Get.isRegistered() + ? Get.find() + : null; + + if (roomController == null) { + return const SizedBox.shrink(); + } + + // 获取当前用户信息 + final currentUserId = GlobalData().userId ?? GlobalData().userData?.id; + final currentUserPhoto = GlobalData().userData?.profilePhoto ?? ''; + + return Obx(() { + // 访问响应式变量以触发更新 + final rtcChannelDetail = roomController.rtcChannelDetail.value; + final isLive = roomController.isLive.value; + + // 判断当前用户是否上麦 + bool isOnSeat = false; + if (currentUserId != null) { + // 方式1:检查当前用户是否在 maleInfo 或 femaleInfo 中(maleAudience/femaleAudience 角色) + if (rtcChannelDetail != null) { + final maleInfo = rtcChannelDetail.maleInfo; + final femaleInfo = rtcChannelDetail.femaleInfo; + + isOnSeat = + (maleInfo != null && + (maleInfo.userId == currentUserId || + maleInfo.miId == currentUserId)) || + (femaleInfo != null && + (femaleInfo.userId == currentUserId || + femaleInfo.miId == currentUserId)); + } + + // 方式2:如果 isLive 为 true,说明用户已连接(可能是 audience 角色) + // 如果已经在 maleInfo/femaleInfo 中,就不需要再检查 isLive + if (!isOnSeat && isLive) { + isOnSeat = true; + } + } + + // 确定显示的图标 + final micIcon = isOnSeat ? Assets.imagesMicOpen : Assets.imagesMicClose; + + return Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + Container( + width: 34.w, + height: 34.w, + margin: EdgeInsets.only(left: 13.w), + child: ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(34.w)), + child: currentUserPhoto.isNotEmpty + ? CachedNetworkImage( + imageUrl: currentUserPhoto, + width: 34.w, + height: 34.w, + fit: BoxFit.cover, + placeholder: (context, url) => Container( + width: 34.w, + height: 34.w, + color: Colors.grey[300], + child: Center( + child: SizedBox( + width: 16.w, + height: 16.w, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.grey[600], + ), + ), + ), + ), + errorWidget: (context, url, error) => Image.asset( + Assets.imagesUserAvatar, + width: 34.w, + height: 34.w, + ), + ) + : Image.asset( + Assets.imagesUserAvatar, + width: 34.w, + height: 34.w, + ), ), ), - ), - Positioned( - bottom: -3.w, - left: 20.w, - child: Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(4.w)), - color: const Color.fromRGBO(0, 0, 0, .65), - ), - child: Center( - child: Image.asset( - Assets.imagesMicClose, - width: 10.w, - height: 11.w, + Positioned( + bottom: -3.w, + left: 20.w, + child: Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(4.w)), + color: const Color.fromRGBO(0, 0, 0, .65), + ), + child: Center( + child: Image.asset(micIcon, width: 10.w, height: 11.w), ), ), ), - ), - ], - ), - ], - ); + ], + ), + ], + ); + }); } } - diff --git a/lib/widget/live/live_room_anchor_showcase.dart b/lib/widget/live/live_room_anchor_showcase.dart index b02883a..22af563 100644 --- a/lib/widget/live/live_room_anchor_showcase.dart +++ b/lib/widget/live/live_room_anchor_showcase.dart @@ -1,10 +1,13 @@ import 'package:agora_rtc_engine/agora_rtc_engine.dart'; import 'package:dating_touchme_app/controller/discover/room_controller.dart'; +import 'package:dating_touchme_app/extension/ex_widget.dart'; import 'package:dating_touchme_app/generated/assets.dart'; import 'package:dating_touchme_app/model/rtc/rtc_channel_detail.dart'; import 'package:dating_touchme_app/rtc/rtc_manager.dart'; +import 'package:dating_touchme_app/widget/live/live_room_guest_list_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'; class LiveRoomAnchorShowcase extends StatefulWidget { @@ -127,7 +130,8 @@ class _LiveRoomAnchorShowcaseState extends State { Widget _buildAnchorVideo(bool joined, int? remoteUid) { final engine = _rtcManager.engine; - if (_roomController.rtcChannelDetail.value?.anchorInfo == null || engine == null) { + if (_roomController.rtcChannelDetail.value?.anchorInfo == null || + engine == null) { return _buildWaitingPlaceholder(); } return ClipRRect( @@ -143,16 +147,17 @@ class _LiveRoomAnchorShowcaseState extends State { ), ) : AgoraVideoView( - controller: VideoViewController.remote( - rtcEngine: engine, - canvas: VideoCanvas( - uid: _roomController.rtcChannelDetail.value?.anchorInfo?.uid, - ), - connection: RtcConnection( - channelId: _rtcManager.currentChannelId!, - ), - ), - ), + controller: VideoViewController.remote( + rtcEngine: engine, + canvas: VideoCanvas( + uid: + _roomController.rtcChannelDetail.value?.anchorInfo?.uid, + ), + connection: RtcConnection( + channelId: _rtcManager.currentChannelId!, + ), + ), + ), ), ); } @@ -170,9 +175,9 @@ class _LiveRoomAnchorShowcaseState extends State { Column( children: [ Image.asset(Assets.imagesWaitting), - Text('暂时离开', style: TextStyle(color: Colors.white,)) + Text('暂时离开', style: TextStyle(color: Colors.white)), ], - ) + ), ], ), ); @@ -197,100 +202,143 @@ class _LiveRoomAnchorShowcaseState extends State { child: SizedBox( width: 177.w, height: 175.w, - child: userInfo != null && userInfo.uid != null && joined && engine != null ? Stack( - children: [ - Positioned( - top: 5.w, - right: 5.w, - child: Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20.w)), - ), - child: Center( - child: Image.asset( - Assets.imagesGiftIcon, - width: 19.w, - height: 19.w, - ), - ), - ), - ), - Positioned( - bottom: 5.w, - right: 5.w, - child: Container( - width: 47.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(20.w)), - color: Colors.white, - ), - child: Center( - child: Text( - "加好友", - style: TextStyle( - fontSize: 11.w, - color: const Color.fromRGBO(117, 98, 249, 1), - ), - ), - ), - ), - ), - Positioned( - left: 5.w, - bottom: 5.w, - child: Row( + child: + userInfo != null && + userInfo.uid != null && + joined && + engine != null + ? Stack( children: [ - Container( - width: 20.w, - height: 20.w, - decoration: BoxDecoration( - borderRadius: BorderRadius.all(Radius.circular(4.w)), - color: const Color.fromRGBO(0, 0, 0, .65), + Positioned( + top: 5.w, + right: 5.w, + child: Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.w), + ), + ), + child: Center( + child: Image.asset( + Assets.imagesGiftIcon, + width: 19.w, + height: 19.w, + ), + ), ), - child: Center( - child: Image.asset( - userInfo.isMicrophoneOn - ? Assets.imagesMicOpen - : Assets.imagesMicClose, - width: 10.w, - height: 11.w, + ), + Positioned( + bottom: 5.w, + right: 5.w, + child: Container( + width: 47.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(20.w), + ), + color: Colors.white, + ), + child: Center( + child: Text( + "加好友", + style: TextStyle( + fontSize: 11.w, + color: const Color.fromRGBO(117, 98, 249, 1), + ), + ), ), ), ), - SizedBox(width: 5.w), - Text( - userInfo.nickName, - style: TextStyle( - fontSize: 11.w, - color: Colors.white, - fontWeight: FontWeight.w500, + Positioned( + left: 5.w, + bottom: 5.w, + child: Row( + children: [ + Container( + width: 20.w, + height: 20.w, + decoration: BoxDecoration( + borderRadius: BorderRadius.all( + Radius.circular(4.w), + ), + color: const Color.fromRGBO(0, 0, 0, .65), + ), + child: Center( + child: Image.asset( + userInfo.isMicrophoneOn + ? Assets.imagesMicOpen + : Assets.imagesMicClose, + width: 10.w, + height: 11.w, + ), + ), + ), + SizedBox(width: 5.w), + Text( + userInfo.nickName, + style: TextStyle( + fontSize: 11.w, + color: Colors.white, + fontWeight: FontWeight.w500, + ), + ), + ], ), ), + AgoraVideoView( + controller: isCurrentUser + ? VideoViewController( + rtcEngine: engine, + canvas: const VideoCanvas(uid: 0), + ) + : VideoViewController.remote( + rtcEngine: engine, + canvas: VideoCanvas(uid: userInfo.uid!), + connection: RtcConnection( + channelId: _rtcManager.currentChannelId ?? '', + ), + ), + ), ], - ), - ), - AgoraVideoView( - controller: isCurrentUser - ? VideoViewController( - rtcEngine: engine, - canvas: const VideoCanvas(uid: 0), ) - : VideoViewController.remote( - rtcEngine: engine, - canvas: VideoCanvas(uid: userInfo.uid!), - connection: RtcConnection( - channelId: _rtcManager.currentChannelId ?? '', + : _roomController.currentRole == CurrentRole.broadcaster + ? Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(isLeft ? 10.w : 0.w), + topRight: Radius.circular(!isLeft ? 10.w : 0.w), + bottomLeft: Radius.circular(isLeft ? 10.w : 0.w), + bottomRight: Radius.circular(!isLeft ? 10.w : 0.w), + ), + color: const Color(0xff15143C), + ), + child: Center( + child: Image.asset(Assets.imagesRoomUserAdd, width: 50.w), ), + ).onTap(() { + _showGuestListDialog(context, isLeft); + }) + : Image.asset( + isLeft ? Assets.imagesMaleEmpty : Assets.imagesFemaleEmpty, ), - ) - ], - ) : Image.asset(isLeft ? Assets.imagesMaleEmpty : Assets.imagesFemaleEmpty), ), ), ], ); } + + void _showGuestListDialog(BuildContext context, bool isMaleSeat) { + SmartDialog.show( + alignment: Alignment.bottomCenter, + maskColor: Colors.black.withOpacity(0.5), + builder: (context) { + return LiveRoomGuestListDialog( + initialTab: isMaleSeat ? 1 : 0, // 0: 女嘉宾, 1: 男嘉宾 + ); + }, + ); + } }