Browse Source

feat(live): 更新直播间UI并增强用户交互功能

- 新增房间用户添加图标资源
- 优化主播展示区域,支持点击添加嘉宾功能
- 改进活跃说话人组件,使用网络图片并添加加载状态
- 实现用户上麦状态判断逻辑
- 添加嘉宾列表弹窗功能
- 优化视频占位图显示逻辑
- 调整UI布局和样式细节
ios
Jolie 4 months ago
parent
commit
a689641154
4 changed files with 252 additions and 131 deletions
  1. BIN
      assets/images/room_user_add.png
  2. 1
      lib/generated/assets.dart
  3. 148
      lib/widget/live/live_room_active_speaker.dart
  4. 234
      lib/widget/live/live_room_anchor_showcase.dart

BIN
assets/images/room_user_add.png

Before After
Width: 200  |  Height: 200  |  Size: 689 B

1
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';

148
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<RoomController>()
? Get.find<RoomController>()
: 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),
),
),
),
),
],
),
],
);
],
),
],
);
});
}
}

234
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<LiveRoomAnchorShowcase> {
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<LiveRoomAnchorShowcase> {
),
)
: 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<LiveRoomAnchorShowcase> {
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<LiveRoomAnchorShowcase> {
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:
);
},
);
}
}
Loading…
Cancel
Save